,看到的不一定是你想的。一个mysql协议背后,可能是tidb;一台linux机器的背后,可能是一个精简的docker;你以为xjjdog是女人,也许他不知道;而你喊php万岁的时候,可能是研发人员跟你开了个玩笑,重写了后缀,但是后端用的是java。大家都知道redis速度快,但是它的容量跟内存容量有关,很容易达到瓶颈。有的互联网公司直接使用redis作为后台数据库(下面佩服)。当业务量激增时,面临redis容量与价格的权衡问题。来不及改业务代码,只好用一些持久化存储来模拟redis的一些数据结构。Redis支持近十种数据类型,其中最常用的有5种。string、hash、zset、set、list等。本文将讨论几种常见数据结构的常见操作的模拟实现。其实我们需要开发的是一个redisproxy代理。redis客户端连接到我们的代理后,将执行协议分析。解析出来的命令会被模拟,然后根据配置的路由定位到对应的mysql中。也就是你用的redis其实是用mysql存储数据的。没有rdb,没有aof。Redis是一个文本协议Redis是一个文本协议,协议名称是RESP。RESP是RedisSerializationProtocol的缩写。它是一种直观的文本协议,具有实现极其简单的优点,并且具有出色的解析性能。如图所示,Redis协议要传输的结构数据可以归纳为五种最小的单元类型。在每个单元的末尾统一加上回车换行符\r\n。以下是一些规则:单行字符串以+开头;多行字符串以$开头,后跟字符串长度;整数值以:开头,后面是整数的字符串形式;错误信息以-符号开头;数组以*开头,后面是数组的长度;例如下面是数组[9,9,6]的消息。*3\r\n:9\r\n:9\r\n:6\r\n所以这个协议的解析组装非常简单。以netty为例,有一个codec-redis模块供我们使用。实现:数据结构设计在数据表的设计中,我们发现kv和hash在效率上没有区别,因为可以直接根据key定位。相反,zset由于其排序功能,导致很多操作的执行效率不尽如人意。另外,由于我们的数据结构不同,使用不同的表来存储。因此,必须对每张表进行删除操作。kv设计kv,即string,是redis中最基本的数据类型。一个键对应一个值,一个字符串类型的值最多可以存储512MB。设计一个专用的数据库表rstore_kv,其中rkey为主键。rkeyvarcharvalvarcharlastTimebigintset操作insertintorstore_kv("rkey","val","lastTime")values($1,$2,$3)onduplicatekeyupdateset"val"=$2,"lastTime"=$3getoperationselectvalfromrstore_kvwhere"rkey"=$1deloperationdeletefromrstore_kvwhere"rkey"=$1existsoperationselectcount(*)asnfromrstore_kvwhere"rkey"=$1ttloperationselectlastTImefromrstore_kvwhere"rkey"=$1hash设计哈希是一组键值对(key=>value)。哈希特别适用于存储对象。设计一个专用的数据库表rstore_hash,其中rkey和hkey是联合主键。rkeyvarcharhkeyvarcharvalvarcharvalcharlastTimebiginthsetoperationinsertintorstore_hash("rkey","hkey","val","lastTime")values($1,$2,$3,$4)onduplicatekeyupdateset"val"=$3,"lastTime"=$4hgetoperationselectvalfromrstore_hashwhere"rkey"=$1and"hkey"=$2hgetalloperationselecthkey,valfromrstore_hashwhere"rkey"=$1hdeloperationdeletefromrstore_hashwhere"rkey"=$1and"hkey"=$2deloperationdeletefromrstore_hashwhere"rkey"=$1hlen,hexistsoperationselectcount(*)asnumfromrstore_hashwhere"rkey"=$1ttloperationselectmax(lastTIme)fromrstore_hashwhere"rkey"=$1zsetdesignRediszset也是和set一样的字符串类型元素的集合,不允许有重复的成员。不同之处在于每个元素都将与一个双精度类型的分数相关联。Redis使用分数将集合的成员从小到大排序。它的底层结构是跳表,效率特别高,但是占用内存大。设计一个专用的数据库表rstore_zset,其中rkey和member是联合主键。rkeyvarcharmembervarcharscoredoublelasTimebigintzadd操作insertintorstore_zset("rkey","member","score","lastTime")values($1,$2,$3,$4)onduplicatekeyupdateupdateset"score"=$3,"lastTime"=$4zscore操作selectscorefromrstore_zsetwhere"rkey"=$1and"member"=$2zrem操作deletefromrstore_zsetwhere"rkey"=$1and"member"=$2"zcard,exists操作selectcount(*)asnumfromrstore_zsetwhere"rkey"=$1zcount操作selectcount(*)asnumfromrstore_zsetwhere"rkey"=$1andscore>=$2andscore<=$3zremrangebyscore操作deletefromrstore_zsetwhere"rkey"=$1andscore>=$2andscore<=$3zrangebyscore操作selectmember,scorefromrstore_zsetwhere"rkey"=$2andscore<=$3orderbyscoreasc,memberasczrange操作selectmember,scorefromrstore_zset$1orderbyscoreascoffset$2limit$3zrank操作selectrankfrom(selectmember,rank()over(orderby"score"asc,"lastTime"asc)asrankfromrstore_zsetwhere"rkey"=$1)mwherem."member"=$2;ttloperationselectmax(lastTIme)fromrstore_zsetwhere"rkey"=$1deloperationdeletefromrstore_zsetwhere"rkey"=$1setdesignrkeyvarcharmembervarcharlastTimebigintsaddoperationinsertintorstorekey_set("","member""lastTime")values($1,$2,$3)onduplicatekeyupdateupdateset"lastTime"=$3scard操作selectcount(*)asnumfromrstore_setwhere"rkey"=$1sismember操作selectmemberfromrstore_setwhere"rkey"=$1and"member"=$2smembers操作selectmemberfromrstore_setwhere"rkey"=$1srem操作deletefromrstore_setwhere"rkey"=$1and"member"=$2del操作deletefromrstore_setwhere"rkey"=$1ttl操作selectmax(lastTIme)fromrstore_setwhere"rkey"=$1End本文只模拟了常用的还有很多常用的函数不支持的数据结构。比较明显的就是分布式锁setnx等,所以代理层的开发要ok就没那么简单了。同时,我们从模拟的角度来了解一下redis的数据结构及其在关系型数据库中的表现形式。这样才能加深我们对redis的理解,了解它存在的价值。
