导读:前段时间有个需求是提供一个接口供客户端增量更新数据,当有数据被删除了以后客户端也需要感知到,并且要支持一定并发;

关键词:高并发,增量更新

前言

何谓增量更新,顾名思义就是只更新变化的部分,这样即经济(尤其对流量敏感型用户)又高效,比如微信朋友圈,微博的消息,头条推荐等等。要实现增量更新,首先要解决三个问题,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实现增量更新的更多相关文章

  1. [置顶]使用scrapy_redis,自动实时增量更新东方头条网全站新闻

    存储使用mysql,增量更新东方头条全站新闻的标题 新闻简介 发布时间 新闻的每一页的内容 以及新闻内的所有图片.项目文件结构. 这是run.py的内容 1 #coding=utf-8 2 from ...

  2. Elasticsearch 索引的全量/增量更新

    Elasticsearch 索引的全量/增量更新 当你的es 索引数据从mysql 全量导入之后,如何根据其他客户端改变索引数据源带来的变动来更新 es 索引数据呢. 首先用 Python 全量生成 ...

  3. 前端遇上Go: 静态资源增量更新的新实践

    前端遇上Go: 静态资源增量更新的新实践https://mp.weixin.qq.com/s/hCqQW1F8FngPPGZAisAWUg 前端遇上Go: 静态资源增量更新的新实践 原创: 洋河 美团 ...

  4. 谈谈混合 App Web 资源的打包与增量更新

    综述 移动 App 的运行环境具有带宽不稳定,流量收费,启动速度比较重要等特点,所以混合 App 如何加载 Web 资源并不是一个新问题.本文目的是总结出一种资源打包下载的思路和方案,并且提供一种打包 ...

  5. SSIS Design2:增量更新

    一般来说,ETL实现增量更新的方式有两种,第一种:记录字段的最大值,如果数据源中存在持续增加的数据列,记录上次处理的数据集中,该列的最大值:第二种是,保存HashValue,快速检查所有数据,发现异动 ...

  6. android studio增量更新

    一.概述 1.1 概念 增量更新即是通过比较 本机安装版本 和 想要安装版本 间的差异,产生一个差异安装包,不需要从官网下载并安装全量安装包,更不需要将本机已安装的版本下载,而仅仅只是安装此差异安装包 ...

  7. Android 增量更新(BSDiff / bspatch)

    Android 增量更新 BSDiff / bspatchhttp://www.daemonology.net/bsdiff/android的代码目录下 \external\bsdiff bsdiff ...

  8. 【转载】Unity 合理安排增量更新(热更新)

    原帖地址:由于我看到的那个网站发的这篇帖子很大可能是盗贴的,我就暂时不贴地址了.避免伤害原作者 原版写的有点乱,我个人修改整理了下. --------------------------------- ...

  9. Unity5 如何做资源管理和增量更新

    工具 Unity 中的资源来源有三个途径:一个是Unity自动打包资源,一个是Resources,一个是AssetBundle. Unity自动打包资源是指在Unity场景中直接使用到的资源会随着场景 ...

随机推荐

  1. C语言 Win32 获取显示设备信息

    函数 BOOL EnumDisplayDevicesA( LPCSTR lpDevice, DWORD iDevNum, PDISPLAY_DEVICEA lpDisplayDevice, DWORD ...

  2. 自动PC端显示 手机端隐藏CSS代码判断实现

    实现场景描述: 有些内容部署在PC端但是有不适合在手机端显示(比如盒子过大,遮挡内容)或者手机端显示毫无意义等.我们可以使用下面的代码来实现:电脑端显示,手机端隐藏 实现方法: CSS控制判断 @me ...

  3. Linux双网卡绑定bond详解--单网卡绑定多个IP

    Linux双网卡绑定bond详解 1 什么是bond 网卡bond是通过多张网卡绑定为一个逻辑网卡,实现本地网卡的冗余,带宽扩容和负载均衡,在生产场景中是一种常用的技术.Kernels 2.4.12及 ...

  4. 如果shell到win上出现乱码怎么办

    如果shell到win上出现这样的乱码怎么办? 如果出现了乱码,不慌一条命令搞定它!!! chcp 65001

  5. P1071 小赌怡情

    P1071 小赌怡情 转跳点:

  6. 数十万PhpStudy用户被植入后门,快来检测你是否已沦为“肉鸡”!

    北京时间9月20日,杭州公安发布<杭州警方通报打击涉网违法犯罪暨‘净网2019’专项行动战果>一文,文章曝光了国内知名PHP调试环境程序集成包“PhpStudy软件”遭到黑客篡改并植入“后 ...

  7. Windows使用Nexus搭建Maven私服

    简介 Maven私服是架设在局域网的一种特殊的远程仓库,目的是代理远程仓库及部署第三方构件,有了私服之后,当 Maven 需要下载构件时,直接请求私服,私服上存在则下载到本地仓库,否则,私服请求外部的 ...

  8. 我用Python帮朋友做了张猪肉数据分析图,结果。。。

    却发现他是这么拿我当兄弟的 事情的经过是这样的: 我开开心心的去一家烧饼店吃饭 .   ​ 抬头一看,二师兄又涨价了 叹了口气,再这么下去真的要吃不起夹肉的烧饼了 点了两个烧饼一碗馄饨 快吃完的时候, ...

  9. JAVA字符串比较问题

    在java中值类型通过==来进行比较值是否相等 而字符串作为一种引用类型,通过==是用来比较其内存位置的,使用equals才是用来比较其值是否相等 使用equals时养成将字符串放在前面的好习惯 字符 ...

  10. Most simple basic of internet programming based on two different machines sharing the same local net

    This blog is just shown for the most simple basic of internet programming based on two different mac ...