前段时间赶项目,忙结婚,各大技术平台都没时间上,不过还是抽出点时间为自己的婚礼做了一点小玩具,今天我就来给大家分享一下。
先来看一下效果
这个项目是基于微信个人订阅号的,订阅号的开发在此我就不再赘述了。
基本开发思路是,服务器接收到用户文本信息,用正则匹配一下然后去掉前面的cg#(这一步其实可有可无,不过为了区分其他的指令而加上),然后存进服务器。本地用浏览器跑一个轮播图,然后拿到弹幕信息就可以在浏览器播放了。
这里有2个关键点:
1、本地如何拿到弹幕信息?两台物理机器如何实时获取信息?这里的信息延迟我要求不能超过1s。这里我找到有两个方案,A 本地设置定时器直接请求远程服务器拉取弹幕信息。B 本地与远程服务器建立一个socket长连接,服务器主动写数据。
2、这个场景交互比较频繁,IO相对比较密集(婚礼上并发可能会高达100+),存数据库的话压力会比较大,如果挂掉了本少爷的脸也挂不住了-_-||
好,接着来一个一个的解决,先从技术选型开始。
1、如果数据库扛不住,那么我不存数据库,直接存缓存行得通吗?弹幕信息带有时序性的属性,redis自带队列数据结构,这很适合,redis对外宣称的并发数为3-5w,刚好之前被推荐买了一本redis实战,也看完了,就用来实践一下。服务器拿到弹幕信息后存进队列
1 /**
2 * [saveInfo description]
3 * @param object $postObj 微信XML实体
4 * @return [type] [description]
5 */
6 private function saveInfo($postObj) {
7 // 数据处理
8 $openid = $postObj->FromUserName;
9 $content = preg_replace("/^(cg#){1}/i", "", $postObj->Content);
10
11 // 准备存入队列的json数据
12 $value = array(
13 "openid" => $openid,
14 "content" => $content,
15 "time" => strval(microtime())
16 );
17 $json = json_encode($value);
18
19 // 把用户的弹幕内容存进队列中
20 $result = $this->lpush(BARRAGE_KEY, $json);
21
22 return $result?$json:false;
23 }
(原本已经调了用户接口,打算搞抽奖的,但我没有搞企业服务号,所以后面就去掉了这一部分了,就是用fromUser这个openID去获取用户nickname,headurl等信息)
2、好了,存好的弹幕信息,就差本地去拿了,获取弹幕信息的方式影响到整个程序能否成功运行,这里我想到的两个方案不知孰优孰劣,唯有一试。
首先尝试定时获取,写一个js定时器去请求远程服务器,进而拉取弹幕信息。方法甫一运行,本地就卡的不要不要的,轮播图也变成了幻灯片,毋论接收弹幕了。另外,这里还会有浏览器跨域的问题存在。
然后,查资料得知,redis本身是依赖于socket运行的,还有自带现成发布订阅功能(publish和subscribe),跟我的需求完美契合。所以我重点写好建立连接和接收数据的回调就好了。
建立连接的代码(这个我为了方便,是用php指令来运行脚本的):
1 /**
2 * [pullBarrages 拉取腾讯云中最新的弹幕]
3 */
4 public function pullBarrages() {
5 try {
6 set_time_limit(0);
7 ini_set("default_socket_timeout", -1);
8 ignore_user_abort(TRUE);
9 // 首先同步一次弹幕
10 $this->synchronizeBarrages();
11 // 订阅新的弹幕频道并保存
12 $this->subscribe(array(BARRAGE_CHANNEL), array($this, "saveBarrage"));
13 } catch (Exception $e) {
14 // 连接超时出现异常时重新建立长连接
15 $host = "IP";
16 $port = 6379;
17 $this->redis = new RedisCache();
18 $this->redis->connect($host, $port);
19 // 检查同步远程主机的弹幕同步情况
20 $this->synchronizeBarrages();
21 // 重新调用长连接订阅方法
22 $this->pullBarrages();
23 }
24 }
这里我写了一段监听异常的代码,因为连接之后过了大概60秒就自动断开,所以我捕捉到异常就重新建立连接并且确认是否有没有拉取到的弹幕信息。不清楚是PHP对socket的支持不太好还是我的PHP环境的原因(我的PHP用的是集成环境),这里我没有太多时间去深究,如果有知道的大牛请告诉我,谢谢~
接收到数据之后的回调函数:
1 /**
2 * [saveBarrages description]
3 * @param object $redis redis对象
4 * @param string $channel 订阅的频道
5 * @param string $barrage 频道订阅返回的弹幕信息
6 * @return Boolean 存储结果
7 */
8 public function saveBarrage($redis, $channel, $barrage) {
9 $localRedis = new RedisCache();
10 $localRedis->connect("127.0.0.1", 6379);
11
12 $result = $localRedis->lpush(BARRAGE_KEY, $barrage);
13 if ($result) {
14 echo "barrage save success!" . PHP_EOL;
15 }else {
16 echo "barrage save fail!" . PHP_EOL;
17 }
18 echo date("Y-m-d H:i:s") . PHP_EOL;
19 }
本地拿到了服务器的弹幕信息,剩下的就随便怎么玩都可以了,拿到弹幕信息大概是秒达的感觉。前端浏览器现在也是有websocket,但是我不会用,所以选择了在浏览器设置一个定时器去请求本地环境的弹幕数据,间隔自己定就可以了(关于前端的东西不细讲,后端做前端的诀窍就是找插件,哈哈)。
还有一点,就是我为了方便写代码,把redis对象省略了,用到了__call方法来调用,这个在我之前的博客中提到过。
断断续续的完成这篇博客,以上写的可能有点乱,抱歉。这次就分享这么多,下次有其他东西再来分享~~
2017.12.04编辑:
关于链接被断开,通过捕捉异常来重新连接redis的部分找到了比较好的解决办法了,自动断开连接是因为redis的subscribe方默认在60s内没有接收到信息的话,就会自动断开,只需要添加一行代码:
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1);
让redis不超时就可以了。