swoole版本
FengMacBookPro:miaosha feng$ php --ri swoole
swoole
Swoole => enabled
Author => Swoole Team
Version => 4.4.16
Built => Feb 27 2020 14:45:23
coroutine => enabled
kqueue => enabled
rwlock => enabled
openssl => OpenSSL 1.0.2j 26 Sep 2016
pcre => enabled
zlib => 1.2.8
async_redis => enabled
Directive => Local Value => Master Value
swoole.enable_coroutine => On => On
swoole.enable_library => On => On
swoole.enable_preemptive_scheduler => Off => Off
swoole.display_errors => On => On
swoole.use_shortname => On => On
swoole.unixsock_buffer_size => 262144 => 262144
php版本
FengMacBookPro:miaosha feng$ php -v
PHP 7.2.10 (cli) (built: Oct 9 2018 14:56:43) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
数据库
CREATE TABLE `orders` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`goods_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
客户端
client.php
//模拟用户抢购,模拟1500个客户端
ini_set('display_errors',1);
error_reporting(-1);
for($i=1;$i<=1500;$i++)
{
go(function () use ($i){
$client = new Swoole\Coroutine\Http\Client('127.0.0.1',9501);
$client->set(["timeout"=>5]);
$client->get("/goods/index?goods_id=1&user_id={$i}");
if($client->body == 'ok')
{
echo $client->body."\n";
}
$client->close();
});
}
http服务端
httpserver.php
//http抢购服务器
ini_set('display_errors',1);
error_reporting(-1);
Co::set(['hook_flags'=> SWOOLE_HOOK_ALL]);
$http = new swoole_http_server("127.0.0.1", 9501);
$http->set([
'enable_coroutine' => true,
'reactor_num' => 4,
'worker_num' => 4, //worker process num
'task_worker_num' => 4,
'max_request' => 1000,
'dispatch_mode' => 2,
'max_connection' => 5000,
]);
$http->on("start", function ($server) {
echo "Http server start http://127.0.0.1:9501/\n";
//初始化一些操作
$redis = new Redis();
$redis->connect('127.0.0.1',6379);
$redis->set("kucun",10);
$redis->del("order_member");
$redis->del("order_goods");
$redis->close();
$mysql = mysqli_connect('127.0.0.1','root','root','orders','3306');
$mysql->query("TRUNCATE table orders");
$mysql->close();
});
$http->on("request",function($request, $response) use ($http){
if ($request->server['path_info'] == '/favicon.ico' || $request->server['request_uri'] == '/favicon.ico') {
return $response->end();
}
$response->header("Content-Type", "text/html; charset=utf-8");
$route = explode('/',trim($request->server['request_uri'],'/'));
$controller = ucfirst($route[0]);
$action = $route[1];
$data = (new $controller())->$action($request->get,$http);
$response->end($data);
});
$http->on('task',function ($http,$task_id,$from_id,$data){
//echo "New AsyncTask[id=$task_id]".PHP_EOL;
$mysql = mysqli_connect('127.0.0.1','root','root','orders','3306');
$mysql->query("set names utf8");
if(!$mysql)
{
echo "Error: Unable to connect to MySQL." . PHP_EOL;
echo "Debugging errno: " . mysqli_connect_errno() . PHP_EOL;
echo "Debugging error: " . mysqli_connect_error() . PHP_EOL;
return ;
}
$data = json_decode($data,true);
$sql = " insert into orders (user_id,goods_id) values ({$data['user_id']},{$data['goods_id']}) ";
$mysql->query($sql);
$data = json_encode($data);
//返回任务执行的结果
$http->finish("$data -> OK");
$mysql->close();
});
//处理异步任务的结果(此回调函数在worker进程中执行)
$http->on('finish', function ($http, $task_id, $data) {
echo "AsyncTask[$task_id] Finish: $data".PHP_EOL;
});
class Goods {
public $redis;
function __construct()
{
$redis = new Redis();
$redis->connect('127.0.0.1',6379);
$this->redis = $redis;
}
function index($params,$http){
if(!$params['goods_id'] || !$params['user_id']) {
return '';
}
//判断是否抢了
$is_exist = $this->redis->sIsMember("order_member",$params['user_id']);
if(!$is_exist)
{
#此出不能使用$redis->get/set
#减库存
$kucun = $this->redis->decr("kucun");
if($kucun >= 0 ) {
//抢到号的入队列
$arr['user_id'] = $params['user_id'];
$arr['goods_id'] = $params['goods_id'];
$json = json_encode($arr);
//队列形式
//$this->redis->lPush("order_goods",$json);
//采用异步任务形式
//$task_id = $http->task($json);
//直接入库
$this->insert($json);
//记录已经抢到的用户
$this->redis->sAdd("order_member",$params['user_id']);
return 'ok';
}else {
$this->redis->set("kucun",0);
return '';
}
}else{
return '';
}
}
public function insert($data){
$mysql = mysqli_connect('127.0.0.1','root','root','orders','3306');
$mysql->query("set names utf8");
if(!$mysql)
{
echo "Error: Unable to connect to MySQL." . PHP_EOL;
echo "Debugging errno: " . mysqli_connect_errno() . PHP_EOL;
echo "Debugging error: " . mysqli_connect_error() . PHP_EOL;
return ;
}
$data = json_decode($data,true);
$sql = " insert into orders (user_id,goods_id) values ({$data['user_id']},{$data['goods_id']}) ";
$mysql->query($sql);
echo json_encode($data)."\n";
$mysql->close();
}
function __destruct()
{
// TODO: Implement __destruct() method.
$this->redis->close();
}
}
$http->start();
消费者端,负责把redis队列的数据入库
consumer.php
//消费抢购的订单
ini_set('display_errors',1);
error_reporting(-1);
//其四个进程处理队列
$workerNum = 4;
$pool = new Swoole\Process\Pool($workerNum);
$pool->on("WorkerStart", function (Swoole\Process\Pool $pool, int $workerId) {
$process = $pool->getProcess();
$redis = new Redis();
$redis->pconnect('127.0.0.1', 6379);
$key = "order_goods";
$mysql = mysqli_connect('127.0.0.1','root','root','orders','3306');
$mysql->query("set names utf8");
if(!$mysql)
{
echo "Error: Unable to connect to MySQL." . PHP_EOL;
echo "Debugging errno: " . mysqli_connect_errno() . PHP_EOL;
echo "Debugging error: " . mysqli_connect_error() . PHP_EOL;
return;
}
while (true) {
$d = $redis->rPop($key);
$d = json_decode($d,true);
$user_id = $d["user_id"];
$goods_id = $d["goods_id"];
if($user_id && $goods_id)
{
$sql = " insert into orders (user_id,goods_id) values ({$user_id},{$goods_id}) ";
$mysql->query($sql);
echo "ok\n";
}else{
//没数据的时候进行休眠,不然循环$redis->rpop会导致redis-server消耗很高
sleep(1);
}
}
});
$pool->on("WorkerStop", function ($pool, $workerId) {
echo "Worker#{$workerId} is stopped\n";
});
$pool->start();