分享

【中间件】Redis

 悦光阴 2022-11-12 发布于北京

概述

Redis 是速度非常快的 非关系型(NoSQL) 内存 键值 数据库。

Redis 支持很多特性:例如数据持久化,使用复制来扩展读性能,使用分片来扩展写性能,Redis Cluster 实现了分布式的支持。

内存管理机制:在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘。

使用场景

  1. 缓存
  2. 会话缓存(Session)
  3. 查找表
  4. 计数器
  5. List可以作为消息队列使用,但是最好用第三方消息队列中间件
  6. Set 可以实现交集、并集等操作,从而实现共同好友等功能。
  7. ZSet 可以实现有序性操作,从而实现排行榜等功能。
  8. 分布式锁实现(利用 SETNX 命令,或官方提供的 RedLock)

数据类型

Redis 可以存储键和五种不同类型的值之间的映射。

的类型只能为字符串。

支持五种数据类型:字符串、列表、集合、散列表、有序集合。

数据类型 可以存储的值 操作
STRING 字符串、整数、浮点数 对整个字符串或者字符串的其中一部分执行操作
对整数和浮点数执行自增或者自减操作
LIST 列表 从两端压入或者弹出元素
对单个或者多个元素进行修剪,
只保留一个范围内的元素
SET 无序集合 添加、获取、移除单个元素
检查一个元素是否存在于集合中
计算交集、并集、差集
从集合里面随机获取元素
HASH 包含键值对的无序散列表 添加、获取、移除单个键值对
获取所有键值对
检查某个键是否存在
ZSET 有序集合 添加、获取、删除元素
根据分值范围或者成员来获取元素
计算一个键的排名

ZSET的底层采用跳跃表实现。跳跃表是基于多指针有序链表实现的,可以看成多个有序链表。在查找时,从上层指针开始查找,找到对应的区间之后再到下一层去查找。

ZSET

与红黑树等平衡树相比,跳跃表具有以下优点:

  1. 插入速度非常快速,因为不需要进行旋转等操作来维护平衡性;
  2. 更容易实现;
  3. 支持无锁操作。

键的过期时间

Redis 可以为每个键设置过期时间,当键过期时,会自动删除该键。

对于散列表这种容器,只能为整个键设置过期时间(整个散列表),而不能为键里面的单个元素设置过期时间。

6 种数据淘汰策略

可以设置内存最大使用量(默认值为0,表示无限制),当内存使用量超出时,会执行数据淘汰策略。

设置内存最大使用量主要目的有以下两种情况:

  1. 缓存场景,限制缓存大小。
  2. 防止Redis所用内存超过物理内存,导致进程被杀死。
策略 描述
volatile-lru 从已设置过期时间的数据集中
挑选最近最少使用的数据淘汰
volatile-ttl 从已设置过期时间的数据集中
挑选将要过期的数据淘汰
volatile-random 从已设置过期时间的数据集中
任意选择数据淘汰
allkeys-lru 从所有数据集中
挑选最近最少使用的数据淘汰
allkeys-random 从所有数据集中
任意选择数据进行淘汰
noeviction 禁止驱逐数据

Redis 4.0 引入了 volatile-lfu 和 allkeys-lfu 淘汰策略,LFU 策略通过统计访问频率,将访问频率最少的键值对淘汰。

作为内存数据库,出于对性能和内存消耗的考虑,Redis 的淘汰算法实际实现上并非针对所有 key,而是抽样一小部分并且从中选出被淘汰的 key。

持久化

作为内存型数据库,Redis虽然速度非常快,但面临一个问题:电脑如果需要重启或者意外宕机,保存在内存中的数据就会丢失。

所以,为了在电脑重启后,能够恢复原来的数据,Redis就需要提供持久化的机制,将存储在内存中的数据,在磁盘中进行备份。这样在重启后,Redis就可以根据磁盘中持久化的备份,将数据重新读取到内存中,避免数据丢失。

RDB(快照持久化)

RDB是Redis DataBase的缩写。

Redis将当前内存中存储的数据写入到磁盘上的一个文件中,将其作为当前的一个快照。当Redis重启时,将这个快照文件中存储的内容加载进内存,即可恢复Redis之前的状态。

功能的两个核心函数是rdbSave(生成RDB文件)和rdbLoad(从文件加载内存)。

RDB文件默认都开启了压缩(LZF算法),虽然耗时,但是体积大大减小。

注意:Redis默认使用RDB的持久化方式。

优点

  1. 可以将RDB文件(体积小,传输快)复制到其它服务器从而创建具有相同数据的服务器副本。
  2. 在恢复大数据集时,消耗时间比AOF要短。

缺点

  1. 系统断电后将会丢失最后一次快照之后的数据变动。
  2. 如果数据量很大,保存一次快照的时间会很长。

AOF

AOF是Append-Only File的缩写,只追加文件。

将Redis执行的所有写指令,追加到AOF文件的末尾。当Redis重启后,读取AOF文件,重新执行其中的写操作,以此达到恢复数据的目的。

<1> 同步选项

使用AOF持久化需要设置同步选项。这是因为AOF 机制并不会立刻将写操作同步到磁盘上,而是先存储到缓冲区,然后根据同步选项决定何时同步到磁盘。

有以下三种同步选项:

  1. always:每个写命令都同步,频繁的IO操作会影响Redis的响应速度,严重降低服务器性能。
  2. everysec:每秒同步一次,可以保证系统崩溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器性能几乎没有任何影响。比较合适。
  3. no:让操作系统来决定何时同步,并不能给服务器性能带来多大的提升,而且也会增加系统崩溃时数据丢失的数量,不安全。

<2> AOF重写(Rewrite)

不断将写指令追加到AOF的末尾,会导致AOF文件越来越大。Redis实现了AOF重写的优化机制,来解决这个问题。

当Redis检查到AOF已经很大时,就会触发重写机制,优化其中的冗余内容,将它优化为能够得到相同结果的最小的指令集合。如,先创建再删除的两条指令,不会保留;多次自增会直接保留最后的值,等等。

<3> 与RDB的比较

优点:

  1. 同步选项采用everysec时,最多只会丢失1秒的修改,更安全。
  2. 有序地保存了对Redis的写操作,方便对其进行分析。

缺点

  1. 对于相同的数据集来说,AOF文件通常大于RDB文件,因为AOF不仅记录了数据,还记录了操作。
  2. 对性能的影响大于RDB。

注意:如果RDB和AOF的持久化方式都配置了,Redis会优先加载AOF。

主从复制架构

Redis不支持主主复制。

主从复制是Redis用来对存储相同数据的多台服务器(集群)进行同步的机制。主从复制依赖于Redis的快照持久化。

主从复制实现了Redis的读写分离,但是仍然有两个问题

  1. 无法缓解主服务器的写压力。
  2. 不具备高可用性。(哨兵机制解决)

1. 主从服务器

在主从复制机制中,Redis将服务器分为主服务器和从服务器。一个从服务器只能有一个主服务器。

通过使用 slaveof 主服务器ip 主服务器port 命令,或者在配置文件中添加该配置项语句,来让一个服务器成为另一个服务器的从服务器。

  • 主服务器负责接收用户提交的修改指令,修改数据库中的数据,同时将修改同步到从服务器中。
  • 从服务器要与主服务器进行数据同步,并分担主服务器的查询请求。除此之外,可以关闭主服务器的持久化操作,让从服务器来进行持久化,从而进一步减轻主服务器压力。

2. 实现过程

完整的主从复制分为同步(发送快照)命令传播(发送写操作) 两个部分。

  1. 主服务器创建RDB快照文件,发送给从服务器,并在发送期间使用缓冲区记录执行的写命令。快照文件发送完毕之后,开始向从服务器发送存储在缓冲区中的写命令;
  2. 从服务器丢弃所有旧数据,载入主服务器发来的快照文件,之后从服务器开始接受主服务器发来的写命令;
  3. 主服务器每执行一次写命令,就向从服务器发送相同的写命令。

随着负载不断上升,主服务器可能无法很快地更新所有从服务器,或者重新连接和重新同步从服务器将导致系统超载。

可以通过主从链解决这个问题:创建一个中间层来分担主服务器的复制工作。中间层的服务器是最上层服务器的从服务器,又是最下层服务器的主服务器。

主从链

3. 部分重同步

上面介绍的同步机制是一种完整重同步,开销大且耗时。

在命令传播阶段,若主从服务器网络断开,重连后主从服务器上的数据就不一致了。在这种情况下,Redis2.8之后提供了一种部分重同步的优化机制,避免了完整重同步。

主服务器在命令传播时,维护一个复制积压缓冲区,这是一个固定长度、先进先出的队列,默认为1 MB。在从服务器重连后,发送自己的复制偏移量,若主服务器判断该偏移量之后的所有字节都在缓冲区中,则可以进行部分重同步,即把该偏移量之后的所有字节发送给从服务器。否则,就需要进行一次完全重同步。

4. 哨兵

Sentinel(哨兵)可以监听集群中的服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。

哨兵机制一定程度上解决了主从复制架构不具备高可用性的问题。

注意:此时,关闭了持久化的主服务器开启自动重启进程选项是危险的,因为快速重启可能还没有触发哨兵机制,重启后仍为主服务器,但数据已丢失,并把空的数据集同步给了所有从服务器。

Redis Cluster(待完成)

事务

Redis 事务的本质是一组命令的集合。服务器在执行事务期间,会按照顺序串行化执行队列中的命令,且不会改去执行其它客户端的命令请求。

<1> 事务相关命令

  • MULTI:开启事务,redis会将后续的命令逐个放入队列中,然后使用EXEC命令来原子化执行这个命令系列。
  • EXEC:执行事务中的所有操作命令。
  • DISCARD:取消事务,放弃执行事务块中的所有命令。
  • WATCH:监视一个或多个key,如果事务在执行前,这个key(或多个key)被其他命令修改,则事务被中断,不会执行事务中的任何命令。
  • UNWATCH:取消WATCH对所有key的监视。

Redis 最简单的事务实现方式是使用 MULTI 和 EXEC 命令将事务操作包围起来。

<2> 注意:与关系型数据库中的事务不同,Redis 的事务不保证原子性,也不支持回滚

多数事务失败是由语法错误或者数据结构类型错误导致的。语法错误在命令入队前就进行检测,因此一旦有语法错误,则事务的所有命令都不执行。而类型错误是在执行时才进行检测,当遇到此类错误时,前面已经执行过的命令结果会保留,不会回退。Redis为提升性能而采用这种简单的事务,这是不同于关系型数据库的。

<3> 监视key

Redis的watch命令保证了,在监视的key被修改后,后面的第一个事务不会执行。

具体来说,需要在MULTI之前使用WATCH来监控某些键值对,然后使用MULTI命令来开启事务,执行对数据结构操作的各种命令,此时这些命令入队列。

当使用EXEC执行事务时,首先会比对WATCH所监控的键值对,如果没发生改变,它会执行事务队列中的命令,提交事务;如果发生变化,将不会执行事务中的任何命令。

无论结果如何,Redis都会取消执行事务前的WATCH命令。

image

彻底搞懂 Redis 事务

Pipelined

实际中Redis的读写速度十分快,而系统的瓶颈往往是在网络通信中的延时。

事务中的多个命令被一次性发送给服务器,而不是一条一条发送,这种方式被称为Pipelined(流水线),它可以减少客户端与服务器之间的网络通信次数从而提升性能。

虽然Redis的事务也提供了队列,可以批量执行任务,但使用事务命令有系统开销,因为它会检测对应的锁和序列化命令。

Redis的流水线是一种通信协议,没有办法通过客户端执行,但是可以通过Java API(如Jedis)或使用Spring操作它。

// 开启流水线
Pipeline pipeline = jedis.pipelined();
// 只同步不返回结果
pipeline.sync();
// 同步且返回结果(List的形式)
List<Object> result = pipeline.syncAndReturnAll();

SessionCallback callback = (SessionCallback)(RedisOperations ops)->{
    for(int i = 0; i < 100000; i++) {
        int j = i + 1;
        ops.boundValueOps("key" + j).set("value" + j);
        ops.boundValueOps("key" + j).get();
    }
    return null;
};
redisTemplate.executePipelined(callback);
List result = rt.executePipelined(callback);

缓存问题

image

缓存穿透

对于缓存和数据库中都没有的数据,用户短时间内不断请求查询。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

解决:

  1. 查询前先根据查询参数进行过滤,如进行有效性校验,或布隆过滤器。
  2. 对于缓存和数据库中都查询不到的数据,可以在缓存中设置一份空数据(过期时间可以设置稍短),防止短时间内同样的查询请求到达数据库。

缓存击穿

对于某一条缓存中没有、但数据库中有的数据(一般是缓存时间到期),此时并发查询该条数据的用户特别多,由于缓存中查不到,则都会去数据库中拉取,导致数据库瞬间压力过大。

解决:

  1. 设置热点数据永不过期。
  2. 加互斥锁:当缓存中查不到,需要去数据库中拉取数据时,需要获取互斥锁(最好根据key值)。这样同样的数据只会有一条请求去数据库中拉取。

缓存雪崩

缓存中的大量数据恰好同时过期,此时去数据库拉取数据的请求量过大,导致数据库短时间内压力过大。

解决:

  1. 缓存数据设置过期时间时使用随机时间。
  2. 设置热点数据永不过期。
  3. 如果缓存数据库是分布式部署,则将热点数据均匀部署在不同节点中。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多