Redis
Redis
五种数据类型:string
、hash
、list
、set
、zset
配置详解
daemonize:守护线程。默认为 no
port:默认 6379
bind:绑定 IP 地址
databases:数据库数量,默认 16
save
<second> <changes>
:指定多少时间、有多少次更新操作,就将数据同步到 数据文件redis 默认配置有三个条件,满足一个即进行 持久化
- save 900 1
- 900 s 有 1 个更改
- save 300 10
- 300 s 有 10 个更改
- save 60 10000
- 60 s 有 10000 更改
- save 900 1
dbfilename:指定本地数据库的文件名,默认 dump.rdb
dir:指定本地数据库的存放目录,默认为 ./ 当前文件夹
requirepass:设置密码,默认关闭
远程:
redis-cli -h <host> -p <port> -a <password>
Redis 关闭
- 使用 kill 命令( 非正常关闭,数据易丢失 )
ps -ef|grep -i redis
kill -9 PID
- 正常关闭
redis-cli shutdown
- 使用 kill 命令( 非正常关闭,数据易丢失 )
公用命令
DEL key
DUMP key
:序列化给定 key,返回被序列化的值EXISTS key
:检查 key 是否存在EXPIRE key second
:为 key 设定 过期时间TTL key
:返回 key 剩余时间PERSIST key
:移除 key 的过期时间,key 将持久保存KEY pattern
:查询所有符号给定模式的 keyRANDOM key
:随机返回一个 keyRANAME key newkey
:修改 key 的名称MOVE key db
:移动 key 至指定数据库中TYPE key
:返回 key 所储存的值的类型
EXPIRE key second 的使用场景
- 限时的优惠活动
- 网站数据缓存
- 手机验证码
- 限制网站访客频率
key 的命名建议
key
不要太长,尽量不要超过 1024 字节( 不仅消耗内存,也会降低查找的效率 )key
不要太短,太短可读性会降低- 在一个项目中,
key
最好使用统一的命名模式,如user:123:password
key
区分大小写
String
(简单动态字符串 simple dynamic string SDS)
set key_name value
:命令不区分大小写,但是 key_name 区分大小写SETNX key value
:当 key 不存在时设置 key 的值( SET if Not eXists )get key_name
GETRANGE key start end
:获取 key 中字符串的子字符串,从 start 开始,end 结束MGET key1 [key2 …]
:获取多个 keyGETSET KEY_NAME VALUE
:设定 key 的值,并返回 key 的旧值。当 key 不存在,返回 nilSTRLEN key
:返回 key 所存储的字符串的长度INCR KEY_NAME
:INCR 命令 key 中存储的值 + 1,如果不存在 key,则 key 中的值话先被初始化为 0 再加 1INCRBY KEY_NAME a
:增加 aDECR KEY_NAME
:key 中的值自减一DECRBY KEY_NAME a
:减 aappend key_name value
:字符串拼接,追加至末尾,如果不存在,为其赋值
String 的实际应用场景
缓存功能:String 字符串是最常用的数据类型,不仅仅是 Redis,各个语言都是最基本类型,因此,利用 Redis 作为缓存,配合其它数据库作为存储层,利用 Redis 支持高并发的特点,可以大大加快系统的读写速度、以及降低后端数据库的压力。
计数器:许多系统都会使用 Redis 作为系统的实时计数器,可以快速实现计数和查询的功能。而且最终的数据结果可以按照特定的时间落地到数据库或者其它存储介质当中进行永久保存。
共享用户 Session:用户重新刷新一次界面,可能需要访问一下数据进行重新登录,或者访问页面缓存 Cookie,但是可以利用 Redis 将用户的 Session 集中管理,在这种模式只需要保证 Redis 的高可用,每次用户 Session 的更新和获取都可以快速完成。大大提高效率。
Hash
- 这个是类似 Map 的一种结构,这个一般就是可以将结构化的数据,比如一个对象( 前提是这个对象没嵌套其他的对象 )给缓存在 Redis 里,然后每次读写缓存的时候,可以就操作 Hash 里的某个字段。
- 但是这个的场景其实还是多少单一了一些,因为现在很多对象都是比较复杂的,比如你的商品对象可能里面就包含了很多属性,其中也有对象
HSET key_name field value
:为指定的 key 设定 field 和 valuehmset key field value[field1,value1]
hget key field
hmget key field[field1]
hgetall key
:返回 hash 表中所有字段和值hkeys key
:获取 hash 表所有字段hlen key
:获取 hash 表中的字段数量hdel key field [field1]
:删除一个或多个 hash 表的字段- 如果 hash 的属性值被删除完,那么 hash 的 key 也会被 redis 删除
List
有序、双向链表
1 | typedef struct listNode{ |
lpush key value1 [value2]
rpush key value1 [value2]
lpushx key value
:从左侧插入值,如果list不存在,则不操作rpushx key value
:从右侧插入值,如果list不存在,则不操作llen key
:获取列表长度lindex key index
:获取指定索引的元素lrange key start stop
:获取列表指定范围的元素lpop key
:从左侧移除第一个元素prop key
:移除列表最后一个元素blpop key [key1] timeout
:移除并获取列表第一个元素,如果列表没有元素会阻塞列表到等待超时或发现可弹出元素为止brpop key [key1] timeout
:移除并获取列表最后一个元素,如果列表没有元素会阻塞列表到等待超时或发现可弹出元素为止ltrim key start stop
:对列表进行修改,让列表只保留指定区间的元素,不在指定区间的元素就会被删除lset key index value
:指定索引的值linsert key before|after world value
:在列表元素前或则后插入元素rpop lpush list1 list2
:移除 list1 最后一个元素,并将该元素添加到 list2 并返回此元素- 用此命令可以实现订单下单流程、用户系统登录注册短信等。
应用
- 可以通过 List 存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的东西。
- 可以通过 lrange 命令,读取某个闭区间内的元素,可以基于 List 实现分页查询,基于 Redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高,就一页一页走。
- 消息队列:Redis的链表结构,可以轻松实现阻塞队列,可以使用左进右出的命令组成来完成队列的设计。比如:数据的生产者可以通过 Lpush 命令从左边插入数据,多个数据消费者,可以使用 BRpop 命令阻塞的“抢”列表尾部的数据。
- 文章列表或者数据分页展示的应用。
- 博客网站的文章列表,当用户量越来越多时,而且每一个用户都有自己的文章列表,而且当文章多时,都需要分页展示,这时可以考虑使用 Redis 的列表,列表不但有序同时还支持按照范围内获取元素,可以完美解决分页查询功能。大大提高查询效率。
rpop lpush list1 list2
用此命令可以实现订单下单流程、用户系统登录注册短信等。
Set
- Set 是无序集合,会自动去重的那种。
- 直接基于 Set 将系统里需要去重的数据扔进去,自动就给去重了,如果你需要对一些数据进行快速的全局去重,你当然也可以基于 JVM 内存里的 HashSet 进行去重,但是如果你的某个系统部署在多台机器上呢?得基于Redis进行全局的 Set 去重。
- 可以基于 Set 玩儿交集、并集、差集的操作,比如交集吧,我们可以把两个人的好友列表整一个交集,看看俩人的共同好友是谁?对吧。
- 反正这些场景比较多,因为对比很快,操作也简单,两个查询一个Set搞定。
sadd key value1[value2]
:向集合添加成员scard key
:返回集合成员数smembers key
:返回集合中所有成员sismember key member
:判断memeber元素是否是集合key成员的成员srandmember key [count]
:返回集合中一个或多个随机数srem key member1 [member2]
:移除集合中一个或多个成员spop key
:移除并返回集合中的一个随机元素smove source destination member
:将member元素从source集合移动到destination集合sdiff key1 [key2]
:返回所有集合的差集sdiffstore destination key1[key2]
:返回给定所有集合的差集并存储在destination中- 对两个集合间的数据[计算]进行交集、并集、差集运算
- 以非常方便的实现如共同关注、共同喜好、二度好友等功能。对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存储到一个新的集合中。
- 利用唯一性,可以统计访问网站的所有独立 IP
Sorted Set(zset)
Sorted set 是排序的 Set,去重但可以排序,写进去的时候给一个分数,自动根据分数排序。
有序集合的使用场景与集合类似,但是set集合不是自动有序的,而Sorted set可以利用分数进行成员间的排序,而且是插入时就排序好。所以当你需要一个有序且不重复的集合列表时,就可以选择Sorted set数据结构作为选择方案。
排行榜:有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜,榜单维护可能是多方面:按照时间、按照播放量、按照获得的赞数等。
用Sorted Sets来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。
微博热搜榜,就是有个后面的热度值,前面就是名称
ZADD key score1 memeber1
ZCARD key
:获取集合中的元素数量ZCOUNT key min max
计算在有序集合中指定区间分数的成员数ZCOUNT key min max
计算在有序集合中指定区间分数的成员数ZRANK key member
:返回有序集合指定成员的索引ZREVRANGE key start stop
:返回有序集中指定区间内的成员,通过索引,分数从高到底ZREM key member [member …]
移除有序集合中的一个或多个成员ZREMRANGEBYRANK key start stop
移除有序集合中给定的排名区间的所有成员(第一名是0)(低到高排序)ZREMRANGEBYSCORE key min max
移除有序集合中给定的分数区间的所有成员常用于排行榜:
- 如推特可以以发表时间作为score来存储
- 存储成绩
- 还可以用zset来做带权重的队列,让重要的任务先执行
zset延时队列
- Zset本质就是Set结构上加了个排序的功能,除了添加数据value之外,还提供另一属性score,这一属性在添加修改元素时候可以指定,每次指定后,Zset会自动重新按新的值调整顺序。可以理解为有两列字段的数据表,一列存value,一列存顺序编号。操作中key理解为zset的名字,那么对延时队列又有何用呢?试想如果score代表的是想要执行时间的时间戳,在某个时间将它插入Zset集合中,它变会按照时间戳大小进行排序,也就是对执行时间前后进行排序,这样的话,起一个死循环线程不断地进行取第一个key值,如果当前时间戳大于等于该key值的socre就将它取出来进行消费删除,就可以达到延时执行的目的, 注意不需要遍历整个Zset集合,以免造成性能浪费。
- https://my.oschina.net/u/3266761/blog/1930360
持久化
Redis
提供了 RDB
和 AOF
两种持久化方式,RDB
是把内存中的数据集以快照形式写入磁盘,实际操作是通过 fork 子进程执行,采用二进制压缩存储;AOF
是以文本日志的形式记录 Redis 处理的每一个写入或删除操作。
RDB
把整个 Redis
的数据保存在单一文件中,比较适合用来做灾备,但缺点是快照保存完成之前如果宕机,这段时间的数据将会丢失,另外保存快照时可能导致服务短时间不可用。
AOF
对日志文件的写入操作使用的追加模式,有灵活的同步策略,支持每秒同步、每次修改同步和不同步,缺点就是相同规模的数据集,AOF
要大于 RDB
,AOF
在运行效率上往往会慢于 RDB
。
过期策略和内存淘汰策略
过期策略
- 定时过期、惰性过期、定期过期
- Redis中同时使用了惰性过期和定期过期两种过期策略
内存淘汰策略(内存不足时淘汰的策略)
- noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。
- allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。
- allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
- volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
- volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
- volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
事务
MULTI、EXEC、WATCH
事务执行过程中,如果服务端收到有EXEC、DISCARD、WATCH、MULTI之外的请求,将会把请求放入队列中排队
redis 不支持回滚
- WATCH 命令是一个乐观锁,可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。
- MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
- EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。
- 通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。
UNWATCH命令可以取消watch对所有key的监控。
主从复制
Redis
主从复制模式可以将主节点的数据同步给从节点,从而保障当主节点不可达的情况下,从节点可以作为后备顶上来,并且可以保障数据尽量不丢失(主从复制可以保障最终一致性)。第二,从节点可以扩展主节点的读能力,一旦主节点不能支持大规模并发量的读操作,从节点可以在一定程度上分担主节点的压力。- 主从复制面临的问题:
- 当主节点发生故障的时候,需要手动的将一个从节点晋升为主节点,同时通知应用方修改主节点地址并重启应用,同时需要命令其它从节点复制新的主节点,整个过程需要人工干预。
- 主节点的写能力受到单机的限制。
- 主节点的存储能力受到单机的限制。
- 原始的故障迁移
- 主节点发生故障后,客户端连接主节点失败,两个从节点与主节点连接失败造成复制中断。
- 如果主节点无法正常启动,需要选出一个从节点(slave-1),对其执行slaveof no one命令使其成为新的主节
- 原来的从节点(slave-1)成为新的主节点后,更新应用方的主节点信息,重新启动应用方。
- 客户端命令另一个从节点(slave-2)去复制新的主节点
- 待原来的主节点恢复后,让它去复制新的主节点
Redis Sentinel的高可用
- 当主节点出现故障时,Redis Sentinel 能自动完成故障发现和故障转移,并通知应用方,从而实现真正的高可用。
- Redis Sentinel 是一个分布式架构,其中包含若干个 Sentinel 节点和 Redis 数据节点,每个 Sentinel 节点会对数据节点和其余 Sentinel 节点进行监控,当它发现节点不可达时,会对节点做下线标识。如果被标识的是“主节点”,它还会和其他的Sentinel节点进行“协商”,当大多数 Sentinel 节点都认为主节点不可达时,它们会选举一个 Sentinel 节点来完成自动故障转移的工作,同时会将这个变化实时通知给Redis应用方。整个过程是自动的,不需要人工干预,解决了 Redis 的高可用问题。
- Redis Sentinel 包含了若干个 Sentinel 节点,这样做也带来了两个好处:
- 对节点的故障判断是由多个 Sentinel 节点共同完成,这样可以有效的防止误判。
- Sentinel 节点集合是由若干个 Sentinel 节点组成的,这样即使个别 Sentinel 节点不可用,整个 Sentinel 节点集合依然是健壮的。
- Redis Sentinel 具有以下几个功能:
- 监控:Sentinel 会定期检测 Redis 数据节点、其余 Sentinel 节点是否可到达
- 通知:Sentinel 会将故障转移的结果通知给应用方。
- 主节点故障转移:实现从节点晋升为主节点并维护后续正确的主从关系。
- 配置提供者:在RedisSentinel结构中,客户端在初始化的时候连接的是Sentinel节点集合,从中获取主节点信息。
Redis Sentinel拓扑结构
Redis Sentinel节点发现和监控机制
- Redis Sentinel 通过三个定时监控任务完成对各个节点的发现和监控
- 每隔 10 秒,每个 Sentinel 会向主节点和从节点发送 info 命令获取最新的拓扑结构
- 每隔 2 秒,每个 Sentinel 节点会向 Redis 数据节点的 Sentinel:hello 频道上发送该 Senitnel 节点对于主节点的判断。以及当前 Sentinel 节点的信息,同时每个 Sentinel 节点也会订阅该频道,来了解其他 Sentinel 节点以及他们对主节点的判断。这个定时任务可以完成以下两个工作:
- 发现新的 Sentinel 节点:通过订阅主节点的 Sentinel:hello 了解其他 Sentinel 节点信息。如果是新加入的 Sentinel 节点,将该 Sentinel 节点信息保存起来,并与该 Sentinel 节点创建连接
- Sentinel节点之间交换主节点状态,作为后面客观下线以及领导者选举的依据
- 每隔 1 秒,每个 Sentinel 节点会向主节点、从节点、其余 Sentinel 节点发送一条 ping 命令做一次心跳检测,来确认当前节点是否可达。与主节点,从节点,其余 Sentinel 都建立起连接,实现了对每个节点的监控。这个定时任务是节点失败判定的重要依据
Redis Sentinel部署技巧
- Sentinel 节点不应该部署在一台物理机上
- 部署至少三个且奇数个的 Sentinel 节点
- 只有一套 Sentinel,还是每个主节点配置一套 Sentinel
- 如果 Sentinel 节点集合监控的是同一个业务的多个主节点集合,那么使用方案 1,否则使用方案 2
- Redis Cluster|数据分区
- Redis 数据分区:Redis Cluster 采用虚拟槽分区,所有的键根据哈希函数映射到 0-16383 整数槽内
- 计算公式:slot=CRC16(key) &16383。每一个节点负责维护一部分槽以及槽所映射的键值数据
Redis虚拟槽分区的特点
- 解耦数据和节点之间的关系,简化了节点扩容和收缩的难度
- 节点自身维护槽的映射关系,不需要客户端或者代理服务维护槽分区元数据
- 支持节点、槽、键之间的映射查询,用于数据路由、在线伸缩等场景。
Redis Cluster
功能限制
- Key 批量操作支持有限。目前只支持同 slot 内的 key 执行批量操作(如mget,mset)
- Key事务操作支持有限。只支持多key在同一个节点上的事务操作,多个key分布在不同节点上时无法使用事务功能。
- Key作为数据分区的最小粒度,因此不能将一个大的键值对象如hash,list等映射到不同节点。
- 不支持多数据库空间,集群模式下只能使用db0空间。
- 复制结构只支持一层,从节点只能复制主节点,不支持嵌套树状复制结构。
集群伸缩
Redis集群提供了灵活的节点扩容和收缩方案,在不影响集群对外服务的情况下,可以为集群添加节点进行扩容也可以下线部分节点进行缩容。
扩容集群的步骤:
- 准备新节点
- 加入集群
- 迁移槽和数据
缩容集群的步骤:
- 首先要确定下线节点是否有负责的槽,如果是,需要把槽迁移到其他节点,保证节点下线后真个集群槽节点映射的完整性
- 当下线节点不再负责槽或者本身是从节点时,就可以通知集群内其他节点忘记下线节点,就可以通知集群内其他节点忘记下线节点当所有的节点忘记该节点后可以正常关闭。
故障发现
- Redis 集群自身实现了高可用。高可用首先需要解决集群部分失败的场景:当少数节点出现故障时,可以通过自动故障转移保证集群可以正常对外提供服务。
故障发现的类型:- 主观下线:指某个节点认为另一个节点不可用,即下线状态,这个状态并不是最终的故障判定,只能代表一个节点的意见,可能存在误判情况。
- 客观下线:指标记一个节点真正的下线,集群内多个节点都认为该节点不可用,从而达成共识的结果。如果持有槽的主节点故障,需要为该节点进行故障转移。
故障恢复
- 故障节点变为客观下线后,如果下线节点是持有槽的主节点,则需要在它的从节点中选出一个替换它。从而保证集群高可用。下线主节点的所有从节点承担故障恢复的义务,当从节点通过内部定时任务发现自身复制的主节点进入客观下线时,将会触发故障恢复流程。