@liufor
2018-03-22T07:26:13.000000Z
字数 6509
阅读 576
Redis
Redis 作者写的将Reids纳入技术栈的高级玩法。
http://oldblog.antirez.com/post/take-advantage-of-redis-adding-it-to-your-stack.html
https://news.ycombinator.com/item?id=2705475
翻译版:http://blog.csdn.net/whuqin/article/details/20065659
https://redis.io/topics/data-types
https://redis.io/topics/data-types-intro
可能会人这样问:Redis支持的数据类型有哪些,其实这样问是不严谨的。
以为Reids的编写语言C未提供符合要求的数据类型,所以Redis自建的一些数据类型,这些数据类型构成了Reids的基础。
OBJECT encoding {key} 命令查看对应key的实际存储类型, TYPE {key}是大类型。
1.SDS:简单动态字符串,不同于C的字符串(\0表示结尾的char[]),SDS虽然名义上叫做字符串,其实本质是任意 byte sequence ,并且是二进制安全的,比如一个JPEG图片,一个Java对象都可以序列化成一个SDS!!
2.list: 双链表实现的列表。支持多态。
3.hash: hash表(字典), 结构体实现,保存dictEntry数组和used、size、sizemask等属性,当发生hash碰撞时,以链表方式存储,也就是 seperate chaining(链地址法),和Java7之前的HashMap处理类似。
以为dictEntry只有指向下一个节点的next属性,没有指向上一个节点的pre属性,也就是出一个单链表,所以发生碰撞时,新节点就放在链表头部而不是尾部。
渐进式的rehash
支持多态。
4.skiplist:跳表,有序的数据结构,在每个节点中维护指向多个其他节点的指针,从而达到快速访问节点的目的。查找最快O(logn),最坏O(n)复杂度,因为有顺序,所以可能批量操作。效率和平衡树类似,但实现却更简单。代码注释里讲了其实是一个特殊编码的双链表(特殊化的list)。用整块内存存放数据,减少链表指针的内存占用。
5.intset : 只包含少量整数的set 。
127.0.0.1:6380> SADD set2 1 2 3 4
(integer) 4
127.0.0.1:6380> OBJECT encoding set2
"intset"
127.0.0.1:6380> type set2
set
127.0.0.1:6380>
6.ziplist: 压缩列表。和skiplist一样,都是为了节省内存而经过特殊编码的双链表(特殊化的list),用整块内存存放数据,减少链表指针的内存占用。
Binary-safe strings.
准确来说是SDS,简单动态字符串, Redis中所有类型的key都是SDS.
set key1 value1 : key1是SDS,value1 也是一个SDS。 SDS和普通
RPUSH fruits “apple” “banana”: 键fruits 在底层是一个SDS。 两个值也是SDS。
SDS 不是普通的字符串,是一个C写的结构。 除了len和free两个属性以外,实际内容是一个 char[] .
Jedis这种Java客户端,会将key、value序列化成 byte 序列。(可能是存文本的,也可能是二进制的),看选择的序列化器不同而不同。
Lists: collections of string elements sorted according to the order of insertion. They are basically linked lists.
实现不可靠Queue,可靠Queue,循环list
LIST 类型上操作,
非blocking版本:LPUSH + RPOP
blocking版本: LPUSH+BRPOP 。
blocking只是在POP的时候才会blocking,以为Redis的list假装是无限长度(实际有限制),所以写入是没有blocking的。
BLPOP和BRPOP返回的是一个两元素数组,因为这两个参数相比非blocking版本,支持在多个list上blocking,直到返回一个值(或者超时),所以第一个元素是弹出值的那个list的key name.
BRPOPLPUSH source dest
注意都是从右边pop,然后左边push。
当然,所谓的可靠依旧受限于Redis的持久化策略,如果宕机可能会丢失部分数据的。
http://www.cnblogs.com/laozhbook/p/redis_queue.html
LIST也能实现优先级MQ,高优先级任务用RPUSH,低优先级任务用LPUHS,这样能实现简陋版的优先级MQ,但是明显高优先级任务是FILO的,这违背了队列的FIFO原则。 但是,幸好Redis支持对多个list同时进行操作!
命令类似:BRPOP hightList1 middleList2 lowList3 0
这样会先从highList1获取任务,如果没有,再从后面的队列获取。当然这样实现的一个缺点是不能支持太多级别的优先级,不过呢,现实生活中,也不会有很多个级别的优先级。
如果的确要实现很多级别的优先级的话,可以通过LSET命令往队列中间插入处理项来解决。
动态优先级呢? 如果优先级会变动的那种,通过LIST解决不了,通过ZSET解决。
相对于LIST而言,Pub/Sub有几个劣势
1)LIST支持N个client消费不同的消息;这点PUB/SUB不支持,所有client只能同时消费同样的消息。
2)LIST相对更可靠,因为除了Redis宕机导致的数据丢失外,如果client不消费,那么消息一直在。 而PUB/SUB是不会等你的,当一个client下线后,在上线之前的数据就不会收到,如果所有client都下线了,那消息就相当于丢失了。
3)没有ACK机制,LIST和PUB/SUB都有这个问题。所以不管Redis怎样做,都不能算是一个特别好用的MQ,只能用在不怕丢失的数据上,比如日志点击分析等。
4)PUB/SUB相比LIST也有好处:能多播(multicast), 更实时(push模式)。
找到最近插入的5000条记录。如果从数据库中获取的话,是 select * from table order by time/id desc;
每次新增,LPUSH。通过 lrange 获取最新的5000条。
还可以ltrim list1 0 5000 把多余的条数删除。
FUNCTION get_latest_comments(start,num_items):
id_list = redis.lrange("latest.comments",start,start+num_items-1)
IF id_list.length < num_items
id_list = SQL_DB("SELECT ... ORDER BY time LIMIT ...")
END
RETURN id_list
END
Hashes, which are maps composed of fields associated with values. Both the field and the value are strings. This is very similar to Ruby or Python hashes.
Sets: collections of unique, unsorted string elements.
最重要的可能就是 sinter, 求交集。
Sorted sets, similar to Sets but where every string element is associated to a floating number value, called score. The elements are always taken sorted by their score, so unlike Sets it is possible to retrieve a range of elements (for example you may ask: give me the top 10, or the bottom 10).
添加元素,有序Set的要点在于每个元素都有一个表示权重的score。
ZADD hackers {score} {uid}
排序从负数到1950年
zrangebyscore hackers -inf 1950
删除1940到1960
zremrangebyscore hackers 1940 1960
zrange hackers 0 -1
redis> ZRANGE salary 0 -1 WITHSCORES # 递增排列
1) "peter"
2) "3500"
3) "tom"
4) "4000"
5) "jack"
6) "5000"
redis> ZREVRANGE salary 0 -1 WITHSCORES # 递减排列
1) "jack"
2) "5000"
3) "tom"
4) "4000"
5) "peter"
6) "3500"
ZREVRANG = z + revert + range
比如游戏积分榜:1)展示TOP 100 2)展示本用户排名(Rank)。
TOP100: ZREVRANGE hackers 0 100 WITHSCORES
或者
ZREVRANGEBYSCORE hackers min max [WITHSCORES] [LIMIT offset count]
http://redisdoc.com/sorted_set/zrangebyscore.html
还带分页功能。
Rank: ZREVRANK hackers 1112222
uid为1112222的用户排名多少。
http://redisdoc.com/sorted_set/zrevrank.html
那如何动态呢?
自增ZSET中某个元素的score,或者ZADD自动remove老的,增加新的。
ZINCRBY key increment member
http://redisdoc.com/sorted_set/zincrby.html
第1种方式是用每秒轮循ZSET来作:
把任务执行时间作为score,任务id(或者其他标识)作为元素,推入ZSET,
代码定时器每秒 ZRANGEBYSCORE tasks unix_time1 unix_time2
unix_time1是任务的开始有效期,早于这个时间的就不用做了。unix_time2是任务应该执行的时间。但是记得要清理过期任务。
如果查询到了数据,则执行任务。 这样其实有个问题,不支持多个client同时获取数据。
第2种是用 keyspance-notification 在对应key过期时自动通知:
题外话:AMQP、RabbitMQ并没有原生支持Delay Queue,但是可以通过死信来变通实现。RocketMQ支持固定时间间隔的(18个级别)定时任务(https://juejin.im/post/5919bc67570c3500695a1180)。
用时间轮算法(Netty的WheelTimer)。
http://www.rowkey.me/blog/2017/12/28/delay-trigger/
Bit arrays (or simply bitmaps): it is possible, using special commands, to handle String values like an array of bits: you can set and clear individual bits, count all the bits set to 1, find the first set or unset bit, and so forth.
BITMAP 并没有引入新的基础类型,都是基于STRING,增加了bit操作,当做 array of bit。
BITMAP通过通过特殊命令,把STRING当做 bit array 进行处理,可以设置/清除 特定bit,计数所有为1的bit,查找第一个为0/1的bit。
以为是基于STRING,而STRING是一个binary-safe 但最大长度是2^32, 512MB, 所以BITMAP也最多支持 2^32 个bit. 单个值操作(SETBIT/GETBIT)都是O(1)的,
BITMAP通常使用:
1)所有类型的实时统计
2)存放跟ObjectID相关的bool信息,能极大节省空间
可以用Bitmap实现BloomFilter,比如这个 https://github.com/erikdubbelboer/Redis-Lua-scaling-bloom-filter 。
又有人通过Redis Module实现了BloomFilter.
HyperLogLogs: this is a probabilistic data structure which is used in order to estimate the cardinality of a set. Don't be scared, it is simpler than it seems... See later in the HyperLogLog section of this tutorial.
和BITMAP一样,HYPERLOGLOG并没有引入新的基础类型,都是基于STRING。
这是一个概率数据类型,用来统计基数(cardinality),基数:集合中不同值的个数。 说他是个概率的,原因是他并不能精确的计算cardinality, 允许有误差率(standard error 小于1%) ,和BITMAP一样,特别省内存,HLL最多耗费 12 bytes。
Redis4.0以后,支持动态模块,于是出现了很多二方三方模块,增加了其他的数据类型。这在官方的https://redis.io/modules 上可以查看。
Redis Module支持有C binding的语言,比如C、C++、Rust、Go等。
三方支持BloomFitler、CuckooFilter、全文索引、时间序列、
LIST 类型上操作,
非blocking版本:LPUSH + RPOP
blocking版本: LPUSH+BRPOP 。
blocking只是在POP的时候才会blocking,以为Redis的list假装是无限长度(实际有限制),所以写入是没有blocking的。
BLPOP和BRPOP返回的是一个两元素数组,因为这两个参数相比非blocking版本,支持在多个list上blocking,直到返回一个值(或者超时),所以第一个元素是弹出值的那个list的key name.
BRPOPLPUSH source dest
注意都是从右边pop,然后左边push。
当source和dest可以是同一个时,就成为了一个循环处理list,并且牛逼在于,可以多个client同时执行,获取元素不会重复,直到所有元素都处理后,然后处理过程就重启了,重新搞一遍了。
在处理过程中,也可以让list中加入新元素。
https://redis.io/commands/rpoplpush
#Reids的线程模式
Reactor的典型应用,单线程单线程,主要请求任务是基于事件的单线程的,其实还有另外2个做后台任务的线程。
Redis基础知识大略可以看这个文章
参考这个文章:https://cloud.tencent.com/developer/article/1004464