1.为什么使用Redis做缓存
redis具有高性能和高并发的特点
redis为什么具有高性能,或者说redis为什么快?
1.首先redis的数据存在内存中,所以比存储磁盘上的数据库快。
2.其次redis本身使用了高效的数据结构
redis内部的基本数据类型有String , List, Set, Hash, Sorted Set
底层的数据结构:
键的数据结构
(Note:存整数时以long来存储)
String-简单动态字符串(Simple Dynamic String,SDS)
1.一个数组存储实际数据buf
2.数组占用长度alloc
3.实际使用长度len
值的数据结构
String - 简单动态字符串
List - 双向链表/压缩列表
Set - 整数数组/哈希表
Hash - 哈希表/压缩列表
SortedSet - 跳表/压缩列表
压缩列表是紧凑的数据结构,占用内存小的同时在数据量不大时访问速度也不慢。
跳表是利用了二分查找的思想,对链表建立了多级索引
键值对的组织结构:
redis维护了两个交替使用的全局hash表,由key指向value的指针。
冲突过多时进行渐进式rehash。
每处理一个请求将该索引位置的所有数据移到新表中。
3.Redis的单线程模式和网络框架
redis采用网络IO多路复用机制,在网络IO操作中能并发的处理大量的请求。
Redis的核心网络模型是单线程的单reactor模型,利用Linux系统提供的epoll/select的IO多路复用技术,在单线程的事件循环中不断处理事件,回写响应数据到客户端。
IO模型是什么?有哪些IO模型?
(152条消息) linux基础编程:IO模型:阻塞/非阻塞/IO复用 同步/异步 Select/Epoll/AIO_xiaohuima_dong的专栏-CSDN博客https://blog.csdn.net/xiaohuima_dong/article/details/45096865?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164629562316781683974719%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164629562316781683974719&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-14-45096865.pc_search_insert_es_download&utm_term=epoll%2Fselect&spm=1018.2226.3001.4187
在linux系统下, 根据IO操作是否被阻塞以及同步异步问题进行分类,可以得到5种IO模型。
1.阻塞I/O模型
2.非阻塞I/O模型
3.I/O复用模型
linux提供了select/poll/epoll 三个系统调用接口,可以将多个fd的集合传入,因此可以同时监控多个socket是否就绪。把多个IO的阻塞复用到一个select之类的阻塞上,从而使得系统在单线程的情况下同时支持处理多个请求。
区别:
select:
(1)数量有限,最多支持1024个fd。
(2) 只知道有事件,不知道在哪个IO流发生的事件,所以对返回的fd集合进行遍历
poll: 和select类似,但是没有数量的限制,fd列表由一个数组表示
epoll:
epoll_create 创建一个epoll的描述符
epoll_ctl 对监听的事件进行注册。 为fd注册回调函数,当可读可写时,发生中断,内核调用该Socket的callback函数,把该fd加入到就绪的事件链表。
epoll_wait 从内核得到事件的链表。
epoll是触发式的,没有以上的缺点。
4.信号驱动异步I/O模型
5.异步I/O模型
redis的IO多路复用提供了一定程度上的高并发的能力。
还可以通过主从复制实现读写分离提供更高程度的并发能力。
Redis的过期删除
惰性删除:客户端访问某个key的时候,如果过期了则删除
定期删除:定期执行扫描,选择随机20个key,如果过期的比例超过25%则重复操作。
Redis的淘汰策略
noeviction 对可能导致内存增大的返回错误
volatile/allkeys - lru/random
volatile-ttl 选择剩余存活时间最短的
Redis的lru是近似lru。随机采样5个key,淘汰最老的。
Redis的持久化机制
redis的持久化机制主要有RDB快照和AOF(Append-Only-File)日志
AOF日志在写命令执行完后会在磁盘记录。记录日志的频率由appendfsync配置。
Always 每次写后记录到磁盘。
Everysec 写后记录在内存缓冲区,每秒更新到磁盘
No 写后记录在缓冲区,由操作系统决定何时写到磁盘。
AOF文件过大会进行重写。
RDB快照
记录内存快照到磁盘中。使用CopyOnWrite的方式复制。
调用bgsave(), 调用fork(), 父子进程共享物理地址,发生写的时候会copy对应的page, 父子各一份。
可以执行全量快照和增量快照,也可以和全量快照和AOF文件结合使用。
周期性持久化数据仍然会丢失:解决方案-Gemfire
缓存雪崩、缓存穿透、缓存击穿
缓存雪崩:大量数据集中在某一时间失效,大量访问数据库。
(1)给key设置一个失效时间的随机波动值。
(2)多级缓存(本地缓存)
(3)热点数据永不过期
缓存穿透:key对应的数据在数据库中不存在,没有缓存值,在缓存中查询不到会到数据库中查。
(1)boolean过滤器,所有可能存在的数据hash到一个bitmap上,如果不存在会被拦截。
(2)空值也缓存到缓存里
缓存击穿:某个数据失效的一瞬间,大量数据访问数据库
(1)对数据的访问加互斥锁,当一个线程访问该数据时,其他缓存不能访问。
(2)热点数据永不过期
并发竞争key问题
多个线程的多个读写操作是非原子性的,所以会出现竞争问题。
多个线程修改同一个key。
解决方案:
1.乐观锁:
watch命令会监控某一个key,如果key被其他线程修改过则事务会回滚。(数据分片情况下不适用)
2.分布式锁
3.时间戳
4.在客户端加锁
缓存和数据库双写时的数据一致性
使用Cache Aside模式, 缓存更新时先更新数据库,然后再让缓存失效,为缓存设置过期时间。
Redis的主从同步
为什么要做主从同步?
同一份数据具有多个备份,提供高可靠性。主从库实现读写分类,提供更高的并发能力
主从同步的机制:
第一次数据同步进行全量复制:
第一步:从库向主库发出同步请求,双方协商
第二步:主库将调用bgsave()创建子进程生成RDB文件,传给从库,从库加载rdb文件
第三步:主库的replication buffer储存了RDB文件后的写操作,传给从库,从库执行这些写操作。
数据同步的维护:
基于长连接的命令传播
网络断开后的恢复
有一个环形缓冲区,主库指向写到的位置,从库指向读到的位置。断网后进行增量复制。
只能实现最终一致性,如果需要强一致性的话,可以用gemfire。
Redis的哨兵机制
redis的哨兵也是redis实例,具有三个功能
监控,选主,通知。
监控通过发送命令是否响应判断服务器工作状态。
(主观下线和客观下线)
主库客观下线要进行选主,执行主从切换
选主
选主操作的执行者称为leader, leader通过投票选举产生。
通知
通知从库与新主库建立连接。
通过发布订阅模式通知客户端与新主库建立连接
Redis的集群
数据过大,单机存储困难。运行速度慢(RDB生成时进行fork(),占用内存越大越慢)
将数据划分为多份,每份用一个redis实例来储存
Redis Cluter 方案 将所有的key映射到一个固定数量的hash槽中,将这些槽在数据库实例中进行分配。可以手动或自动分配。
客户端会存储哈希槽和实例的对应关系,查找数据时先计算哈希槽,再去对应实例中取。
Redis事务
Redis是单线程的数据库,能够单线程运行得益于
1. 数据储存在内存中,读取和修改很快(没有IO)
2.Transaction很短,修改和读取的数据少
所以Redis的事务不支持交互式的事务,而是以Stored Procedure的形式运行事务。用户将预先写好的事务代码提交,Redis以脚本的方式运行,但不额外提供事务的atomicity。
Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。
如果要执行真正的事务,可以通过LUA脚本执行。Lua脚本具有原子性。
Lua脚本和Redis的事务一样,都是完全在Server端运行,不需要IO,具有局部性执行快。