使用Redis SortedSet实现增量更新
导读:前段时间有个需求是提供一个接口供客户端增量更新数据,当有数据被删除了以后客户端也需要感知到,并且要支持一定并发;
关键词:高并发,增量更新
前言
何谓增量更新,顾名思义就是只更新变化的部分,这样即经济(尤其对流量敏感型用户)又高效,比如微信朋友圈,微博的消息,头条推荐等等。要实现增量更新,首先要解决三个问题,1.如何识别数据的变化,2.如何识别增量更新的起始位置,3.如何感知数据被删除。
初步分析
首先说说如何识别数据的变化,简单来说就是每条记录都需要有一个版本信息,可能是时间戳或者是一个自增的数字,为了简答起见我选用的是时间戳,但是时间戳在并发极其高的时候可能会重复,从而导致增量更新永远结束不了,这个需要特别注意,总体来说,整个数据集需要有一个全局递增的版本号,有了这个前提才能满足第二点“识别增量更新的起始位置”,这样很容易就能想到只要每次查询的时候让前端把最后一条记录的版本信息带上来作为查询条件就可以满足需求了,至于第三点,其实就是对数据做逻辑删除。画个简答的时序图加深理解。

直抒胸臆
我分析完之后首先想到的就是使用redis的SortedSet来实现,用member存储uid,用score来存储uid的最后更新时间,借助ZRANGEBYSCORE实现增量更新。当用户信息添加或者修改的时候使用ZADD来修改uid的最后更新时间。删除的时候就稍微麻烦一些,不仅要修改最后更新时间,还需要将删除的uid保存起来,使用一个SortedSet实现不了,需要将删除的uid暂存到另一个SortedSet(set更简单,为什么不用?)中,这样在查询数据的时候判断下uid是否已被删除然后打标即可让前端感知到数据被删除。
剥茧抽丝
前面说过我会使用ZADD来修改uid对应的score,也就是最后更新时间,那这个score由谁产生呢?第一版使用的是服务器的当前时间(System.currentTimeMillis),似乎一切都很完美,但是跑了几天以后客户端反馈说有个用户的信息已经删除了,但是客户端却没有感知到,查了服务端的操作日志并没有发现什么问题,uid的score是最新的,那为什么增量接口没有感知到呢?后来在分析客户端请求日志的时候发现了一个细节,客户端携带的version(本质就是个时间戳)居然比有问题uid的score要大,那就好解释为什么查询不到了,我是将version作为ZRANGEBYSCORE的min参数来实现增量更新的,如果version已经是最新的了,就不会返回数据,那version怎么就超前了呢?前面说过我是使用服务器的当前时间作为score,有没有可能程序里取到的时间忽早忽晚呢,说到这儿可能有人不信,但这就是事实,在集群环境下时钟不是强同步的,通过一个表格来还原现场。

原因找到了,怎么解决呢?有人会说,搞单台服务器,对不起,我们毕竟是一个追求高可用的系统。用消息队列,单线程消费呢?这个倒是可行,但说实话复杂了,能不能让redis内部消化呢,我们都知道,redis执行指令内部也是排队的,如果这个score让redis生成就可以解决问题,带着问题我查看了redis的api,发现TIME命令可以满足需求,TIME命令返回两个字符串,第一个代表当前的秒,第二个代表当前这一秒已经过去的微秒,做一个简单的计算就就可以得到当前时间对应的微秒,最终的jua脚本如下:
local times=redis.call('TIME') ;
local score = times[1]*1000000+times[2] //通过计算得到当前时间的微秒
redis.call('zadd',KEYS[2],score,ARGV[1])
接着再来聊下删除操作的细节,删除时有两步操作要完成,为了确保这两步操作的原子性,还是要借助lua来实现,最终lua脚本如下:
local times=redis.call('TIME') ;
local score = times[1]*1000000+times[2] ;
redis.call('zadd',KEYS[1],score,ARGV[1]) ;//修改uid最后更新时间
redis.call('zadd',KEYS[2],score,ARGV[1]) ;//插入删除set
查询时如何给用户打标,首先通过ZRANGEBYSCORE查询出一批uid,然后遍历uid判断是否已删除,如果已删除给uid打标,考虑到性能,这里还是使用lua来实现,最终lua脚本如下:
local signlist = redis.call('zrangebyscore',KEYS[1],ARGV[1],ARGV[2],'WITHSCORES','LIMIT',ARGV[3],ARGV[4])
local signTable = {}
//使用lua脚本判断uid是否删除可以避免多次网络请求(加入将signList返回给服务端,服务端再判断)
for i = 1, #signlist, 2 do
local h = {} h['uid'] = signlist[i] h['t']= signlist[i + 1] h['status']=1
if(redis.call('ZRANK',KEYS[2],signlist[i])) then
h['status'] =0 //使用ZRANK判断uid是否在删除列表中,如果已删除标志位删除状态
end
table.insert(signTable,h)
end
local result = #signTable>0 and cjson.encode(signTable) or '[]'
return result
最后的一点优化
截止到这,整个方案可以说介绍完了,不知道细心的你有没有发现一个问题,那些逻辑删除的数据终将会成为一颗炸弹,想象一下redis被撑爆的那天,送给自己两句诗,“待到山花烂漫时,她在丛中笑”。为了避免悲剧,还是早日将优化提到日程上来吧(不知道从哪听到的一句话,程序员都抱有侥幸心理),怎么优化呢?归根节点就是要将这部分数据给清除了,但是物理删除客户端就感知不到了,似乎陷入了矛与盾的世界中。搞什么增量更新,真是麻烦啊,要不每次拉全量。每次全量绝对不行,倒是可以偶尔拉一次全量,这个偶尔最终被赋予的含义是“如果客户端两天没在线就拉全量数据,否则拉增量数据”,鉴于此客户端还需要一个最后请求时间(有人会问为什么不用最后一条记录的更新时间呢?毕竟不是一直有数据被更新啊),这样服务端就可以将两天之前逻辑删除的数据做物理删除了,前面有个疑问是“存储删除用户的uid用set不是更简单吗,为什么用SortedSet”,正好在这解答下,因为我可以很方便的找出两天之前被删除的那些uid。
总结
看似一个简单的需求,当你用心去完成的时候一定会带给你意想不到的收获,如果觉得不错,请点个推荐。
使用Redis SortedSet实现增量更新的更多相关文章
- [置顶]使用scrapy_redis,自动实时增量更新东方头条网全站新闻
存储使用mysql,增量更新东方头条全站新闻的标题 新闻简介 发布时间 新闻的每一页的内容 以及新闻内的所有图片.项目文件结构. 这是run.py的内容 1 #coding=utf-8 2 from ...
- Elasticsearch 索引的全量/增量更新
Elasticsearch 索引的全量/增量更新 当你的es 索引数据从mysql 全量导入之后,如何根据其他客户端改变索引数据源带来的变动来更新 es 索引数据呢. 首先用 Python 全量生成 ...
- 前端遇上Go: 静态资源增量更新的新实践
前端遇上Go: 静态资源增量更新的新实践https://mp.weixin.qq.com/s/hCqQW1F8FngPPGZAisAWUg 前端遇上Go: 静态资源增量更新的新实践 原创: 洋河 美团 ...
- 谈谈混合 App Web 资源的打包与增量更新
综述 移动 App 的运行环境具有带宽不稳定,流量收费,启动速度比较重要等特点,所以混合 App 如何加载 Web 资源并不是一个新问题.本文目的是总结出一种资源打包下载的思路和方案,并且提供一种打包 ...
- SSIS Design2:增量更新
一般来说,ETL实现增量更新的方式有两种,第一种:记录字段的最大值,如果数据源中存在持续增加的数据列,记录上次处理的数据集中,该列的最大值:第二种是,保存HashValue,快速检查所有数据,发现异动 ...
- android studio增量更新
一.概述 1.1 概念 增量更新即是通过比较 本机安装版本 和 想要安装版本 间的差异,产生一个差异安装包,不需要从官网下载并安装全量安装包,更不需要将本机已安装的版本下载,而仅仅只是安装此差异安装包 ...
- Android 增量更新(BSDiff / bspatch)
Android 增量更新 BSDiff / bspatchhttp://www.daemonology.net/bsdiff/android的代码目录下 \external\bsdiff bsdiff ...
- 【转载】Unity 合理安排增量更新(热更新)
原帖地址:由于我看到的那个网站发的这篇帖子很大可能是盗贴的,我就暂时不贴地址了.避免伤害原作者 原版写的有点乱,我个人修改整理了下. --------------------------------- ...
- Unity5 如何做资源管理和增量更新
工具 Unity 中的资源来源有三个途径:一个是Unity自动打包资源,一个是Resources,一个是AssetBundle. Unity自动打包资源是指在Unity场景中直接使用到的资源会随着场景 ...
随机推荐
- Day4-B-最短路径问题 HDU3790
给你n个点,m条无向边,每条边都有长度d和花费p,给你起点s终点t,要求输出起点到终点的最短距离及其花费,如果最短距离有多条路线,则输出花费最少的. Input输入n,m,点的编号是1~n,然后是m行 ...
- 使用Spring Cloud Gateway保护反应式微服务(一)
反应式编程是使你的应用程序更高效的一种越来越流行的方式.响应式应用程序异步调用响应,而不是调用资源并等待响应.这使他们可以释放处理能力,仅在必要时执行处理,并且比其他系统更有效地扩展. Java生态系 ...
- 使用six库将Python2的项目完全转移到python3
SIX是用于python2与python3兼容的库. 它存在的目的是为了拥有无需修改即可在Python 2和Python 3上同时工作的代码.话虽这么说,但是这并不代表在Python 3中引用该库就可 ...
- linux中df和du查看磁盘大小不一致解决方法
挂了一块50G到/data目录下# df -h Filesystem Size Used Avail Use% Mounted on /dev/xvdb1 50G 46G 1.2G 98% /dat ...
- Console-terminal-tty-shell-kernel
Console-terminal-tty-shell-kernel 1. 先看图表 1.1 简表 1.2 shell与内核的示意图 1.3 Console-terminal-t ...
- Redis详解(六)——哨兵机制
Redis详解(六)--哨兵机制 一.概述 Redis Sentinel是一个分布式系统,为Redis提供高可用性解决方案.可以在一个架构中运行多个 Sentinel 进程(progress), 这些 ...
- STM32 MacOS开发
CLion + STM32CubeMX + STLINK 安装CLion jetbrain官网 汉化补丁 安装homebrew ...略 安装STlink命令工具 $ brew install stl ...
- python中numpy矩阵运算操作大全(非常全)!
python中numpy矩阵运算操作大全(非常全) //2019.07.10晚python矩阵运算大全1.矩阵的输出形式:对于任何一个矩阵,python输出的模板是:import numpy as n ...
- JuJu团队12月4号工作汇报
JuJu团队12月4号工作汇报 JuJu Scrum 团队成员 今日工作 剩余任务 困难 于达 调试 无 无 婷婷 和陈灿一起提升acc 无 无 恩升 纠正chunk evaluator 无 无 ...
- NO13 Linux的基础优化-关闭SELinux功能-Linux的7种运行级别-防火墙设置-中文显示设置
壹 安装Linux系统后调优及安全设置: 1 关闭SELinux功能: [root@localhost data]# sed 's#SELINUX=enforcing#SELINUX=disable ...