使用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场景中直接使用到的资源会随着场景 ...
随机推荐
- Day10 - D - 矿场搭建 HYSBZ - 2730
煤矿工地可以看成是由隧道连接挖煤点组成的无向图.为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处.于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖 ...
- 产品原型 UI 设计工具
产品原型设计工具 Balsamiq Mockups Axure RP 图像处理.绘制工具 ps,AI 跨平台 UI开发工具 QT , Unity3D
- Unity ShaderLab 学习笔记(一)
因为项目的问题,有个效果在iOS上面无法实现出来- 因为shader用的HardSurface的,在android上面跑起来没有问题- 以为在iOS上也不会有问题,但是悲剧啊,技能效果一片漆黑- 而且 ...
- texlive 安装
texlive 可以从下面两个网址下载 https://mirrors.tuna.tsinghua.edu.cn/CTAN/systems/texlive/Images/ https://mirror ...
- SciPy 插值
章节 SciPy 介绍 SciPy 安装 SciPy 基础功能 SciPy 特殊函数 SciPy k均值聚类 SciPy 常量 SciPy fftpack(傅里叶变换) SciPy 积分 SciPy ...
- Day6-T3
原题目 某个帝国修了一条非常非常长的城墙来抵御外敌,城墙共分N段,每一段用一个整数来描述坚固程度. 过了几年,城墙年久失修,有很多段都己经损坏,于是皇帝决定派你去修理城墙,但是经费有限. 所以你准备先 ...
- word中图片的导出
楼上说到的方法都是可行的,其实还有个更方便快捷的保存方式,特别是看到一篇word文档里有很多好看的图片想以图片格式单独保存下来观赏,用作其它,如QQ表情等,此方法更见优势:打开文档——文件——另存为— ...
- java中的几种单例模式
目前比较常见的有4种(DCL为懒汉模式的线程安全版本). 单例模式的实现一般需要满足以下条件: 1.构造方法私有化,实例属性私有化. 2.必须仅在类的内部完成实例的初始化过程. 3.提供公共静态方法, ...
- 031.SAP上查看所有的用户账号,查询SAP用户账号的后台数据库表
01. 输入事务代码SU11, 然后输入SAP用户账号数据表USER_ADDR 02. 点击实用程序,再点击内容 03.点击查询 04. 将查看到的结果通过Excel表格导出 不忘初心,如果您认为这篇 ...
- eshop5-maven 安装
1. Maven 安装 2.下载地址:https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/3.0.5/binaries/ 3. 通过ta ...