数据类型:
特征:
类型 | 特征 |
---|---|
string | - 可以存储字符串、数字、二进制数据 - 除了值是string之外,所有的键也是string类型 - string最大存储512M的数据 |
list | - 线性有序、数据可以重复 - 支持lpush、blpush、rpop、brpop,可以当做简单的消息队列使用 - 一个list最多能存储2^32-1个元素 |
hash | - 值本身也是一个键值对结构 - 一个hash最多能存储2^32-1个元素 |
set | - 无序、不可重复 - 支持多个set求交集、并集、差集,适合实现共同关注之类的需求 - 一个set最多存储2^32-1个元素 |
zset | - 有序、不可重复、通过给每个元素设置一个score作为排序的依据 - 一个zset最多存储2^32-1个元素 |
总结 | string最大512M数据,集合最多2^32-1个元素 |
详情:
结构部分详看《redis设计与实现》
单线程IO多路复用:
单线程模型的前提:
- Redis网络IO和键值对读写是单线程操作
- Redis的其他功能,如持久化、异步删除、集群数据同步等,是依赖其他线程来执行
优点:可以简化数据结构和算法的实现,并且可以避免线程切换和竞争造成的消耗
缺点:如果某个命令执行时间过长,则会造成其他命令的阻塞
服务器基于多个socket与客户端相连,单线程(IO多路复用程序)监听的是多个socket的状态,当符合要检测的状态的时候,把这些socket放入一个队列中,然后用一个线程(文件事件分派器)给不同的事件处理器处理
IO多路复用程序和底层操作系统之间的关系:每一种实现都支持,根据操作系统选择一个进行高性能的实现
select、epoll、evport、kqueue
持久化机制:
RDB持久化(默认采用):
RDB持久化既可以手动执行也可以根据服务器配置选项定期执行,它可以将某个时间点上的数据库状态(服务器上非空数据库以及它们的键值对)保存到一个RDB文件中(快照),RDB文件是一个经过压缩的二进制文件,这个文件可以还原生成RDB文件时的数据库状态;
手动执行bgsave命令本身不是一个阻塞行为,只有在父进程fork子进程的时候阻塞,fork后解除阻塞继续执行读写操作
1.执行bgsave,查看父进程有没有子进程,如果已经有子进程则返回
2.没有子进程则父进程fork一个子进程,fork时父进程阻塞,fork后父进程解除阻塞继续响应其他的命令
3.子进程存储父进程内存中的数据到RBD文件中
4.通知父进程替换旧的RDB文件
此时有一个问题:子进程在存父进程中的数据的同时父进程也同时在执行其他读写命令,会不会产生冲突?
不会,因为redis使用操作系统底层的写时复制技术copyonwrite
内存中最小的管理单元是页,数据有很多页,比如说子进程正在读取数据所在的某一页的数据,此时父进程也在读取这一页的数据(这是没有问题的,可以同时读),但是如果此时父进程正在写这一页的数据,就会有冲突,而写时复制技术就是说谁要修改这一页的数据,就把这一页的数据copy出一份副本,然后父进程往副本中进行写操作,RDB是基于快照的形式在执行命令的那一刻把父进程中的数据锁住,如果父进程要修改就修改副本页
AOF持久化:
当我们对redis进行命令写入的时候,redis会把命令以追加的形式缓存到aof_buf缓冲区中,缓冲区中的命令最终会同步到AOF文件中,至于什么时候同步是由操作系统决定(1.每次命令写入都刷盘、2.由操作系统决定,操作系统什么时候觉得buffer满了什么时候刷盘、3.自己配置多少时间刷一次盘,一般配置1s,对于计算机来说可以接受,对于用户来说丢失一秒的数据也可以接受),重启redis服务会从AOF文件读取数据
关键的问题是:AOF文件中记录了很多命令,体积很大,怎么去重写AOF文件对其进行压缩是关键问题
AOF文件重写基于bgrewriteaof命令,这个命令是服务器自动触发的,重写步骤:
1.执行bgrewriteaof命令,如果正在进行AOF重写则返回,如果正在执行bgsave命令则推迟执行
2.父进程fork子进程,fork时子进程阻塞,fork完父进程接触阻塞继续响应命令写入然后往buffer中追加命令
3.子进程将快照数据写入到新AOF文件中
4.通知父进程替换新的AOF文件
此时有一个问题:子进程在将快照数据写入新AOF文件时,父进程也在响应其他命令,新的命令被追加到buffer中从而同步到旧的AOF文件中,然后新的AOF文件把旧的AOF文件替换后,那么旧AOF文件中新响应的数据就会丢失,丢失的数据怎么处理?
此时又有一个新的内存空间rewrite_buf,父进程解除阻塞后响应新的命令往aof_buf中追加命令的同时也将新的命令追加到rewrite_buf中
在第四步通知父进程替换新的AOF文件的时候,父进程会先把rewrite_buf中的数据同步到新的AOF文件中,然后才替换旧的AOF文件
RDB-AOF混合持久化:
- 从Redis4.0开始支持,基于AOF实现
- 在AOF重写时,执行BGSAVE命令生成RDB文件,再将新处理的命令追加到AOF文件末尾
分布式缓存:
缓存淘汰策略:
数据过期策略:
- 惰性删除
访问一个key时,Redis会先检查它的过期时间,如果过期则删除
- 定期删除
1.将设置过期时间的key放入一个独立字典中
2.对该字典进行每秒10次的扫描,并删除扫描到的过期的key
3.扫描采用贪心策略,每次随机20个key,若已过期比例超过25%则再次随机
内存淘汰策略:
当写入数据时发现已经超出maxmemory限制时,则采用指定的淘汰策略进行删除
8种内存淘汰策略:
noeviction:直接返回错误
volatile-ttl:从设置了过期时间的key中选择过期时间最小的键淘汰
volatile-random:从设置了过期时间的key中随机选择key淘汰
volatile-lru:从设置了过期时间的key中选择最近最少被访问的key淘汰
volatile-lfu:从设置了过期时间的key中选择访问次数最低的淘汰,if访问次数相同,看访问时间,访问时间早的淘汰
allkeys-random:从所有键中随机选择key淘汰
allkeys-lru:所有键中选择最近最少使用的淘汰
allkeys-lfu:所有键中选择访问次数最低的淘汰,if访问次数相同,看访问时间,访问时间早的淘汰
缓存与数据库的同步:
- 被动:缓存数据因到期被淘汰,下一次数据库的查询会将数据同步到缓存
- 主动:先更新数据库,再删除缓存,建议这种方式
先删缓存,再更新数据库,不同步
①A、B两个线程,A线程先删缓存,B线程读缓存,读不到,然后查数据库后在set到缓存中,此时A线程在修改数据库,最终导致数据不同步
②A线程先删缓存,然后更新数据库,此时B线程从缓存中get数据,get不到,然后去数据库查询,此时查询的是A线程还没有修改的数据,然后set到缓存中,之后线程A修改数据库失败后重试导致数据库数据修改成功,造成数据不同步
先更新数据库,再删缓存,同步,但是一些线程会得到旧数据
A线程先更新数据库,在删除缓存,如果删除缓存失败,此时B线程从缓存中得到的是旧数据,A线程会有重试机制使得最终缓存中的数据被删除;这种情况是缓存和数据库数据时同步的,但是一些线程会得到旧数据,无伤大雅
分布式缓存常见问题:
缓存穿透:
客户端查询根本不存在的数据
,使得请求直接到达存储层,导致其负载过大,甚至宕机;可能是业务层误将缓存和数据库中的数据删除了,也可能是有人恶意攻击,专门访问数据库中不存在的数据
解决方案:
1.缓存空对象:存储层未命中后,仍将空值存入缓存层,客户端再次访问数据时,缓存层直接返回空值
2.布隆过滤器:将数据存入布隆过滤器,访问缓存之前用过滤器拦截,若请求的数据不存在则直接返回空值
缓存击穿:
一份热点数据
,它的访问量非常大,在其缓存失效的瞬间,大量请求直达存储层,导致数据库宕机
解决方案:
1、永不过期:
设置热点数据永不过期,这是物理上的“永不过期”;
或者为每个数据设置逻辑的过期时间,当发现数据逻辑过期时,使用单独的线程重建缓存
2、加互斥锁:
为数据的访问加互斥锁(数据库层面),当一个线程访问该数据时,其他线程等待,这个线程访问过后,缓存中的数据将会被重建,届时其他线程就可以直接从缓存中取值
缓存雪崩:
在某一时刻
,缓存层无法继续提供服务,导致所有的请求直达存储层
,使数据库宕机。可能是缓存中有大量数据同时过期
,也可能是Redis节点发生故障,导致大量请求无法处理
解决方案:
1.避免数据同时过期
设置过期时间时,附加一个随机数,避免大量的key同时过期
2.启动降级和熔断措施
在发生雪崩时,若访问的数据不是核心数据,则直接返回预定义信息/空值/错误信息;
在发生雪崩时,对于访问缓存接口的请求,客户端并不会把请求发给Redis,而是直接返回
3.构建高可用的缓存服务
采用哨兵或集群的模式,部署多个Redis实例,个别节点宕机,依然可以保持服务的整体可用