离线做好模型之后,线上调用,当离线模型的特征很多数据量很大,线上需要高并发快速获从redis中获取特征数据,来保证线上的实时性。
一、场景
用户特征数据(hash类型,41个field)存放redis集群中,数据量大概35G左右,由于数据量大,不能做本地缓存,如何进行大量数据keys的读取?
二、解决方法
方法1:单次循环调用
缺点:大量keys请求延迟严重,网络IO次数多O(keys),单次执行一次耗时17ms左右
方法2:pipeline调用,redisCluster版本不支持pipeline,需自己继承PipelineBase重写相应方法实现。
缺点:由于redisCluster依赖于slot进行数据存储,大量keys需要进行不同节点之间的漂移,数据请求延迟严重,出现大量读超时问题。
方法3:pipeline调用,redisCluster基于CRC16本地计算每个key的slot,然后通过slot映射到相应的node节点。基于这种方式,我们首先将keys做slot-node节点的映射,映射完成后,每个node节点可以通过pipeline的方式执行命令,最后做数据的汇总。
主要代码点:



缺点:超时读取问题偶尔还会存在,但优于方法2
方法4:基于方法3进行优化,引入多线程,由于方法3上已经做了jedisPool-keys的分组,这样就相当于每个jedisPool执行一批次的keys,jedisPool可以创建多个jedis连接,基于jedis的pipeline执行keys数据获取。
每个jedisPool执行的keys按照1000维度进行分组,多任务执行,每个任务使用一个jedis执行一个分组。
每个jedis通过pipeline执行key值获取,由于pipeline执行数据是多条命令串行执行,受执行命令数以及命令返回数据限制,我们可以按照5/10/15/20/30/40/50的batchSize维度执行pipeline,分别统计各个batchSize的平均耗时,以便得到最优的维度。
主要代码点:




多线程执行,每个线程执行1000个key,pipeline每批次执行不同数据量batchSize的数据统计分析如下:
根据业务情况,线上环境实际选择了batchSize=10,线上执行数据量耗时如下:

三、redisCluster相关名词解释
slot:普通主从redis实例没有槽位概念,分片集群在主库上分散16384个槽位,redisCluster基于CRC16本地计算每个key的slot,然后通过slot映射到相应的node节点。
集群的主节点大于15秒操作,就会阻塞,进行主从切换,主从切换槽位不会发生变更,槽位与各个节点的对应关系也不会发生变更。如果集群中主节点挂了,会进行主从切换,数据也不会丢失。
集群中的节点如果有槽位的话,节点不能被删除。
集群中添加节点,会引起槽位的变化,槽位与节点的对应关系也会发生变化。
因此方法4,还需要加上刷新集群槽位的方法。
测试发现:3节点集群,刷新槽位节点,耗时在8ms左右;5节点集群,刷新槽位节点,耗时在30ms左右。
解决方式:
1、定时任务刷新集群槽点信息,比如10s刷新一次
2、离线调用的话,在每次类的实例化过程中,初始化刷新一次
方法4改进:因为是离线调用,每次在类的实例化过程中,刷新一次
代码片段:
(1)、刷新集群槽点:

(2)、实例化:

(3)、调用:

















