redis 总结

超卖:乐观锁 mysql的,where 更新之前的数据=更新之后的数据。并发情况下:同一时刻错误性太高:有库存遗留问题。解决办法 where 要更新的数据>0

缓存穿透:指数据库和缓存里面都没的数据,解决办法:对空值""进行缓存。如:

查询缓存没得值,就去查数据库,没得值,我们久将查询的数据设置未空值,缓存进redis,并设置过期时间,下次查询,就会命中空值缓存,从而不是打进数据库。

缓存击穿:(互斥锁的方式)个别key失效的情况,互斥锁 setnx 串行化。一个请求释放锁后,另一个请求拿到锁。

如:查询商铺数据,一个线程查询key失效,就去获取锁,获取锁成功,就去查询数据库,并更新缓存,同1时刻,另外的线程拿不到锁,就重试(sleep ,在调用自己函数,递归)确定:并发性差,优点一致性高

缓存击穿:(逻辑过期的方式,(事先就有key,给这个key一个时间属性)在互斥锁的方式上优化,我们不阻塞线程,线程拿不到锁就返回旧数据,线程拿到锁,判断逻辑过期没有,过期了,就开个线程去查询数据库,并更新缓存,本身线程依旧返回旧数据,缺点:一致性比较差,存在查询到旧数据的可能性,优点:并发性好)

缓存的更新策略:先更新数据库,在删除缓存(查询的时候没有命中缓存,就回去查询数据库,然后再建立缓存)

Redisson 如何解决锁相关问题:

Redisson如何解决分布式锁主从一致性问题:

基操作:key只有一种类型 String  

String:       set key value   | get key

 底层实现是一个结构体里面有个char数组保存value的值

Hash:        hset key   key1 value1  key2 value2  |  hget key key1

 

 

hash底层的结构是 ziplist 和 hashtable

那么,什么时候会从ziplist转成hashtable呢?这个在redis.conf中有相关的配置,如下:

默认情况下:

  1. 当ziplist中entry的数量超过512的时候,会转成hashtable ziplist底层是个双链表
  2. 单个元素的值超过64字节的时候,会转成hashtable

List: 

 应用场景: 针对有先后顺序的数据 :比如朋友圈点赞,评论 哪个先点 哪个后点是有顺序的

左侧是头 右测是尾 lpush 在左侧左边插入 不会再尾巴插入

事务:

用到的命令:multi exec discard

reids事务是否支持acid:

Atomicity:reids不具备原子性(一个事务的多个操作必须完成,或者都不完成)在reidis中事务分两个步骤完成 1、组队阶段 2、命令执行阶段

在组队阶段 set k3 没设置value 所以报错 ,然后在执行exec也报错,组队阶段具备原子性

下面例子在执行阶段报错,会导致只有错误的命令不会执行,没问题的命令依然会继续成功,所以redis不能保证原子性

一致性 redis可以保证

一致性会受到错误命令、实例故障发生时机的影响,按照命令出错实例故障两个维度的发生时机,可以分三种情况分析。

EXEC 执行前,入队报错

事务会被放弃执行,所以可以保证一致性。

EXEC 执行后,实际执行时报错

有错误的执行不会执行,正确的指令可以正常执行,一致性可以保证。

EXEC 执行时,实例故障

实例故障后会进行重启,这就和数据恢复的方式有关了,我们要根据实例是否开启了 RDB 或 AOF 来分情况讨论下。

如果我们没有开启 RDB 或 AOF,那么,实例故障重启后,数据都没有了,数据库是一致的。

如果我们使用了 RDB 快照,因为 RDB 快照不会在事务执行时执行。

所以,事务命令操作的结果不会被保存到 RDB 快照中,使用 RDB 快照进行恢复时,数据库里的数据也是一致的。

如果我们使用了 AOF 日志,而事务操作还没有被记录到 AOF 日志时,实例就发生了故障,那么,使用 AOF 日志恢复的数据库数据是一致的。

如果只有部分操作被记录到了 AOF 日志,我们可以使用 redis-check-aof 清除事务中已经完成的操作,数据库恢复后也是一致的。

隔离性

事务执行又可以分成命令入队(EXEC 命令执行前)和命令实际执行(EXEC 命令执行后)两个阶段。

所以在并发执行的时候我们针对这两个阶段分两种情况分析:

  1. 并发操作在 EXEC 命令前执行,隔离性需要通过 WATCH 机制保证;

  2. 并发操作在 EXEC 命令之后,隔离性可以保证。

持久性: Redis 本身是内存数据库,持久性并不是一个必须的属性,我们更加关注的还是原子性、一致性和隔离性这三个属性。

为什么要用到事务:

举个例子说说事务冲突

有三个人都用你的账号去购物 现在你的账号余额有10000 ,其中一个人准备买8000的东西,一个准备买5000的东西,一个人准备买2000,第一个人手快一点先去买了8000的商品,同时第二个人在去买,然后第三个买,假设同意时刻他们都进入到if分支内部,程序上已经校验过金额

 最后会导致数据库内部的金额数据为负数,也就是常说的超卖问题,库存,金额不能为负。

为避免这种情况的产生需要使用事务 并进行加锁(乐观锁)解决但会导致库存遗留问题或者采用分布式锁解决或者采用lua机制进行保证

Redis的两种锁:

1、悲观锁 :

 2、乐观锁:

        

先去操作的人,更新了数据后,也会同时更新版本号,其他人在更新数据的时候会先对比版本号是否一致,不一致则不能操作。乐观锁采用watch实现

终端1 2 同时监视balance 然后开启事务 执行incrby 然后分别执行exec 结果如下:

 

秒杀案列的实现以及产生的各种问题

static boolean doSecKill(String uid,String prodid) throws IOException{

        1. 判断 uid及proid是否为空

        2.连接redis

        3.拼接key 1 库存key 2秒杀成功用户key

        4.获取库存,如果库存null 秒杀还没开始

        5 判断用户是否重复秒杀操作 

        6 判断商品数量,库存数量小于 1 ,秒杀结束

        7 秒杀过程 商品-1 秒杀成功用户+1

        8.关闭redsi连接 

}

下面用 ab 工具进行并发模拟 会出现商品数量出现负数的情况:这种情况叫做超卖。

出现原因:

在并发情况下,假设某一时刻 redis种实际商品数量大于0,而此时多个线程或者进程代码执行已经到步骤6 且校验通过,在执行步骤7的时候都会成功,所以会导致超卖的问题,在多线程和多进程的情况下,这种并发并非安全。

解决措施:(乐观锁)事务+wtach +连接池(解决超时问题)

监视一下库存数量  multi 将步骤7采用事务进行处理 exc 

但上述操作依然会存在问题:在并发场景下会产生库存遗留问题 ,在并发场景下,多个进程或者线程处于redis事务组队阶段,在最先执行的进程过线程在执行exec过后,会更新版本,其他线程或者进程执行exec 不会成功。(假设1000张票  2000个人抢票 ,在某一并发时刻,2000人种有1500个人处于并发事务中,其中1500个人中有一个人先更新数据 其余1499个人都更新不了数据,就会导致 票没被抢完的情况。

解决措施: redis默认没有悲观锁 将步骤7中的秒杀过程写进lua 进行保证。

分布式锁 setnx (上锁)+ expire(设置过期时间) del 释放锁  

三联问:

1.使用setnx 上锁,通过del释放锁

2.自己忘记del释放锁或者服务器突然出现问题导致key没释放,可以设置key的过期时间

3.原子操作:上锁后,服务突然断电,过期时间无法生效,一直无法释放锁

解决措施 上锁的的时候同时设置过期时间  set key val nx ex 10 保证原子操作,但是又会出现4的问题(key过期时间设置的过小或者程序出现卡顿,导致程序还没执行完key就过期了,第二个人就可以拿到所,然后第二个人执行程序的时候,第一个人程序卡顿恢复了,最后调用del手动释放锁,把第二个的锁释放了,相当于没上锁)所以用uuid 自己的锁只能自己释放!

4.UUID 防止误删

 1、使用uuid 例:

       set lock uuid nx ex 10 释放锁的时候,首先判断get当前的uuid和设置是否一样,一样则释放

但是这个也有一个问题,释放的时候我们要先get key的值,get和释放又不是一个原子操作:a在判断 当前的uuid和设置的一样后 且在删除之前(出现卡顿) 过期时间刚好到了自动释放,B就可以获取到这个锁,然后B在执行的时候,锁又被A释放了。

解决措施:将 删除操作(get key 判断 和del key)这两个操作写进lua里面,调用lua保证原子性。

总结分布式锁的用法:

1.setnx lock uuid nx ex|px

2.执行具体操作

3:将 删除操作(get key 判断 和del key)这两个操作写进lua里面,调用lua保证原子性。

什么是缓存穿透 

 也就是说外部访问一个不存在的数据,缓存没有 这样每次去查数据库,导致数据库压力大

什么是缓存击穿

数据源是存在的,只是由于访问的个别Key过期正好当时有大量访问该key的操作,导致去查数据库,导致数据库压力增大甚至崩溃

1.使用互斥锁 左边的,缺点:同一时刻可能存在多个请求的堆积,优点:一致性高。2、使用逻辑过期的方式,优点:并发性高,但是一致性差一些,返回的可能是旧数据。

什么是缓存雪崩

 在极少的时间段,查询大量的key集中过期的情况

Redis的持久化操作:

1、RDB(redis databasde)

 RDB备份的执行过程:

RDB方式配置相关

默认名字

 生成dump.rdb的路径

硬盘满了 关闭redis的写操作

 

持久化策略 多长时间至少多少个key变化 就写一次rdb 不建议使用:save是阻塞的

 

rdb的优点

1、适合大规模的数据恢复

2、对数据完整性和一致性要求不高更适合使用

3、节省磁盘空间

4、恢复速度快

rdb的缺点

1、Fork的时候,内存中的数据被克隆了一份,大致两倍的膨胀性需要考虑

2、写实拷贝技术,数据量大的时候还是比较消耗性能

3、备份周期在一定间隔做一次持久化,所以Redis意外down掉的话,就会丢失最后一次快照后的所有修改。

AOF(append only file)

如果AOF和RDB同时开始,reids默认选取AOF的数据(数据不会丢失)

 AOF 默认不开启

AOF同步频率设置 always始终同步 everysec 每秒同步  no 交给操作系统决定什么时候同步

AOF持久化流程:

AOF重写策略

AOF的优点        

    备份机制更稳健,丢失概率更低

    可读日志文本,通过操作AOF稳健,可以处理误操

AOF的缺点

   比起RDB占用更多的磁盘空间

   恢复备份速度更慢

   每次读写都同步的话,有一定的压力      

   存在个别bug,造成不能恢复

Redis 集群

1.主从:

启动多个redis服务 选择从机执行slaveof ip(主机)+port(主机)

用info replication 查看主从是否设定成功

主机可读可写 从机只能读

优点:读写分离

缺点:主机挂了 就真挂了 无法做到高可用

薪火相传:一个slave可以事另外一台slave的master 

反客为主:master挂了 slave里面执行slaveof no one 可以将slave升为master 缺点:手动执行

2.哨兵模式:

 在master挂掉了 可以通过一系列算法将salve自动选举为master,后面master重启后 变为slave。

配置:

增加配置文件

1、取名 sentinel.conf 里面增加一行 sentinel monitor mymaster 127.0.0.1 1 其中mymaster为监控服务对象起的服务名 1至少有多少个同意迁移的数量

2、启动 redis-sentinel  sentinel.conf 默认端口26379

选举规则 1、选择优先级靠前的 2、选择偏移量最大的 3、选择runid最小的从服务

3.无中心化集群:

 解决什么问题: 1、容量不够如何进行扩容  2、并发写操作 如何分摊

 配置:

1、基于主从基础配置文件中 每个redis服务的配置中增加下面三个配置

 2.启动各个redis服务

3.将多个节点合成一个集群:到redis/src目录下执行 redis-cli --cluster create --cluster-replicas 1 192.168.11.101:6379 192.168.11.101:63780 192.168.11.101:6381 192.168.11.101:6382 192.168.11.101:6383 192.168.11.101:6384

--cluster-replicas 1 采用最简单的方式搭建集群 一台主机 一台从机

客户端采用 redis-cli -c -p 进行登录

采用cluster nodes 查看集群信息

一个集群至少要求三个主节点

根据 cluster-require-full-coverage 决定单一主从全挂掉 是否整个集群不可用

c++ 连接redis集群: 利用hredis进行连接 1、先随意连接一台redis服务,2、写入的时候根据redis返回数据 截取ip 端口 在进行连接执行写入命令,获取也是一样的。具体代码如下

C连接redis集群

Redis 存储原理:

1、对用户输入的key 进行hash(siphash函数)  得到一个64位的hash整形值
2、在对这个hash值对4(数组的大小) 取余,得到应该放在哪个数组下标下面, 用链表串联起来 采用头部插入法。
注:下标是经过key hash函数得到hash值在对数组大小取余得到的。

Rehash:
随着操作的不断执⾏, 哈希表保存的键值对会逐渐地增多或者减少, 为了让哈希表的负载因⼦(
ratio)
维持在⼀个合理的范围之内, 当哈希表保存的键值对数量太多或者太少时, 程序需要对哈希表的⼤⼩进⾏
相应的扩展或者收缩。
渐进式hash:
扩展或收缩哈希表需要将 ht[0] ⾥⾯的所有键值对 rehash 到 ht[1] ⾥⾯, 但是, 这个
rehash 动作并不是⼀次性、集中式地完成的, ⽽是分多次、渐进式地完成的。
这样做的原因在于, 如果 ht[0] ⾥只保存着四个键值对, 那么服务器可以在瞬间就将这些键值对全部
rehash 到 ht[1] ; 但是, 如果哈希表⾥保存的键值对数量不是四个, ⽽是四百万、四千万甚⾄四亿个键
值对, 那么要⼀次性将这些键值对全部 rehash 到 ht[1] 的话, 庞⼤的计算量可能会导致服务器在⼀段时
间内停⽌服务。
因此, 为了避免 rehash 对服务器性能造成影响, 服务器不是⼀次性将 ht[0] ⾥⾯的所有键值对全部
rehash 到 ht[1] , ⽽是分多次、渐进式地将 ht[0] ⾥⾯的键值对慢慢地 rehash 到 ht[1] 。
以下是哈希表渐进式 rehash 的详细步骤:
1. 为 ht[1] 分配空间, 让字典同时持有 ht[0] 和 ht[1] 两个哈希表。
2. 在字典中维持⼀个索引计数器变量 rehashidx , 并将它的值设置为 0 , 表示 rehash ⼯作正式开 始。
3. 在 rehash 进⾏期间, 每次对字典执⾏添加、删除、查找或者更新操作时, 程序除了执⾏指定的操作
以外, 还会顺带将 ht[0] 哈希表在 rehashidx 索引上的所有键值对 rehash 到 ht[1] , 当 rehash ⼯
作完成之后, 程序将 rehashidx 属性的值增⼀。
4. 随着字典操作的不断执⾏, 最终在某个时间点上, ht[0] 的所有键值对都会被 rehash ⾄ ht[1] , 这
时程序将 rehashidx 属性的值设为 -1 , 表示 rehash 操作已完成。
渐进式 rehash 的好处在于它采取分⽽治之的⽅式, 将 rehash 键值对所需的计算⼯作均滩到对字典的每
个添加、删除、查找和更新操作上,甚⾄是后台启动⼀个定时器,每次时间循环时只⼯作⼀毫秒, 从⽽避
免了集中式 rehash ⽽带来的庞⼤计算量。

redis里面hash冲突的解决办法 数组加链表,同一个数组索引下面的不同k v 按照链表头插法进行插入。

1、分治的思想:每次操作,增删改查 set get del rehash一次,一次是数组的槽位(链表)

2、如果没有持久化操作,在1ms内执行多个100次操作这里的一次也是数组的一个槽位数据

 

Reids 主从复制原理

 一、2.8 版本以前的方案:全量复制

 

三种情况: 1、新节点加入 2、主从连接故障 3、从节点重启

以上三种情况在2.8 版本都是全量复制:主要分为以上三步骤进行全量复制

1、master完成持久化写rdb 之后的命令写入发送缓冲区,发送rdb

2、slave接受rdb,加载至内存

3、master 发送缓冲区命令 slave一次处理

问题:主从连接故障时间短 完全没有必要全量更新,只需将少量的写操作更新。

二、2.8 版本增量同步

1、slave记录偏移量 psync 主id offset  发送给master master判断offset是否在环形缓冲区内。

 增量同步:

Redis 常见面试问题

1、redis是单线程还是多线程?

 2、Redis单线程为什么还能这么快?

 3、Redis key过期了 为什么内存没有释放?

 1、set key ex 后 在对这个key设置,没有加上过期参数

 4、Redis key没设置过期时间为什么被redis主动删除了

   有可能配置了针对所有key进行删除的策略b)默认第8个

 5、Redis淘汰的算法LRU和LFU区别

 6、删除key的命令del key会阻塞redis吗

  会阻塞 

  时间复杂度 删除单个字符串类型的key 时间复杂度为0(1).

  删除单个列表、集合、有序集合或哈希表类型的key,时间复杂度o(M),M为以上数据结构内的元素数量。

 7、Redis主从切换导致的缓存雪崩是怎么造成的?

    

 8、Redis持久化RDB AOF 混合持久化是怎么回事?

 8、

 9、

 10、Redis线上的数据如何备份?

 11、Redis集群网络抖动导致频繁主从切换怎么处理

 12、Redis集群为什么至少需要3个master节点

 13、

缓存雪崩解决办法:

1、将多个key的过期时间设置为不同,这样保证同一时间不会有多个key同时失效

2、程序刚启动的时候 redis里面是没有数据的 需要缓存预热 先将mysql的数据同步到redis里面

3、枷锁

 14、如何解决缓存击穿问题?

1、个别热点key失效,并发过高导致的缓存击穿,导致数据库瞬间的压力增大,考虑key延期或者永不过期哦。

2、加互斥锁。

 15、如何解决缓存穿透问题?

1、用户鉴权 id校验 id<0直接拦截(业务方便隔离部分)

2、对空值缓存(大量不存的id 导致reids内存增加)设置过期时间解决+读延期

3、采用布隆过滤器

16、如何保证数据库与缓存的一致性

1、先更新数据库在更新redis 缓存可能更新失败,读到老数据

2、先删除缓存在更新数据库,并发时,数据库没更新完成,读操作会读到老数据设回缓存

3、先更新数据 ,再删除缓存。可能存在删除失败的情况,(删除失败重试保证)3比 1 2 好

4、

 5、分布式队列 先删除key 将更新数据库操作放进queue里面,再将读reids失败的操作 也一依次加进队列里面,串行执行,保证更新操作完成后再执行redis读操作。能保证数据的一致性:

缺点:

 综上:选择方案三 先更新数据再删除缓存,读的时候没有查数据库,读完写入redis

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值