Redis-消息队列和异步处理

之前公司有个app的项目,里面有即时聊天,需要一个存放聊天记录的接口

每一条聊天记录都直接存放mysql,来一条insert一条,量不大还好说,基本都能应付。但是如果量特别大,一天几百万条,上千万条,这个insert操作就会执行几百万次,会不会响应不过来?用户体验会不会降低?

类似的案例还有

一个帖子,用户每访问一次就要set visitor = visitor+1,每天访问上百万次,就要update上百万次,,这个聊天记录表又是访问请求最高的,会不会锁死?

其实这些都可以优化

Redis读的速度是110000次/s,写的速度是81000次/s 。

第一个案例里面可以用Redis做消息中转站,用一个list存放我们的聊天记录。

利用Redis的性能,我们可以把这些请求都写到缓存。然后后台启用一个crontab定时任务,每分钟执行,把队列里的数据拿出来,存到MySQL,甚至可以每10秒,5秒做一次操作

这里给大家一个简单的demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//插入消息队列
public function actionPush()
{
$key = 'chatLog:' . date("Ymd");
$content = '测试内容';
$data = [
'user_id' => 1,
'create_time' => date("Y-m-d H:i:s"),
'content' => $content
];
RedCache::rPush($key, json_encode($data));
}

//pop插入数据库
public function actionPop()
{
$key = 'chatLog:' . date("Ymd");
$length = RedCache::lLen($key);
if ($length > 0) {
for ($i = 0; $i < $length; $i++) {
$data = RedCache::lPop($key);
TestService::insertChat(json_decode($data, true));
}
}
}

利用linux的crontab定时任务异步的去调用actionPop

因为crontab的最快频率是1分钟调用一次,如果实时性要求很高,比如2秒

可以写个脚本

1
2
3
4
5
6
7
8
step=2 #间隔

for (( i = 0; i < 60; i=(i+step) )); do
$(curl http://127.0.0.1/kxds/web/index.php?r=redis/list/test2)
sleep $step
done

exit 0

然后创建crontab任务

每分钟去执行这个脚本

1
* * * * * /web/chat.sh

这样就实现了每2秒钟去掉我们的pop-insert接口

利用Apache的ab做测试
1000次请求100个并发结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
erver Software:         nginx/1.8.1
Server Hostname: 127.0.0.1
Server Port: 80

Document Path: /kxds/web/index.php?r=redis/list/test1
Document Length: 0 bytes

Concurrency Level: 100
Time taken for tests: 9.936 seconds
Complete requests: 1000
Failed requests: 0
Total transferred: 357000 bytes
HTML transferred: 0 bytes
Requests per second: 100.65 [#/sec] (mean)
Time per request: 993.563 [ms] (mean)
Time per request: 9.936 [ms] (mean, across all concurrent requests)
Transfer rate: 35.09 [Kbytes/sec] received

可以看到结果接近10秒钟

而直接接受消息同步存入mysql的测试结果是13.288 seconds

这个响应时间相差还是比较大的

不过这里注意的是,异步更新的内容,属于“丢了其实关系也不大”的数据,如果是非常核心的数据,异步更新要注意数据丢失的危险

第二个案例类似的还有很多

这个数据,其实实时性需要不是那么高,不是每个请求都必须立即处理

这里可以用一个Redis的hash来存放,帖子id对应访问量,每次请求的时候把帖子id对应的值incr增1

同样异步的定时的去从hash里读取数据,update数据库

这样就做到了请求合并和异步更新,可能每次去更新的时候都有几十次上百次增量,这样就合并吊了几十次上百次请求