redis记录

文章目录

      • 简介
      • 数据类型
      • 事务
      • 持久化机制
        • RDB
        • AOF
      • Redis快速的原因
      • 主从复制
      • 哨兵Sentinel
      • 集群
      • 过期键删除策略
      • 内存淘汰策略
      • 应用场景
      • 如何保证缓存与数据库双写时的数据一致性
      • 缓存问题
        • 缓存穿透
        • 缓存雪崩
        • 缓存击穿
      • pipeline
      • LUA脚本

参考:
http://doc.redisfans.com/index.html
https://mp.weixin.qq.com/s/QSyvSV2BYcNiidP3WpSeCw

简介

RedisRemote Dictionary Server,使用C语言编写的非关系型键值对存储数据库。

特性:

  • 基于内存运行,性能高效
  • Redis是单线程的,避免线程切换开销及多线程的竞争问题。单线程是指网络请求使用一个线程来处理,Redis运行时不止有一个线程,如数据持久化时会开启另一个线程处理。
  • 支持多种数据类型,包括StringHashListSetZset
  • 原子性操作,Redis还支持对几个操作合并后的原子性执行
  • 可持久化,Redis支持RDBAOF两种持久化机制,可以有效地避免数据丢失问题。
  • 支持lua脚本

Memcached和Redis的区别:

  • MemCached数据结构单一,仅用来缓存数据,而Redis支持多种数据类型
  • MemCached不支持数据持久化
  • 网络IO模型不同,Memcached是多线程非阻塞IO复用,Redis使用单线程的IO复用模型
  • 内存管理方式不同,Memcached使用预分配的内存池的方式,Redis使用现场申请内存的方式来存储数据
  • Redis提供主从同步机制和cluster集群部署能力,能够提供高可用服务。Memcached没有提供原生的集群模式,需要依靠客户端实现往集群中分片写入数据。

数据类型

  1. String: 最常用的一种数据类型,String类型的值可以是字符串、数字或者二进制,但值最大不能超过512MB

  2. Hash:一个键值对集合

  3. Set:无序去重的集合。Set提供了交集、并集等方法

  4. List:有序可重复的集合,底层是依赖双向链表实现的。

  5. SortedSet:有序Set。内部维护了一个score的参数来实现。适用于排行榜和带权重的消息队列等场景。

特殊的数据类型

1、Bitmap:位图,可以认为是一个以位为单位数组,数组中的每个单元只能存0或者1,数组的下标在Bitmap中叫做偏移量。Bitmap的长度与集合中元素个数无关,而是与基数的上限有关。

2、HyperloglogHyperLogLog是用来做基数统计的算法,其优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。典型的使用场景是统计独立访客。

3、Geospatial :主要用于存储地理位置信息,并对存储的信息进行操作,适用场景如定位、附近的人等。

事务

Redis原子性的执行一系列命令

事务执行流程:

  1. 使用MULTI开启一个事务
  2. 缓存所有需要执行的命令
  3. 使用EXEC按顺序执行所有命令

WATCH命令可以监控一个或多个键,一旦其中有一个键被修改,之后的事务就不会执行(类似于乐观锁)。可使用UNWATCH取消监控

Redis事务不保证原子性,即事务中的所有命令不一定都成功,其中一条命令出错不会影响其他命令的执行

持久化机制

即将数据保存到磁盘的方式,保证服务崩溃后不会丢失数据

Redis支持RDBAOF两种持久化方式

在启用AOFRDB持久性的情况下,Redis重新启动AOF文件将用于重建原始数据集,因为它保证数据是最完整的。

RDB

默认的持久化方案

触发方式:

  • 手动触发:执行SAVEBGSAVE命令
    • SAVE命令执行一个同步保存操作,将当前 Redis 实例的所有数据快照(snapshot)以RDB文件的形式保存到硬盘
    • BGSAVE在后台异步(Asynchronously)保存当前数据库的数据到磁盘。BGSAVE命令执行之后立即返回 OK ,然后Redis fork出一个新子进程,原来的Redis进程(父进程)继续处理客户端请求,而子进程则负责将数据保存到磁盘,然后退出
  • 被动触发:
    • 根据配置规则进行自动快照,如SAVE 100 10,100秒内至少有10个键被修改则进行快照
    • 如果从节点执行全量复制操作,主节点会自动执行BGSAVE生成 RDB 文件并发送给从节点。
    • 默认情况下执行shutdown命令时,如果没有开启AOF持久化功能则自动执行BGSAVE

优点:

  • 通过RDB文件恢复数据比AOF
  • 通过BGSAVE使用单独子进程进行持久化,主进程不会进行任何IO操作

缺点:

  • 无法做到实时保存数据,服务宕机后存在丢失数据的风险
  • 如果数据集很大,保存数据时会很耗时
AOF

AOF(Append Only File)AOF持久化记录服务器接收到的每个写操作,在服务器启动时再次执行,重建原始数据集。命令使用与Redis协议本身相同的格式以仅附加方式记录。当日志变得太大时,Redis能够在后台重写日志

通过appendonly参数启用:appendonly yes

日志数据会先写道缓存中,然后可配置如下参数确定何时将缓存中数据写入到磁盘上:

appendfsync always # 每次写入aof文件都会执行同步,最安全最慢
appendfsync everysec  # 每秒同步既保证性能也保证安全,建议配置
appendfsync no # 由操作系统决定何时进行同步操作

优点:

  • 可以更好的保证数据不丢失
  • AOFappend-only的模式写入,所以没有磁盘寻址的开销,写入性能非常高
  • 已易于理解和解析的方式保存所有操作的日志。如不小心删除了所有数据,只要在这期间没有执行日志重写,只要到处AOF文件并将最后一条删除指令删除,即可恢复数据

缺点:

  • AOF文件通常比相同数据集的等效RDB文件大
  • 重启后数据恢复速度慢

Redis快速的原因

  1. 基于内存:使用内存存储数据,没有磁盘IO上的开销,读写速度非常快

  2. 单线程实现(Redis 6.0以前):Reids使用单个线程处理请求,避免多个线程之间线程切换和锁资源竞争的开销

  3. IO多路复用:Redis采用IO多路复用技术。Redis使用单线程来轮询描述符,将数据库的操作都转换成了事件,不在网络I/O上浪费过多的时间

  4. 高效的数据结构:Redis每种数据类型底层都做了优化

考虑单线程的原因:

  • 可维护性:使用单线程模型方便开发和调试
  • 并发处理:使用单线程模型也能并发的处理客户端的请求,即利用I/O处理多个客户端的连接请求
  • 性能瓶颈:Redis服务大多数操作的性能瓶颈都不是CPU,而是网络传输,即网络I/O

主从复制

重要特性:

  • Redis使用异步复制。 从Redis 2.8开始, 从服务器会以每秒一次的频率向主服务器报告复制流(replication stream)的处理进度。

  • 一个主服务器可以有多个从服务器。

  • 从服务器也可以有自己的从服务器, 多个从服务器之间可以构成一个图状结构。

  • 复制功能不会阻塞主服务器,即使有一个或多个从服务器正在进行初次同步, 主服务器也可以继续处理命令请求。

  • 复制功能也不会阻塞从服务器: 只要在 redis.conf 文件中进行了相应的设置, 即使从服务器正在进行初次同步, 服务器也可以使用旧版本的数据集来处理命令查询。

    不过, 在从服务器删除旧版本数据集并载入新版本数据集的那段时间内, 连接请求会被阻塞。

    你还可以配置从服务器, 让它在与主服务器之间的连接断开时, 向客户端发送一个错误。

  • 复制功能可以单纯地用于数据冗余(data redundancy), 也可以通过让多个从服务器处理只读命令请求来提升扩展性(scalability): 比如说, 繁重的SORT命令可以交给附属节点去运行。

  • 可以通过复制功能来让主服务器免于执行持久化操作: 只要关闭主服务器的持久化功能, 然后由从服务器去执行持久化操作即可。

复制过程:

当建立一个从服务器时, 从服务器都将向主服务器发送一个SYNC命令。

接到 SYNC 命令的主服务器将开始执行BGSAVE, 并在保存操作执行期间, 将所有新执行的写入命令都保存到一个缓冲区里面。

BGSAVE执行完毕后, 主服务器将执行保存操作所得的 .rdb 文件发送给从服务器, 从服务器接收这个 .rdb 文件, 并将文件中的数据载入到内存中。

之后主服务器会以Redis命令协议的格式, 将写命令缓冲区中积累的所有内容都发送给从服务器。

你可以通过telnet命令来亲自验证这个同步过程: 首先连上一个正在处理命令请求的Redis服务器, 然后向它发送SYNC命令, 过一阵子, 你将看到telnet会话(session)接收到服务器发来的大段数据(.rdb 文件), 之后还会看到, 所有在服务器执行过的写命令, 都会重新发送到telnet会话来。

即使有多个从服务器同时向主服务器发送 SYNC , 主服务器也只需执行一次BGSAVE命令, 就可以处理所有这些从服务器的同步请求。

从服务器可以在主从服务器之间的连接断开时进行自动重连, 在Redis 2.8版本之前, 断线之后重连的从服务器总要执行一次完整重同步(full resynchronization)操作, 但是从Redis 2.8版本开始, 从服务器可以根据主服务器的情况来选择执行完整重同步还是部分重同步(partial resynchronization)

部分重同步:

从 Redis 2.8 开始, 在网络连接短暂性失效之后, 主从服务器可以尝试继续执行原有的复制进程(process), 而不一定要执行完整重同步操作。

这个特性需要主服务器为被发送的复制流创建一个内存缓冲区(in-memory backlog), 并且主服务器和所有从服务器之间都记录一个复制偏移量(replication offset)和一个主服务器ID (master run id), 当出现网络连接断开时, 从服务器会重新连接, 并且向主服务器请求继续执行原来的复制进程:

  • 如果从服务器记录的主服务器ID和当前要连接的主服务器的ID相同, 并且从服务器记录的偏移量所指定的数据仍然保存在主服务器的复制流缓冲区里面, 那么主服务器会向从服务器发送断线时缺失的那部分数据, 然后复制工作可以继续执行。
  • 否则的话, 从服务器就要执行完整重同步操作。

Redis 2.8的这个部分重同步特性会用到一个新增的PSYNC内部命令, 而Redis 2.8以前的旧版本只有SYNC命令, 不过, 只要从服务器是Redis 2.8或以上的版本, 它就会根据主服务器的版本来决定到底是使用PSYNC还是SYNC

  • 如果主服务器是Redis 2.8或以上版本,那么从服务器使用PSYNC命令来进行同步。
  • 如果主服务器是Redis 2.8之前的版本,那么从服务器使用SYNC命令来进行同步。

哨兵Sentinel

主从复制存在不能自动故障转移、达不到高可用的问题。哨兵模式解决了这些问题。通过哨兵机制可以自动切换主从节点。

客户端连接Redis的时候,先连接哨兵,哨兵会告诉客户端Redis主节点的地址,然后客户端连接上Redis主节点并进行后续的操作。当主节点宕机的时候,哨兵监测到主节点宕机,会重新推选出某个表现良好的从节点成为新的主节点,然后通过发布订阅模式通知其他的从服务器,让它们切换主机。

RedisSentinel中关于下线(down)有两个不同的概念:

  • 主观下线(Subjectively Down, 简称 SDOWN)指的是单个Sentinel实例对服务器做出的下线判断。
  • 客观下线(Objectively Down, 简称 ODOWN)指的是多个Sentinel实例在对同一个服务器做出SDOWN判断, 并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后, 得出的服务器下线判断。 (一个Sentinel可以通过向另一个Sentinel发送 SENTINEL is-master-down-by-addr 命令来询问对方是否认为给定的服务器已下线。)

每个Sentinel都需要定期执行的任务:

  • Sentinel以每秒钟一次的频率向它所知的主服务器、从服务器以及其他Sentinel实例发送一个PING命令。
  • 如果一个实例(instance)距离最后一次有效回复PING命令的时间超过down-after-milliseconds选项所指定的值, 那么这个实例会被Sentinel标记为主观下线。 一个有效回复可以是: +PONG-LOADING 或者 -MASTERDOWN
  • 如果一个主服务器被标记为主观下线, 那么正在监视这个主服务器的所有Sentinel 要以每秒一次的频率确认主服务器的确进入了主观下线状态。
  • 如果一个主服务器被标记为主观下线, 并且有足够数量的Sentinel(至少要达到配置文件指定的数量)在指定的时间范围内同意这一判断, 那么这个主服务器被标记为客观下线。
  • 在一般情况下, 每个Sentinel会以每 10 秒一次的频率向它已知的所有主服务器和从服务器发送 INFO命令。 当一个主服务器被Sentinel标记为客观下线时,Sentinel向下线主服务器的所有从服务器发送INFO命令的频率会从10秒一次改为每秒一次。
  • 当没有足够数量的Sentinel同意主服务器已经下线, 主服务器的客观下线状态就会被移除。 当主服务器重新向SentinelINFO命令返回有效回复时, 主服务器的主观下线状态就会被移除。
  • 哨兵节点会选举出哨兵leader,负责故障转移的工作。
  • 哨兵leader会推选出某个表现良好的从节点成为新的主节点,然后通知其他从节点更新主节点信息。

集群

哨兵模式解决了主从复制不能自动故障转移、达不到高可用的问题,但还是存在主节点的写能力、容量受限于单机配置的问题。而cluster模式实现了Redis的分布式存储,每个节点存储不同的内容,解决主节点的写能力、容量受限于单机配置的问题。

Redis cluster集群节点最小配置6个节点以上(33从),其中主节点提供读写操作,从节点作为备用节点,不提供请求,只作为故障转移使用。

Redis cluster采用虚拟槽分区,所有的键根据哈希函数映射到0~16383个整数槽内,每个节点负责维护一部分槽以及槽所映射的键值数据。

哈希槽是如何映射到Redis实例上的:

Redis集群使用数据分片(sharding)而非一致性哈希(consistency hashing)来实现: 一个Redis集群包含 16384 个哈希槽(hash slot), 数据库中的每个键都属于这 16384 个哈希槽的其中一个, 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 keyCRC16校验和 。

集群中的每个节点负责处理一部分哈希槽。 举个例子, 一个集群可以有三个哈希槽, 其中:

  • 节点 A 负责处理 0 号至 5500 号哈希槽。
  • 节点 B 负责处理 5501 号至 11000 号哈希槽。
  • 节点 C 负责处理 11001 号至 16384 号哈希槽。

这种将哈希槽分布到不同节点的做法使得用户可以很容易地向集群中添加或者删除节点。 比如说:

  • 如果用户将新节点 D 添加到集群中, 那么集群只需要将节点 A 、B 、 C 中的某些槽移动到节点 D 就可以了。
  • 与此类似, 如果用户要从集群中移除节点 A , 那么集群只需要将节点 A 中的所有哈希槽移动到节点 B 和节点 C , 然后再移除空白(不包含任何哈希槽)的节点 A 就可以了。

因为将一个哈希槽从一个节点移动到另一个节点不会造成节点阻塞, 所以无论是添加新节点还是移除已存在节点, 又或者改变某个节点包含的哈希槽数量, 都不会造成集群下线。

优点:

  • 支持动态扩容
  • 数据按照slot存储分布在多个节点,节点间数据共享,可动态调整数据分布
  • 高可用性。部分节点不可用时,集群仍可用。集群模式能够实现自动故障转移(failover),节点之间通过gossip协议交换状态信息,用投票机制完成SlaveMaster的角色转换。

缺点:

  • 不支持批量操作(pipeline)
  • 数据通过异步复制,不保证数据的强一致性。
  • 事务操作支持有限,只支持多key在同一节点上的事务操作,当多个key分布于不同的节点上时无法使用事务功能。
  • key作为数据分区的最小粒度,不能将一个很大的键值对象如hashlist等映射到不同的节点。
  • 不支持多数据库空间,单机下的Redis可以支持到16个数据库,集群模式下只能使用1个数据库空间。

过期键删除策略

  1. 被动删除。在访问key时,如果发现key已经过期,那么会将key删除。
  2. 主动删除。定时清理key,每次清理会依次遍历所有DB,从db随机取出20key,如果过期就删除,如果其中有5key过期,那么就继续对这个db进行清理,否则开始清理下一个db
  3. 内存不够时清理。Redis有最大内存的限制,通过maxmemory参数可以设置最大内存,当使用的内存超过了设置的最大内存,就要进行内存释放, 在进行内存释放的时候,会按照配置的淘汰策略清理内存

内存淘汰策略

Redis的内存超过最大允许的内存之后,Redis会触发内存淘汰策略,删除一些不常用的数据,以保证Redis服务器正常运行。

Redisv4.0前提供 6 种数据淘汰策略

  • volatile-lruLRULeast Recently Used),最近最少使用。利用LRU算法移除设置了过期时间的key
  • allkeys-lru:当内存不足以容纳新写入数据时,从数据集中移除最近最少使用的key
  • volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
  • volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
  • allkeys-random:从数据集中任意选择数据淘汰
  • no-eviction:禁止删除数据,当内存不足以容纳新写入数据时,新写入操作会报错

Redisv4.0后增加以下两种

  • volatile-lfuLFULeast Frequently Used,最不常用,从已设置过期时间的数据集中挑选最不经常使用的数据淘汰。
  • allkeys-lfu:当内存不足以容纳新写入数据时,从数据集中移除最不经常使用的key

内存淘汰策略可以通过配置文件来修改,相应的配置项是maxmemory-policy,默认配置是noeviction

应用场景

  1. 缓存热点数据,缓解数据库压力
  2. 计数器,利用reids的原子性的自增操作,可以实现计数器的功能,如用户点赞数、用户访问数等
  3. 简单的消息队列,可以使用Redis自身的发布/订阅模式或者List来实现简单的消息队列,实现异步操作
  4. 限速器,可用于限制某个用户访问某个接口的频率,如:先获取key的当前值,如果没有超出限制再执行INCR1,如果key不存在,使用redis的事务初始化key和过期时间。
  5. 集合取交集、并集、差集等实现一些实用功能
  6. 分布式锁,利用setnx(或set nx)命令实现

如何保证缓存与数据库双写时的数据一致性

  1. 先删除缓存再更新数据库

    进行更新操作时,先删除缓存,然后更新数据库,后续的请求再次读取时,会从数据库读取后再将新数据更新到缓存。

    存在的问题:删除缓存数据之后,更新数据库完成之前,这个时间段内如果有新的读请求过来,就会从数据库读取旧数据重新写到缓存中,再次造成不一致,并且后续读的都是旧数据。

  2. 先更新数据库再删除缓存

    进行更新操作时,先更新MySQL,成功之后,删除缓存,后续读取请求时再将新数据回写缓存。

    存在的问题:更新MySQL和删除缓存这段时间内,请求读取的还是缓存的旧数据,不过等数据库更新完成,就会恢复一致,影响相对比较小。

  3. 异步更新缓存

    数据库的更新操作完成后不直接操作缓存,而是把这个操作命令封装成消息扔到消息队列中,然后由Redis自己去消费更新数据,消息队列可以保证数据操作顺序一致性,确保缓存系统的数据正常。

缓存问题

缓存穿透

缓存穿透是指查询一个不存在的数据,由于缓存是不命中时被动写的,如果从DB查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到DB去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了。

  1. 缓存空值,不会查数据库。
  2. 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,查询不存在的数据会被这个bitmap拦截掉,从而避免了对DB的查询压力。

布隆过滤器的原理:当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。查询时,将元素通过散列函数映射之后会得到k个点,如果这些点有任何一个0,则被检元素一定不在,直接返回;如果都是1,则查询元素很可能存在,就会去查询Redis和数据库。

缓存雪崩

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DBDB瞬时压力过重挂掉。

解决方法:在原有的失效时间基础上增加一个随机值,使得过期时间分散一些。

缓存击穿

缓存击穿:大量的请求同时查询一个key时,此时这个key正好失效了,就会导致大量的请求都落到数据库。缓存击穿是查询缓存中失效的 key,而缓存穿透是查询不存在的 key。

解决方法:加分布式锁,第一个请求的线程可以拿到锁,拿到锁的线程查询到了数据之后设置缓存,其他的线程获取锁失败会等待50ms然后重新到缓存取数据,这样便可以避免大量的请求落到数据库。

pipeline

Redis客户端执行一条命令分4个过程:发送命令、命令排队、命令执行、返回结果。使用pipeline可以批量请求,批量返回结果,执行速度比逐条执行要快。

使用pipeline组装的命令个数不能太多,不然数据量过大,增加客户端的等待时间,还可能造成网络阻塞,可以将大量命令的拆分多个小的pipeline命令完成。

原生批命令(mset和mget)pipeline对比:

  1. 原生批命令是原子性,pipeline非原子性pipeline命令中途异常退出,之前执行成功的命令不会回滚
  2. 原生批命令只有一个命令,但pipeline支持多命令

LUA脚本

Redis通过LUA脚本创建具有原子性的命令:当lua脚本命令正在运行的时候,不会有其他脚本或 Redis命令被执行,实现组合命令的原子操作

作用:

  1. Lua脚本在Redis中是原子执行的,执行过程中间不会插入其他命令。

  2. Lua脚本可以将多条命令一次性打包,有效地减少网络开销。

令完成。

原生批命令(mset和mget)pipeline对比:

  1. 原生批命令是原子性,pipeline非原子性pipeline命令中途异常退出,之前执行成功的命令不会回滚
  2. 原生批命令只有一个命令,但pipeline支持多命令

你可能感兴趣的:(redis,redis)