1.分布式锁在项目中的应用
使用分布式锁的场景一般需要满足以下场景:
- 系统是一个分布式系统,集群集群,java的锁已经锁不住了。
- 操作共享资源,比如库里唯一的用户数据。
- 同步访问,即多个进程同时操作共享资源。
2.分布式锁有哪些解决方案
SETNX 格式:setnx key value 将 key 的值设为 value ,当且仅当 key 不存在。 若给定的 key 已经存在,则 SETNX 不做任何动作,操作失败。
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
加锁:set key value nx ex 10s
释放锁:delete key
- 比如程序执行15秒,但是锁只有10秒
增加一个watch dog给锁续约
- 保证不删除其他线程加的锁 删除的时候get key的值,进行对比,只能删除自己加的锁
3.Redis分布式死锁有哪些情况,如何解决
- 加锁,没有释放锁。需要加释放锁的操作,比如delete kye
- 加锁后,程序还没有释放锁,程序挂了,需要设置key的过期机制
4.Redis如何做分布式锁
Step1: 服务A为了获得锁,向Redis发起如下命令: SET productId:lock 0xx9p03001 NX EX 30000 其中,"productId"由自己定义,可以是与本次业务有关的id,"0xx9p03001"是一串随机值,必须保证全局唯一,“NX"指的是当且仅当key(也就是案例中的"productId:lock”)在Redis中不存在时,返回执行成功,否则执行失败。"EX 30000"指的是在30秒后,key将被自动删除。执行命令后返回成功,表明服务成功的获得了锁。
Step2: 服务B为了获得锁,向Redis发起同样的命令: SET productId:lock 0000111 NX EX 30000 由于Redis内已经存在同名key,且并未过期,因此命令执行失败,服务B未能获得锁。服务B进入循环请求状态,比如每隔1秒钟(自行设置)向Redis发送请求,直到执行成功并获得锁。
Step3: 服务A的业务代码执行时长超过了30秒,导致key超时,因此Redis自动删除了key。此时服务B再次发送命令执行成功,假设本次请求中设置的value值为0000222。此时需要在服务A中对key进行续期,watch dog。
Step4: 服务A执行完毕,为了释放锁,服务A会主动向Redis发起删除key的请求。注意: 在删除key之前,一定要判断服务A持有的value与Redis内存储的value是否一致。比如当前场景下,Redis中的锁早就不是服务A持有的那一把了,而是由服务2创建,如果贸然使用服务A持有的key来删除锁,则会误将服务2的锁释放掉。此外,由于删除锁时涉及到一系列判断逻辑,因此一般使用lua脚本,具体如下:
if redis.call("get", KEYS[1])==ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
5.Redis持久化机制 RDB和AOF
-
RDB的优点
-
RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集.
-
与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些.
RDB的缺点
-
如果你希望在redis意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么RDB不适合你.虽然你可以配置不同的save时间点(例如每隔5分钟并且对数据集有100个写的操作),是Redis要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在Redis意外宕机,你可能会丢失几分钟的数据.
-
RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求.如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒,AOF也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度.
AOF 优点
-
使用AOF 会让你的Redis更加耐久: 你可以使用不同的fsync策略:无fsync,每秒fsync,每次写的时候fsync.使用默认的每秒fsync策略,Redis的性能依然很好(fsync是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据.
-
AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用redis-check-aof工具修复这些问题.
-
Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
-
AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单: 举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。
AOF 缺点
-
对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
-
根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。
6.Redis的过期键有哪些删除策略
Keys的过期时间使用Unix时间戳存储(从Redis 2.6开始以毫秒为单位)。这意味着即使Redis实例不可用,时间也是一直在流逝的。
要想过期的工作处理好,计算机必须采用稳定的时间。 如果你将RDB文件在两台时钟不同步的电脑间同步,有趣的事会发生(所有的 keys装载时就会过期)。
即使正在运行的实例也会检查计算机的时钟,例如如果你设置了一个key的有效期是1000秒,然后设置你的计算机时间为未来2000秒,这时key会立即失效,而不是等1000秒之后。
过期时间按照绝对时间点去进行过期
7.Redis如何淘汰过期的keys
Redis keys过期有两种方式:被动和主动方式。
当一些客户端尝试访问它时,key会被发现并主动的过期。
当然,这样是不够的,因为有些过期的keys,永远不会访问他们。 无论如何,这些keys应该过期,所以定时随机测试设置keys的过期时间。所有这些过期的keys将会从密钥空间删除。
具体就是Redis每秒10次做的事情:
- 测试随机的20个keys进行相关过期检测。
- 删除所有已经过期的keys。
- 如果有多于25%的keys过期,重复步奏1.
这是一个平凡的概率算法,基本上的假设是,我们的样本是这个密钥控件,并且我们不断重复过期检测,直到过期的keys的百分百低于25%,这意味着,在任何给定的时刻,最多会清除1/4的过期keys。
8.在复制AOF文件时如何处理过期
为了获得正确的行为而不牺牲一致性,当一个key过期,DEL将会随着AOF文字一起合成到所有附加的slaves。在master实例中,这种方法是集中的,并且不存在一致性错误的机会。
然而,当slaves连接到master时,不会独立过期keys(会等到master执行DEL命令),他们任然会在数据集里面存在,所以当slave当选为master时淘汰keys会独立执行,然后成为master。
9.redis缓存如何回收
回收策略
noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
allkeys-random: 回收随机的键使得新添加的数据有空间存放。
volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
allkeys-lfu:从所有键中驱逐使用频率最少的键
如果没有键满足回收的前提条件的话,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。
选择正确的回收策略是非常重要的,这取决于你的应用的访问模式,不过你可以在运行时进行相关的策略调整,并且监控缓存命中率和没命中的次数,通过RedisINFO命令输出以便调优。
一般的经验规则:
- 使用allkeys-lru策略:当你希望你的请求符合一个幂定律分布,也就是说,你希望部分的子集元素将比其它其它元素被访问的更多。如果你不确定选择什么,这是个很好的选择。.
- 使用allkeys-random:如果你是循环访问,所有的键被连续的扫描,或者你希望请求分布正常(所有元素被访问的概率都差不多)。
- 使用volatile-ttl:如果你想要通过创建缓存对象时设置TTL值,来决定哪些对象应该被过期。
allkeys-lru 和 volatile-random策略对于当你想要单一的实例实现缓存及持久化一些键时很有用。不过一般运行两个实例是解决这个问题的更好方法。
为了键设置过期时间也是需要消耗内存的,所以使用allkeys-lru这种策略更加高效,因为没有必要为键取设置过期时间当内存有压力时。
回收进程如何工作
理解回收进程如何工作是非常重要的:
- 一个客户端运行了新的命令,添加了新的数据。
- Redi检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。
- 一个新的命令被执行,等等。
- 所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。
如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。
10.redis集群方案有哪些
常见集群分类
主从复制集群
分片集群
redis有那些:
主从复制集群,手动切换
带有哨兵的HA的主从复制集群
客户端实现路由索引的分片集群
使用中间件代理层的分片集群
redis自身实现的cluster分片集群
11.雪崩、穿透、击穿
缓存穿透(缓存不存在,数据库不存在,高并发,少量key)
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。
解决方案
有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
缓存击穿(缓存不存在,数据库存在,高并发,少量key)
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。
缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
解决方案
缓存失效时的雪崩效应对底层系统的冲击非常可怕。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线 程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。这里分享一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
缓存雪崩
缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
解决方案
1.使用互斥锁(mutex key) 业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。 SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。在redis2.6.1之前版本未实现setnx的过期时间
2."提前"使用互斥锁(mutex key): 在value内部设置1个超时值(timeout1), timeout1比实际的memcache timeout(timeout2)小。当从cache读取到timeout1发现它已经过期时候,马上延长timeout1并重新设置到cache。然后再从数据库加载数据并设置到cache中。
3."永远不过期": 这里的“永远不过期”包含两层意思:
(1) 从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。
(2) 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期
从实战看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受。
总结
穿透:缓存不存在,数据库不存在,高并发,少量key
击穿:缓存不存在,数据库存在,高并发,少量key
雪崩:缓存不存在,数据库存在,高并发,大量key
语义有些许差异,但是,都可以使用限流的互斥锁,保障数据库的稳定
12.数据结构
1.Hash :
商品购物车
用户的ID作为key 商品id作为filed 商城数量作为value
- 添加商品到购物车:hset key(用户id) filed(商品id) value(商品数量)
- 显示购物车所有商品及每个商品对应数量:hgetall key
- 增加、减少商品数量:hincrby key(用户id) field(商品id) 1(数量+1)/-1(数量-1)
- 删除选中的商品: hdel key(用户id) field(商品id)..
- 获取购物车总量:hlen key(用户id)
2.String:
点赞
如在掘金中对一篇文章进行点赞,增加点赞量、显示文章浏览量.
- 初始化
- set post:id:00001:views 0
- set post:id:00001:live 0
- 增加点赞、增加阅读量、显示阅读量:
- incr post:id:00001:live
- incr post:id:00001:views
- get post:id:00001:views
3.List
朋友圈点赞,需要顺序展示点赞者
- rpush key(文章id) value(好友信息) 点赞
- lrem key(文章id) count(移除的数量,这里为1) value(好友信息) 取消点赞
- lrange key(文章id) 0 -1 顺序显示所有点赞信息
4.Set
网站访问量统计。
- PV(网站被访问次数,刷新访问量会+1),使用string类型即可实现,调用incr key增加访问量、通过get key获取总访问量。
- UV(网站被用户访问的次数,根据用户id来统计),利用set集合的数据去重功能,使用一个set集合来存储访问网站的用户的id,访问网站时调用sadd userId,当同一个用户多次访问时,当第一次访问后再添加不会成功,从而实现UV统计。
- IP(网站被不同IP访问的次数,IP不变,用户变,IP访问数不变),统计使用set集合的去重,将访问Ip保存到set集合中。
5.Zset (sortedset)
有序集合,可以关联一个score进行排序
- 存储 zadd key score[依据这个值来排序] valu
13.主从
1、主库挂了之后带来的问题
- 主库真的挂了吗?
- 该选择哪个从库作为主库?
- 怎么把新主库的相关信息通知给从库和客户端呢?
2、哨兵机制
Redis 主从集群中,哨兵机制是实现主从库自动切换的关键机制,它有效解决了上面三个问题。
哨兵的三个主要任务:
- 监控:包含主观下线和客观下线。
主观下线:通过 PING 命令来判断是否超时。
客观下线:当有 N 个哨兵实例时,最好有 N/2 +1 个实例判断主库为下线。
- 选主
筛选:除了要检查从库的在线状态,还要检查它之前的网络状态。
打分:三个规则依次打分: -
- 从库优先级最高。
- 和旧主库同步复制程度最接近的(从库的slave_repl_offset设置)。
- 如果前两个相同,选择 ID 最小的。
- 通知
总结
哨兵机制,它是实现 Redis 不间断服务的重要保证。
具体来说, 主从集群的数据同步,是数据可靠的基础保证;而在主库发生故障时,自动的主从切换是服务不间断的关键支撑。
Redis 的哨兵机制自动完成了以下三大功能,从而实现了主从库的自动切换,可以降低 Redis 集群的运维开销:
监控主库运行状态,并判断主库是否客观下线;
在主库客观下线后,选取新主库;
选出新主库后,通知从库和客户端。
为了降低误判率,在实际应用时,哨兵机制通常采用多实例的方式进行部署,多个哨兵实 例通过“少数服从多数”的原则,来判断主库是否客观下线。
一般来说,我们可以部署三个哨兵,如果有两个哨兵认定主库“主观下线”,就可以开始切换过程。当然,如果你希 望进一步提升判断准确率,也可以再适当增加哨兵个数,比如说使用五个哨兵。
但是,使用多个哨兵实例来降低误判率,其实相当于组成了一个哨兵集群,会因此面临着一些新的挑战,例如