由于每一个客户端都是需要登录才能使用的,所以第一步自然是注册。
这里就设计的比较简单,直接利用 Redis
来存储用户信息;用户信息也只有 ID
和 userName
而已。
只是为了方便查询在 Redis
中的 KV
又反过来存储了一份 VK
,这样 ID
和 userName
都必须唯一。
登录接口
这里的登录和 cim-server
中的登录不一样,具有业务性质,
- 登录成功之后需要判断是否是重复登录(一个用户只能运行一个客户端)。
- 登录成功后需要从
Zookeeper
中获取服务列表(cim-server
)并根据某种算法选择一台服务返回给客户端。
- 登录成功之后还需要保存路由信息,也就是当前用户分配的服务实例保存到
Redis
中。
为了实现只能一个用户登录,使用了 Redis
中的 set
来保存登录信息;利用 userID
作为 key
,重复的登录就会写入失败。
类似于 Java 中的 HashSet,只能去重保存。
获取一台可用的路由实例也比较简单:
- 先从
Zookeeper
获取所有的服务实例做一个内部缓存。
- 轮询选择一台服务器(目前只有这一种算法,后续会新增)。
当然要获取 Zookeeper
中的服务实例前自然是需要监听 cim-server
之前注册上去的那个节点。
具体代码如下:
也是在应用启动之后监听 Zookeeper
中的路由节点,一旦发生变化就会更新内部缓存。
这里使用的是 Guava 的 cache,它基于
ConcurrentHashMap
,所以可以保证清除、新增缓存
的原子性。
群聊接口
这是一个真正发消息的接口,实现的效果就是其中一个客户端发消息,其余所有客户端都能收到!
流程肯定是客户端发送一条消息到服务端,服务端收到后在上文介绍的 SessionSocketHolder
中遍历所有 Channel
(通道)然后下发消息即可。
服务端是单机倒也可以,但现在是集群设计。所以所有的客户端会根据之前的轮询算法分配到不同的 cim-server
实例中。
因此就需要路由层来发挥作用了。
路由接口收到消息后首先遍历出所有的客户端和服务实例的关系。
路由关系在 Redis
中的存放如下:
由于 Redis
单线程的特质,当数据量大时;一旦使用 keys 匹配所有 cim-route:*
数据,会导致 Redis 不能处理其他请求。
所以这里改为使用 scan 命令来遍历所有的 cim-route:*
。
接着会挨个调用每个客户端所在的服务端的 HTTP
接口用于推送消息。
在 cim-server
中的实现如下:
cim-server
收到消息后会在内部缓存中查询该 userID 的通道,接着只需要发消息即可。