Redis
Redis数据结构及使用场景
String
- 最简单的k-v类型
- 常用命令:set,get,strlen,exists,dect,incr,setex
List
- 数组类似JAVA里面的LinkedList双向链表结构,添加、删除快,但是随即访问困难
- 常用命令:rpush,lpop,lpush,rpop,lrange
Hash
- 哈希类似JAVA里面的HashMap,但是Redis里面的Hash是采用数组加链表的结构
- 常用命令:hset,hmset,hexists,hget,hgetall,hkeys,hvals
Set
- 类似JAVA里面的HashSet,是一种无序、不可重复的结构
- 常用命令:sadd,spop,smembers,sismember,scard,sinterstore,sunion
Sorted Set
- 在Set结构上面有多加了一个score权重参数,使得集合里面的元素能够按照score进行排序
- 常用命令:zadd,zcard,zscore,zrange,zrevrange,zrem
删除策略
Redis让CPU处理的指定很多,CPU一下子处理不过来,但是删除的操作又没有那么重要,所以可以先选择被删除,先保留在内存里面,至于什么时候进行删除就是删除策略要做的事情。
- 惰性删除:每次查询的时候会对数据进行过期检查,这样做的好处是对CPU消耗比较小,但是可能造成大量过期的数据没有被删除
- 定期删除:每个一段时间抽取一批数据进行删除
Redis默认采用惰性删除+定期删除 还有Redis内存淘汰机制
Redis持久化
Redis的所有数据都是保存在内存中,然而我们也可以通过配置开启持久化的功能,一种是定时将当前数据的快照压缩成二进制文件保存(RDB);一种是把每一次的操作数据的变化都写入文件里面(AOF)
- 快照:
优点:定时生成快照文件,可以把快照文件转移到其他存储物质上面进行备份,发生灾难性故障的时候可以选择要恢复的快照文件进行恢复;
缺点:服务器会根据策略定时保存或者在接收到关闭的指令后进行保存,但是如果是服务器宕机了就会丢失数据;
定期快照配置:save 900 1 在900秒之后,如果至少有1个key发生变化,则进行内存快照(可修改参数)
- AOF:
优点:提供了3种同步策略,即每秒同步(服务器发生宕机即便选择了每秒同步也只是丢失这一秒内的数据)、每修改同步(耗费性能)、根据计算机根据机器的sync刷盘(服务器遭遇意外停机时,丢失命令的数据是不确定的)
缺点:AOF相当于操作日志,里面保存了增删改的操作命令,相对于RDB来说需要更多的存储空间,而且恢复起来也需要花费更多的时间;
AOF配置命令:
appendfsync always #每修改同步
appendfsync everysec #每秒执行一次磁盘写入
appendfsync no #根据计算机根据机器的sync刷盘
Redis事务
RRedis单个命令都是原子性的,所以这边的事务确保的是命令集合的事务性;
Redis将命令集合序列化并确保处于一个事务(命令集合连续且不被打断)的执行;
Redis不支持事务回滚;
命令:
MULTI:标记一个事务的开始,Redis会将后续的命令逐条放入队列中,然后使用EXEC这个命令原子化执行这个命令序列
EXEC:在一个事务中,执行了所有先前放入队列的命令,然后恢复正常的连接状态
WATCH [KEY...]:当某个事务需要按条件执行时,使用WATCH key将指定的键设置为受监控的状态,类似乐观锁,WATCH监控某个key后,其他线程对key进行了修改,则无法提交事务,返回null
UNWATCH:清除先前为事务监控的键,事务提交之后记得即时释放
DISCARD:清除先前放入队列的命令,并恢复连接
内存淘汰策略
当Redis内存满了或者使用超出限制了,服务就会发生内存溢出的现象(OMM),这个时候就是内存淘汰策略出场了;
八种内存淘汰策略(v6.0.8):
noeviction:默认的策略,不做其他的操作,直接返回一个写操作错误的返回;
allkeys-lru:使用LRU算法(最近最少使用的数据,以最近一次访问时间为参考)移除数据;
volatile-lru:对所有设置了过期时间的数据使用LRU算法移除数据;
allkeys-random:对所有设置了过期时间的数据随机删除;
volatile-random:对所有设置了过期时间的数据随机删除;
volatile-ttl:删除即将过期的数据;
allkeys-lfu:对所有的数据使用LFU(最近一次被访问次数最少的数据,以次数为参考)算法进行删除;
volatile-lfu:对所有设置了过期时间的数据使用LFU算法进行删除;
集群模式
Redis作为一种高性能的内存数据库,普遍用于目前主流的分布式架构系统中。为了提高系统的容错率,使用多实例的Redis也是必不可免的。
三种模式:
主从模式
哨兵模式
Cluster模式
主从模式:
Redis的主从模式指的就是主从复制,主服务器进行写操作,从服务器进行读操作,通过 SLAVEOF 命令或者配置的方式,让一个服务器去复制另一个服务器即成为它的从服务器;哨兵模式:
哨兵模式引入了一个Sentinel系统去监视主服务器及其所属的所有从服务器。一旦发现有主服务器宕机后,会自动选举其中的一个从服务器升级为新主服务器以达到故障转义的目的;Cluster模式:
它采用去无心节点方式实现,集群将会通过分片方式保存数据库中的键值对,解决了哨兵模式中写入操作都是在主节点中的性能瓶颈;主从模式
主从模式架构图:
同步流程图:
主从模式的SYNC和PSYNC的区别:
SYNC:全量同步,从服务器第一次向主服务器发送SYNC命令,主服务器将所有数据生成RDB发送给从服务器进行数据同步PSYNC:增量同步,从服务器向主服务器发起 PSYNC命令,主服务器根据双方数据的偏差量判断是否是需要完整重同步还是仅将断线期间执行过的写命令发给从服务器。
PSYNC实现增量同步过程:
1.记录复制偏移量当主服务器向从服务器发送N个字节的数据后,会将自己的复制偏移量+N。
当从服务器收到主服务器N个字节大小数据后,也会将自己的复制偏移量+N
当主从双方数据是同步时,这个偏移量是相等的。而一旦有个从服务器断线一段时间而少收到了部分数据。那么此时主从双方的服务器偏移量是不相等的,而他们的差值就是少传输的字节数量。如果少传输的数据量不是很大,没有超过主服务器的复制积压缓冲区大小,那么将会直接将缓冲区内容发送给从服务器避免完全重同步。反之还是需要完全重同步的。
2.复制积压缓冲区
复制积压缓冲区是由主服务器维护的一个先进先出的字节队列,默认大小是1mb。每当向从服务器发送写命令时,都会将这些数据存入这个队列。每个字节都会记录自己的复制偏移量。从服务器在重连时会将自己的复制偏移量发送给主服务器,如果该复制偏移量之后的数据存在于复制积压缓冲区中,则仅需要将之后的数据发送给从服务器即可。
3.记录服务器ID
当执行主从同步时,主服务器会将自己的服务器ID(一般是自动生成的UUID)发送给从服务器。从服务器在断线恢复后会判断该ID是否为当前连接的主服务器。如果是同一个ID则代表主服务器没变尝试部分重同步。如果不是同一个ID代表主服务有变动,则会与主服务器完全重同步。
具体流程图:
哨兵模式
Redis主从模式虽然能做到很好的数据备份,但是他并不是高可用的。一旦主服务器点宕机后,只能通过人工去切换主服务器。因此Redis的哨兵模式也就是为了解决主从模式的高可用方案。
哨兵模式引入了一个Sentinel系统去监视主服务器及其所属的所有从服务器。一旦发现有主服务器宕机后,会自动选举其中的一个从服务器升级为新主服务器以达到故障转义的目的。
同样的Sentinel系统也需要达到高可用,所以一般也是集群,互相之间也会监控。而Sentinel其实本身也是一个以特殊模式允许Redis服务器。
哨兵模式架构图:

哨兵模式原理:
1.Sentinel与主从服务器建立连接:Sentinel服务器启动之后便会创建于主服务器的命令连接,并订阅主服务器的sentinel:hello频道以创建订阅连接;
Sentinel默认会每10秒向主服务器发送 INFO 命令,主服务器则会返回主服务器本身的信息,以及其所有从服务器的信息;
根据返回的信息,Sentinel服务器如果发现有新的从服务器上线后也会像连接主服务器时一样,向从服务器同时创建命令连接与订阅连接;
2.判断主服务器是否下线:
每一个Sentinel服务器每秒会向其连接的所有实例包括主服务器,从服务器,其他Sentinel服务器)发送 PING命令,根据是否回复 PONG 命令来判断实例是否下线。
3.判定下线:
主观下线
如果实例在收到 PING命令的down-after-milliseconds毫秒内(根据配置),未有有效回复。则该实例将会被发起 PING命令的Sentinel认定为主观下线;
客观下线
当一台主服务器没某个Sentinel服务器判定为客观下线时,为了确保该主服务器是真的下线,Sentinel会向Sentinel集群中的其他的服务器确认,如果判定主服务器下线的Sentinel服务器达到一定数量时(一般是N/2+1),那么该主服务器将会被判定为客观下线,需要进行故障转移;
Cluster模式介绍:
Redis哨兵模式实现了高可用,读写分离,但是其主节点仍然只有一个,即写入操作都是在主节点中,这也成为了性能的瓶颈,因此Redis在3.0后加入了Cluster模式,它采用去无心节点方式实现,集群将会通过分片方式保存数据库中的键值对。Cluster架构图:

Cluster模式大概思路:
所有的redis节点彼此互联(PING-PONG机制);节点的fail是通过集群中超过半数的节点检测失效时才生效;
redis-cluster把所有的物理节点映射到[0-16383]slot上(不一定是平均分配),cluster 负责维护node,slot,value
Redis集群预分好16384个桶,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中;
新增一个节点,redis cluster的这种做法是从各个节点的前面各拿取一部分slot到新节点上;
删除一个节点也是类似,移动完成后就可以删除这个节点了;
问题
Redis为什么快
- 使用单线程,避免了上下文切换以及死锁的问题
- 数据都存在内存里面,并且是采用k-v
- 采用I/O多路复用:I/O多路复用程序将多个客户端(Socket)注册到内核里面去,由一个线程去监听每个事件(读、写)是否发生
Redis给缓存设置过期时间有什么用
因为服务器的内存是有限的,如果没有所有的东西都没有设置过期时间的话,很容易造成内存溢出
Reids如果判断数据是否过期
Redis有一个过期字段,里面保存了数据的过期时间,过期字典的键指向Redis数据库里面的某个key
缓存穿透
请求的key在Redis缓存中没有存在,直接作用在数据库上面
解决方案
- 不合法的请求直接抛出
- 缓存无效的key
- 布隆过滤器
缓存雪崩
缓存在同一时间内大面积失效或者Redis宕机,大量的请求都直接落到数据库上面
解决方案
- 采用集群,个别服务器宕机缓存也能正常使用
- 限流
- 设置不同的过期时间
- 不设置过期时间
Redis命令
String:
SET [key] [value]: 设置指定 key 的值
GET [key]: 获取指定 key 的值
GETRANGE [key] [start] [end]: 返回 key 中指定下标开始到结束的字符串值
GETSET [key] [value]: 将给定 key 的值设为 value ,并返回 key 的旧值
MGET [key...]: 获取多个给定 key 的值
SETEX [key] [seconds] [value]:设置指定key的值,并设置过期时间(单位秒)
PSETEX [key] [milliseconds] [value]:设置指定key的值,并设置过期时间(单位毫秒)
SETNX [key] [value]: 只有在 key 不存在时设置 key 的值
STRLEN [key]:返回 key 所储存的字符串值的长度
MSET [key value...]:同时设置一个或多个 key-value 对
MSETNX [key value ...]:同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在
STRLEN [key]:将 key 中储存的数字整形值增一
INCRBY [key] [increment]:将 key 所储存的数字整形值加上给定的增量值(整形)
INCRBYFLOAT [key] [increment]:将 key 所储存的数值加上给定的浮点增量值
DECR [key]: 将 key 中储存的整形数字值减一
DECRBY [key] [decrement]:key 所储存的整形数字值减去给定的减量值(整形)
APPEND [key] [value]:向指定key的值追加
Hash:
HSET [key] [field] [value]:将哈希表 key 中的字段 field 的值设为 value
HDEL [key] [field...]:删除指定key中多个字段的值
HGET [key] [field]: 获取指定key中的指定字段的值
HEXISTS [key] [field]:查询指定key中指定字段是否存在
HGETALL [key]:获取指定key的所有字段与值
HINCRBY [key] [field] [increment]:为 key 中的指定字段的整数值加上增量 (整形)
HINCRBYFLOAT [key] [field] [increment]:为key 中的指定字段的浮点数值加上增量(数字)
HKEYS [key]:获取key中所有的字段
HLEN [key]:获取key中字段的数量
HMGET [key] [field]:获取key中所有给定字段的值
HMSET [key] [field value...]:同时将多个 field-value 设置到 key 中
HSETNX [key] [field] [value]:只有在字段 field 不存在时,设置指定字段的值
List:
- RPUSHX [key] [value]:从右边为指定key添加值
- RPUSH [key] [value...]:从右边为指定key添加多个值
- LPUSHX [key] [value]:从左边为指定key添加值
- LPUSH [key] [value...]:从左边为指定key添加多个值
- LLEN [key]:获取指定key的列表长度
- LSET [key] [index] [value]:通过索引设置列表元素的值
- LRANGE [key] [start] [stop]:获取指定key从下标start到stop的值
- RPOP [key]:从右边移除并获取指定key的值
- LPOP [key]:从左边移除并获取指定key的值
- LINDEX [key] [index]:通过索引获取列表中的元素
- LREM [key] [count] [value]:
count > 0 : 从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为 COUNT
count < 0 : 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值
count < 0 : 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值
Set:
SADD key [member...]:向集合添加一个或多个成员
SCARD [key]:获取集合的成员数
SDIFF [key1] [key2...]:返回第一个集合与其他集合之间的差异
SDIFFSTORE [result_key] [key...]:返回给定所有集合的差集并存储在 指定集合中,指定的key存在会被覆盖
SINTER [key1] [key2...]:返回给定所有集合的交集
SINTERSTORE [result_key] [key...]:返回给定所有集合的交集并存储在 result_key中
SISMEMBER [key] [member]:判断 member 元素是否是集合 key 的成员
SMEMBERS [key]:返回集合中的所有成员
SMOVE [key1][key2] [member]:将 member 元素从 key1集合移动到key2集合
SPOP [key]:移除并返回集合中的一个随机元素
SRANDMEMBER [key] [count]:返回集合中一个或多个随机数
SREM [key] [member...]:移除集合中一个或多个成员
SUNION [key1] [key2...]: 返回所有给定集合的并集
SUNIONSTORE [result_key] [key1] [key2...]:所有给定集合的并集存储在 result_key集合中
Sort set:
ZADD [key] [score member...]:向有序集合添加一个或多个成员,或者更新已存在成员的分数
ZCARD [key]:获取有序集合的成员数
ZCOUNT [key] [min] [max]:计算在有序集合中指定区间分数的成员数
ZINCRBY [[key] [increment] [member]:有序集合中对指定成员的分数加上整形增量
ZINTERSTORE [result_key]numkeys key [key ...]:计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 result_key中
ZLEXCOUNT [key] [min] [max]:在有序集合中计算指定字典区间内成员数量 如ZLEXCOUNT key(a [b: 字典大于a 小于等于b区间的成员数
ZRANGE [key] [start] [stop] [score]:同等分数通过索引区间返回有序集合指定区间内的成员
ZRANGEBYLEX [key] [min] [max]:通过字典区间返回有序集合的成员
ZRANGEBYSCORE [key] [min] [max]:通过分数返回有序集合指定区间内的成员
ZRANK [key] [member]:返回有序集合中指定成员的索引
ZREM key [member ...]:移除有序集合中的一个或多个成员
ZREMRANGEBYLEX [key] [min] [max]:移除有序集合中给定的字典区间的所有成员