上文说到mysql比较难以承受几万的tps,所以我们就选择了redis。
redis有key的失效机制,和服务发现的心跳超时失效非常贴合,天生适合啊。
问:redis做持久化吗,redis的持久化机制并不好,数据丢了怎么办
答:redis不做持久化,服务发现这种场景比较特殊,因为有心跳,数据会短时间内会恢复
方案整体架构:

方案描述:
1.service结点通过rest接口,定时(5s)向Luna server重新注册(包括结点信息和服务信息);
2.Luna server在本地维护每个服务的存活结点列表(loading cache,1分钟有效);每隔1S拉取有变更的服务列表(小于最大事件id的),并将本地loading cache失效

结点注册
结点通过注册接口每5秒重新注册一次(无状态);
a. 通过pipeline发送以下命令:getset(设置结点信息),expire(设置超时)和sadd(添加到服务结点key set),把结点注册进去;
b. 根据getset的结点决定是否需要生成一条服务列表改变事件,如果返回nil,则inc srv:event:id:max得到最大id,然后set svr:event:{maxid} 服务ID, hset [srv:info:xxx](http://srvinfoxxx/) version{maxid}
结点取消注册
a. srem 从key set中删除结点
b. del 删除结点
c. 生成服务列表改变事件: inc srv:event:id:max得到最大id,然后set svr:event:{maxid} 服务ID,hset [srv:info:xxx](http://srvinfoxxx/) version{maxid}
luna server获取服务列表
luna server定时(每1秒)读取srv:event:{min}至[srv:event](http://srveventxxx/):{max}之间的N条记录,并清除本地的loading cache,通知在等待该服务的long polling请求;
luna server在本地维护每一个服务列表的loading cache(有效期为1分钟);
当loading cache失效时,处理策略如下:
a. smembers srv:list:xxxx 获取结点列表;
b. mget node0 node1 xxx 获取全部结点信息,已经过期的结点,获取到nil;
c. 如果上一步获得到的结点信息中有nil,则将这个key从svr:list:xxxx中删除(srem node{x});
d. 对于已经删除的key,再次判断exists node{x} 如果存在,则把它再加回到svr:list:xxx中(sadd node{x});
方案的总体思路
心跳保持item节点,这个是源数据。我们关系是服务有哪些节点,就是srv:list,这个是结果数据。如果每次都要去比对这两个数据是否一致会比较麻烦。所以正常的注册 和反注册是通过产生事件,luna server和redis之间比对最大事件id就知道数据有无变化了。
但还是会有kill -9这种异常情况,所以loading cache的缓存时间不能太长,缓存失效后就会做一个检查,保证数据的一致性。
方案优化
主要的tps是心跳,心跳是 实例数 x服务数 /心跳间隔时间,如果这个能够优化,那效果就是很明显了。其实我们的实例数不是很多,就是1万左右,但是我们一个实例平均会有20个服务左右,从这个角度来说,确实是微服务了啊。但是这20个服务基本是同生共死的,因为都是同一个实例进程。
所以我们认为“实例对应服务的心跳” 可以用 “实例的心跳” 来取代。
这样tps就降低了一个数量级,1万实例/5秒心跳间隔=2000 tps,这个tps单台redis都没什么压力了。
问:loading cache失效的时候,发现服务列表数据不一致,会下线这个实例服务,如果这个实例正好上线,会有并发问题吧。
答:刚开始是想半夜3-4点的时候才会做下线操作,这个时候基本没有服务上线,这种做法并不好。选择的做法是下线完再检查一次,如果item节点又存在了,补偿注册。
问:可能多个luna server的loading cache同时失效,会有并发处理下线操作啊
答:是的,所以用loading cache的失效来做服务的下线操作是不合适的,读写应该分离,loading cache只负责读就好。
所以这就是最后采用的方案了吗?
并不是啊。这里有个重要的考虑点的。正常的注册和反注册都是1s就能告诉client,但是kill -9这种情况呢,最差是redis的key过期时间(20s)+lodingcache的失效时间(50s)=70s,这个时间太长了,已经不可以接受了。
如果用定时任务来下线失效的,上文已经算过其实tps还是挺高的,而且redis cluster没有事务,并发情况下数据一致性很难得到保证。
网友评论