Redis的底层模型
Redis的NIO:
Redis是单线程的,底层通过IO多路复用(NIO)实现对多个文件描述符的监控,是基于epoll实现。其优点除了epoll本身不用让客户端去轮询所有的IO操作外,还在于:
- epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于 2048, 一般来说这个数目和系统内存关系很大 ,具体数目可以 cat /proc/sys/fs/file-max 察看。
- 效率提升, Epoll 最大的优点就在于它只管你“活跃”的连接 ,而跟连接总数无关,因此在实际的网络环境中, Epoll 的效率就会远远高于 select 和 poll 。
- 内存拷贝, Epoll 在这点上使用了“共享内存 ”,这个内存拷贝也省略了。
Redis的数据结构
前面我们讲到Redis的五种基本数据类型,在Redis中五种数据类型对应着八种数据结构,以应对不同应用场景下,Redis能更好地进行数据处理
1.SDS (simple synamic string):
一种名为简单动态字符串(SDS)的抽象类型。 redis中所有场景中出现的字符串,基本都是由SDS来实现的。
2.list :
双向链表
3.dict :
使用双哈希表实现的, 是一种用于保存键值对的抽象数据结构
4.zskiplist :
附加了后向指针的跳跃表 ,跳跃表是一种有序数据结构,它通过在每个节点中维持多个指向其它节点的指针,从而达到快速访问节点的目的。
5.intset :
用于存储整数数值集合的自有结构
6.ziplist :
压缩列表。 redis的列表键和哈希键的底层实现之一。此数据结构是为了节约内存而开发的。和各种语言的数组类似,它是由连续的内存块组成的,这样一来,由于内存是连续的,就减少了很多内存碎片和指针的内存占用,进而节约了内存
7.quicklist:
一种以ziplist作为结点的双链表结构
8.zipmap :
一种用于在小规模场合使用的轻量级字典结构
Redis缓存雪崩、击穿、穿透
缓存雪崩:产生的原因是缓存挂掉,这时所有的请求都会穿透到 DB。
解决方法:redis高可用(配置多个redis集群),采用熔断机制(搭配线程池),采用降级(对热点的key加锁实现)
缓存穿透:是指缓存和数据库中都没有的数据,而用户不断发起请求,我们数据库的 id 都是1开始自增上去的,如发起为id值为 -1 的数据或 id 为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大,严重会击垮数据库。
解决方法:布隆过滤器
缓存击穿:缓存雪崩是因为大面积的缓存失效,打崩了DB,而缓存击穿不同的是缓存击穿是指一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个完好无损的桶上凿开了一个洞。
解决方法:降级或者设置热点永不过期
Redis分布式锁
用set + 秒数可以实现分布式锁
因为key值不可以重复,所以当某个线程使用set + <K,V>时便能获取到该数据,当另一个线程要获取时,使用set会因为key值已经存在而操作失败,那么就实现的对数据的加锁。同时,设置过期时间,可以保证锁的释放。
Redis的同步机制
启动一台slave 的时候,他会发送一个psync命令给master ,如果是这个slave第一次连接到master,他会触发一个全量复制。master就会启动一个线程,生成RDB快照,还会把新的写请求都缓存在内存中,RDB文件生成后,master会将这个RDB发送给slave的,slave拿到之后做的第一件事情就是写进本地的磁盘,然后加载进内存,然后master会把内存里面缓存的那些新命名都发给slave。
其具体过程如下:
主从同步原理:
全同步过程:
Slave发送Sync命令到Master
Master启动一个后台进程,将Redis中的数据快照保存到文件中
Master将保存数据快照期间接收到的写命令缓存起来
Master完成写文件操作后,将该文件发送给Slave
使用新的AOF文件替换旧的AOF文件
Master将这期间收集的增量写命令发送给Slave端
增量同步
Master接收到用户的操作指令,判断是否需要传播到slave
将操作记录追加到AOF文件
将操作传播到其他Slave:1.对齐主从库2.往响应缓存写入指令
将缓存中的数据发送给Slave
Redis的过期策略(又称内存淘汰策略)
Redis的过期策略,是有定期删除+惰性删除两种。
定期删除:默认100ms就随机抽一些设置了过期时间的key,去检查是否过期,过期了就删了。
惰性删除:等到查询时才检查是否过期,过期就删除,并且不返回。类似懒汉式(用到才new)
如果,定期没删,我也没查询,那可该怎么办?
内存淘汰机制-LRU:
1.Redis系统中与LRU功能相关的配置参数有三个:
maxmemory. 该参数即为缓存数据占用的内存限制. 当缓存的数据消耗的内存超过这个数值限制时, 将触发数据淘汰. 该数据配置为0时,表示缓存的数据量没有限制, 即LRU功能不生效.
maxmemory_policy. 淘汰策略. 定义参与淘汰的数据的类型和属性.
maxmemory_samples. 随机采样的精度. 该数值配置越大, 越接近于真实的LRU算法,但是数值越大, 消耗的CPU计算时间越多,执行效率越低.
2.Redis系统提供五种淘汰策略,即参数maxmemory_policy有五种取值:
noeviction: 如果缓存数据超过了maxmemory限定值,并且客户端正在执行的命令会导致内存分配,则向客户端返回错误响应.
allkeys-lru: 所有的缓存数据(包括没有超时属性的和具有超时属性的)都参与LRU算法淘汰.
volatile-lru: 只有超时属性的缓存数据才参与LRU算法淘汰.
allkeys-random: 所有的缓存数据(包括没有超时属性的和具有超时属性的)都参与淘汰, 但是采用随机淘汰,而不是用LRU算法进行淘汰.
volatile-random: 只有超时属性的缓存数据才参与淘汰,但是采用随机淘汰,而不是用LRU算法进行淘汰.
volatile-ttl: 只有超时属性的缓存数据才参与淘汰. 根据缓存数据的超时TTL进行淘汰,而不是用LRU算法进行淘汰.
Redis 的高可用----Sentinel(哨兵):
解决主从同步Master宕机后的主从切换问题
1.监控:检查主从服务器是否运行正常
2.提醒:通过API向管理员或者其他应用程序发送故障通知
3.自动故障迁移:主从切换
Redis分布式锁
用set + 秒数可以实现分布式锁
首先我们要知道什么是分布式锁?
要介绍分布式锁,首先要提到与分布式锁相对应的是线程锁、进程锁。
线程锁:主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如synchronized是共享对象头,显示锁Lock是共享某个变量(state)。
进程锁:为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronized等线程锁实现进程锁。
分布式锁:当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。
例如:有这样一个情境,线程A和线程B都共享某个变量X。
如果是单机情况下(单JVM),线程之间共享内存,只要使用线程锁就可以解决并发问题。
如果是分布式情况下(多JVM),线程A和线程B很可能不是在同一JVM中,这样线程锁就无法起到作用了,这时候就要用到分布式锁来解决。
Redis保证数据的一致性
首先,不管是先删缓存再更新数据库还是先更新数据库再删缓存,都有一定的缺陷,需要根据实际业务场景来判断。
解决方法:通过设置队列和缓存过期时间来保证数据一致性,具体后续更新…