Redis:Remote Dictionary Server

杂项

Redis可以用来做什么

  1. Redis最常用来做缓存,是实现分布式缓存的首先中间件。
  2. Redis作为数据库,实现诸如点赞,关注,排行等对性能要求较高的互联网需求
  3. Redis可以作为计算工具,能用很小的代价,统计诸如PV/UV、用户在线天数等数据。
  4. 可以实现分布式锁,可以作为消息队列使用。

    Redis和传统的关系型数据库有什么不同

    Redis是一种基于键值对的NoSQL数据库,而键值对的值是由多种数据结构和算法组成的。Redis的数据存储在内存中,因此他的速度惊人,读写可以达到10万/秒,远超关系型数据库。
    关系型数据库是基于二维数据表来存储数据的,它的数据格式更为严谨,并支持关系查询。关系型数据库的数据存储于磁盘上,可以存放海量的数据,但是性能远不如redis。

Redis有哪些数据类型

Redis支持5种基本数据类型:字符串,哈希,列表,集合,有序集合。
Redis还提供了Bitmap等数据类型,是基于上述核心数据类型实现的。
Redis5.0新增了Streams数据类型,它是一个功能强大的,支持多播的、可持久化的消息队列

Redis为什么这么快

  1. 对服务端来说,线程切换和锁通常是性能杀手,而redis的单线程避免了线程切换和竞争所产生的消耗。
  2. redis的大部分操作是在内存上完成的,这是它实现高性能的一个重要原因
  3. redis使用了IO多路复用机制,使其在网络IO操作中能并发处理大量的客户端请求,实现高吞吐率

image.png

Redis在持久化时fork出一个子进程,这时已经有两个进程了,怎么能说是单线程呢?
redis是单线程的,主要是指redis的网络IO和键值对读写是由一个线程来完成的。而redis的其他功能,如持久化,异步删除,集群数据同步等,则是依赖其他线程来执行的。所以,说redis是单线程的只是一种习惯的说法,事实上它的底层不是单线程的。

Redis的watch命令

很多时候,要确保事务中的数据没有被其他客户端修改才执行该事务,Redis提供了watch命令来解决这类问题,这是一种乐观锁的机制。客户端通过watch命令,要求服务器对一个或者多个key进行监视,如果在客户端执行事务之前,这些key发生了变化,则服务器将拒绝执行客户端提交的事务,并且返回一个空值。

redis 的过期策略

惰性删除:客户端访问一个key的时候,redis会先检查它的过期时间,如果发现过期就立刻删除这个key。
定期删除:redis会将设置了过期时间的key放进一个单独的字典中,并对该字典进行每秒10次的定期扫描。

过期扫描不会遍历字段中所有的key,而是采用了一种简单的贪心策略:

  1. 从过期字典中随机选择20个key
  2. 删除这20个key中已过期的key
  3. 如果已过期key的比例超过25%,则重复步骤一

如何设计redis的过期时间

  1. 热点数据不设置过期时间,避免缓存击穿问题。
  2. 在设置过期时间的时候,可以附加一个随机数,避免大量的key同时过期,导致缓存雪崩。

redis命令参考:redisdoc.com

redis的缓存淘汰策略

当写入数据即将超出maxmemory限制时,redis会采用maxmemory-policy所指定的策略进行数据淘汰。

LRU是按照最近最少使用原则来筛选数据,即最不常用的数据会被筛选出来。
LRU不足之处在于,若一个key很少被访问,只是刚刚偶尔被访问了一次,则它就被认为是热点数据,短时间内不会被淘汰。

LFU算法正式用于解决上述问题,LFU是redis4新增的淘汰策略,它根据可以最近的访问频率进行淘汰。LFU在LRU的基础上,为每个数据增加了一个计数器,来统计这个数据的访问次数。

缓存穿透

如果每次都去查一个“缓存和数据库中都必不存在的数据(如id=-1的数据)”,因为缓存中不存在,那么每次请求都会打到DB上,从而导致缓存失去意义,在高并发的情况下就可能导致数据库崩溃,这就是缓存穿透。

缓存穿透的解决方案
1、规范key过滤
规范key的命名,并且统一缓存查询的入口,在入口处对key的命名格式进行检测,过滤掉不规范key的访问,这样可以过滤掉大部分的恶意攻击。如约定项目中Redis缓存key的前缀都是以”公司名项目名_REDIS“开头,不符合这 个约定的key在一开始就过滤掉。
2、缓存空值
简单粗暴,如果查询DB返回的数据为空,我们仍然把这个空值放到Redis缓存中,只是将它的过期时间设置的很短,另外为了避免不必要的内存消耗,可以定期清理空值的key。
3、加锁
根据key从缓存中获取到的value为空时,先锁上,再去查DB将数据加载到缓存,若其它线程获取锁失败,则等待一段时间后重试,从而避免了大量请求直接打到DB。单机可以使用synchronized或ReentrantLock加锁,分布式环境需要加分布式锁,如Redis分布式锁。
4、布隆过滤器
我们想这样一个问题,如果想判断某个元素是不是在一个集合里,一般做法是将集合中所有的元素保存起来,然后通过比较确定,比如HashMap。但是随着集合中元素的增加,数据量超大时,我们需要的存储空间也越来越大,甚至超过服务器内存,这时我们就不能再用HashMap等数据结构了。
这时布隆过滤器就出场了,它的空间效率非常好,它是一个二进制向量,每一位存放的是0或1,初始时默认为0
当一个元素加入集合时,通过 K 个 Hash 函数将这个元素映射成 k 个值 :K1、K2、K3…,把向量中下标为K1、K2、K3…的位置置为1 。
比如,元素X进来,将X作为参数,通过3个hash函数的计算,分别得到3个值:Hash1(X)=5;Hash2(X)=2;Hash3(X)=9;那么我们就将布隆过滤器中下标为5、2、9的位置分别置为1
布隆过滤器根本没有存放完整的数据,只是运用一系列随机映射函数计算出位置,然后填充二进制向量,所以它的空间效率非常好。

※ 布隆过滤器是怎样解决缓存穿透的?

预先将所有缓存数据的key存放到布隆过滤器中,当一个查询请求过来的时候,先判断这个key在布隆过滤器中是否存在?
> 如果不存在,直接返回提示,都不用去查缓存更不用说DB了;
> 如果存在,则去查缓存,但我们知道布隆过滤器判断存在有一定的误判率,这里我是这样理解的,如果这个误判率针对你们的业务场景是可被接受的则可以忽略,另外我们在用Guava实现布隆过滤器的时候可以指定误判率不超过多少,你可以指定一个可被你接受的值。再或者,因为布隆过滤器可以过滤掉绝大多数的恶意key,针对少部分的漏网之鱼,我们可以在缓存层面使用功能上面说过的缓存空值或加锁的方案。

缓存击穿

缓存击穿和缓存穿透不一样!
说缓存击穿之前,我们先来了解一个概念——热点key,某个访问非常频繁,访问量非常大的一个缓存key,我们叫做热点key。
缓存击穿是指某个热点key在失效的瞬间(一般是缓存时间到期),持续的大并发请求穿破缓存,直接打到数据库,就像在一个屏障上凿开了一个洞,造成数据库压力瞬间增大,这就是缓存击穿。
缓存击穿的解决方案
1、设置热点key永不过期
2、加锁,根据热点key从缓存中获取到的value为空时,先锁上,再去查DB将数据加载到缓存,若其它线程获取锁失败,则等待一段时间后重试,从而避免了大量请求直接打到DB。单机可以使用synchronized或ReentrantLock,分布式需要加分布式锁,如Redis分布式锁。【为了不阻塞对其他key的请求,此处可以用热点key来加锁】

缓存雪崩

缓存雪崩是指缓存由于某些原因整体或者大量失效,导致大量请求打到后端数据库,从而导致数据库崩溃,整个系统崩溃,发生灾难。
导致缓存整体或大量失效的场景一般有:
1、缓存服务宕机,如Redis集群彻底崩溃;
2、在某个集中的时间段内,系统预加载的缓存集中失效了;

预防和解决缓存雪崩

1、保证缓存层服务高可用性,如使用Redis Sentinel 和 Redis Cluster,双机房部署,保证Redis服务高可用。
2、通过设置不同的过期时间,来错开缓存过期,从而避免缓存集中失效。

保证缓存与数据库双写一致性

一般情况下,我们选择的是删除缓存重新写入而不是更新缓存,频繁的更新缓存会影响服务器性能。

无论是先更新数据库再删除缓存还是先删除缓存再更新数据库,都存在着失败的可能。
先更新数据库,再删除缓存是影响最小的方案,删除缓存失败的时候,可以采取重试机制解决。

延时双删:
先删缓存,再更新数据库也可以采用延时双删:

  1. 删除缓存
  2. 更新数据库
  3. sleep N毫秒
  4. 再次删除缓存

读写分离架构的数据库就可以采用延时双删策略。

第二次删除失败了可以增加删除次数。

数据类型

redis的五大数据类型

String

List

Set

Hash

Zset

介绍一下zset类型底层的数据结构

解析配置文件

持久化

Redis支持RDB持久化、AOP持久化、RDB-AOF混合持久化这三种持久化方式。

RDB

RDB(Redis DataBase)是redis默认的持久化方式,它以快照的方式将进程数据持久化到硬盘中。RDB会创建一个经过压缩的二进制文件,文件以.rdb结尾,内部存储了各个数据库的键值对数据等信息。RDB持久化的触发方式有两种:

  • 手动触发:通过SAVE或者BGSAVE命令触发RDB持久化操作,创建.rdb文件;
  • 自动触发:通过配置选项让服务器在满足指定条件时自动执行BGSAVE命令。

其中,SAVE命令在执行期间,服务器会阻塞直到.rdb文件创建完成。
BGSAVE命令是异步版本的SAVE命令,它会使用redis服务器进程的子进程去创建.rdb文件。BGSAVE命令在创建子进程的时候会存在短暂的阻塞,之后服务器便可以继续处理其他客户端的请求。总之BGSAVE是针对SAVE阻塞问题做的优化,redis内部所有涉及RDB的操作都采用BGSAVE的方式,SAVE命令已经废弃。

BGSAVE命令的执行流程:

image.png

RDB的优缺点:

优点:RDB生成紧凑压缩的二进制文件,体积小,使用该文件恢复数据的速度比较快。
缺点:BGSAVE每次运行都要执行fork操作创建子进程,属于重量级操作,不宜频繁执行

RDB持久话没办法做到实时的持久化。

AOF

AOF(Append Only File),解决了数据持久化的实时性,是目前redis持久化的主流方式。AOF以独立日志的方式,记录了每次写入命令,重启时再重新执行AOF文件中的命令来恢复数据。
AOF的工作流程包括:命令写入,文件同步,文件重写,重启加载
image.png
AOF默认不开启,需要修改配置项来启用它。

AOF持久化的优缺点:
优点:与RDB持久化可能丢失大量的数据相比,AOF持久化的安全性要高很多。
缺点:AOF文件存储的是协议文本,它的体积要比二进制格式的.rdb文件大很多。恢复速度也比RDB慢很多。

AOF在进行重写时也需要创建子进程,在数据库体积较大时将占用大量资源,会导致服务器的短暂阻塞。

RDB-AOF混合持久化

redis从4.0开始引入了RDB-AOF混合持久化模式,这种模式是基于AOF持久化构建而来的。用户可以通过配置文件来开启AOF混合持久化。

  • 像执行BGSAVE命令一样,根据数据库当前的状态生成相应的RDB数据,并将其写入AOF文件中。
  • 对于重写之后执行的redis命令,则以协议文本的方式追加到AOF文件的末尾,即RDB数据之后。

通过使用RDB-AOF混合持久化,用户可以同时获得RDB和AOF的持久化的优点,服务器既可以通过AOF文件包含的RDB数据来实现快速的数据恢复操作,又可以通过AOF文件包含的AOF数据来将丢失数据的时间窗口限制在1s以内。

事务

发布订阅

复制

Jedis