概述

依据Swoole的websocket服务,再之前的音讯体系系列的第4篇,完成了愈加杂乱的事务场景,是对音讯推送的完善和优化,代码自身就是不断自我优化的进程。

完成计划

技能的完成计划点首要PMQ,2组客户端(用户端、客服办理端),3个首要的部分组成(Push推送音讯+Pull拉取未读音讯+MessageQueue音讯队列),详细流程和交互方法见上面的架构流程图。

Swoole - webSocket客服IM消息系统方案实践篇

1.树立链接,借鉴Tcp3次握手的原理,将每一次的用户问询新增一个联系,问询结束时再将联系开释,因为每次随机分配的客服是不一致的,客服办理员控制台,进入控制台会触发检测客服映射联系的程序,以保证联系的唯一性。

2.客服分配:客服分配是依据用户是否为第一次进入链接进行断定依据,首次会随机分发配给在线客服中的其间一个,假如之前分配过的客服也在线,优化分配存在客服,这样处理的原因是客服不易变,用户异变,防止反复链接/断开操作,减少网络开销。

3.并发锁:相同用户在同一时间有3s的确定状态,用来防止联系紊乱,在客户端发来恳求时优先获取缓存,近少或许的拜访数据库,提高服务的稳定性和性能。

//设置分布式锁,3s之内只能恳求一次
$lock = RedisPool::invoke(function (Redis $redis) use ($toUid) {
    return $redis->get(Category::$openLock . $toUid);
}, self::REDIS_CONN_NAME);
if ($lock) {
    $msgErrorRet['code'] = 416;
    $msgErrorRet['msg'] = 'Please try again';
    return $this->response()->setMessage(json_encode($msgErrorRet));
}
//查询是否存在链接联系
$imUserRelation = RedisPool::invoke(function (Redis $redis) use ($toUid) {
    $redis->setEx(Category::$openLockPrefix . $toUid, 3, $toUid);
    return $redis->get(Category::$imUserRelationName . $toUid);
}, self::REDIS_CONN_NAME);

4.网络反常处理,收回服务:针对App崩溃、网络反常断开的链接,主动监听断开的fd,进行联系处理,对一切断开链接的websocket,进行收回,清除联系。

static function onClose(\swoole_server $server, int $fd, int $reactorId)
{
    $info = $server->getClientInfo($fd);
    $fd = intval($fd);
    if ($info && $info['websocket_status'] === WEBSOCKET_STATUS_FRAME) {
        TaskManager::getInstance()->async(function () use ($fd) {
            RedisPool::invoke(function (Redis $redis) use ($fd) {
                //收回用户
                $uid = $redis->hGet('PUSH_MSG_SOCKET_FD', $fd);
                if (isset($uid) && !empty($uid) && is_numeric($uid)) {
                    $redis->zRem('PUSH_MSG_USER_LOGIN', $fd);
                    //检测是否有客服联系未断开
                    $redis->del(Category::$imUserRelationName . $uid);
                    $redis->hDel('PUSH_MSG_SOCKET_FD', $fd);
                }
                //收回客服办理用户
                $cUid = $redis->hGet('PUSH_CUSTOMER_MSG_SOCKET_FD', $fd);
                if (isset($cUid) && !empty($cUid)) {
                    $redis->zRem('PUSH_CUSTOMER_MSG_USER_LOGIN', $fd);
                    $redis->hDel('PUSH_CUSTOMER_MSG_SOCKET_FD', $fd);
                }
            }, 'redis');
        });
    }
}

5.获取离线音讯分配算法,依照客服办理员在线人数,把离线音讯依照用户来从头拼装,平均分配给在线办理员,假如数量不能被整除,也不会形成分配不均情况。

//验证客服办理员在线
$vUid = [];
$server = ServerManager::getInstance()->getSwooleServer();
foreach ($virtualUid as $fd => $vid){
    $info = $server->getClientInfo($fd);
    if ($info && $info['websocket_status'] == 3) {
        $vUid[$fd] = $vid;
    }
}
if (!empty($pullData) && !empty($vUid)) {
    $uIds = array_keys($pullData);
    $row = ceil(count($uIds) / count($vUid));
    $share = array_chunk($uIds, $row, true);
    $keyDict = $vUid;
    $pushList = [];
    // code 拼装代码略... 
}

6.websocket目标不收回:从控制台翻开新窗口时,就会新增一个websocket目标,后来在浏览器中改写处理的,没有找到收回的方法。

7.心跳:客服的websocket心跳运用的是实时push音讯,5s循环一次,防止链接断开,服务下线。

实践Swoole里的坑

链接数改变正常,但是内存如同没有得到很好的开释,并且进程里也呈现了许多野进程,野进程多或许存在的原因是这样的,你没有守护启动,然后主进程挂了,后边的进程找不到父进程,变成了僵尸进程或者是孤儿进程。

Swoole - webSocket客服IM消息系统方案实践篇

内存也不对劲,大概率是我执行脚本里出了问题,去掉了修正配置的语句,在Base类里加入了unset,及时开释掉内存。

Swoole - webSocket客服IM消息系统方案实践篇

呈现问题的处理的原因是我在Crontab脚本里加了结束时长形成的问题,cli形式下的php生命周期进程得不到开释形成的,合理运用Swoole中的协程就好了。

ini_set('memory_limit', '1024M');
set_time_limit(0);

效果

上线2年的时间里,进行了5次升级和优化,活泼用户10w+,最高峰值6w/s,130w/h拜访量,是一个十分成功的实践结果。

用最简单的技能完成方法,节省企业本钱,减少体系开发和维护本钱,提高工作效率才是技能人应该做的事儿,做处理实践杂乱事务处理计划并落地的技能人,En。