Redis有什么作用?为什么要用Redis/缓存?
- 访问速度快:传统数据库将数据存储到磁盘上,而使用redis就可以将高频访问的数据放到redis中,直接从内存中读取。
- 高并发:mysql数据的并发能力有限,使用缓存接收的数据库请求数量远大于直接访问数据库的。
- 功能全面:除了缓存,还可以提供分布式锁、消息队列、限流、延时队列等场景。
- 使用Reids,不使用本地缓存的原因:
| 特性 | 本地缓存 | Redis缓存 |
|---|---|---|
| 数据一致性 | 多服务器部署时存在数据不一致 | 数据一致 |
| 内存限制 | 受限于单台服务器内存限制 | 独立部署,内存空间大 |
| 数据丢失问题 | 服务宕机,数据丢失 | 可持久化,不易丢失 |
| 管理维护 | 分散不易维护 | 集中管理,有丰富的管理工具 |
Redis除了做缓存之外,还能做什么?
- 分布式锁:用redis来做分布式锁是比较常见的方式, 通过Redisson来是实现分布式锁
- 限流: 可以通过Redis+Lua脚本来实现限流,或者可以通过Redisson的RRateLimiter来实现分布式限流,底层就是基于Lua代码+令牌桶来实现。
- 消息队列:Redis自带数据结构List可以做队列,5.0之后增加了stream 数据结构更适合做消息队列。类似于Kafka, 有主题、消费者组的概念,支持消息持久化以及ACK机制。、
- 延时队列: Redisson支持延迟队列(基于 Sorted Set 机制实现 )
- 分布式Session: 利用String或Hash存储 session数据,所有服务器都可访问。
如何基于Redis实现延时队列
- Redis实现延迟任务有两种方法:
- Redis过期事件监听
- Redisson 内置延时队列
1.Redis过期事件监听
-
Redis 过期事件监听实现延迟任务的原理:
- Redis有发布订阅功能(sub/pub),其中引入了channel(频道),类似于消息队列topic的概念。 pub生产者将消息发送给指定的channel, sub消费者则订阅对应的channel获取消息
- 发布者通过
PUBLISH投递消息给指定channel - 消费者通过
SUBSCRIBE订阅指定的channel, 并且订阅者可以订阅一个或多个channel
- 发布者通过
- Redis中有些默认的channel, 这些channel是Redis本身向它们发送消息,不是我们编写的代码;其中
_keyevent@0__:expired,就是一个默认channel,负责监听key的过期事件。 也就是说,当一个key过期,redis会发送一个消息到__keyevent@0__:expired这个channel中。 - 而我们只需要订阅这个channel,就可以实现延迟队列了
- Redis有发布订阅功能(sub/pub),其中引入了channel(频道),类似于消息队列topic的概念。 pub生产者将消息发送给指定的channel, sub消费者则订阅对应的channel获取消息
-
缺陷:
-
时效性差: 过期事件消息是在Redis删除key是发送的,而不是过期之后就会发送。
- 其中Redis的过期删除策略就两个:定期删除和惰性删除,定期删除是每隔一段时间会抽出一部分有过期时间的数据进行检查,如果过期就删除;而惰性删除,只有在取出key的时候才会进行过期检查。
- 因为两者删除策略各有千秋,所以使用定期删除+惰性删除的方案,所以就会存在,我设置了过期时间的key已经过期,但还没有删除的情况。
-
丢消息:redis的sub/pub模式中消息没有持久化,如果没有订阅者订阅指定channel,消息就会直接被丢弃,不会存储在redis中。
-
重复消息:redis的sub/pub模式只有广播模式,意味着消费者发送消息给指定channel, 订阅当前channel的所有订阅者都会收到消息。
-
2.Redisson内置延时队列
- Redisson 内部是如何实现延时任务的?
- 通过Redisson内部的延时队列
RDelayedQueue来实现延时任务RDelayedQueue延时队列基于Redis的zset有序集合来实现的,其中zset有序集合中每个元素都可以设置一个分数,来表示该元素的权重值。Redisson利用这一特性,将需要延迟执行的任务放入zset中,并给它们设置相应的过期时间作为分数。- Redisson在客户端其中一个定时任务,到时间后使用
zrangebyscore扫描zset中的过期元素(分数小于当前时间的),然后将过期的元素删除,并加入到就绪列表中。
- 通过Redisson内部的延时队列
- 优势:
- 减少了丢消息的可能:
RDelayedQueue的消息会被持久化,即使Redis宕机了,也可能只丢失一小部分数据,也可以用扫描数据库的方式作为补偿机制。 - 消息不存在重复消费的可能:都是从同一个队列中获取消息的,不存在重复消费的问题。
- 减少了丢消息的可能:
分布式缓存常见的选型方案:
-
Memcached 和 Redis
-
共同点:
- 都是内存数据库,一般当缓存使用
- 都有过期策略
- 性能比较高
-
区别:
- 数据类型方面:
- Redis支持多种数据类型,除了简单key/value,还有list,set,zset,hash等,支持复杂应用场景
- Memcached:只支持key/value键值对的类型
- 数据持久化:
- Redis会将数据持久化到磁盘中,重启后会自动加到内存中,相当于有灾难恢复机制
- Memcahced: 只存储到内存中
- 集群支持:
- Redis:3.0后就提供了原生的集群支持
- Memcached: 没有提供原生的集群,依赖客户端往集群分片中写入数据
- 线程模型:
- Redis:使用单线程的IO多路复用模式(6.0后 网络数据的读写引入了多线程)
- Memecahed: 多线程、非阻塞IO复用模式
- 过期删除策略:
- Redis: 支持惰性删除和定时删除
- Memecache: 只支持惰性删除。
- 数据类型方面:
Redis常见的数据结构有哪些?底层数据结构
- 5种基础类型:String:字符串、list:列表、set: 集合、 zset:有序集合、hash: 散列
- 3中特殊类型:HyperLogLog:基数统计、BitMap:位图、Geospatial: 地理位置、Bloom fileter布隆过滤器
Redis的有序集合底层为什么要用跳表?
- 跳表诞生的初衷就是为了解决平衡树的一些缺点,使用的是概率平衡而不是严格强制的平衡,因此跳表的插入和删除算法要比平衡树的等效算法简单,速度也更快
使用Redis统计网站UV怎么做?
-
系统活跃度常用指标:
- PV: PageView 页面浏览量,能够反映网站页面被浏览或者刷新的次数
- UV: Unique View 用来统计1天内访问某个站点的用户数
- VV: Visit View,用来统计1天内用户访问站点的次数
- IP: 独立IP, 一天内访问站点的独立Ip数量。
-
简单版本:访问量比较少时
- 指定网页维护一个哈希表,使用网页id+日期作为key, 每个访问站点的用户ID或IP作为value
- 当需要为指定网页增加UV时,需要先判断用户id或IP是否存在于set中
- 计算UV只需要计算set的长度即可。
-
内存消耗少的版本:使用HyperLogLog基数统计,是一种基数计数概率算法,占用的空间很小,并且计算的结果是近似值,有一定的误差。对于统计UV,这个误差可以忽略不计
- 相关的操作:
PFADD key valus: 用于数据添加,一次可以添加多个,会自动去重。PFCOUNT key: 对数据进行统计PRMERGE destkey sourcekey1 sourcekey2 ...: 将多个表进行合并,可以一次添加多个表,并对重复的元素进行去重。
- 相关的操作:
实现一个排行榜怎么做?
-
使用Order by:
- 通过直接用数据库Order by排序查询等到结果,但是对于数据量大时,性能较低。可以通过加索引并限制排序数据量。 如果实际需要排序的数据量小且业务不复杂可以使用orderby.
-
使用Redis的 Sorted set (zet)
- Sorted set的介绍: 相对于set,zset增加了一个
score的权重参数,使得集合中元素可以按照score有序排列,可以通过score获取元素的列表 - 常用命令:
- Sorted set的介绍: 相对于set,zset增加了一个
| 命令 | 介绍 |
|---|---|
| ZADD key score1 memeber1 score2 memeber2 ... | 向有序集合中添加一个或多个元素 |
| ZCARD key | 获取指定有序集合的数量 |
| ZSCORE key memeber | 获取指定有序集合中指定元素的score值 |
| ZINTERSTORE destination numkeys key1 key2 key3.. | 将所有有序集合的交集存储到destination中,对相同元素的score值进行sum聚合操作,numkeys表示有序集合的数量 |
| ZUNIONSTORE destination numkeys key1 key2 key3... | 求并集,其他与ZINTERSTORE一致 |
| ZDIFF destination numkeys key1 key2 key3... | 求差集, 其他与ZINTERSTORE一致 |
| ZRANGE key start end | 获取指定有序集合的start 和 end 之间的元素(score从低到高) |
| ZREVRANGE key start end | 获取指定有序集合的start 和 end 之间的元素(score从高到低) |
| ZREVRANK key member | 获取指定有序集合中指定元素的排名(score从大到小) |
- 案例:
- 如果查看包含所有用户的排行榜?
ZREVRANGE cur_order_set 0 -1- ZREVRANGE从大到小, ZRANGE从小到大
- 只查看包含前三名的排行榜?
ZREVRANGE cur_order_set 0 2 - 查询某个用户的分数:
ZSCORE cur_order_set "user1" - 查询某个用户具体的排名:
ZREVRANGE cur_order_set "user1" - 如何对某个用户的排名数据进行更新:
ZINCRBY cur_order_set +2 "user1"或ZINCRBY cur_order_set -1 "user2" - 如何实现多条件排序:
对score做文章,比如要加上时间先后条件,直接对 score 加上时间戳即可。 - 如何实现指定日期的用户数据排序?
数据的名字使用 时间日期, 如果要求n天的用户数据排行榜,使用ZUNIONSTORE 对这几个日期求并集即可
- 如果查看包含所有用户的排行榜?
Redis的单线程模型了解吗?/既然是单线程,为什么可以监听多个客户端连接?/为什么Redis快?
- Redis基于 Reactor 模式设计了文件事件处理器(file event handler)是单线程运行的,所以说Redis是单线程运行的。
- 文件事件处理器,使用了IO多路复用,通过监听多个socket,并将感兴趣的事件以及类型(读写)注册到内核,监听这些事件是否发生。
- 不需要多线程来监听大量的客户端连接,减低了资源消耗。
- 文件事件处理器分为四个部分:
- 多个socket(客户端连接)
- IO多路复用程序(支持多个客户端连接的关键)
- 文件事件分派器(将对应的连关联到对应的事件处理器上)
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
Redis6.0之前为什么不使用多线程?
-
主要原因:
- 单线程编程容易且更容易维护
- Redis的性能瓶颈不在于CPU,而是在于内存和网络
- 多线程存在死锁,线程上下文切换等问题,甚至会影响性能。
-
Redis4.0就引入了多线程:主要是对大键值对的删除操作命令,使用这些命令,就会使用主线程之外的线程,进行异步处理,从而减少对主线程的影响。
-
Redis4.0增加的异步命令:
- UNLINK: 可以看作是
DEL的异步版本 - FLUSHALL ASYNC: 用于清除所有数据库中所有的键,不限于当前
SELECT数据库 - FLUSHDB ASYNC: 用于清除当前
SELECT数据库的所有键。
- UNLINK: 可以看作是
Redis6.0 之后要引入多线程?
- 引入多线程的原因:提高网路IO读写性能。 由于Redis的主要性能瓶颈在于 内存和网络。
- 多线程只在网络读写这类耗时操作上使用,普通命令执行还是单线程,这样就不用担心线程安全问题。
- Redis6.0 默认关闭多线程,使用主线程,如果需要使用,需要修改redis.conf文件
io-threads 4: 设置为1,就只会开启主线程,需要多线程需要>1
Redis给缓存数据设置过期时间有什么用?
- 有助于缓减内存消耗(内存资源有限,如果一直增长,最终会导致OOM的)
- 如果使用传统数据库来判断过期,还有进行判断,会很麻烦且性能会差好多。
Redis是如何判断数据是否过期的?
- Redis中有个过期字典,键指向Redis数据库中某个键, 而value存储过期事件是longlong类型的
- 查询一个key时,会先查询是否存储于过期字典中,如果没有就直接返回,如果有,判断是否过期,过期就删除,并返回null.
过期数据的删除策略?
- 惰性删除: 方式时,判断是否过期,过期就删除,对CPU友好,但可能会造成大量缓存未删除
- 定期删除:周期性的从有过期时间的缓存中抽取一批,逐个检查,过期的就删除。对内存友好,对CPU不友好
- 延迟队列: 把设置过期时间的数据存放到延时队列中,如果过期就删除,但是维护延迟队列比较麻烦,队列本身也要占空间。
- 定时删除:每个设置了过期时间的数据都会在到期时立即被删除。这样可以保证内存中不会存在过期数据。但是这个对CPU的压力最大,因为需要为每个键都放个定时器。
- 最终策略:
定期删除+惰性删除
内存淘汰机制了解吗
- 内存淘汰策略是,内存中的数据达到配置的阈值时触发。通过redis.conf中的maxmemory中配置
- 8种淘汰策略:
- volatile-lru:Last Recently Used, 从已设置过期时间的数据中,选择最近最少使用的数据进行淘汰
- volatile-ttl: 从已设置过期时间的数据集中,挑选将要到期的进行淘汰
- volatile-random: 从已设置过期时间的数据集中,随机挑选数据进行淘汰
- allkeys-lru: 从数据集中选取最近最少使用的数据进行淘汰
- allkeys-random: 从数据集中随机选取数据进行淘汰
- no-eviction(默认内存淘汰策略):禁止驱逐数据,当内存不足以容纳新数据时,新写入数据时会报错。
- 4.0之后新增
- volatile-lfu: Last-Frequently-used,从已设置过期时间的数据集中,选择最不经常使用的数据进行淘汰
- allkeys-lfu: 从数据集中选取最不经常使用的数据进行淘汰。
怎么保证Redis重启之后,数据可以恢复?
- 依赖数据持久化机制:Redis通过快照获取存储在内存中数据某个时间点的副本。可以将快照留在原地服务器重启时恢复数据时使用。
什么是RDB持久化?
- RDB持久化:就是通过快照获取存储在内存中数据的某个时间点的副本。
- 快照创建之后,可以将快照复制给其他服务器,使得其他服务器具有相同数据的副本。
- 也可以原地保持不变,服务器重启时恢复数据时使用。
- RDB快照是默认的持久化机制
- Redis提供了两个命令来生成RDB快照,区别是否阻塞主线程。
- save: 同步保存操作,会阻塞主线程
- bgsave: 会fork一个子线程,子线程执行,不会阻塞主线程。是默认选项。
什么是AOF持久化?
- AOF: 将每条使得redis数据发生变动的语句,写入AOF缓存区中
server.aof_buf,然后写入AOF文件中,再根据持久化方式的配置,决定何时将内核缓冲区的数据同步到到磁盘中。 - AOF文件的位置与RDB的位置相同,aof的默认文件名为:appendonly.aof
- AOF重写:是有歧义的名字与原来的AOF文件无关,是通过读取数据库键值对来实现的,AOF变得太大时会自动重写一个新的AOF文件,与原来的AOF所保存的数据库状态一样,并且体积更小。
- 重写期间还会维护一个 重写缓冲区,存储重写期间对数据库操作修改的操作。当子进程完成重写后,会将缓冲区的所有内容追加到AOF文件的末尾。最后用新的AOF文件替换旧的AOF文件。
Redis4.0对持久化机制做了哪些优化?
- 由于RDB和AOF各有优势,所以4.0之后开始使用AOF+RDB的混合模式(默认关闭,通过
aof-use-rdb-preamble开启) - 如果把混合持久化模式打开,AOF重写时就会直接把RDB内容写到AOF文件开头。结合RDB和AOF的优势,快速加载同时避免丢失过多数据。缺点是,RDB存储到AOF是压缩格式不再是AOF格式,可读性较差。
RDB和AOF的区别
| 特性 | RDB | AOF |
|---|---|---|
| 持久化方式 | 快照形式,定期保存内存数据的快照 | 日志形式,记录每个写操作命令 |
| 启动速度 | 快,因为只需加载二进制快照文件 | 慢,因为需要重放所有写操作命令 |
| 数据安全性 | 较低,存在数据丢失风险 | 较高,数据丢失风险极低 |
| 文件大小 | 较小,因为是压缩的二进制文件 | 较大,因为记录了每个写操作命令 |
| 适用场景 | 适合冷备份和大规模数据恢复 | 适合数据敏感场景和实时性要求高的应用 |
| 性能开销 | fork子进程时有较大性能开销,但通常较快 | 如果每次写操作都同步,性能开销较大 |
如何使用Redis事务?
- 可以使用
EXECMULTIDISCARDWATCH等命令来实现- 开始事务(
MULTI) - 命令入队,FIFO
- 执行事务(
EXEC)
- 开始事务(
DISCARD:表示清空事务队列中的保存的所有命令,并取消事务。WATCH监控一个key的值,如果在事务执行时(EXEC),key的值被别的客户端/session所修改,整个事务都不会执行。 如果修改操作与WATCH在同一个session,同一个事务中,就会执行。
Redis事务支持原子性吗?
- Redis的事务与我们正常理解的关系型数据库的事务不同
- 事务的的四大特性:
- 原子性:事务是最小单位,不可分割。事务保证要么全部完成,要么完全不起作用
- 一致性:执行事务前后,数据保持一致,多个事务对同一个数据读取结果相同
- 隔离性:并发访问时,一个用户的事务不被其他事务干扰,每个事务的数据库时独立的。
- 持久性:一个事务提交之后,数据库的变化时持久的,即使数据库发生故障也不会对其有影响。
- Redis的事务在运行有错误的情况下,除了执行过程中出现错误外,其他正常命令正常执行。另外Redis不支持回滚操作,因此Redis其实是没有原子性的。
Redis事务支持持久性吗?
-
Redis支持持久化,有三个持久化方式:
- RDB快照
- AOF只追加文件
- 混合模式 RDB+AOF
-
与RDB的持久化相比,AOF的持久化实时性更高,在Redis配置文件中存在三种不同的AOF持久化方式(
fsync策略),分别是:- appendfsync always:每次数据又修改就会调用fsync函数,同步AOF文件,fsync线程完成后返回,严重降低Redis的速度
- appendfsync everysec: 每秒调用fsync函数同步一次AOF文件
- appendfsync no: 让操作系统决定何时进行同步,大约30秒一次。
-
AOF的fsync策略的everysec no ,都会存在丢失部分数据,always可以基本满足持久化要求,但是性能太差,实际开发中不会使用
-
所有Redis事务持久没办法保证。
Redis事务还有什么缺陷?
- Redis事务除了满足原子性外,事务的每条命令都会与Redis服务进行网络交互,这是比较浪费资源的行为,明明一次批量执行读条命令就好了。
如何解决Redis事务的缺陷?
- Redis2.6之后支持了Lua脚本,可以使用Lua脚本批量执行命令,这样就只用和Redis进行一次网络交互,在Redis服务器中一次被执行,大大减少了网络开销。
- 一段Lua脚本可以看作是一个命令执行,一段Lua脚本执行过程中不会又其他脚本和命令同时执行,保证了操作不会被影响。
- 不过Lua脚本原型中途出错会直接结束,出错的命令不会被执行,但是出错前执行的命令也无法撤销。所以要保证lua脚本的正确性。 由于不能回滚,所以通过Lua脚本批量执行Redis命令也不算支持原子性。
什么是bigkey? 有什么危害?
-
一个key对应的value值占用的内存比较大时,那么这个key就可以看作是bigKey了,大概的标准:
- String类型的value超过1MB
- 复合类型(list,set,zset,hash)的value元素超过5000个
-
危害:bigkey会造成阻塞问题
- 客户端阻塞:由于Redis命令是单线程处理,操作执行bigkey会比较耗时,那么就会阻塞Redis,从客户端看就是很久没有响应
- 网络阻塞:每次获取bigkey的网络流量较大,如果一个bigkey的大小是1MB,每秒访问量为1000,那么每秒就会产生1000MB的流量,对普通千兆网卡的服务器是一种灾难
- 工作线程阻塞:如果del大key时,会阻塞工作线程,无法处理后续命令。
如何发现bigkey?
-
使用Redis自带的--bigkeys参数查找
redis-cli -p 6379 --bigkeys- 此命令会扫描所有的key,会对性能造成一些影响,并且这种方式只能找到每种数据结构的top 1 bigkey(String的value超过1MB, 复合类型的元素个数超过5000)
- 执行此命令时,可以控制频率
redis-cli -p 6379 --bigkeys -i 3,每次休息3秒后再执行。
-
Redis自带的SCAN命令:
SCAN会按照一定模式和数量返回匹配的key,获取key后,可以通过STRLENHLENLLEN等命令获取value的长度和数量- 还可以使用
MEMORY USAGE,这个命令会返回键值对的空间。
- 还可以使用
| 数据结构 | 命令 | 说明 |
|---|---|---|
| String | STRLEN | 字符串值的长度 |
| List | LLEN | 列表的元素个数 |
| Hash | HASH | 哈希的字段个数 |
| Set | SCARD | 无序集合的元素个数 |
| Zset | ZCARD | 有序集合的元素个数 |
-
借助开源工具来分析bigkeys:
-
领用RDB来分析找出bigkeys
- redis-rdb-tools:Python 语言写的用来分析 Redis 的 RDB 快照文件用的工具。
- rdb_bigkeys:Go 语言写的用来分析 Redis 的 RDB 快照文件用的工具,性能更好。
-
-
借助公有云的Redis分析服务
如何处理bigkey:
- 将Bigkey分割成多个小的key
- 手动清理,使用
UNLINK异步清理一个或多个指定key - 采用适合的数据结构,比如HyperLogLog 基数计数
- 开启lazy-free(惰性删除): redis4.0之后,会fork子线程来执行删除动作,异步执行,避免阻塞主线程。
如何避免大量key集过期?
- key设置随机过期时间
- 设置开启惰性删除,lazy-free,4.0后引入,是采用异步线程延迟释放key的内存,将该操作交给子线程,避免阻塞主线程。
什么是Redis内存碎片? 为什么会有内存碎片?
-
内存碎片:那些不可用的空闲内存,比如操作系统分配了一个32字节的内存,但是存储数据只占了24字节,还有8字节的数据是没办法分配给其他使用的。
-
原因:
- Redis存储数据时向操作系统请求的空间一般会大于实际存储的空间。
- 频繁修改Redis数据时也会产生内存碎片: Redis的某个数据删除时,不会轻易释放内存给操作系统。
什么是缓存击穿? 什么是缓存穿透? 什么是缓存雪崩
- 缓存击穿:高并发时,一个key突然过期,直接将大量访问打在数据库上进行查询
- 解决方案:
- 永不过期(不推荐):设置热点数据永不过期或过期时间长
- 提前预热(推荐):针对热点数据提前预热,将其存入缓存并设置合理的过期时间,比如秒杀场景下,热点数据在秒杀结束前不过期
- 加锁(看情况):缓存过期后,设置互斥锁保证只有一个请求到数据库并更新缓存
- 解决方案:
- 缓存穿透:用户查询的数据,缓存中不存在,数据库也不存在
- 解决方案:
- 缓存无效key:如果缓存和数据库都查不到,就将这个无效key写入redis中,并设置过期时间。这样可以解决key变化不频繁的场景,如果变化频繁,会导致redis存储大量无效的key,如果非要使用此种方式,尽量设置过期时间短一点。
- 布隆过滤器: 将所有可能请求的值都存在bloomfilter中,当用户请求是,先判断用户发来的请求值是否存在于布隆过滤器中,不存在的话,直接返回给客户端错误信息,存在才走下面的流程
- 接口限流:根据用户和IP对接口进行限流,对于异常频繁访问行为,还可以设置黑名单,将异常访问的用户放入黑名单。
- 解决方案:
- 缓存雪崩:Redis实例宕机或者大量key集体过期,大量请求打在数据库上,导致数据压力骤增甚至宕机。
- 解决方案:
- 针对Redis服务不可用的情况:
- Redis集群:采用Redis集群,避免单实例出现问题导致缓存服务不可用。Redis Cluster和Redis Sentinel都是常用的Redis集群方案
- 多级缓存:设置多级缓存,比如Redis缓存+本地缓存,即使Redis服务不可用,还可以从本地缓存得到部分数据
- 针对大量缓存同时失效的情况:
- 设置随机过期时间:比如在固定的过期时间加上一个随机值,避免大量缓存同时过期
- 提前预热:针对热点数据放入缓存,并设置热点事件,比如秒杀事件的缓存在秒杀结束前不会失效。
- 持久缓存策略: 看情况,对于一些关键并且变化不频繁的可以考虑这种策略。
布隆过滤器
-
什么是布隆过滤器: 一种检索元素是否在给定的大集合中的数据结构,这种数据结构高效且性能好,但是缺点是具有一定的错误识别率和删除难度。 并且,理论情况下,添加到集合中的元素越多,误报的可能性就越大
-
原理:
-
当元素往布隆过滤器中添加时:
-
- 使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)
-
- 根据哈希值,在对位数组中把对应下表设置为1
-
-
当需要判断一个元素是否存在于布隆过滤器中时:
-
- 对给定元素进行相同的哈希计算,得到哈希值
-
- 根据哈希值,判断数组中对应下表的每个元素值是否都为1, 如果都为1则说明值在布隆过滤器中,如果存在一个值不为1,说明该元素不在布隆过滤器中。
-
-
-
总结:布隆过滤器如果说某个元素存在,有概率会误判;但是如果说某个元素不在,那一定不在。
如何保证缓存和数据库的一致性?
-
方案一:双写策略
- 同时更新数据库和Redis缓存,以确保数据的一致性
- 事务:先将数据写入Redis缓存,再更新MySQL数据库的数据,这个过程增加事务,确保Redis和MySQL的更新动作要么全部成功,要么都失败。
- 增加重试机制:实际操作可能会遇到网络故障,服务宕机等情况,导致Redis或Mysql更新失败,需要增加重试机制,当更新失败时,自动重试一定次数,直到成功为止。
- 保证幂等性:多次执行得到相同结果,在更新Mysql时,增加乐观锁或唯一约束避免更新重复
- 性能影响:双写策略会增加操作系统写操作负担,每次更新都需要同时更新Redis和MySQL。
-
方案二:延迟双删
- 先删除缓存的数据,再更新数据库的数据,之后再删除一次缓存的数据。
- 流程:
- 先删除Redis缓存的数据,保证后续读取操作不会命中旧数据。
- 更新MySQL的数据,确保数据持久化,保证数据不丢失
- 延时删除缓存:更新完数据库数据,等一段时间(几百毫秒),再删除Redis缓存数据。确保高并发场景下,其他线程会读取旧数据。
- 实现细节:
- 延迟一段时间再删除缓存,保证一致性
- 增加重试机制,确保数据可以被更新
- 幂等性,增加数据的唯一约束或乐观锁,确保不被重复更新
-
并发场景还是都会出现数据不一致的场景,所以需要平衡性能和一致性,大多数以一致性为主。
如何保证Redis服务的高可用?
-
使用Redis Sentinel集群
-
如何检测节点是否下线?
- 主观下线(Subjective DOWN, SDOWN):某个sentinel认为此节点下线;master节点在指定时间内未响应sentinel的PING命令,或返回无效回复,则认为此master节点主观下线。
- 客观下线(Objective DOWN, ODOWN):大多数的sentinel都认为下线; 当一个Sentinel认为master主观下线后,向其他sentinel发送
is-master-down-by-addr命令,询问其他sentinel是否master主观下线了,如果足够数量(配置)的sentinel的实例认为master主观下线了,那么此master就会被标记为客观下线。 - 故障转移(fail-over): 只有master节点被标记为ODWON,sentinel才会故障转移,选取新的master节点。
- Master恢复的处理:
- SDWON后,ODWON之前恢复,sentinel取消SDOWN标记,不会触发故障转移
- ODOWN后恢复,已故障转移,sentinel将此节点自动作为master的从节点。
-
新的master的选举?
- 优先级:根据配置(replica-priority)手动设置优先级,最大100,值越小,优先级越高。0表示不参与选举。如果没有优先级,根据复制进度选举。
- 复制进度:sentinel期望选举出数据最完整的节点作为master,复制进度越快,得分越高
- runid: 如果复制进度相同的多个节点,选择runid最小的。
Sentinel 哨兵有什么用?
- 监控所有Redis(包括哨兵)的状态是否正常
- 故障转移:如果master出现故障,哨兵会进行故障转移,会将一台slave升级为master,确保redis系统服务可用性
- 通知:通知slave新的master连接信息,让他们执行replicaof成为master的slave.
- 配置更新:客户端向sentinel请求master地址,如果发生故障转移,sentinel会将新的master地址通知客户端。
Redis缓存数量太大怎么办?
- 使用Redis数据库切片集群:部署多台master节点,这些节点没有主从之说,同时对外提供读写服务。数据库的缓存相对均匀的分布在这些Redis节点上,客户端请求通过路由规则转发到相应的master节点上。
- 同时为了保证redis的高可用,还有给每个master分配一个或多个slave
Redis虚拟槽分区有什么优点?
- Redis采用哈希槽,每个键值对对应一个哈希槽,一般为16384个哈希槽。初始化Redis Cluter时,会将哈希槽平均分配到每个节点。
- 客户端连接RedisCluster时的任意一个master节点,便可访问Cluster的数据。客户端发送请求命令时,先将key经过一些系列计算找到哈希槽,再根据哈希槽和节点的映射关系,找到目标节点。如果哈希槽确实是当前节点的,就直接返回结果。如果不由当前节点负责,返回
-MOVED,告知客户端此哈希槽是由哪个节点负责,然后客户端向目标节点请求。- 找不到目标节点的原因:扩容或缩容会重新分配哈希槽,可能会导致哈希槽与节点的映射关系有误
- 解耦了数据与节点之间的关系,增加了集群的横向扩展性和容错性
Redis Cluster节点是如何实现数据一致性的?或者说是怎么通信的?
- 各个节点就是通过gossip协议通信
- 有了Redis Cluster就不需要Sentinel了,有gossip协议,相当于内置sentinel,各个节点通过Gossip互探健康状态,出现故障,自动切换。