缓存redis 相关

1. 常用的缓存工具

我讲讲我在项目中使用的缓存工具吧, 我在叁一的系统中使用了 caffeine 作为一级缓存,redis 作为二级缓存,通过多级缓存来缓解对mysql 的压力
1. Caffeine 
Caffeine 是目前当值无愧的本地缓存之王,是java 提供的缓存库, 在缓存操作等方面具有更出色的性能表现
springboot 1.x 版本中的本地缓存是guava, 但是在spring5.x (springboot 2.x )以后, 官方放弃了guava,而是使用了caffenie作为默认的缓存组件
其提供了异步加载缓存数据、丰富的统计和监控功能,可以监控缓存的命中率、加载时间等指标,同时还提供了多种内存管理策略
3. redis 是基于内存的kv 数据库, 同时还支持事务、lua、持久化 以及多种集群方案等
读写非常快,每秒可以处理超过10W次读写操作,同时还提供有特殊的数据结构类型,如GEO,hpyerloglog 和bitmap 等

针对多种缓存,可以根据具体的服务需求去进行选型
比如我在叁一运营平台中使用了redis + caffeine 作为缓存, 当时的考虑是因为, 针对我们游戏的配置数据, 需要可以对其进行持久化到磁盘中,
所以考虑选用redis, 并且我们还需要缓存一些排行榜数据, 那么使用redis 的 zset 是最为合适的,因为其自带排序
同时针对redis 还拥有多种集群方案, 并且其可以作为分布式锁使用
同时使用caffeien 做为我们的一级缓存, 用于对我们项目配置数据的本地缓存,用于降低对redis 的冲击, 同时还可以通过caffnien 提供的监控系统,进行优化一级缓存
当时的运营平台,qps 基本上在每秒100次左右, 而且主要都是用于获取项目配置数据的, 所以当时针对这个, 使用了cdn+多级缓存+数据库,用于降低mysql 的压力

2. redis 的优势

redis 的读取数据快, 因为其数据存在内存中, 获取特别快, 单机轻松10W+
并且redis 是使用c语言程序实现的,众所周知C语言程序运行时要比其他语言快,因为它离底层机器很近
还支持多种数据结构, 包括字符串、列表、集合、有序集合、哈希等
同时还有其他的丰富功能,比如 数据持久化,集群方案,可以实现消息队列、分布式锁等

3. redis 的单线程与多线程

在早期版本,redis 主要采用单线程模型,优点就是避免了多线程竞争和上下文切换带来的开销,同时也保证了命令的有序性,简化了并发控制,无需复杂的锁机制,降低了出错风险

在redis 6.0 之后,为了进一步提升性能和可扩展性, 引入了多线程
但是针对这个多线程,也是用于一些非核心的数据处理任务,比如网络数据的读取和写入,以提高效率,降低单线程的压力, 还有数据备份等操作中使用多线程来加快处理速度

4. redis 为什么快

1. redis 是基于内存的kv操作
redis的操作全都是基于内存的,所以cpu不是redis的性能瓶颈,机器内存和网络带宽才是redis 的瓶颈
2. redis是单线程操作的
使用单线程可以省去多线程时cpu上下文切换的时间,也不用去考虑各种锁的问题,不存在加锁解锁操作,没有死锁问题导致的性能消耗. 多次读写都在一个CPU上,没有上下文切换效率就是最高的, 虽然后面redis 引入了多线程,但是这个多线程也是针对的网络IO,以及数据备份等操作,并不涉及redis 的kv操作
3. IO多路复用
redis 采用了IO多路复用技术, 这也进一步加快了redis 速度. 
redis是单线程的,所有操作都会按照顺序线性执行,但是由于操作系统等待用户输入或者输出都是阻塞,所以IO操作在一般情况下往往不能直接返回,这会导致某一文件的IO阻塞导致整个进程无法对其他客户提供服务,而IO多路复用就是为了解决这个问题出现的
4. reactor 设计模式
redis 的文件处理器是由四个部门组成:套接字、IO多路复用、文件分派器、实际处理器
文件事件是对套接字的操作,每当一个套接字准备好执行连接应答、写入、读取、关闭等操作时,就会产生一个文件事件
IO多路复用器负责通过loop 循环监听多个套接字,同时将一系列套接字按循环存储到一个队列中,由队列向文件事件分配器传送到队列套接字中,这个队列套接字是有序的,它会当一个套接字事件被处理完毕后,立马向文件事件分配器传送下一个套接字,
文件事件分配器接受队列中的套接字并根据套接字产生的事件类型,相应调用不同的事件处理器

5. redis 场景数据类型和运用场景

1. 字符串 
可以用于缓存, 起到加速读写和降低后端压力的作用
使用redis 作为计数的基础工具,可以实现快速计数、查询缓存的功能,同时数据可以异步落地到其他数据源
共享session 信息, 使用redis 将用户的session 进行集中管理, 
限速, 比如用户手机验证码
2. hash 类型
java 里提供了hashMap, redis 也有类似的数据结构,就是hash类型. 
hash 类型比较适宜存放对象数据类型
3. list 列表
list 用于存储多个有序的字符串,一个列表最多可以存储 2^32-1 个元素
比如可以存储每个用户的文章列表,需要分页展示文章列表时,可以考虑使用列表,因为列表是有序的,同时可以按照索引范围获取元素
同时list 也可以用作消息队列,通过lpush + brpop 命令保证消费的负责均衡和高可用性
4. set 集合
set 类型也是用来保存多个的字符串元素,但和列表类型不一样的是,集合中不允许有重复元素,并且元素中的元素是无序的,不能通过索引下标获取元素
可以用作标签, 针对不同用户打上标签,有了这些标签就可以针对不同标签的用户做不同类型的推荐
5. zset 有序集合
有序集合相对于集合来说,保留了集合不能重复成员的特性,但是不同的是有序集合中的元素可以排序,依据每个元素设置score作为排序的依据
有序集合中的元素不能重复,但是score可以重复
比如可以做排行榜系统等

6. redis 的高级数据类型和运用场景

1. bitmaps
redis 提供了bitmaps 这个数据结构可以实现对位的操作,但是其本身不是一种数据结构,实际上它就是字符串,可以对字符串的位进行操作,单独提供了一套命令,所以其和使用字符串的方法不太相同
bitmaps 可以做布隆过滤器, 用于判断一个元素是否在一个集合中,判断存在时,不一定存在,判断不存在时,一定不存在
2. hypeloglog 
hypeloglog 也不是新的数据结构,实际类型也是字符串,通过一种技术算法,hypeloglog 可以利用极小的内存空间完成独立计数的统计,数据集可以是IP、email、ID等
比如每个网页UV数据,虽然可以使用一个页面一个独立的set集合来存在,但是如果一个爆款页面几千万的UV,就需要一个很大的set 集合来统计,就会非常浪费空间. 然后如果页面很多,需要的存储就更大了。为这样一个去重浪费这么多的存储空间,实际上是很不值得的. 所以就可以采用hypeloglog, hypeloglog 提供的去重技术方案虽然不精确,但是误差率是0.81%,这样的京都已经满足了UV统计需求
3. GEO redis 3.2 版本提供了GEO功能,支持存储地理位置信息用来实现诸如附近位置等这类依赖于地理位置信息的功能, 但是这个实际上在项目中并没有使用过

7. redis 的事务

redis 提供了事务功能,但是这个事务很弱,在回滚机制上,redis 只能对基本的语法错误进行判断
其原理是redis 实现在服务端的行为,用户只需multi 命令时,服务器会将这个用户对应的客户端对象设置为一个特殊的状态,在这个状态后续用户的查询命令不会被真的执行,而是被服务器缓存起来,直到用户执行exec命令为止,服务器会将这个用户对应的客户端对象中缓存的命令按照提交顺序一次执行

8. redis 的持久化

redis 提供两种持久化方式,分别是rdb 和 aof 
rdb 就是把当前数据生成快照保存到硬盘
aof 以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令以达到恢复数据的目的, 主要是解决了数据持久化的实时性

rdb 持久化,可能会丢失较多的数据, 因为在执行快照之后的操作,如果没有及时触发rdb, 那么redis重启就可能会丢失
aof 持久化,是基于缓冲的,redis 的每次命令,都会先写入到aof 缓冲区中,根据参数来进行持久化

8.1 redis 持久化原理

rdb 的持久化,是写时复制, 当输入bgsave 或者触发配置文件中的save 时,会fork 出一个子进程,子进程与父进程共享内存空间,当读取时,无任何操作,当修改时,则会进行写时复制,此时子进程会以页为单位进行拷贝,使用拷贝后的新物理空间,将修改值写入到新物理空间中,然后进行快照,子进程写完后会通知主进程,主进程会将缓存池中的数据也写入到rdb 临时文件中,然后修改rdb 文件的引用,将临时文件替换为正常的rdb 文件
aof 持久化 仅仅只是文件的追加,会在aof 缓冲区进行命令转换,然后根据配置信息追加到aof文件中
aof 会触发一个重写, 是为了解决aof 文件过大的问题, 压缩和优化aof 文件的内容,减少冗余和无效命令
当触发aof 重写, 会fork 一个子进程,子进程会扫描redis 数据并进行优化处理,如压缩转换,合并一些命令、去除无效数据,将其写入到一个新的临时文件中。同时主进程还继续接受命令,并将这些新命令添加到重写缓冲区。子进程在处理过程中,对于主进程新产生的数据通过写时复制写入到临时文件中。重写完成后,主进程会将缓冲区的内容追加到临时文件,然后临时文件替换aof 文件,关闭旧的aof文件

9. redis 的渐进式rehash

9.1 全局hash表

为了实现从键到值的快速访问,redis 使用了一个hash表来保存所有的键值对,一个hash表,其实就是一个数据,数组的每个元素称为一个hash桶,所以,会经常说,一个hash表是由多个hash桶组成的,每个hash桶中保存了键值对数据. hash桶中的entry 元素保存了key 和 value 指针,分别指向了实际的键和值,这样一来,即时值是一个集合,也也可以通过 *value 指针被查到, 因为这个hash表保存了所有的键值对,所以会将其称为全局hash表

hash表的最大好处很明显,就是可以使用O(1)的时间复杂度来快速查找到键值对,只需要计算键的hash值,就可以知道它所对应的hash桶位置,然后就可以访问相应的entry 元素
但是当往redis 中写入大量数据后,就有可能发现操作有时候突然变慢了, 这是因为hash表的冲突和reshah 带来的操作阻塞

9.2 redis 的渐进式rehash

rehash 在rehash 阶段,不会在发生扩容和缩容,必须等rehash结束

redis 的渐进式rehash 是指在进行hash表扩容或缩容时,redis 采取的一种渐进式、非阻塞的方式来进行rehash操作,以减少对服务的影响
具体就是, 当redis 需要对hash表进行扩容或缩容时,它会创建一个新的hash表,并将原有hash表中的数据逐步迁移到新的hash表中,整个过程分为两段
1. 迁移数据, redis 会在新hash表中为每个槽位(slot)保留空位,并逐步将原hash表中的数据按照一定的规则迁移到新的hash表中,这个过程是逐步进行的,每次只迁移一部分数据,以减少对服务的影响
2. 同步数据, 在数据迁移的过程中, 新旧hash表会同时存在,redis 会在后台不断将新旧hash表中的数据进行同步,确保数据的一致性,一旦所有数据都成功迁移并同步完成, redis会将新hash表替换为原有hash表,完成rehash过程
3. redis 的渐进式原则
3.1 分治思想: 将reshah 分到之后的每步增删改查的操作当中, 即每次处理redis时,复制一个key
3.2 在定时器中, 最大执行毫秒rehash, 每次复制100个数组key槽位

9.3 rehash 过程遇到增删改查时会怎么做

查: 查找数据时,会先在hash表1中查找数据,没找到就会在hash表2中去找
增: 新增数据时,只会增加到hash表2中,不会在hash表1中做任何操作
删&改: 在两个表上都会操作

10. redis 过期策略

过期处理策略: redis所有的数据结构都可以设置过期时间, 时间一到,就会自动删除, 但是如果同一时间太多的key 过期, 就会导致redis 出现卡顿, 因为redis 时单线程的, 删除的时间也会占用线程的处理时间

定时扫描: redis 会将每个设置了过期时间的key放入到一个独立的字典只能够, 会定时扫描这个字典来删除过期key, 默认每秒10次过期扫描, 但是不会遍历所有key, 而是采用简单的贪心策略, 从过期字典中随机20个key, 删除这20key中已经过期的Key, 如果过期的key 比率超过1/4, 就会重复扫描

从库的过期策略: 从库不会进行过期扫描, 从库对过期的处理是被动的, 主库在key 过期时, 会在aof 文件里增加一条del 指令, 同步到所有的从库, 从库通过执行这条del 指令来删除过期key。因为指令同步时异步进行的,所以主库过期的key 的del 指令没有及时同步到从库的话,会出现主从数据的不一致,主库没有的数据在从库还存在

惰性删除: 所谓惰性删除策略就是在客户端访问这个Key 的时候, redis对key 的过期时间进行检查, 如果过期了就立即删除, 不会返回任何东西

11. 内存淘汰算法

当redis 内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换, 交换会让redis 的性能急剧下降, 对于访问量比较频繁的redis 来说, 这样龟速的存取效率基本上等于不可用
针对内存淘汰策略, redis 提供了8种 策略
Noeviction: 不会继续服务写请求, 但是删读请求可以继续进行, 这样可以保证不会丢失数据, 但是会让线上的业务不能持续进行, 默认的淘汰策略
volatile-lru: 尝试淘汰设置了过期时间的key,最老使用的key 优先被淘汰, 没有设置过期时间的key 不会被淘汰, 可以保证需要持久化的数据不会突然丢失
volatile-ttl: 根据key 的剩余首批ttl的值, ttl越小越优先被淘汰
volatile-random: 过期key集合中随机的Key
allkeys-lru: 全体key 最老使用
allkeys-random: 全体key随机

11.1 LRU

lru: 如果数据最近被访问过, 那么这个数据未来被访问的几率也会更大
实现lru 算法除了需要k/v 字典外, 还是需要附近一个链表, 链表中的元素按照一定的顺序进行排列, 当空间满的时候, 会踢掉链表尾部的元素。当字典的某个元素被访问时,它在链表中的位置会被移动到表头,所以链表的元素排序顺序就是元素最近被访问的时间顺序

近似LRU: redis 使用的是近似LRU算法,它跟lru算法还不太一样,之所以不使用lru,是因为需要消耗大量的额外内存,需要对现有的数据结构进行较大的改造
在现有数据结构的基础上使用随机采样法来淘汰元素,能达到和lru算法非常近似的效果
redis 位每个key 增加了一个额外的小字段, 这个字段的长度是24B, 也就是最后一次被访问的时间戳,当redis执行写操作时,发现内存超出,就会执行一次LRU算法,就是随机采样5个key,然后淘汰最旧的key,淘汰后内存还是超出,继续步骤

11.2 lfu

lfu: 根据key的最近被访问的频率进行淘汰, 很少被访问的优先被淘汰,被访问的多的则被留下来
lru 强调最近最少使用, 关注的是最近有没有使用过
lfu 强调的是一段时间的使用次数, 关注的是频次
lfu 算法能更好的表示key 被访问的热度。如果使用的是lru 算法,一个key 很久没有被访问到,只刚刚是偶尔被访问了一次,那么它就被认为是热点数据,不会被淘汰. 而有些key 将来是很有可能被访问到的则被淘汰了。如果使用lfu 算法则不会出现这种情况, 因为使用一次并不会是一个key 成为热点数据.
lfu 原理使用计数器对key进行排序, 每次key 被访问的时候, 计数器增大, 计数器越大,可以约等于访问越频繁. 具有相同引用计数的数据块则按照时间排序

12. 什么是缓存击穿, 缓存穿透,

缓存击穿是指一个key非常热点, 在不停的扛着大并发,大并发集中对一个点进行访问, 当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个完好无损的桶上凿开了一个洞
缓存穿透 是指查询一个根本不存在的数据, 缓存层和存储层都不会命中,于是这个请求就可以随意访问数据库,这个就是缓存穿透
缓存雪崩 由于缓存层承载着大量请求,有效的保护了存储层,但是如果缓存层由于某些原因不能提供服务, 比如同一时间缓存数据大面试失效, 那一瞬间redis 跟没有一样, 于是所有的请求就会达到存储层, 存储层的调用量会暴增, 造成存储层也会级别宕机的情况

13. bigkey

big key 是指key对应的value 所占的内存空间比较大, 例如一个字符串的value 可以最大存到512M, 一个列表类型的value 最多可以存储23-1个元素
会导致内存空间不均匀, 例如redis cluster, big key 会造成节点的内存空间使用不均匀
超时阻塞: 由于redis 单线程的特性, 操作bigkey 比较耗时, 也就意味着阻塞redis 可能性增大
网络拥塞: 每次获取bigkey 产生的网络流量较大

13.1 统计Bigkey

redis-cli --bigkeys 可以命令统计Bigkey 的分布

13.2 解决bigkey

主要思路为拆分, 对big key 存储的数据, 进行拆分,变成value1, value2 等等

14. 热点key

在redis 中, 访问频率高的key称为热点key

14.1 危害

流量集中, 达到物理网卡上限
请求过多,缓存分片服务被打垮
db 击穿,引起业务雪崩

15. redis 主从、哨兵、集群问题分析

15.1 主从复制原理

首先是建立连接,从节点向主节点发送Sync 命令,请求同步数据
然后主节点接收到请求后,会将所有数据生成一份rdb 文件并发送给从节点,从节点接收并加载rdb 文件来初始化数据
在全量同步完成后, 主节点后续执行的写命令会实时同步给从节点,从节点执行这些命令来保持与主节点的数据一致性, 主节点通过向从节点发送ping包来确认连接,同时发送命令数据
主节点会维护一个复制积压缓冲区, 用于暂存最近执行的命令, 当从节点因为网络等原因暂时断开连接后重新连接时,可以根据缓冲区中的数据进行部分数据的同步
从节点会记录自己的复制偏移量,与主节点的偏移量进行对比, 以确保同步的准确性

15.2 redis集群方式

主从集群: 由一个主节点和多个从节点组成, 主节点负责处理写操作, 从节点复制主节点数据, 主要提供数据冗余和读性能提升
哨兵集群: 基于主从集群, 通过哨兵节点来监控主从节点的状态,实现自动鼓掌转移, 保障高可用性
cluster 集群: redis提供的分布式集群方案, 采用哈希槽机制将数据分布到多个节点上,节点之间通过特定协议通信和协调, 实现高可用和可扩展性

15.3 哨兵和 cluster 的区别

哨兵主要侧重于主从架构下的高可用保障, 自动监控和处理主节点的故障切换
cluster 则侧重于提供分布式存储和计算能力, 实现数据的分布式存储和处理

哨兵模式的数据主要基于主从同步, 数据都是一致的
cluster 采用hash槽将数据均匀分布在各个节点上

哨兵模式扩展相对有限,主要是增加从节点数量
cluster 可以更灵活的通过增加节点来扩展集群规模和性能

哨兵主要有从节点和主节点,外加哨兵节点监控
cluster 有主从节点,同时节点还需要成功数据路由等功能

哨兵和cluster 的网络通信机制和协议不同, 哨兵是通过发布订阅机制来互相通信, 获取和交换主从节点状态
cluster 节点之间通过bus 协议进行通信, 包括节点信息的交换、槽的分配管理、故障检测等多种信息的传递, 同时每个节点会不断向其他节点发送心态消息以维持集群状态的一致性和实时性

哨兵更适合相对简单的高可用场景,对分布式特性要求不高
cluster 更适合大规模、高并发、对分布式存储和处理有较高要求的场景

posted on   antor  阅读(4)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示