Redis基础
介绍
Redis的数据是存在内存中,是个内存数据库,所以读写速度非常快,一般用于缓存方向,另外也可以做分布式锁,甚至消息队列。
缓存处理的流程
- 如果用户请求的数据在缓存中就直接返回;
- 缓存中不存在就查看数据库是否存在;
- 数据库存在就更新缓存的数据;
- 数据库不存在直接返回空数据
为什么要使用缓存
主要是“高并发”与“高性能”
- 高并发 一般Mysql这类的数据库QPS大概1W左右,使用Redis缓存之后很容易达到10W
QPS:服务器每秒可以执行的查询次数
- 高性能
- 操作缓存就是操作内存,速度相当快
Redis常见的数据结构以及使用场景分析
- string
- 是简单key-value类型;
- 一般用于需要计数的场景,比如访问的次数,点赞和转发的数量等;
- 常用命令
set,get,strlen,exists,dect,setex
- list
- list即链表,Redis的list为双向链表,可以支持反向查询和遍历;
- 一般用于发布订阅或者消息队列;
- 常用命令
rpush,lpop,lpush,rpop,llen
- hash
- 类似于HashMap,内部实现(数组+链表)
- 一般用于存储对象数据
- 常用命令
hset,hmset,hexists,hget
- set
- 类似于HashSet,无序集合,自动去重
- 一般用于存放数据不重复的集合,比如共同好友
- 常用命令
sadd,spop,smembers,scard
- sorted set
- 有序集合自动去重
- 一般用于存放数据不重复排序的集合,比如排行榜
- 常用命令
zadd,zscore,zrange,zcard
衍生问题
- 为什么list和set使用跳表?跳表原理
1.跳表操作时间复杂度和红黑树相同
2.跳表代码实现更易读
3.跳表区间查找效率更高
为什么这么快
- 基于内存,绝大部分是存粹的内存操作
- 采用单线程,避免了不必要的上下文切换与竞争,也不存在多进程切换而消耗cpu,不用考虑加锁释放锁的问题;
- IO多路复用(非阻塞IO),IO多路复用程序会监听多个Socket,将socket放到一个队列中进行排队,每次从队列中取出一个socket给事件分派器,再把socket给对应的事件处理器
每次我们一个socket请求过来 和 redis中的 server socket建立连接后,通过IO多路复用程序,就会往队列中插入一个socket,文件事件分派器就是将队列中的socket取出来,分派到对应的处理器,在处理器处理完成后,才会从队列中在取出一个
这里也就是用一个线程,监听了客户端的所有请求,被称为Redis的单线程模型。
衍生问题:
- Redis大key问题排查以及解决方案? 大key指的是Redis的字符串类型过大,非字符串类型元素过多会产生以下问题:
- Redis阻塞:因为Redis单线程特性,如果操作某个Bigkey耗时比较久,则后面请求会被阻塞。
- 内存空间不足
- 过期时可能阻塞:如果大key设置了过期时间,当过期后,这个key被删除,如果没有过期异步删除,会存在阻塞Redis的可能性
-
如何排查? redis 可使用 redis-cli 的 “–bigkeys” 选项查找大Key
- 如何处理
- 删除大key:4.0之前使用SCAN命令读取数据再进行删除,避免线程阻塞
- 拆分:对于string类型的,可以拆分成多个key-vlaue;hash或者list可以拆分成多个hash或list
删除策略
惰性删除
主动查询是否过期,过期就删除不返回,没过期不管。
定期删除
默认100ms随机抽取一定数量设置了过期的key,检查是否过期,过期就删除;
Redis采用的定期删除+惰性删除
Redis内存相关
Redis内存默认多少?哪里查看?如何设置修改?
- 打开配置文件redis.conf,找到关键字
maxmemory
,如果没有配置或者设置最大内存大小为0.在64位操作系统下是不限制内存大小,在32位操作系统下最多使用3GB内存;maxmemory
是bytes字节类型,注意单位转换。
生产上如何配置?
- 一般推荐Redis设置内存为最大物理内存的四分之三
如何设置Redis的内存大小
- 直配置文件接在对应的
maxmemory
,后面加上大小;或者命令config set maxmemory [大小]
如何查看Redis内存使用情况
info memory
命令如果Redis打满了怎么玩办?
- 故意把Redis最大值改为1个byte试试,会报OOM异常错误
如果Redis内存使用超出了设置的最大值会怎么样?
- 引出淘汰策略
内存淘汰机制
Redis提供6种数据淘汰策略
- volatile-lru:从设置了过期时间的数据集中选出最少使用数据淘汰
- volatile-ttl:从设置了过期时间的数据集中选出要过期的数据淘汰
- volatile-random:从设置了过期时间的数据集中任意选择数据淘汰
- allkeys-lru:当内存不足写入新数据,移除最少使用的key(常用)
- allkeys-random:从数据中任意选择数据淘汰
- no-eviction:内存不足,写入数据报错
4.0后增加两种
- volatile-lfu:从设置了过期时间的数据集中选出不常用的数据淘汰
- allkeys-lfu:当内存不足写入新数据,移除最不常用使用的key
Redis的持久化
Redis支持持久化,且有两种不同的持久化操作,一般会RDB+AOF一起使用,RDB做冷备
RDB(快照)
它将生成数据集的时间点快照
原理
使用多进程COW(copy on write)。Redis在持久化会fork一个子进程,相当于在当前进程复制了一个进程;主进程和子进程会共享内存的代码块与数据段。所以快照持久化完全可以交给子进程处理,父进程继续处理客户端请求。子进程做数据持久化,不会改变现有的内存数据结构,只是对数据结构进行遍历读取并序列化到磁盘中。
- 可能产生的问题
- redis是一个单线程的程序,意味着不仅要响应用户的请求,还需要内存快照,内存快照必须进行IO操作,拖慢服务器的性能
- 持久化的同时,内存数据接口可能还在变化,结果一个请求把它删除,但是持久化才刚结束
AOF(只追加文件)
每次修改内存的操作都会被记录,类似于记录日志 配置文件./redis-server --appendonly yes
原理
aof的synv属性,在写每条指令sync磁盘,就不会丢失数据,一般使用定时sync,1s一次,这个时候最多丢失1s的数据
-可能产生的问题
- redis长期运行过程中,AOF的日志会越来越多,如果重放AOF日志非常耗时,导致Redis无法立即对外提供服务,需要对日志进行瘦身。
- Redis提供了bgwriteaof指令对aof进行日志瘦身,原理就是开辟了一个子进程对日志进行遍历转为一系列的操作指令,序列化到一个新的AOF中,序列化完成后再将操作期间的增量AOF追加到新的AOF中,追加完后立即代替旧的AOF文件
实际场景操作
重启Redis,一般很少使用RDB恢复,因为会丢失大量数据,一般使用AOF日志重放,只是AOF的性能比RDB慢很多,启动需要花费比较长的时间
Redis4.0带来了一个新的持久化选项-混合持久化。将RDB文件的内容与AOF日志放在一起,这个时候AOF不再是全量日志,而是持久化开始到持久化结束这段时间的增量日志(这部分AOF日志比较小)。即先加载RDB内容,然后重放增量aof日志。
如何保证数据库和缓存的一致性
- 先更新数据库,后删除缓存 更新数据库成功,如果删除缓存失败或者还没有来的及删除,那么,其他线程从缓存中读取到的就是旧值,还是会发生不一致。
利用消息队列,更新数据库,成功后往队列发送消息,消息达到后删除缓存,可以利用消息队列的重试机制达到最终一致性(建议使用)
缺点:需要引入第三方中间件,维护成本高,且引入消息队列需要考虑消息丢失、顺序等问题
- 先删除缓存,后更新数据库 请求A先删除Redis中的数据,然后去数据库进行更新操作,此时请求B看到Redis中的数据是空的,就会去数据库中查询,补录到缓存,但是此时请求A并没有更新成功,或者事务还未提交。
延时双删:
1.先删除Redis缓存;
2.更新数据,确保事务提交;
3.延迟删除redis缓存
集群方案
Redis Cluster (数据量大的情况)
Redis Cluster详解 可以帮助我们解决 Redis 大数据量缓存的问题,并且,也方便我们进行横向拓展(增加 Redis 机器)
Redis Cluster 通过分片(sharding)来进行数据管理,并提供复制和故障转移等功能。Redis Cluster 并没有使用一致性哈希,每一个键值对都属于一个被称作 hash slot 的东西。
通常 Redis Cluster 有 16384 hash slots ,要计算给定 key 的 hash slot ,我们只需要通过对每个 key 计算 CRC16 值,然后对 16384(hash slot的数量) 取模。
假设集群有 3 个 Redis 节点组成,每个节点负责整个集群的一部分数据:
- Node 1 : 0 - 5500 的 hash slot.
- Node 2 : 5501 - 11000 的 hash slot. .
- Node 3 : 11001 - 16383 的 hash slot..
Redis Cluster 是去中心化的,任何一台机器宕机,另外两个节点,不影响的。因为 key 找的是 hash slot,不是机器。并且,我们想要添加新的节点比如 Node4 进入 Redis Cluster 也非常方便,只需要将一些 hash slot 从 节点 Node1,Node2,Node3 移动到 Node4 即可。
主从复制(数据量小的情况)
Redis主从架构 一主(master)多从(slave)来提高可用性和读吞吐量,slave的多少取决于你的读吞吐量
- 同步方式 启动一台slave的时候,会发送一个psync命令给master,如果slave第一次连接到master,会触发一个全量复制,master会启动一个线程,生成RDB快照,会把新的请求都缓存到内存中,RDVB生成之后,master会把这个快照发送给slave,slave拿到后写进本地磁盘,然后加载进内存,master之后会把内存的缓存都发给slave
如何保证高可用
哨兵模式
Sentinel 是 Redis 的一种运行模式 ,它主要的作用就是对 Redis 运行节点进行监控。当 master 节点出现故障的时候, Sentinel 会帮助我们实现故障转移,确保整个 Redis 系统的可用性。整个过程完全自动,不需要人工介入。
- 集群监控
- 消息通知
- 故障转移
- 配置中心
- 定时监控任务
- 每隔10s,slave节点向主节点发送info命令获取最新的拓扑结构;
- 每隔2s,slave节点向某频道发送slave节点对于主节点的判断以及当前salve节点信息,每个哨兵节点会订阅频道了解其他slave节点对主节点的判断;
- 每隔1s,每个slave会向主从节点发送一条ping命令,做心跳检查,确认节点是否可达
- 主客观下线
- 主观下线,根据三个定时任务对没有回复的节点做主观下线;
- 客观下线,如果主观下线的是主节点,会咨询其他slave节点对该主节点的判断,如果超过半数,主节点客观下线;
- 选举某一哨兵节点偶为领导进行故障转移,raft算法,每个slave有一票同意权,哪个slave做出主观下线,会询问其他slave节点是否同意其为领导者,超过半数则为领导者,一般来说,谁先做出客观下线,谁就成为领导者。
Redis常见问题
缓存穿透
数据库中不存在的数据,导致每次请求都直接打到数据库,会把数据库打崩
- 解决方案
- 缓存无效的key,不存在的值,默认设置value为null,并设置失效时间(不推荐),每次请求可能会导致Redis内存打满,还有就是会缓存大量无效的key或者value
- 利用布隆过滤器,可以设置预估大小,并设置误差率,把数据放到布隆过滤器中
布隆过滤器,原理是把key值的3次hash放在hash数组上
1.一个元素如果判断结果不存在,则一定不存在;如果判断存在,元素不一定存在;
2.布隆过滤器可以添加元素,但是不能删除元素,删除元素会导致误判率增加;
3.hash数组大小越小或者数据量越大,则误差率越大(即增加数组长度 )
布窿过滤器可能存在问题,是内存占用过大,所以可以使用布窿算法降低内存占用
- 衍生问题
2个文件,里面有100亿个url,最高效率找出这两个文件的交集,分别给出精准算法和模糊算法
- 精准算法:使用分治,拆分成小文件,比如第一个A文件里面其中的url1放到第一个小文件,依次类推,B文件也如此,只需要比较A的第一个文件与B的第一个文件,依次比较;
- 模糊算法:利用布隆过滤器,A文件的信息利用hash函数放到数组里,之后B文件也进行hash,判断放到的位置为1还是为0,为1说明碰撞,为0说明不同,但是有一定的误判率
缓存雪崩
大量的hot key同时失效,导致数据全打在数据库上
- 解决方案
- 往Redis存数据的时候可以把key的失效时间加上随机值,保证数据不在同一时间大面积失效
setRedis(Key,Value,time+math.random()*1000)
- 热点数据设置永不失效
- 如果Redis集群部署,那么将热点数据分布在不同的库,但是最好还是要加上失效时间随机
- 往Redis存数据的时候可以把key的失效时间加上随机值,保证数据不在同一时间大面积失效
缓存雪崩事前事中事后解决方案
- 事前:Redis高可用,主从+哨兵,Redis cluster,避免全盘崩溃
- 事中:本地ehcache缓存 + hystrix 限流&降级,避免MySQL被打死。
- 事后:Redis持久化,一旦重启,自动从磁盘上加载数据,加速恢复缓存数据。
用户发送一个请求,A系统收到请求后,先查本地ehcache缓存,如果没有再去查Redis。如果两个都没有,再去查数据库,将数据库的结果写入echache与Redis中。 限流组件,可以设置每秒的请求,有多少能通过组建,剩余没通过的请求,可以走降级,返回一些默认值、友情提示或者空值。
优点
- 数据库绝对不会挂,组件确保每秒的请求数
- 只要数据库不挂,一半的请求都可以被处理
- 只要一半的请求可以被处理,意味着系统没有挂,可能只是影响了用户的体验。
缓存击穿
hot key 缓存突然失效,全部打中数据库
- 解决方案
- 给hot key设置永不失效,并加上互斥锁
脑裂问题
就是在主从集群中,同时有两个主节点,它们都能够接收写请求。而脑裂最直接的影响就是客户端不知道应该往那个主节点写入数据,结果就是不同的客户端往不同的主节点写入数据,严重的话,脑裂会导致数据丢失。
在主从集群机制配置项中查找是否有限制主库接收请求的设置 redis 提供了2个配置项限制主库的请求处理
- min-slaves-to-write:这个配置项设置了主库能进行数据同步的最少从库数量
- min-slaves-max-lag:这个配置项设置了主从库间进行数据复制时,从库给主库发送ACK消息的最大延迟
常用配置:
假设从库有K个,可以将min-slaves-to-write设置为K/2+1(如果K为1,就设为1),将min-slaves-max-lag设置为十几秒(如10s~20s),这个配置下,如果有一半以上的从库和主库消息延迟超过十几秒,就禁止住哭接收客户端写请求
分布式锁
setNX+Lua脚本
- 可以通过香Redis发送[set key value NX 过期时间]命令实现原子的加锁操作;比如某个客户端想要设置一个key为 “SU”的锁,此时需要执行
set SU random_value NX PX 30000
(random_value最好设置为随机数,为了更加安全释放锁,释放锁需要检测key是否存在,并且key对应的value是否相同,相同才能释放) - 如果setNX命令返回OK,说明拿到了锁,就可以做一些业务逻辑操作,完成之后释放锁调用redis的del命令删除
- 但是使用随机值无法保证原子性操作,多线程依旧有问题;比如A执行完业务逻辑,在获取锁的同时判断value是否一致,此时A的锁突然过了过期时间,导致B获得锁,还没执行的时候,A执行完后执行del方法删除B的锁,这时可以利用Lua脚本
Redission
- Redission所有指令通过lua脚本执行,Redission设置一个key的默认过期时间为30s,如果客户端持有一个锁超时,Redission有一个watchdog(看门狗),它会在获取锁之后,每隔10s把key的超时时间设为30s,这样就算一直持有锁也不会出现锁过期
- 当超过一般数量获取锁成功才算是真正的成功
zookeeper
每个客户端对方法加锁的时候,在zookeeper上的方法对于的指定节点目录下,生成唯一一个瞬时有序的节点。判断是否获取锁的方式,只需要判断是否有序节点最小的那个,当释放的时候,只需要删除这个瞬时节点即可
生产环境的部署
Redis cluster,10台机器,5台部署了Redis主实例,另外5台机器部署了Redis的从实例,每个主实例挂了一个从实例,5个节点对外提供读写服务,每个节点读写高峰QPS可以达到每秒5W,5台机器最多是每秒25W读写请求。任何一个主实例宕机,都会自动故障转移,从实例会自动变成主实例提供读写服务。分配给每一台Redis的内存是10g。
Redis给缓存数据设置过期时间的作用
因为内存是有限的,如果缓存数据一直保持,很容易OOM
Redis如何判断数据是否过期
Redis通过一个叫做过期字典(可以看作是hash表)保持数据过期时间。过期字典的键只想Redis数据库中的某个key,过期字典的值是一个long 类型的整数,保存了key所指向的数据库key的过期时间(毫秒的时间戳)
Enjoy Reading This Article?
Here are some more articles you might like to read next: