文章目录
- 基础
- 什么是Redis?
- Redis有哪些优点?
- Redis有哪些缺点?
- Redis有哪些适合的场景?
- Redis 为什么是单线程的
- 单线程的Redis为什么这么快
- 为什么Redis6.0之后又改用多线程呢?
- Redis的线程模型了解吗?
- Memcache与Redis的区别都有哪些?
- Redis的数据类型,以及每种数据类型的使用场景
- 每种数据结构实现原理
- 过期策略
- 基于LinkedHashMap实现的LRU缓存
- 持久化
- Redis持久化机制
- RDB的优缺点
- RDB持久化的原理和工作方式
- RDB持久化命令save和bgsave的区别
- AOF的优缺点
- AOF重写机制原理
- Redis 4.0 对于持久化机制的优化
- 高级特性
- 发布-订阅
- 管道Pipeline
- 事务
- 怎么理解Redis事务?
- Redis事务相关的命令有哪几个?
- Lua脚本
- 主从复制
- 概念
- 作用
- 原理
- 哨兵模式
- 概念
- 作用
- 集群
- 说说 Redis 哈希槽的概念?
- Redis 集群方案什么情况下会导致整个集群不可用?
- 是否使用过 Redis 集群,集群的原理是什么?
- redis集群方案应该怎么做?都有哪些方案?
- Redis 集群最大节点个数是多少?
- Redis集群如何选择数据库?
- Redis集群的主从复制模型是怎样的?
- Redis集群会有写操作丢失吗?为什么?
- Redis集群之间是如何复制的?
- 优化
- Redis 常见性能问题和解决方案
- Redis如何做内存优化?
- 应用
- MySQL 里有 2000w 数据,redis 中只存 20w 的数据,如何保证 redis 中的数据都是热点数据?
- 假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以 某个固定的已知的前缀开头的,如果将它们全部找出来?
- 使用过 Redis 做异步队列么,你是怎么用的?
- 如果有大量的 key 需要设置同一时间过期,一般需要注意 什么?
- 如何解决缓存穿透,缓存雪崩,缓存击穿?
- 你有没有考虑过,如果你多个系统同时操作(并发)Redis带来的数据问题?
- 你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?
- 布隆过滤器的原理
- 一致性Hash算法的原理
- 如何实现分布式锁
- 生产环境中的 redis 是怎么部署的?
基础
什么是Redis?
redis全称:Remote Dictionary Server。
Redis本质上是一个高性能Key-Value类型的内存数据库,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。
Redis有哪些优点?
- 性能极高 – Redis 能读的速度是 110000 次/s,写的速度是 81000 次/s 。
- 丰富的数据类型 – Redis 支持 String, List, Hash, Set及 Sorted Set 数据类型。
- 支持数据持久化,支持AOF和RDB两种持久化方式。
- 线程安全 – Redis 的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过 MULTI 和 EXEC 指令包起来。
- 丰富的特性 – Redis 还支持 publish/subscribe, 通知, key 过期等等特性。
- 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。
- 支持哨兵,集群模式,保证高可用。
Redis有哪些缺点?
- 因为是纯内存操作,所以数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
Redis有哪些适合的场景?
- Session共享(单点登录)
- 页面缓存
- 队列
- 排行榜/计数器
- 发布/订阅
- 分布式锁
Redis 为什么是单线程的
- 因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。**
- 缓存数据都是内存操作 IO 时间不会太长,单线程可以避免线程上下文切换产生的代价。
这里我们一直在强调的单线程,只是在处理我们的网络请求的时候只有一个线程来处理,一个正式的Redis Server运行的时候肯定是不止一个线程的,这里需要大家明确的注意一下!例如Redis进行持久化的时候会以子进程或者子线程的方式执行.
单线程的Redis为什么这么快
- 纯内存操作:绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
- 数据结构简单,对数据操作也简单。
- 单线程操作,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。
- 采用了多路非阻塞I/O复用机制,这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程
为什么Redis6.0之后又改用多线程呢?
redis使用多线程并非是完全摒弃单线程,redis还是使用单线程模型来处理客户端的请求,只是使用多线程来处理数据的读写和协议解析,执行命令还是使用单线程。
这样做的目的是因为redis的性能瓶颈在于网络IO而非CPU,使用多线程能提升IO读写的效率,从而整体提高redis的性能。
Redis的线程模型了解吗?
Redis 内部使用文件事件处理器 file event handler
,这个文件事件处理器是单线程的,所以 Redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 Socket,根据 Socket 上的事件来选择对应的事件处理器进行处理。
文件事件处理器的结构包含 4 个部分:
- 多个 Socket
- IO 多路复用程序
- 文件事件分派器
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
多个 Socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 Socket,会将 Socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。
Memcache与Redis的区别都有哪些?
- redis支持持久化
- Redis支持的数据类型更多:
- 使用底层模型不同
Redis的数据类型,以及每种数据类型的使用场景
- String
最常规的set/get操作,value可以是String也可以是数字。一般用来缓存常用字符串或者做一些复杂的计数功能的缓存。
- Hash
这里value存放的是结构化的对象,可以比较方便的就是操作其中的某个字段。一般用来缓存一个对象。
- list
使用List的数据结构,可以做简单的消息队列的功能。另外可以利用lrange命令,做基于redis的分页功能,性能极佳,用户体验好。
- set
因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能,可以实现抽奖功能。另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
- sorted set
sorted set多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜,取TOP N操作,作为带权重的队列。
- Bitmap
bitmap是面向位操作的字符串类型,占用空间小,可以用来统计用户签到,统计活跃用户,各种状态值等。
- HyperLogLog
HyperLogLog是用来做基数统计的算法,用来做一些去重的,大数据里的统计,比如网格UV值。
- GEO
Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,比如位置距离计算或者根据半径计算位置等,用来实现附近的人,计算最优地图路径。
每种数据结构实现原理
String:内部的实现是通过 SDS(Simple Dynamic String )来存储的。SDS 类似于 Java 中的 ArrayList,可以通过预分配冗余空间的方式来减少内存的频繁分配。
过期策略
redis采用的是定期删除+惰性删除策略
为什么redis不用定时删除策略?
定时删除是指用一个定时器来负责监视key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key。因此没有采用这一策略。
定期删除+惰性删除是如何工作的呢?
定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每隔100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。
于是,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。
采用定期删除+惰性删除就没其他问题了么?
不是的,如果定期删除没删除key。然后你也没及时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高,最终有可能会内存占满,服务就挂了。此时就应该采用内存淘汰机制。
在redis.conf中有一行配置,用来配内存淘汰策略:
maxmemory-policy volatile-lru
一共有6种内存淘汰策略:
- no-eviction:不会继续服务写请求(DEL请求可以继续服务),读请求可以继续进行。这样可以保证不会丢失数据,但是会让线上的业务不能持续进行。这是默认的淘汰策略。
- volatile-lru:尝试淘汰设置了过期时间的 key,最少使用的 key 优先被淘汰。没有设置过期时间的key不会被淘汰,这样可以保证需要持久化的数据不会突然丢失。
- volatile-ttl:跟上面一样,除了淘汰的策略不是 LRU,而是 key 的剩余寿命 ttl 的值,ttl 越小越优先被淘汰,即淘汰将要过期的数据.
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中随机选择数据淘汰
- allkeys-lru:区别于 volatile-lru,这个策略要淘汰的 key 对象是全体的 key 集合,而不只是过期的 key 集合。这意味着没有设置过期时间的 key 也会被淘汰。
- allkeys-random:从全体的key集合(server.db[i].dict)中任意选择数据淘汰
如果key没有设置过期时间,那么volatile-lru,volatile-ttl,volatile-random这些策略的行为就类似于no-eviction
基于LinkedHashMap实现的LRU缓存
class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int CACHE_SIZE;
/*** 传递进来最多能缓存多少数据
** @param cacheSize 缓存大小
*/public LRUCache(int cacheSize) {
// true 表示让 linkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。
super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
CACHE_SIZE = cacheSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
// 当 map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。
return size() > CACHE_SIZE;
}
}
持久化
Redis持久化机制
Redis是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化。当Redis重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的。
两种持久化机制:RDB和AOF。
RDB:Redis默认的持久化方式。按照一定的时间周期策略把内存的数据以快照的形式保存到硬盘的二进制文件。即Snapshot快照存储,对应产生的数据文件为dump.rdb,通过配置文件中的save参数来定义快照的周期。
AOF:Redis会将每一个收到的写命令都通过Write函数追加到aof文件最后。当Redis重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。
RDB的优缺点
优点:
- 对性能影响最小:Redis在保存RDB快照时会fork出子进程进行接下来的保存工作,父工程无须执行任何磁盘IO,几乎不影响Redis处理客户端请求的效率。
- 数据恢复快:使用RDB文件进行数据恢复比使用AOF要快很多。
- 数据恢复可靠:每次快照会生成一个完整的数据快照文件,所以可以辅以其他手段保存多个时间点的快照(例如把每天0点的快照备份至其他存储媒介中),作为非常可靠的灾难恢复手段。
缺点:
- 数据安全性低,会丢失数据:redis发生故障时,会丢失最近一次生成快照之后更改的所有数据。
- 如果数据集非常大且CPU不够强时,Redis在fork子进程时可能会消耗相对较长的时间,此时会造成服务器在一定时间内停止处理客户端请求.
RDB持久化的原理和工作方式
- Redis主进程会fork一个子进程,将当前父进程的数据库数据复制到子进程的内存中。
- 子线程负责快照写入磁盘,父线程继续处理命令请求。
- 当子线程完成对新RDB文件的写入时,redis用新RDB文件替换原来的RDB文件,并删除旧的RDB文件.
RDB持久化命令save和bgsave的区别
最大的区别就是父线程是否会继续响应命令请求。
- BGSAVE命令: 客户端向Redis发送 BGSAVE命令 来创建一个快照。Redis主线程会fork一个子进程,然后子进程负责将快照写入硬盘,而父进程则继续处理命令请求。
- SAVE命令: 客户端还可以向Redis发送 SAVE命令 来创建一个快照.接到SAVE命令的Redis服务器在快照创建完毕之前不会再响应任何其他命令。SAVE命令不常用,我们通常只会在没有足够内存去执行BGSAVE命令的情况下,又或者即使等待持久化操作执行完毕也无所谓的情况下,才会使用这个命令。
AOF的优缺点
优点:
- 数据备份的最安全,在启用appendfsync always时,任何已写入的数据都不会丢失,使用在启用appendfsync everysec也至多只会丢失1秒的数据。
- 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容,即使出现了某条日志只写入了一半的情况,也可以使用redis-check-aof工具轻松修复。
- AOF文件易读,可修改,在进行了某些错误的数据清除操作后,只要AOF文件没有rewrite,就可以把AOF文件备份出来,把错误的命令删除,然后恢复数据。
缺点:
- AOF文件通常比RDB文件更大
- 性能消耗比RDB高
- 数据恢复速度比RDB慢
AOF重写机制原理
Redis提供了AOF rewrite功能,可以重写AOF文件,只保留能够把数据恢复到最新状态的最小写操作集。
- Redis执行 fork() ,fork出子进程,现在同时拥有父进程和子进程。
- 子进程开始将新 AOF 文件的内容写入到临时文件。
- 对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有AOF文件的末尾:这样即使在重写的中途发生停机,现有的AOF文件也还是安全的。
- 当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾。
- 最后Redis用新文件替换旧文件,并重命名,之后所有命令都会直接追加到新 AOF 文件的末尾.
Redis 4.0 对于持久化机制的优化
Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。
如果把混合持久化打开,那么程序会优先使用 AOF 文件来恢复数据集,因为AOF文件所保存的数据通常是最完整的。
AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差.
高级特性
发布-订阅
功能是订阅发布功能,可以用作简单的消息队列。
管道Pipeline
可以批量执行一组指令,一次性返回全部结果,可以减少频繁的请求应答。前提是pipeline执行的指令之间没有因果相关性。
事务
怎么理解Redis事务?
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
但redis提供的不是严格的事务,redis只保证串行执行命令,并且能保证全部执行,但是执行命令失败时并不会回滚,而是会继续执行下去。
redis通过MULTI、EXEC、WATCH等命令来实现事务机制,事务执行过程将一系列多个命令按照顺序一次性执行,并且在执行期间,事务不会被中断,也不会去执行客户端的其他请求,直到所有命令执行完毕。
Redis事务相关的命令有哪几个?
MULTI、EXEC、DISCARD、WATCH、UNWATCH
Lua脚本
Redis 支持提交 Lua 脚本,利用他的原子性,来执行一系列的功能。
主从复制
概念
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点。
作用
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
- 读写分离:可以用于实现读写分离,主库写、从库读,读写分离不仅可以提高服务器的负载能力,同时可根据需求的变化,改变从库的数量。
- 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
原理
- 当建立一个从服务器时,无论是初次连接还是重新连接,从服务器都将向主服务器发送一个SYNC 命令.
- 接到SYNC 命令的主服务器将启动一个后台进程开始执行BGSAVE操作,将数据库快照保存到文件中,并在保存操作执行期间,将所有新执行的写入命令都保存到一个缓冲区里面.
- 当BGSAVE 执行完毕后,主服务器将执行保存操作所得的 .rdb 文件发送给从服务器,从服务器接收这个.rdb 文件,slave会先写入本地磁盘,然后再从本地磁盘加载到内存中,然后slave会清空数据,执行RDB文件恢复数据.此时会基于旧的数据版本对外提供服务
- 然后主服务器将写命令缓冲区中积累的所有内容都发送给从服务器,从服务器继续解析这些命令.
- 之后master每执行一个写命令,就向从服务器发送相同的写命令.
哨兵模式
概念
Redis Sentinel可以配合Redis的复制功能使用,并对下线的主服务器进行故障转移,是Redis的高可用的解决方案,哨兵模式并不能提升性能。哨兵必须用三个实例去保证自己的健壮性的。
作用
- 监控( Monitoring):Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
- 提醒( Notification):当被监控的某个 Redis 服务器出现问题时,Sentinel 可以通过 API 向管理员 或者其他应用程序发送通知。
- 自动故障迁移( Automatic failover):当一个主服务器不能正常工作时,Sentinel 会开始一次自动故障迁移操作,它会将失效主服务器的其中一个从服务器升级为新的主服务器,并让失效主服务器的其他从服务器改为复制新的主服务器;当客户端试图连接失效的主服务器时,集群也会向客户端返回 新主服务器的地址,使得集群可以使用新主服务器代替失效服务器。
集群
说说 Redis 哈希槽的概念?
Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有 16384 个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽, 集群的每个节点负责一部分 hash 槽。
Redis 集群方案什么情况下会导致整个集群不可用?
有 A,B,C 三个节点的集群,在没有复制模型的情况下,如果节点 B 失败了, 那么整个集群就会以为缺少 5501-11000 这个范围的槽而不可用。
是否使用过 Redis 集群,集群的原理是什么?
- Redis Sentinal 着眼于高可用,在 master 宕机时会自动将 slave 提升为 master,继续提供服务。
- Redis Cluster 着眼于扩展性,在单个 redis 内存不足时,使用 Cluster 进行 分片存储,Cluster也支持主从切换保证高可用。
redis集群方案应该怎么做?都有哪些方案?
- twemproxy
大概概念是,它类似于一个代理方式,使用方法和普通redis无任何区别,设置好它下属的多个redis实例后,使用时在本需要连接redis的地方改为连接twemproxy,它会以一个代理的身份接收请求并使用一致性hash算法,将请求转接到具体redis,将结果再返回twemproxy。使用方式简便(相对redis只需修改连接端口),对旧项目扩展的首选。 问题:twemproxy自身单端口实例的压力,使用一致性hash后,对redis节点数量改变时候的计算值的改变,数据无法自动移动到新的节点。
- codis
目前用的最多的集群方案,基本和twemproxy一致的效果,但它支持在 节点数量改变情况下,旧节点数据可恢复到新hash节点。
- redis cluster3.0
redis自带的集群,特点在于他的分布式算法不是一致性hash,而是hash槽概念,以及自身支持节点设置从节点。
- 在业务代码层实现
起几个毫无关联的redis实例,在代码层,对key进行hash运算,然后去对应的redis实例操作数据。
Redis 集群最大节点个数是多少?
16384 个。
Redis集群如何选择数据库?
Redis集群目前无法做数据库选择,默认在0数据库。
Redis集群的主从复制模型是怎样的?
为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有N-1个复制品。
Redis集群会有写操作丢失吗?为什么?
redis并不能保证数据的强一致性,这意味着在实际中集群在特定的条件下可能会丢失写操作。
Redis集群之间是如何复制的?
异步复制
优化
Redis 常见性能问题和解决方案
- Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
- 如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次。
- 为了主从复制的速度和连接的稳定性,Master 和 Slave 最好在同一个局域网。
- 尽量避免在压力很大的主库上增加从库。
- 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…这样的结构方便解决单点故障问题,实现 Slave 对 Master 的替换。如果 Master 挂了,可以立刻启用 Slave1 做 Master,其他不变。
Redis如何做内存优化?
尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。
比如你的web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面。
应用
MySQL 里有 2000w 数据,redis 中只存 20w 的数据,如何保证 redis 中的数据都是热点数据?
redis内存数据集大小上升到一定大小的时候,就会实行内存淘汰策略。可以设置内存淘汰策略为allkeys-lru或者volatile-lru
假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以 某个固定的已知的前缀开头的,如果将它们全部找出来?
使用 keys 指令可以扫出指定模式的 key 列表。
对方接着追问:如果这个 redis 正在给线上的业务提供服务,那使用 keys 指令会有什么问题?
这个时候你要回答 redis 关键的一个特性:redis是单线程的。keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长。
使用过 Redis 做异步队列么,你是怎么用的?
一般使用 list 结构作为队列,rpush 生产消息,lpop 消费消息。当 lpop 没有消息的时候,要适当 sleep 一会再重试。
如果对方追问可不可以不用 sleep 呢?
list 还有个指令叫 blpop,在没有消息的时候,它会阻塞住直到消息到来。
如果对方追问能不能生产一次消费多次呢?
使用 pub/sub 主题订阅者模式,可以实现 1:N 的消息队列。
如果对方追问 pub/sub 有什么缺点?
在消费者下线的情况下,生产的消息会丢失,这时就得使用专业的消息队列如 RabbitMQ 等。
如果对方追问 redis 如何实现延时队列?
使用 sortedset,拿时间戳作为 score,消息内容作为 key 调用 zadd 来生产消息,消费者用zrangebyscore 指令 获取 N 秒之前的数据轮询进行处理。
如果有大量的 key 需要设置同一时间过期,一般需要注意 什么?
如果大量的 key 过期时间设置的过于集中,到过期的那个时间点,redis 可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一 些。
如何解决缓存穿透,缓存雪崩,缓存击穿?
- 缓存穿透:查询一个一定不存在的数据,缓存中无该数据,每次请求都会打到DB,最终导致DB挂了。
解决方法:1. 布隆过滤器, 2.缓存空值
- 缓存雪崩:大量缓存key同时失效,导致大量请求打到DB,导致DB挂了
解决方法:1. 缓存时间加上随机值 2. 搭建高可用集群 3. 缓存降级
- 缓存击穿:一个热点key突然失效,导致大量请求打到DB,导致DB挂了
解决方法: 1. 分布式锁,一次只允许一个请求去请求DB. 2. 缓存永不过期 3. 二级缓存
你有没有考虑过,如果你多个系统同时操作(并发)Redis带来的数据问题?
如果对这个 Key 操作,不要求顺序,这种情况下,准备一个分布式锁,大家去抢锁,抢到锁就做 set 操作即可。
如果对这个 Key 操作,要求顺序,假设有一个 key1,系统 A 需要将 key1 设置为 valueA,系统 B 需要将 key1 设置为 valueB,系统 C 需要将 key1 设置为 valueC。
期望按照 key1 的 value 值按照 valueA > valueB > valueC 的顺序变化。
这种时候我们在数据写入数据库的时候,需要保存一个时间戳,从数据库读取数据的时候,把时间戳也查出来。每次要写之前,先判断一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。
你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?
最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern
- 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
- 更新的时候,先更新数据库,然后再删除缓存
布隆过滤器的原理
使用布隆过滤器可以实现大量数据的去重操作。
优点: 占用的内存要比使用HashSet要小的多,也适合大量数据的去重操作。
缺点:有误判的可能。没有重复可能会判定重复,但是重复数据一定会判定重复。
布隆过滤器可以插入元素,但不可以删除已有元素。其中的元素越多,误报率越大,但是漏报是不可能的。
核心思想:当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。
一致性Hash算法的原理
一致性Hash算法也是通过取模的方法,普通的hash算法是对服务器的数量取模,一致性Hash算法是对2^32取模.
一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环
第一步将各个服务器使用Hash进行一个哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。
第二步将数据key使用相同的函数Hash计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器!
根据一致性Hash算法,数据A会被定为到Node A上,B被定为到Node B上,C被定为到Node C上,D被定为到Node D上。
一般的,在一致性Hash算法中,如果增加一台服务器,则受影响的数据仅仅是新服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它数据也不会受到影响。
综上所述,一致性Hash算法对于节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性.
为了解决数据倾斜的问题,一致性Hash算法引入了虚拟节点机制,即对每一个服务节点计算多个哈希,每个计算结果位置都放置一个此服务节点,称为虚拟节点。具体做法可以在服务器IP或主机名的后面增加编号来实现。
如何实现分布式锁
1.加锁使用setnx命令,同时设置超时时间,避免死锁。
SET key value[EX seconds][PX milliseconds][NX|XX]
EX seconds: 设定过期时间,单位为秒
PX milliseconds: 设定过期时间,单位为毫秒
NX: 仅当key不存在时设置值
XX: 仅当key存在时设置值
2.value使用uuid,必须要具有唯一性,保证自己的锁自己解。
3.解锁使用lua脚本,保证原子性,先判断是否是自己的锁,是的话,才能解锁。
4.注意锁的续期,不然线程1锁超时,自动解锁,导致线程2抢到了锁,这时候线程1执行完业务把锁删了,如果不判断锁是否是自己的,就会导致把线程2的锁给删除了。
5.redis实现的分布式锁,在主从复制的时候,会有问题。
生产环境中的 redis 是怎么部署的?
redis cluster,10 台机器,5 台机器部署了 redis 主实例,另外 5 台机器部署了 redis 的从实例,每个主实例挂了一个从实例,5 个节点对外提供读写服务,每个节点的读写高峰qps可能可以达到每秒 5 万,5 台机器最多是 25 万读写请求/s。
机器是什么配置?32G 内存+ 8 核 CPU + 1T 磁盘,但是分配给 redis 进程的是10g内存,一般线上生产环境,redis 的内存尽量不要超过 10g,超过 10g 可能会有问题。
5 台机器对外提供读写,一共有 50g 内存。
因为每个主实例都挂了一个从实例,所以是高可用的,任何一个主实例宕机,都会自动故障迁移,redis 从实例会自动变成主实例继续提供读写服务。
你往内存里写的是什么数据?每条数据的大小是多少?商品数据,每条数据是 10kb。100 条数据是 1mb,10 万条数据是 1g。常驻内存的是 200 万条商品数据,占用内存是 20g,仅仅不到总内存的 50%。目前高峰期每秒就是 3500 左右的请求量。
其实大型的公司,会有基础架构的 team 负责缓存集群的运维。