redis 6.X 正确使用姿势

redis6.X 正确使用姿势

1 官网

https://redis.io/
http://www.redis.cn/
http://doc.redisfans.com/

2 配置文件

6.0.8版本 安装后需要修改配置文件

1 修改daemonize 改为 daemonize yes
2 修改protected-mode yes 改为 protected-mode no
3 注释掉 #bin 127.0.0.1

3 redis单线程&多线程问题

  • redis 是单线程还是多线程呢???
    通常说的redis是单线程究竟何意?
    在这里插入图片描述

redis的版本有很多3.x 4.x 6.x 版本不同架构也是不同
1:3.x版本 最早的版本,单线程(优点:数据结构简单 避免锁的开销和上下文切换 可以有很高的QPS)
2:4.x版本 严格意义来说也不是单线程,而是负责处理客户端请求的线程是单线程, 但是开始加了点多线程的东西(异步删除 unlink)
为什么开始引入多线程的内容和功能呢?
为了解决单线程 操作 bigKey阻塞问题, 为了解决这个问题引入多线程 实现异步多线程删除
3:6.x 增加了多线程io 也就是:redis工作线程是单线程,但是,整个redis来说,是多线程了

  • redis3.x 在单线程时代依旧很快的原因是什么
    1: 基于内存操作:redis的所有数据都存在内存中,因此所有的运算都是内存级别的,所以他的性能比较高
    2: 数据结构简单:redis的数据结构是专门设计的,而这些简单的数据结构的查找和操作的时间大部分复杂度都是0(1)
    3:多路复用和非阻塞I/O:redis使用I/O多路复用功能来监听多个socket连接客户端,这样就可以使用一个线程连接来处理多个请求,减少线程切换带来的开销,同时也避免了I/O阻塞操作
    4:避免上下文切换:因为是单线程模型,因此就避免了不必要的上下文切换和线程竞争,这就省去了多线程切换带来的时间和性能上的消耗,而且单线程不会导致死锁问题
    redis开发者说过:redis是基于内存操作的,因此它的瓶颈可能是机器的内存或者网络带宽而非CPU,既然CPU不是瓶颈,那么自然就采用单线程的解决方案了,况且使用多线程比较麻烦,但是的redis4.0以后开始支持多线程了 eg 后台删除功能

  • redis4.0 之前一直采用单线程的主要原因有以下三个
    1:使用单线程模型是redis的开发和维护更简单,因为单线程模型方便开发和调试
    2:即使使用单线程模型也并发的处理多客户端的请求,主要使用的是多路复用+非阻塞IO
    3:对于redis系统来说,主要的性能瓶颈是内存或者网络带宽而并非CPU

  • 优化redis的三个方面: cpu(不重要) 内存(加钱) 网络IO (redis6.x 主要优化方向)
    redis主要性能瓶颈:网络IO redis6 真正的多线程登场
    正常情况下使用del 指令可以很快的删除数据,而当被删除的key是一个非常大的对象时,例如包含了成千上万个hash集合时候,那么del指令就会造成redis主线程卡顿–》这就是redis3.x单线程时代最经典的故障,大key删除的头疼问题
    (由于redis是单线程的, del bigKey…) 等待很久这个线程才会释放,类似加了个synchronized锁,你可以想象高并发下,程序堵成什么样子 如何解决这个问题!!!

如何解决:使用懒惰删除可以有效的避免redis卡顿问题
比如当我 需要删除一个bigKey的时候,因为是单线程同步操作,这就会导致redis服务卡顿,于是在redis4.0以后新增了多线程,当然此版本中多线程主要是为了解决删除树效率比较低的问题的

unlink key
flushdb async
flushall async
把删除的工作交给子线程去做

因为redis是单个主线程处理,redis之父antriez 一直强调"Lazy Redis is better Redis" 而lazy free的本质就是把某些cost(主要是时间复杂度,占用线程CPU时间片)较高的删操作 从redis主线程剥离让bio子线程来处理

  • io 多路复用入门
    redis主要性能瓶颈:网络IO redis6 真正的多线程登场
    redis 必须安装进Linux unix系统中!!! 因为只有Linux系统 才有select poll epoll 函数
    unix 网络编程中的五种I/O模型
    //TODO
    io多路复用: 这是IO模型的一种,既经典的Reactor设计模式,
    I/O多路复用,简单来说就是通过监测文件的读写事件在通知线程执行相关操作,保证redis的非阻塞I/O能够顺利执行完成的机制。
    多路 指的是多个socket连接
    复用 指的是复用一个线程,多路复用主要有三种技术:select poll epoll

epoll 是最新的 也是目前最好的多路复用技术,采用多路I/O复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),且Redis在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈)主要以上两点造就了redis具有很高的吞吐量
在这里插入图片描述
在这里插入图片描述

  • redis6 默认是否开启了多线程
    redis将所有的数据放在内存中,内存的响应时长大约为100纳秒,对于小数据包,redis服务器可以处理8w到10w的QPS,这个也就是redis的极限了,对于80%的公司来说,单线程的redis已经够用了

redis6 中多线程的功能默认是关闭的 需要修改配置文件
设置 io-thread-do-reads 配置项为yes, 表示开启多线程
设置线程个数 id-threads 6 关于线程数的设置,官方建议是 如果为4核的CPU,建议线程设置为2或者3, 如果8核的cpu建议设置为6, 线程数一定要小于机器核数

4 redis删除策略

redis实际使用的过期键删除策略有两种:

  • 定期删除(当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key)
  • 惰性删除(由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一批已过期的key)
  • maxmemory 删除 (当前已用内存超过maxmemory限定时,将会触发)
  • 懒惰删除 (也就是4.X 以后的异步删除 unlink key flushdb flushall)

redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定时遍历这个字典来删除到期的 key。除了定时遍历之外,它还会使用惰性策略来删除过期的 key,所谓惰性策略就是在客户端访问这个 key 的时候,redis 对 key 的过期时间进行检查,如果过期了就立即删除。定时删除是集中处理,惰性删除是零散处理。

通过配合使用这两种删除策略,服务器可以很好地合理使用cpu时间和避免浪费内存空间之间取得平衡

定期删除

Redis 默认会每秒进行十次过期扫描,过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。

从过期字典中随机 20 个 key;
删除这 20 个 key 中已经过期的 key;
如果过期的 key 比率超过 1/4,那就重复步骤 1;
同时,为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认不会超过 25ms。

如果某一时刻,有大量key同时过期,Redis 会持续扫描过期字典,造成客户端响应卡顿,因此设置过期时间时,就尽量避免这个问题,在设置过期时间时,可以给过期时间设置一个随机范围,避免同一时刻过期。

1.1 如何配置定期删除执行时间间隔

redis的定时任务默认是1s执行10次(100ms一次,值越大说明刷新频率越快,最Redis性能损耗也越大),如果要修改这个值,可以在redis.conf中修改hz的值。

redis.conf中,hz默认设为10,提高它的值将会占用更多的cpu,当然相应的redis将会更快的处理同时到期的许多key,以及更精确的去处理超时。 hz的取值范围是1~500,通常不建议超过100,只有在请求延时非常低的情况下可以将值提升到1001.2 单线程的redis,如何知道要运行定时任务?

redis是单线程的,线程不但要处理定时任务,还要处理客户端请求,线程不能阻塞在定时任务或处理客户端请求上,那么,redis是如何知道何时该运行定时任务的呢?

Redis 的定时任务会记录在一个称为最小堆的数据结构中。这个堆中,最快要执行的任务排在堆的最上方。在每个循环周期,Redis 都会将最小堆里面已经到点的任务立即进行处理。处理完毕后,将最快要执行的任务还需要的时间记录下来,这个时间就是接下来处理客户端请求的最大时长,若达到了该时长,则暂时不处理客户端请求而去运行定时任务。

过期键的定期删除策略由activeExpireCycle函数实现,每当Redis服务器的周期性操作serverCron函数执行时,activeExpireCycle函数就会被调用,它在规定的时间内,分多次遍历服务器中的各个数据库,从数据库的expires字典中随机检查一部分键的过期时间,并删除其中的过期键。

activeExpireCycle函数的大体流程为:
函数每次运行时,都从一定数量的数据库中随机取出一定数量的键进行检查,并删除其中的过期键,比如先从0号数据库开始检查,下次函数运行时,可能就是从1号数据库开始检查,直到15号数据库检查完毕,又重新从0号数据库开始检查,这样可以保证每个数据库都被检查到。
划重点:
每次随机删除哪些key呢?可以提下LRU算法(Least Recently Used 最近最少使用)
redis 内存淘汰机制有以下几个:
• noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
• allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
• allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
• volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。
• volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
• volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。
惰性删除
过期键的惰性删除策略由expireIfNeeded函数实现,所有读写数据库的Redis命令在执行之前都会调用expireIfNeeded函数对输入键进行检查:
如果输入键已经过期,那么将输入键从数据库中删除
如果输入键未过期,那么不做任何处理
以上描述可以使用如下流程图表示:

在这里插入图片描述

5 redis 数据类型

现在可以分为9大数据类型 经典是五种数据类型

String 字符类型
SET MSET     GET   MGET
INCY KEY    INCYBY KEY increment   多用于:转发量 点赞数 阅读数  量的积累
DECR KEY    DECRBY KEY decrement 
setnx       分布式锁
Hash 散列类型
Map<String,Map<Object,Object>>   数据结构相当于
经典应用场景:JD早期手机端购物车,目前大厂不适合再用,但是小厂依然可以用
关于购物车见下图:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

List 列表类型
一个双端链表的结构,容量是2的32次方-1,大概40多亿,主要功能有push/pop等,一般用在栈,队列,消息队列等场景
左边添加 lpush key value[...]
右边添加 rpush key value[...]
查看列表 lrange key start stop
获取列表中元素个数 llen key
经典应用场景:微信公账号订阅消息
        	             商品评论列表

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Set 集合类型
最大特点 无重复数据的容器
SADD  添加元素
SREM  删除元素
SMEMBERS  遍历集合中的所有元素
SISMEMBER key member  判断元素是否在集合中
SCARD key 获取集合中元素总数
SRANDMEMBER key[数字]    从集合中随机弹出一个元素,元素不删除     默认1    这俩多用于抽奖!!!
SPOP key[数字]    从集合中随机弹出一个元素,出一个删一个      默认1         这俩多用于抽奖!!!
集合运算   集合A abc12     集合B123ax
集合的差集运算A-B   SDIFF key [key...]
集合的交集运算A交B   SINTER key [key...]
集合的并集运算AUB   SUNION key [key...]
经典应用场景: 微信抽奖小程序
			 微信朋友圈点赞
			微博好友关注社交关系
			QQ内推可能认识的人

在这里插入图片描述

在这里插入图片描述
微信朋友圈点赞
在这里插入图片描述
在这里插入图片描述
我关注的人也关注了他
SISMEMBER S1 3
SISMEMBER S2 3

QQ内推可能认识的人
SDIFF s1 s2
SDIFF s2 s1

SortedSet 有序集合类型
简称zset
向有序集合中加入一个元素和该元素的分数
添加元素  ZADD key score member[score member...]
按照元素分数从小到大的顺序返回索引从start到stop之间的所有元素 ZRANGE key start stop[withscores]
获取元素的分数 ZSCORE key member
删除元素 ZREM key member[member...]
获取指定分数范围的元素 ZRANGEBYSCORE key min max[withscores][limit offset count]
增加某个元素的分数 ZINCRBY key increment member
获取集合中元素数量 ZCARD key
获得指定分数范围内的元素个数 ZCOUNT key min max
按照排名范围删除元素 ZREMRANGEBYRANK key start stop
获取元素的排名 从小到大 ZRANK key member

经典应用场景: 各种排行榜

在这里插入图片描述
以抖音vcr最新的留言评价为案例,所有的评论需要两个功能,按照时间排序+分页显示
能够排序+分页显示的redis数据结构是什么合适?
答案 list zset
list 可以做 有瑕疵 见下图
zset 见下图
在面对需要展示最新列表,排行榜等场景时,如果数据更新频繁或者需要分页显示建议使用 zset
在这里插入图片描述
抖音排行榜
在这里插入图片描述
这是对一下三种数据类型:
亿级数据的收集+统计 三个问题:{1 数据收集 2 数据清洗 3 数据统计}
!!!一句话也就是: 存的进 取得块 多统计 !!!
滴滴打车:
cardId 经纬度 1秒钟出一个数据并高频道更新数据源 mysql update
亿级系统中常见的四种统计:
聚合统计:
统计多个集合元素的聚合结果, 就是前面讲解过的 交差并等集合统计 (交并差集和聚合函数的应用)
排序统计
order By zSet 见下图
二值统计
集合元素的取值就只有0和1两种
在钉钉上班到打卡的场景中,我们只用记录有签到(1)或没签到(0)
用 bitMap
基数统计
统计一个集合中 不重复的元素个数
用 hyperloglog

亿级数据还是顶不住
在这里插入图片描述

Bitmap 位图

底层原理:底层是用String类型作为底层数据结构 实现的一种统计二值状态的数据类型 ,他对应的就是Linux的ASCII码 get命令就可以直接获取底层ASCII码表 对应的值
type k1 查看类型
是什么:由 0 / 1 状态表现的二进制位的bit数组
8位一个byte自动扩容 最大2的32次方个字节信息
能干嘛:
用于状态统计
大厂真实案例:
日活统计
连续签到打卡
最近一周活跃用户
统计指定用户一年之中的登录天数
某用户按照一年365天,哪几天登陆过?那几天没有登录?全年中登录的天数共计是多少?
常见命令
setbit setbit key offset value (setbit 键 偏移量 只能零或者1) bitmap偏移量是从零开始算的
getbit
bitcount bitcount key [start end] 统计为1的数量
strlen 统计字节数 占了多少
bitop 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上
BITOP AND destkey key [key …] ,对一个或多个 key 求逻辑并,并将结果保存到 destkey

京东签到日历
在这里插入图片描述
在签到统计时, 每个用户一天签到用1个bit位就能表示, 一个月(假设是31天)的签到情况用31个bti位就可以,一年的签到也只需要用365个bit位, 根本不用太复杂的类型

用户编号映射 0 -张三 uid:121212
在这里插入图片描述
在这里插入图片描述

HyperLogLog 统计

HyperLogLog 核心:去重复
不直接存储数据本身
通过牺牲准确率来换取空间,它不是准确的 误差仅在0.81%左右
常见名词:
UV unique vistor 独立访客 一般理解为客户端ip。 需要去重
PV page view 页面浏览量 不需要去重
DAU daily active user 日活跃用户量(登录或者使用了某个产品的用户数 去重复登录的用户) bitMap 也可以做
MAU monthly active user 月活跃用户量
看需求:
统计某个网站的UV 统计某个文章的UV
用户搜索网站关键词的数量
统计用户每天搜索不同词条个数
是什么:
去重复统计功能的基数估计算法-就是HyperLogLog 也叫基数统计
在Redis里面,每个HyperLogLog建只需要花费12KB内存,就可以计算接近2的64次方个不同元素的基数
基数:是一种数据集,去重复后的真实个数
常见的去重统计:java:hashSet redis:set bitmap(亿级还是吃不住) hyperLogLog
概率算法:
通过牺牲准确率来换取空间,对于不要求绝对准确率的场景下使用,因为概率算法不直接存储数据本身,通过一定的概率统计方法预估基数值,同时保证误差在一定范围内,由于又不存储数据故此可以大大节约内存。HyperLogLog就是一种概率算法的实现。
HyperLogLog: 只是进行不重复的基数统计,不是集合也不保存数据,只记录 数量 而不是具体内容。
有误差 非精确统计,牺牲准确率来换取空间,误差仅在0.81%左右
命令: pfadd key element 将所有元素添加到key中
pfcount key 统计key的估算值(不精确)
pgmerge new_key key1 key2 合并key至新key
实战:::UV统计去重 (公司首页 UV)
pfadd key element + pfcount key 就可以搞定

GEO 地理位置的处理

type key zset 它的数据类型还是 zset
1 地理位置的处理
2设施啊
主要分三步: GEOHASH GEOHASH GEOHASH
1将三维的地球变为二维的坐标
2 在将二维的坐标转换为一维的点块
3最会将一维的点块转换为二进制在通过base32编码

命令:
GEOADD 多个经度,维度 位置名称添加到指定的key
GEOPOS 从建里面返回所有给定位置元素的位置(经度 维度)
GEODIST 返回两个给定位置之间的距离
GEORADIUS 以半径为中心 寻找附近的XXX
GEOHASH 返回一个或多个位置元素的Geohash表示

实战案例:
美团地图附近的酒店推送
在这里插入图片描述

Stream 主要用于消息队列 简单了解即可

6 布隆过滤器 BloomFilter

问题:现有50亿个电话号码,有10万个电话号码,如何要快速准确的判断这些电话号码是否已经存在?
也就是 亿级大数据,里面作为家底,一亿里查询是否存在10W个
是什么: 有一个初始值都为0的bit数组和多个哈希函数构成,用来快速判断某个数据是否存在
原理: 1 通常 我们会遇到很多要判断一个元素是否在某个集合种的业务场景,一般想到的是将集合中所有元素保护起来,然后通过比较确定。 链表 树 哈希表等等数据结构都是这个思路。

特点:

  • 高效地插入和查询,占用空间少,返回的结果是不确定性的
  • 一个元素如果判断为存在的时候不一定存在,但是判断结果为不存在的时候则一定不存在
  • 布隆过滤器可以添加元素,但是不能删除元素,因为删掉元素会导致误判率增加
  • 误判只会发生在过滤器没有添加过的元素,对于添加过得元素不会误判
  • 也就是:一个大大集合中,判断某个元素是否存在,   有 极大可能有  无则一定无
    

布隆过滤器使用场景:

  • 主要 解决缓存穿透的问题
  • 黑名单校验

在这里插入图片描述
在这里插入图片描述
原理:

  • 主要分三个步骤

  • 初始化

  • 添加

  • 判断是否存在
    在这里插入图片描述
    121
    下图就是为什么 不能删除元素的原因 因为位数组的标志可能来源于多个元素一旦删除了对其他元素受影响!!!
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    判断是否存在~~~!!!!!!
    在这里插入图片描述
    hash碰撞
    在这里插入图片描述
    一个hash冲突的案例
    在这里插入图片描述
    小总结:

  • 是否存在(有:有可能有 无:则一定无)

  • 使用时最好不要让实际元素数量远大于初始化数量

  • 当实际元素数量超过初始化数量时,应该对布隆过滤器进行重建,重新分配一个size更大的过滤器,在将所有的历史元素批量add进去

缓存雪崩:缓存中有大量的数据同时过期

  • 如何发生:redis主机挂了,redis挂了,或者缓存中有大量数据同时过期

  • 如何解决:1: 事前。redis缓存高可用 2: 事中。本地缓存+服务限流降级(sentinel) 3: 事后。开始redis持久化机制 尽快回恢复redis在这里插入图片描述 缓存穿透:

  • 是什么:请求去查询一条记录,先redis后mysql发现都查不到该条记录,倒是请求每次都会打到数据库上面去,导致后台数据压力倍增,这种现象我们称之为缓存穿透,这个redis变成了一个摆设 简单来说:就是本来无一物,既不在redis中也不在mysql中

  • 危害:第一次来查询后,一般我们有会写redis机制 第二次来查询的时候redis就有了,偶尔出现穿透现象一般情况无关紧要。

  • 解决:方案一: 空对象缓存或缺省值 (一旦发生缓存穿透,我们就可以针对查询的数据,在redis中缓存一个空值或是业务层协商确定的缺省值(例如,库存的缺省值可以设为0) 紧接着,应用发送的后续请求在进行查询时,就可以直接从redis中读取空值或缺省值,返回给业务应用了,避免了把大量请求发送给数据库处理,保持了数据库的正常运行)
    但是如果黑客恶意攻击: 黑客会对你的系统进行攻击,拿一个不存在的id去查询数据,会产生大量的请求到数据库去查询。可能会导致你的数据库由于压力过大而宕掉
    id相同打你:
    第一次打到mysql 空对象缓存后第二次就返回null了 避免mysql被攻击,不用在到数据库中去一圈了
    id不同打你:redis没有拦截到 并且由于bull对象缓存与默认值 导致了redis无关紧要的key 越来越多了(记得过期时间)

     	方案二: google布隆过滤器 Guava解决缓存穿透       !!!单机版!!!
     	![在这里插入图片描述](https://img-blog.csdnimg.cn/0433e452a810453387d73bacb4ee6d9a.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAQVNLQS5sdg==,size_20,color_FFFFFF,t_70,g_se,x_16)
    
  • 方案三:redis布隆过滤器解决缓存穿透
    1 guava提供的布隆过滤器的实现还是很不错的,但是它有个重大的缺陷就是只能单机使用,而现在互联网一般都是分布式场景 为了解决这个redis boolemfilter出现了
    在这里插入图片描述
    2 白名单过滤器
    架构上图
    误判问题,概率低 可以接受。不能从过滤器删除
    全部合法的key都需要放入过滤器+redis里面,不然数据就是返回null

重要总结:
在这里插入图片描述

黑名单在这里插入图片描述
以上三种都需要代码解决,redis4.X 以后天生支持了,不用靠代码就可以为redis安装一个boolemFilter作为带刀护卫了

boolemFilter 安装
  • docker
    redis 在4.x以后就有了插件功能(module) 可以使用外部的扩展功能,可以使用RedisBloom 作为Redis布隆过滤器插件
    docker run -p6379:6379 --name=redis6379bloom -dredislabs/rebloom
    docker exec -it redis6379bloom /bin/bash
    redis-cli

boolemFilter 常用命令
bf.reserve key error_rate 的值initial_size 默认的error_rate是0.01 initial_size是100
bf.reserve filter 0.03 10000000 !!!
bf.add key 值
bf.exists key 值
bf.madd 一次添加多个元素
bf.mexists 一次查询多个元素是否存在

  • 安装包
    在这里插入图片描述

7 分布式锁

7.1 缓存击穿

1 是什么: 大量的请求同时查询一个key时,此时这个key正好失效了,就会导致大量的请求都打到数据库上面去, 简单来说就是热点key突然失效了,暴打mysql

2 如何解决:
方案一: 缓存击穿 热点key失效 互斥更新/ 随机退避/ 差异失效时间/
方案二: 对于访频繁的热点key 干脆不要设置过期时间
方案三: 互斥独占锁 (多个线程同时去查数据库的这条数据,name我们可以在第一个查询数据的请求上 使用一个互斥锁来锁住它, 其他线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后缓存,后面的线程就来发现已经有缓存了 直接走缓存)

7.2 高并发淘宝聚划算 实战案例

在这里插入图片描述
分析过程
在这里插入图片描述
产品存在 redis List当中
高并发接口!!!
高并发 + 原子性的对立和统一

如何解决缓存击穿 定时轮询出来的数据: 互斥更新 差异化失效时间
1 新建 开辟两块缓存,主A从B,先更新B在更新A 严格按照这个顺序
2 查询 先查询主缓存A 如果A中没有 在去查询B

lua脚本解决多个命令的同步执行 但是解决不来高并发!!!

redis 分布式锁

redis 常见面试题!!!
在这里插入图片描述
锁的种类:
线程锁 单机锁: 单机版同一个JVM虚拟机内, sfnchronized 或者 Lock
进程锁 分布式锁:分布式不同个JVM虚拟机内,单机的线程锁机制不在起作用,资源类在不同服务器之间共享

一个靠谱的分布式锁所要具备的条件和刚需:
1 独占性 onlyOne, 任何时刻只能有且仅有一个线程持有
2 高可用 若redis集群环境下,不能因为某一个节点挂了而出现获取锁和释放锁失败
3 防死锁 杜绝死锁,必须有超时控制机制或者撤销机制, 有个兜底终止撤销方案
4 不乱抢 防止张冠李戴,不能私下unlock别人的锁,只能自己加锁 自己释放
5 重入性 同一个节点的同一个线程如果获得锁之后,它也可以再次获取这个锁

分布式锁: setnx key value

案例上存在的问题 也是分布式锁逐渐优化的过程:
1 首先加单机锁
2 加redis setnx 锁
3 必须要在 finally 中关闭锁资源 防止出异常无法释放锁
4 代码层面没走到到 finally之前就宕机了 没办反保证百分之百解锁 所以加锁要考虑过期时间
5 两个命令 不具有原子性

stringRedisTemplate.opsForValue().setIfAbsent(KEY, "value");
stringRedisTemplate.expire(KEY, 10L, TimeUnit.SECONDS);
stringRedisTemplate.opsForValue().setIfAbsent(KEY, "value", 10L, TimeUnit.SECONDS);

6 还有严重问题: 张冠李戴,私下unlock别人的锁

String value = UUID.randomUUID().toString()+Thread.currentThread().getName();
stringRedisTemplate.opsForValue().setIfAbsent(KEY, value, 10L, TimeUnit.SECONDS);
//....
finally{
//判断加锁解锁是不是同一个客户端
	if(stringRedisTemplate.opsForValue().get(KEY).equalsIgnoreCase(value)){
	//若在此时,这把锁突然不是这个客户端的,则会误解锁
	stringRedisTemplate.delete(KEY);
	}
}

在这里插入图片描述
7 上面的检查和删除 代码又不是原子性的 这里只能用Lua脚本了

finally{
Jedis jedis = RedisUtils.getJedis();
String script = "if redis.call('get',KEYS[1]) == ARGV[1]" +
"then " +
"return redis.call('del', KEYS[1])" +
"else "+
"  return 0" +
"end";

try{
Object result = jedis.eval(script ,Collections.singLetonList(KEY), Collections.singLetonList(value));
if("1".equals(result.toString())){
	//删除success
}else{
    //删除error
}
} finally{
if(null != jedis){
jedis.close();
}
}
}

基于单个redis 分布式锁差不多了
8 如何能确保redisLock 过期时间大于业务执行时间的问题?
缓存过期key的。缓存续命问题
redis 分布式锁如何续期???
集群+CAP redis 对比 zookeeper
1 redis单机是CP 集群AP
2 redis集群 redis异步复制造成的锁丢失 主节点没来得及把数据同步到从节点就挂了
3 zookeeper集群 zookeeper同步复制

9 也是避免张冠李戴 解锁太快了,解锁的时候把别人的锁给接了 !!!!!!
我这边出现这个问题是锁设置的时间小于了被锁程序的执行时间。导致redisson解锁时,锁已经因为超时被释放掉了。故抛出改异常

lock.isLocked():判断要解锁的key是否已被锁定。
lock.isHeldByCurrentThread():判断要解锁的key是否被当前线程持有。

finally{
redissonLock.unlock();
}

解决:

finally{
if(redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()){
		redissonLock.unlock();
		}
}

在这里插入图片描述

redlock 算法
使用场景:多个服务之间保证同一时刻同一时间段内同一用户只能有一个请求(防止关键业务出现并发攻击)
redis 分布式锁比较正确的使用姿势就是采用redisson 这个客户端
天上飞的理念 redlock 必然有落地的实现 redisson

redlock 理念:http://redis.cn/topics/distlock.html 超链接

单机案例:

三个重要元素:
1 加锁 实际上就是在redis中,给key建设置一个值,为了避免死锁,并给定一个过期时间
2 解锁 将key 删除。但也不能乱删,不能说客户端1的请求将客户端2的锁给删除掉,只能自己删自己的 (lua脚本)
3 超时 锁key注意过期时间 不能长时间占用

加锁 解锁关键代码
在这里插入21211212121212121212121212121我的描述
在这里插入图片描述
小公司 并发不高 上面的完全就可以用了!

多机案例:
如果必须要在生产中,上分布式锁
1 必须多机三台以上 官网建议三台以上
2 容错率公式
在这里插入图片描述
在这里插入图片描述
1单机redis分布式锁不ok
2多机redis集群也不ok master=slave模式行不通

怎么解决上述问题呢?

  • 有人会说 多机同步复制 那不就是zookeeper了么 那就不要用redis锁
  • 不要异步出现锁丢失,而是全部master,一起获得
    多主集群 需要计算容错率 N = 2X + 1
    要保证数据OK 比如,我网络中死了一台机器,我要求还是OK的,可以用,请问 多主集群至少几台机器
    N = 2 * 1 + 1 = 3台
  • redis 提出了redlock 解决这个问题

redlock 锁设计理念:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
案例!!!
见代码
redison 保证了加锁解锁原子性!!!
在这里插入图片描述

在这里插入图片描述
!!!缓存续命!!!
守护线程续命 额外起一个线程,定期检查线程是否还持有锁,如果有则延长过期时间。
redisson 里面就实现了这个方案,使用看门狗 定期检查(1/3的锁时间 检查一次),如果线程还持有锁,则刷新过期时间

在这里插入图片描述
![在这里插入图片描述](https://img-blog.csdnimg.cn/aa32cf9d30734fbf85a1e6b27e86835e.pn

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

老版本
在这里插入图片描述

在这里插入图片描述

新版本
在这里插入图片描述

第一段 初始化
第二段 可重入
第三段 返回过期时间

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

unlock
unlockAsync
在这里插入图片描述
在这里插入图片描述

8 redis缓存过期淘汰策略

!!!相关经典面试题!!!
在这里插入图片描述

8.1 redis 内存满了怎么办
  • redis 默认内存 ? maxmemory
    如果在不设置最大内存大小或者设置最大内存大小为0,在64位操作系统下不限制内存大小(基本上就是机器最大内存),在32位操作系统下最多使用3GB内存
  • 一般生产上配置多大呢 ?
    主服务器与缓存服务器在一台机器上 一般推荐物理内存的3/4
    主服务器与缓存服务器分开 可不配置,就默认最大好了
  • 如何修改内存配置
    1 通过修改配置文件
    2 通过命令修改,立即生效。重启后会失效
    config set maxmemory 1024
    再将改动写入系统配置文件,使其永久有效
  • info memory 查看内存适用情况
  • 真要打满了会怎么样,如果redis内存使用超过了设置的最大值? OOM
    在这里插入图片描述

往redis 里面写的数据是怎么没有的呢?是如何删除的呢? 见8.2

8.2 内存淘汰策略

1 redis过期建的删除策略 三种
立刻删除 对CPU不好 对内存友好
在这里插入图片描述
惰性删除 空间换时间
在这里插入图片描述
定期删除 中和之道
在这里插入图片描述在这里插入图片描述
那么接下来 兜底方案就来了
redis 缓存淘汰策略!

8.3 redis 缓存淘汰策略 有八种

在这里插入图片描述
默认配置是哪个 ? noeviction 默认值
你们公司用的是哪个 ? allKeys-lru 阿里云配置的也是这个!
方面记忆!
2个维度
过期键中筛选
所有键中筛选
4个方面
LRU
LFU
random
TTL
在这里插入图片描述

9 redis经典五种数据类型 底层原理

在这里插入图片描述

1 KV

我们平时说的redis是字典数据库 KV键值对到底是什么鬼?
redis 是key-value 存储系统,其中key类型一般为字符串,value类型则为redis对象 redisObject
也可以相当于 dictEntry 类似于 string redisObject
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

2 string 数据格式介绍

1 string三大编码
int
保存long型(长整型)的64位(8个字节)有符号整数
只有整数才会使用int ,如果是浮点数,redis内部其实先将浮点转化为字符串值,然后在保存
embstr
代表embstr 格式的SDS(simple dynamic string) 简单动态字符串 保存长度小于44字节的字符串
embstr 顾名思义:embedded string 表示嵌入式的String
raw
保存长度大于44字节的字符串

2 什么是SDS
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3 SDS为什么新建一套 好处呢?
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

set key1 123
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
为什么这么设计?
极致压榨redis性能,减少内存碎片
redis内部会根据用户给的不同建值而使用不同的编码格式,自适应地选择优化的编码格式,而这一切对用户完全透明的!

3 hash 数据格式介绍

hash-max-ziplist-entries: 使用压缩列表保存时,哈希集合中的最大元素 512
hash-max-ziplist-value: 使用压缩列表保存时,哈希集合中单个元素最大长度 64byte

hash类型键的字段个数小于 hash-maxziplist-entries 并且每个字段名和字段值的长度小于 hash-max-ziplist-value时候, redis才会使用OBJ_ENCODING_ZIPLIST来存储该键,前述条件任意一个不满足则会转换为OBJ_ENCODING_HT的编码方式。

redis里面的hash 有两种数据结构 ziplist + hashtable
在这里插入图片描述
在这里插入图片描述
在节省内存方面 ziplist 》 hashtable

ziplist
ziplist 压缩列表是一种紧凑编码格式,总体思想是多花时间来换取节约空间,既以部分读写性能为代价,来换取极高的内存空间利用率,因此只会用于字段个数少,且字段值也较小的场景,压缩列表内存利用率极高的原因与其连续内存的特性是分不开的。

当一个hash对象只包含少量键值对的键和值要么就是小整数要么就是长度较短的字符串,那么它用ziplist作为底层实现

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
压缩链表节点的构成
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

hashTable
在redis中 hashtable被称为字典(dictionary), 它是一个数组+链表的结构。

在这里插入图片描述
在这里插入图片描述

4 List数据结构

list 用quicklist 来存储,quicklist存储了一个双向链表,每个节点都是一个ziplist
quicklist 是ziplist和linkedlist的结合体
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

qwqw

在这里插入图片描述
在这里插入图片描述

5 Set数据结构

在这里插入图片描述

6 zSet数据结构

在这里插入图片描述
在这里插入图片描述

小总结
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
skiplist : 跳表
主要是考:何为多级索引?
是什么? 跳表是可以实现二分查找的有序链表 总结来说:跳表 = 链表+多级索引
在这里插入图片描述
说说链表和数组的优缺点? 以及为什么引出跳表?
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

10 redis与mysql数据双写一致性工程落地案例

在这里插入图片描述
在这里插入图片描述

canal

主要用于 mysql 数据库增量日志数据的订阅,消费和解析
效果,mysql动了,被canal组件捕获,立刻完整一致的将写操作,同步给我们的redis缓存源
在这里插入图片描述
在这里插入图片描述
!!!!原理!!!!
传统mysql 主从复制原理
在这里插入图片描述
在这里插入图片描述
canal 工作原理
在这里插入图片描述
实战!!!!
1 sql 脚本
CREATE TABLE t_user(
id BIGINT(20) NOT null auto_increment,
userName VARCHAR(100) not null,
PRIMARY KEY (id)
)ENGINE=INNODB auto_increment=10 DEFAULT CHARSET=utf8mb4
2 查看mysql版本 select version()
3 查看当前主机的二进制日志 show master status
4 查看是否开启了binlog日志 show VARIABLES like ‘log_bin’
5 开启binlog日志,并重启mysql才会生效 在次确认开启 show VARIABLES like ‘log_bin’
在这里插入图片描述
6 授权canal 连接mysql的账号 !!重要!!
在这里插入图片描述
新建用户后 重启mysql

select * from mysql.user

drop user 'canal'@'%'
create user 'canal'@'%' IDENTIFIED by 'canal'
grant all PRIVILEGES on *.* to 'canal'@'%' IDENTIFIED by 'canal'
FLUSH PRIVILEGES

select * from mysql.user

7 下载canal
8 配置修改
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
配置 需要备份的数据库表 一般在代码里面去指定
在这里插入图片描述8 启动 canal
在这里插入图片描述
9 java 代码
log
在这里插入图片描述

什么叫缓存双写一致性
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
同步直写 异步缓写
在这里插入图片描述
数据库和缓存一致性的几种更新策略
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  • 先更新数据库,在更新缓存 先 update mysql 在 update redis
    可以用,先更新数据库,在更新缓存,但是redis如果异常更新失败(或者redis更新不及时),导致从redis还是读取到了老的数据。

!! redis 更新不及时!!在这里插入图片描述
在这里插入图片描述

  • 先删除缓存,在更新数据库
    2 低并发 回写旧值 高并发 缓存击穿在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

总结:
在这里插入图片描述
解决方案:延时双删策略
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
sleep的时间一般都是几百毫秒 500ms
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 先更新数据库 在删除缓存 !!!工作中用的最多!!!
    在这里插入图片描述
    解决方案: 重试机制 + 引入MQ 也就是之前讲的canal
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

11实战 微信抢红包案例

面试的时候可以说:
APP 活动 发红红包
部门抢积分兑换礼物
抢礼品券
抢电话充值卡
在这里插入图片描述
先放入redis存储要快
在丢到mq 去消费数据
消费成功在入mysql库
数据消费 有重试机制 ,超过重试阈值则触发报警 通知运维人员
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

发红包:
1 你觉得redis中,那个数据结构满足 ( list LPUSH list:aska 20 30 20 15 15)
2 高并发操作, 发红包是否需要加锁 (1个人发啊 肯定不加锁啊)
在这里插入图片描述
2 抢红包
1 高并发操作, 抢红包是否需要加锁 ()
不加锁,但是具备有原子性+完整性的加锁效果
redis 具有高并发+实时+原子效果(不用加单机锁或分布式锁)
redis 天生骄傲,就带着
redis的命令,本身就是原子性,不用额外加锁且保证了锁的效果
直接上: LPOP list:aska 就保证了上面的特性

3 记
1 你觉得redis中,那个数据结构满足 ( hash )
2 记的时候 需要加锁么? (不需要了 个记个的)

4 拆 红包均值分配算法
在这里插入图片描述
!!!二倍均值算法!!!
在这里插入图片描述

代码实战

12 案例实战:B站视频 淘宝购物 分享短连接推广

把长链接 映射成小链接
在这里插入图片描述
好处:
简单方便,利于推广使用
http传输好了很多,有助于带宽节约和高并发
防止尾巴参数泄密,不安全

短链接算法
映射匹配
短-跳转-长的真实地址

在这里插入图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值