java后端知识点梳理——Redis
阅读原文时间:2022年02月15日阅读:1

redis都支持哪些数据类型?应用场景有哪些?

redis支持五种数据类型作为其Value,redis的Key都是字符串类型的。

  • string:redis 中字符串 value 最大可为512M。可以用来做一些计数功能的缓存(也是实际工作中最常见的)。
  • list:简单的字符串列表,按照插入顺序排序,可以添加一个元素到列表的头部(左边)或者尾部(右边),其底层实现是一个链表。可以实现一个简单消息队列功能,做基于redis的分页功能等。
  • set:是一个字符串类型的无序集合。可以用来进行全局去重等。
  • sorted set:是一个字符串类型的有序集合,给每一个元素一个固定的分数score来保持顺序。可以用来做排行榜应用或者进行范围查找等。
  • hash:键值对集合,是一个字符串类型的 Key和 Value 的映射表,也就是说其存储的Value是一个键值对(KeyValue)。可以用来存放一些具有特定结构的信息。

其实redis还支持三种特殊的数据类型,分别为BitMap、Geo和HyperLogLog

一般情况下,可以认为redis的支持的数据类型有上述五种,其底层数据结构包括:简单动态字符串,链表,字典,跳表,整数集合以及压缩列表。

redis是单线程的吗?为什么执行速度这么快?

redis是单线程的,redis的单线程是指网络请求模块使用了一个线程,所以不需考虑并发安全性。但是对于需要依赖多个操作的复合操作来说,还是需要锁的,而且有可能是分布式锁。

那么单线程的redis为什么执行速度如此之快?

  • 基于内存实现,完全内存计算
  • 单线程操作,避免了线程上下文切换操作
  • 多路I/O复用的线程模型,实现了一个线程监控多个IO流,及时响应请求
  • redis对外部的依赖比较少,属于轻量级内存数据库

redis的线程模型多路I/O复用机制是一个比较重要并且常见的考察点。目前支持I/O多路复用的系统调用有select,pselect,poll,epoll等函数。I/O多路复用就是通过一种机制一个进程可以监视多个描述符,一旦某个描述符读就绪或者写就绪,其能够通知应用程序进行相应的读写操作。

多路I/O复用机制与多进程和多线程技术相比系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

关于常见函数的特点如下所示:

  • select函数:

    • 会修改传入的参数数组,这个对于一个需要调用很多次的函数,是非常不友好的。
    • 有最大监听连接数1024个的限制
    • 如果任何一个sock(I/O stream)出现了数据,select没有返回具体是哪个返回了数据,需要采用轮询的方式去遍历获取
    • 线程不安全(当你在一个线程中已经监听该socket,另一个线程想要将该socket关闭,则结果会不可预知)
    • “If a file descriptor being monitored by select() is closed in another thread, the result is unspecified”
  • poll函数:

    • 去掉了1024的限制(使用链表搞定)
    • 不再修改传入的参数数组
    • 依然是线程不安全的
  • epoll函数:

    • epoll 不仅返回socket组里面数据,还可以确定 具体哪个socket有数据
    • 线程安全

缓存

缓存雪崩

对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。

这就是缓存雪崩。

缓存雪崩的事前事中事后的解决方案如下。

事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。

事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。

事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

用户发送一个请求,系统 A 收到请求后,先查本地 ehcache 缓存,如果没查到再查 redis。如果 ehcache 和 redis 都没有,再查数据库,将数据库中的结果,写入 ehcache 和 redis 中。

限流组件,可以设置每秒的请求,有多少能通过组件,剩余的未通过的请求,怎么办?走降级!可以返回一些默认的值,或者友情提示,或者空白的值。

好处:

  • 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。
  • 只要数据库不死,就是说,对用户来说,部分的请求都是可以被处理的。
  • 只要有部分的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来一次。

缓存穿透

对于系统A,假设一秒 5000 个请求,结果其中 4000 个请求是找不到的

比如发出的那 4000 个请求,缓存中查不到,每次你去数据库里查,也查不到。

举个栗子。数据库 id 是从 1 开始的,结果黑客发过来的请求 id 全部都是负数。这样的话,缓存中不会有,请求每次都“视缓存于无物”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。

解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 set -999 UNKNOWN。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。

缓存击穿

缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。

解决方式也很简单,可以将热点数据设置为永远不过期;或者基于 redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据。

数据库和缓存的双写一致性问题:

在高并发请求下很容易导致数据不一致的问题,如果你的业务需要保证数据的强一致性,那么建议不要使用缓存。在数据库中和缓存数据的删除或者写入过程中,如果有失败的情况,会导致数据的不一致。

解决办法:

双删延时的解决办法。可以先删除缓存数据,然后再更新数据库数据,最后再隔固定的时间再次删除缓存。

更新数据库产生的binlog订阅(使用canal)。将有变化的key记录下来,并且尝试去不断的去删除缓存(如果上次删除缓存失败)

redis的持久化方式有哪些?

将当前内存中的数据集快照写入磁盘,实现数据的持久化,恢复时可以将快照重新载入内存。

触发方式:

  • 自动触发:在配置文件中,可以配置执行了多少次save就自动触发自动持久化。
  • 手动触发:通过bgsave命令,在后台异步进行生成快照的操作,同时还可以响应客户端的请求。通过redis进程fork操作创建子进程,生成的快照由子进程负责,客户端请求只会在fork阶段被阻塞。

快照恢复:

将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务,redis会自动加载快照文件数据到内存。但是,redis 服务器在载入 RDB 文件期间,会一直处于阻塞状态,直到载入工作完成为止。

优缺点分析:

  • RDB持久化方式存在数据的丢失,因为其没有办法实现实时持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高,会影响系统性能。自动触发也存在丢失部分数据的情况。
  • 在恢复大数据集时候,RDB方式相对于AOF要快。

在 redis配置文件的 APPEND ONLY MODE 中,可以设置AOF持久化。通过记录redis服务器所执行的写命令来记录数据库状态。恢复时可以将AOF文件载入内存,并且可以通过redis-check-aof --fix 进行修复AOF文件。

AOF日志重写:

  • AOF文件会随着服务器运行的时间越来越大,可以通过AOF重写来控制AOF文件的大小。
  • AOF重写会首先读取数据库中现有的键值对状态,然后根据类型使用一条命令来替代前面对键值对操作的多条命令。
  • 使用命令 bgrewriteaof 来实现AOF重写

AOF重写缓存区:

redis 是单线程工作,当AOF文件较大时重写时间会比较长,在重写 AOF 期间,redis将长时间无法处理客户端请求。为了解决这个问题,可以将 AOF 重写程序放到子进程中执行,好处如下:

  • 子进程进行 AOF 重写期间,服务器进程(父进程)可以继续处理其它客户端请求。
  • 子进程带有父进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性。

子进程中AOF重写导致的问题:

  • 子进程在进行 AOF 重写期间,服务器进程依然可以处理其它客户端请求,这就会导致数据库状态已经发生了改变,使得当前数据库数据状态和重写后的 AOF 文件中的数据不一致。
  • 也就是出现了AOF文件和数据库中数据不一致的问题。

数据状态不一致解决办法:

  • redis 服务器设置了一个 AOF 重写缓冲区。这个缓冲区在创建子进程后开始使用,当redis服务器执行一个客户端的写请求命令,之后将这个写命令也发送到 AOF 重写缓冲区。

  • 当子进程完成 AOF 日志重写之后,给父进程发送信号,父进程接收此信号后,将 AOF 重写缓冲区的内容写到新的 AOF 文件中,保持数据的一致性。

  • AOF文件可以做到秒级持久化,使用追加写的方式来写入,可读性强并且可以使用命令进行文件修复。

  • 相比于RDB文件,同样数据下AOF文件体积要大。在redis负载较高时,秒级更新AOF文件会影响性能

  • AOF更安全,可将数据及时同步到文件中,但需要较多的磁盘IO,AOF文件尺寸较大,文件内容恢复相对较慢也更加完整。

  • RDB持久化,安全性较差,它是正常时期数据备份及 master-slave数据同步的最佳手段,文件尺寸较小并且恢复速度较快。

redis数据的过期回收策略与内存淘汰机制

redis中的数据过期回收策略使用了定期删除和惰性删除相结合的方式。

定期删除:

redis会每隔一定的时间去抽查一定量的数据判断其是否过期,过期则进行删除。

惰性删除:

在获取一个key的时候,redis会检查这个key是否已经过期,若过期,则会进行删除操作。

内存淘汰机制:

在配置文件中,我们可以对内存淘汰机制进行配置。当内存使用达到最大值时,redis可以使用的清除策略如下:

  • volatile-lru:利用LRU算法移除设置过过期时间的key (LRU:最近使用 Least Recently Used )
  • allkeys-lru:利用LRU算法移除任何key
  • volatile-random:移除设置过过期时间的随机key
  • allkeys-random:移除随机key
  • volatile-ttl:移除即将过期的key(minor TTL)
  • noeviction :不移除任何key,只是返回一个写错误 ,默认选项

主从复制简单介绍

当项目比较大的时候,我们可以使用主从架构Master/Slave机制,Master 以写为主,Slave 以读为主,Master 主节点更新后根据配置,自动同步到从机Slave 节点。

主从复制的原理包括旧版同步和命令传播,主从复制的代价就是系统复制较重的时候会导致主从延迟,并且根据CAP理论,无法同时保证服务可用性和数据一致性。

什么是CAP理论?

CAP理论是指 当网络分区发生时,一致性和可用性不可能同时保证。

  • C:Consistent 一致性
  • A:Availability 可用性
  • P:Partition tolerance 分区容忍度
  • 网络分区:分布式系统的节点往往都是分布在不同的机器上进行网络隔离开的,这意味着必然会有网络断开的风险,网络断开也就意味着发生了网络分区。
  • 最终一致性:Redis可以保证最终一致性,从节点会努力追赶主节点,最终从节点的状态会和主节点的状态将保持一致。

redis对事务支持

redis对事务的支持主要可以概括如下:

  • 隔离性:redis 是单进程的程序,保证在执行事务时,不会对事务进行中断,事务可以运行直到执行完所有事务队列中的命令为止。所以redis的事务支持隔离性。
  • redis会将一个事务中的所有命令序列化,然后按顺序执行。redis不可能在一个事务的执行过程中插入执行另一个客户端发出的请求。可以保证Redis将这些命令作为一个单独的隔离操作执行。

redis操作事务的相关命令如下所示:

  • MULTI:标记一个事务块的开始。
  • EXEC:执行所有事务块内的命令。
  • DISCARD:取消事务,放弃执行事务块内的所有命令。
  • UNWATCH:取消 WATCH 命令对所有 key 的监视。
  • WATCH key [key …]:监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

需要注意的是redis的事务不支持回滚操作,redis以 MULTI 开始一个事务,然后将多个命令入队到事务中,最后由 EXEC 命令触发事务, 一并执行事务中的所有命令。只有当被调用的redis命令有语法错误时,这条命令才会执行失败,或者对某个键执行不符合其数据类型的操作,但是应该在将命令入队列的时候就应该并且能够发现这些问题,所以redis的事务不支持进行回滚操作。

后续更新

  • 哨兵模式
  • 分布式锁
    • 关于分布式锁,为了使得加锁操作具有原子性,不可以使用多条命令来完成,我们可以使用带多个参数的set命令来完成,如下所示:jedis.set(String key, String value, String nxxx, String expx, int time)
    • 第一个为key,我们使用key来当锁,因为key是唯一的。
    • 第二个为value,我们传的是requestId,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。
    • 第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
    • 第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。
    • 第五个为time,与第四个参数相呼应,代表key的过期时间。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章