1. Redis概述
Redis是一款支持多种数据结构的内存存储系统,基于C语言编写,单线程服务,可用于数据缓存、数据存储、消息中间件等功能,支持常见数据类型有:字符串、List、Hashes表、集合、有序集合Zsort等,另外不常用的HyperLogLogs、BitMaps、GEO地理空间位置。
同时Redis服务支持内置主从负载,通过集群、哨兵等多种内置部署模式,提供服务的高可用;
Redis可以使用管道、LUA脚本来加速Redis的执行(CS模式,合并操作指令,降低TTL);
Redis仅支持LRU算法回收内存,在超过内存大小配置,按一定策略驱逐内存key,回收内存;
Redis键不存在,则创建,若聚合类型元素为空(比如LPOP或SREM等),则键被删除;
键命名很关键,过长影响性能,过短可读性较差,选择适中方式;
2. Redis数据类型说明 1
2.1. Keys操作
- 删:DEL
- 查:KEYS、EXISTS、TTL、PTTL、TYPE
- 迭代查:SCAN/HSCAN/SSCAN/ZSCAN(解决KEYS、SMEMBERS阻塞服务器)
- 改:RENAME、RENAMNX、TOUCH(更新键访问时间)
- 过期:EXPIRE、EXPIREAT、PEXPIRE、PEXPIREAT
- 持久化:PERSIST
- 迁移:MIGRATE(迁移)、MOVE
- 排序:SORT(返回列表、集合、有序集合 key 中经过排序的元素)
- 备份恢复:DUMP(导出)、RESTORE
|
|
2.2. String字符串
- 增:INCR、INCRBY、INCRBYFLOAT
- 减:DECR、DECRBY
- (批量)设置: SET、MSET、GETSET
- (批量)获取:GET、MGET、
- NX/EX: SETEX(秒级覆盖写入)、PSETEX(毫秒级)、SETNX(不存在设置)、MSETNX(批量设置不存在key)
- RANGE: SETRANGE、GETRANGE
- BIT: BITCOUNT、BITOP、SETBIT、GETBIT
- 其他:APPEND(字符串增加)、STRLEN(字符串长度)
2.3. HASH表
- 删:HDEL
- (批量)设置/获取:HSET、HMSET、HGET、HMGET(指定多个项)、HGETALL(所有的项和值)
- 改:HINCRBY、HINCRBYFLOAT(支持添加负数)
- 其他:
- HLEN(项数)、HKEYS(返回项)、HVALS(返回值)、HEXISTS(项检查)、HSCAN(迭代hash)
- HSETNX(不存在才设置)
2.4. LIST列表
- 插入列表(首、尾):LPUSH、RPUSH、LPUSHX(仅列表存在才插入)、RPUSHX
- 移除列表(首、尾):LPOP、RPOP
- 阻塞移除:BLPOP、BRPOP、BRPOPLPUSH(可做安全队列)
- 截断:LTRIM
- 查:LRANGE、LLEN、LINDEX
- 删:LREM
- 改:LSET(设置索引元素值)、LINSERT(中间插入)、
2.5. Set集合
- 增:SADD
- 查:SCARD(元素数量)、SMEMBERS(所有元素)、SSCAN(游标迭代输出)、SISMEMBER(元素存在确定)、SRANDMEMBER(随机取一个元素,不删除)
- 删:SREM(删除指定的元素)、SPOP(随机pop指定数量元素)、SMOVE(从集合A移到集合B)
- 集合操作:交(SINTER)、并(SUNION)、差(SDIFF),以及结果操作结果存储到另一集合(SINTERSTORE/SUNIONSTORE/SDIFFSTORE)
2.6. SortedSet集合
- 增:ZADD(支持键、分数检测条件添加)
- 改:ZINCRBY(增加成员得分)
- 查:ZCARD(元素数量)、ZSCORE(元素得分)
- 正向查(从低到高):ZRANGE(返回元素列表)、ZRANGEBYSCORE、ZRANGEBYLEX
- 反向查(从高到低):ZREVRANGE、ZREVRANGEBYSCORE(在指定范围内的得分元素排序返回)
- SCORE(得分):ZCOUNT、ZRANGEBYSCORE、ZREMRANGEBYSCORE(基于得分查、删)
- LEX(字典):ZLEXCOUNT、ZRANGEBYLEX、ZREMRANGEBYEX(按字典顺序查、删)
- RANK(索引):ZRANK(集合索引,从小大到,从0开始)、ZREMRANGEBYRANK(基于索引查、删)
- 删:ZREM(删除指定元素)、ZPOPMAX(弹出最高得分的N个元素)、ZPOPMIN(弹出最小N个元素)
- 集合操作:交、并
3. Redis特性支持
3.1. Pub/Sub
- 订阅:SUBSCRIBE、PSUBSCRIBE(pattern模式订阅)
- 取消订阅:UNSUBSCRIBE、PUNSUBSCRIBE
- 发布:PUBLISH
- 信息检查:PUBSUB
- PUBSUB CHANNELS [pattern]:当前活动通道情况(不含模式订阅匹配客户端 - 以PSUBSCRIBE订阅的客户端)
- PUBSUB NUMSUB [pattern]:当前普通订阅客户端数(不含模式订阅匹配客户端)
- PUBSUB NUMPAT: 查看模式订阅客户端数量
|
|
3.2. 事务处理
事务中的所有命令都被序列化并按顺序执行,要么处理所有命令,要么不处理任何命令,因此Redis事务也是原子的(但如果事务执行过程中,服务崩溃,以aof方式添加的依旧可能有部分写入的情况,可以基于redis-check-aof
修复部分写入内容)
事务用法:
- MULTI:标记事务开始,阻塞当前连接,子命令将被排队作为原子操作,并直至EXEC
- 如果在排队命令时出错,命令无法加入队列(比如不存在的命令操作),大多数客户端将中止丢弃该事务的事务;
- 如果命令排队成功,那么即使命令失败(比如操作不存在的元素
LPOP a
),队列中的所有其他命令也会被处理; - MULTI不能嵌套,否则报错
- EXEC在没有开启MULTI下,也会报错
- Redis不支持回滚
- DISCARD:重置连接为正常,刷新事务队列并退出事务;
- DISCARD后,不用在执行EXEC,类似于ROLLBACK
|
|
- WATCH:用于为Redis事务提供检查和设置(CAS Check-AND-SET)行为,进行乐观锁定,适用不太可能发生冲突的场景
- 使EXEC成为条件的命令,只有在WATCH观察的键没有修改情况下(Check),EXEC才会执行事务(Set)
- 可以向单个WATCH呼叫发送任意数量的密钥
- 可以多次调用WATCH
- 可以使用
UNWATCH
命令(不带参数)来刷新所有被监视的键 - 当EXEC被调用时,所有按键都UNWATCH
- Redis脚本是事务性,也可以基于脚本来实现事务
|
|
4. Redis内存回收过程
4.1. 过期键的删除处理
Redis 使用以下两种方式删除过期的键:
- 当一个键被访问时,程序会对这个键进行检查,如果键已经过期,那么该键将被删除。
- 底层系统会在后台渐进地查找并删除那些过期的键,从而处理那些已经过期、但是不会被访问到的键。
因此,Redis 产生expired
通知的时间为过期键被删除的时候, 而不是键的生存时间变为0的时候(可能不会立即回收)。
4.2. 内存回收策略与最大内存配置关系
Redis使用LRU算法(最近最少使用)来驱逐过期缓存(MC也是采用该缓存过期策略),支持Maxmemory最大内存配置(redis.conf配置),当达到最大内存配置,Redis服务采用不同的策略对内存进行回收,具体情况依赖于应用场景;
常见超过最大设置内存回收策略有:
- allkeys-lru:基于key的LRU算法进行回收,键可能没有过期(希望部分的子集元素将比其它其它元素被访问的更多,符合热度集中,应用不确定,优先选这款)
- allkeys-random:所有key被随机回收(符合等概率key)
- volatile-ttl:仅在已过期的键中,基于TTL最短的最新回收
- volatile-lru: 仅在已过期的键中,基于LRU算法回收
- volatile-random: 仅在已过期的键中,随机回收
回收在何时进行?当客户端发送命令时候,Redis服务会检测是否超过最大内存限制,如果超过,启用内存回收策略;
LRU不是全量,而是会有一个采样量,可以通过配置调整每次回收时检查的采样数量:maxmemory-samples 5
,使用10个采样大小的Redis 3.0的近似值已经非常接近理论的性能(消耗更多的CPU时间以实现更真实的LRU算法)。2
5. Redis分区
5.1. 分区目的/好处
- 提供更大的数据存储空间,可以将一个大的数据集散列到多个子Redis实例中;
- 提供更大的吞吐,因为不同机器可以独立提供网卡、CPU、内存资源,以提供服务;
5.2. 分区的理解
假设有RO~R3四个实例,我们存放用户数据user:1~user:n
,简单方式有:
- 按范围分区存储,比如用户1~10k存R0,10k~20k存R1…,弊端:需要映射表关系,而且后续调整不方便,同时还可能有热点问题
- 按散列分区存储,基于特定的散列函数,比如
CRC32
得到数值后,比如crc32(foobar)=93024922
,在93024922 mod 4=2
,存储在R2实例 - 基于一致性hash,在环形上面基于Hash函数初始化机器节点,针对Key值进行一致性Hash,方便在节点变更时候,缓存可以最少限度的重新调整
5.3. 分区实现(前中后实现)
- 客户端分区:由客户端直接求得与Rx实例通信
- 代理分区:代表
Twemproxy
,客户端与代理进行Redis协议通信,同时代理与真正的Redis实例Rx进行通信 - 路由查询:将查询发送到随机实例,有实例自行转发到正确的节点(Redis集群就是这个)
5.4. 分区带来的问题
- 不支持涉及多个键的操作(比如集合操作,可能已跨实例了)
- 涉及多key操作的事务无法使用
- 仅Key的粒度,无法针对大Key数据集进行拆分(比如很大的集合数据)
- 使用分区,数据被散落到多个分区上面,数据维护更加复杂(必须处理了多份RDB/AOF文件)
- 容量不容易调整,数据重新平衡代价比较高(客户端分区和代理分区基本无法支持)
5.5. 数据缓存Or存储
- 如果使用Redis作为缓存,则使用一致哈希可以轻松扩展和缩小。(比如Predis支持一致性Hash)
- 如果Redis用作存储,则使用固定的键到节点映射,因此节点数必须固定且不能变化。否则,需要一个能够在添加或删除节点时在节点之间重新平衡密钥的系统,目前只有Redis Cluster能够执行此操作
另外还有可以通过Presharding
来折中数据再分配的问题的问题
6. Redis常见应用模式
6.1. 消息队列:FIFO (阻塞和非阻塞)
- RPUSH+LPOP模式(队尾插入,队首移除)/LPUSH+RPOP模式
- 队列消费
- 轮询:耗CPU和服务Redis查询资源
- 阻塞:BRPOP/BLPOP
|
|
Tips:如果直接消费出队列,消息可能会丢失,可以采用RPOPLPUSH
放入安全队列处理
6.2. 栈:LIFO
- RPUSH+RPOP模式
|
|
6.3. 翻页:LRANGE
|
|
6.4. 时间轴timeline
场景适用:针对获取最新的N条消息内容,比如最新发布的5条帖子
利用队首插入LPUSH
,保证最后写入的是最小的内容,同时利用LTRIM
保证链表长度为5,然后利用LRANGE
提前列表内容
|
|
6.5. 循环队列,循环检服务情况
场景适用:针对需要循环处理的内容,比如监控一批Web服务的状态
可以利用RPOPLPUSH
到同一个队列,形成循环队列
|
|
6.6. TOPN排序 - ZSORT
常见电商热销榜,基于一定规则生成热销商品(比如基于浏览量或者销售量),这个值是变化的,使用LIST不灵活,可以考虑使用有序集合,并结合ZREVRANGE
提取指定TopN得分的商品或分类
|
|
6.7. 随机取值 - 扑克牌游戏(SADD+SPOP)
批量导入:
|
|