Redis技术分享
2022-11-19 22:48:31 1 举报
AI智能生成
redis缓存专题
作者其他创作
大纲/内容
Redis介绍
什么是Redis
Redis(Remote Dictionary Server) 是一个使用 C 语言编写的,开源的(BSD许可)高性能非关系型(NoSQL)的键值对数据库
为什么Redis能这么快?
读写性能优异, Redis能读的速度是11W次/s,写的速度是8.1W次/s
完全基于内存,绝大部分请求是纯粹的内存操作,执行效率高
数据结构简单,对数据操作也简单
采用单线程,单线程也能处理高并发请求,想多核也可启动多实例
没有创建线程、销毁线程带来的消耗
避免了上线文切换导致的 CPU 消耗
避免了线程之间带来的竞争问题,例如加锁释放锁死锁等等
使用多路I/O复用模型,异步非阻塞IO
多路: 指的是多个socket网络连接;
复用: 指的是复用一个线程。
多路复用主要有三种技术:select,poll,epoll。epoll是最新的, 也是目前最好的多路复用技术
复用: 指的是复用一个线程。
多路复用主要有三种技术:select,poll,epoll。epoll是最新的, 也是目前最好的多路复用技术
成本/收益
收益
加速读写
降低后端负载
成本
数据不一致性:缓存层和存储层的数据存在着一定时间窗口的不一致性,时间窗口跟更新策略有关
代码维护:加入缓存后,需要同时处理缓存层和存储层的逻辑,增大了开发者维护代码的成本
运维成本:独立中间件,集群、主从同步等加入复杂性
Memcache和Redis的区别
Memcache:代码层次类似hash
支持简单数据类型
不支持数据持久化存储
不支持主从
不支持分片
Redis
数据类型丰富,除了支持string类型的value外还支持hash、set、zset、list等数据结构
支持数据持久化存储,支持AOF和RDB两种持久化方式
支持主从复制,主机会自动将数据同步到从机,可以进行读写分离
支持分片
主要数据类型与其应用场景
String类型
热点数据缓存
设置内存的最大使用量以及淘汰策略来保证缓存的命中率;短信验证码,配置信息等
计数器
INT 类型,INCR 方法
帖子文章的阅读量,点赞数,允许一定的延迟,先写入 Redis 再定时同步到 数据库
分布式锁
自带的 SETNX 命令实现或RedLock 分布式锁实现,只有不存在时才能添加成功,返回 true。
分布式 Session
全局 ID
INT 类型,INCRBY,利用原子性
限流
INT 类型,INCR 方法
以访问者的 IP 和其他信息作为 key,访问一次增加一次计数,超过次数则返回 false。
位统计
String 类型的 BITCOUNT(1.6.6 的 bitmap 数据结构介绍)。
hash
存储字符串,Hash 与 String 的主要区别?
把所有相关的值聚集到一个 key 中,节省内存空间
只使用一个 key,减少 key 冲突
当需要批量获取值的时候,只需要使用一个命令,减少内存/IO/CPU 的消耗
不适合的场景:
1、Field 不能单独设置过期时间
2、没有 bit 操作
3、需要考虑数据量分布的问题(value 值非常大的时候,无法分布到多个节点)
String 可以做的事情,Hash 都可以做,适合用于存储对象
List有序集合
存储有序的字符串(从左到右),元素可以重复。可以充当队列和栈的角色
场景:
比较适合存储一些有序且数据相对固定的数据。如省市区表、字典表等
消息队列(发布/订阅功能)
通过 lpush 和 rpop 写入和读取消息
Set无序集合
随机获取元素
点赞、签到、打卡
商品标签
查找两个人共同的好友、查找两个人共同的好友
差集
sdiff set1 set2
交集
sinter set1 set2
并集
sunion set1 set2
zset有序集合
自动会根据score的值从小到大进行排序,去重但可以排序,排行榜
bitmap位图
可以把bitmap想象成一个以位为单位的数组, 数组的每个单元只能存储0和1
非常适合对整型的海量数据进行查询统计、排序、去重;适合对两个集合做交集、并集运算
拓展:布隆过滤器 Bloom Filter
判断一个元素是否在集合中存在
存在一定的误判率
过滤器越长误判率越小;哈希函数越多,效率越差,误判率越小
经典问题:
如何在海量元素中(例如 10 亿无序、不定长、不重复)快速判断一个元素是否存在?
大名鼎鼎的布隆过滤器
从容器的角度来说
如果布隆过滤器判断元素在集合中存在,不一定存在
如果布隆过滤器判断不存在,一定不存在!!!这个特性可以帮助我们去解决缓存穿透,用来判断数据库里一定没有这个数据,那么就可以直接返回错误了。
从元素的角度来说:
如果元素实际存在,布隆过滤器一定判断存在
如果元素实际不存在,布隆过滤器可能判断存在
A,B 两个文件,各存放 50 亿条 URL,每条 URL 占用 64 字节,内存限制是 4G,让你找出 A,B 文件共同的 URL
持久化
持久化意义
用作灾难恢复,数据恢复,保证高可用
解决缓存雪崩的问题
通过备份数据快速恢复,支撑企业级业务
RDB(默认开启)
默认情况下,按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb二进制文件。通过配置文件中的save参数来定义快照的周期
优点
容灾性好,全量数据快照,文件小,恢复快
缺点
数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失
AOF(默认关闭)
将Redis执行的每次写命令记录到单独的日志文件appendonly.aof中(先写入os cache,每隔一段时间刷到磁盘),当重启Redis会重新将持久化的日志中文件恢复数据
优点
可读性高,适合保存增量数据,数据不易丢失
安全性高,通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题
缺点
AOF 文件比 RDB 文件大,且恢复速度慢
数据集大的时候,比 RDB启动效率低
配置
AOF持久化,默认是关闭的,默认是打开RDB持久化,appendonly yes,可以打开AOF持久化机制,在生产环境里面,一般来说AOF都是要打开的,保证数据完整性
当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复,因为AOF数据比较完整
AOF三种同步策略
always: 每次写入一条数据,立即将这个数据对应的写日志同步到磁盘中去,性能非常差,吞吐量很低,确保说redis里的数据一条都不丢。
everysec(推荐):每秒将os cache中的数据同步到磁盘足够快,并且在故障时只会丢失1s中的数据。,这个最常用,生产环境一般都这么配置,性能很高,单机QPS还是可以上万的
no: 仅仅redis负责将数据写入os cache就撒手不管了,然后后面os自己会不时有自己的策略将数据写入磁盘,不可控了
Redis数据备份策略
写定时调度脚本
每小时copy一份rdb或aof目录的备份到一个目录中去,仅仅保留最近48小时的备份
每小时copy一份rdb或aof目录的备份到一个目录中去,仅仅保留最近48小时的备份
每天都保留一份当日的数据,可以保留最近一个月的备份
每次copy备份的时候,都把太旧的备份删了
每天晚上将当前机器上的备份复制到其他机器上,以防机器损坏
应用与常见问题
客户端
Jedis
pipeline管道
Pipeline 通过一个 队列把所有的命令缓存起来,然后把多个命令在一次连接中发送给服务器
使用pipeline批量写入数据,对于结果的实时性和成功性要求不高
jedis线程不安全
不支持字符串操作,不支持排序、事务、管道、分区等Redis特性
Redission(官方推荐)
基于 Netty 实现,采用非阻塞 IO,性能高
支持异步请求
支持连接池
不支持事务,官方建议以 LUA Scripting 代替事务
主从、哨兵、集群都支持。Spring 也可以配置和注入 RedissonClient。
应用
分布式锁
为什么需要分布式锁
多线程程序时,避免同时操作共享变量产生问题,通常会使用一把锁来互斥,以保证共享变量的正确性
如果是多个进程,需要同时操作一个共享资源,需要引入分布式锁实现
分布式锁基本要求
1、互斥性:只有一个客户端能够持有锁
2、不会产生死锁:即使持有锁的客户端崩溃,也能保证后续其他客户端可以获 取锁
3、只有持有这把锁的客户端才能解锁
官方推荐使用RedLock 分布式锁
思想:让客户端和多个独立的 Redis 实例依次请求加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁了,否则加锁失败
加锁
加锁步骤
1、获取客户端时间
2、客户端按顺序依次向 N 个 Redis 实例执行加锁操作
3、一旦客户端完成了和所有 Redis 实例的加锁操作,客户端就要计算整个加锁过程的总耗时。
2、客户端按顺序依次向 N 个 Redis 实例执行加锁操作
3、一旦客户端完成了和所有 Redis 实例的加锁操作,客户端就要计算整个加锁过程的总耗时。
加锁成功条件
客户端从超过半数(大于等于 N/2+1)的 Redis 实例上成功获取到了锁
客户端获取锁的总耗时没有超过锁的有效时间
客户端获取锁的总耗时没有超过锁的有效时间
解锁
执行N个解锁的Lua脚本
jedis、Redission可以实现
Zookeeper也可以实现
常见问题
缓存预热
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统
缓存穿透
数据在redis和数据库中都不存在,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉
造成这样的原因,1可能上一次错误查询,2.是恶意请求,故意制造的查询
(1)缓存空数据 (2)缓存特殊字符串,比如&&
但是如果是恶意请求的时候,上面这个方案就不适用了,因为你会缓存N多个垃圾请求的数据
解决方案
接口层增加校验,非法参数直接拦截,如用户鉴权校验,id做基础校验,id<=0的直接拦截
从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
采用布隆过滤器(推荐),将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力
缓存雪崩
缓存雪崩就是 Redis 的大量热点数据同时过期(失效),因为设置了相同的过期时间,刚好这个时候 Redis 请求的并发量又很大,就会导致所有的请求落到数据库,造成数据库短时间内承受大量请求而崩掉
解决方案
缓存数据的过期时间设置随机随机数,使 key 在不同的时间过期
一般并发量不是特别多的时候,使用最多的解决方案是加互斥锁或者使用队列,针对同一个 key 只允许一个线程到数据库查询
热点数据预热,缓存定时预先更新,避免同时失效
高可用分片集群,主从+哨兵,避免全盘崩溃
缓存永不过期
事中:准备降级方案,比如限流降级,避免数据库被打死;Redis出现问题,不去数据库查询,而是直接返回默认值给用户
事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据
缓存击穿
缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库读取数据,造成数据库短时间内承受大量请求而崩掉
缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库;击穿是一个雪花的雪崩,雪崩是所有雪花的穿透
解决方案
缓存永不过期
加互斥锁
0 条评论
下一页