Java面试问题整理笔记-Redis基础知识

1.什么是Redis?

Redis是现在最受欢迎的NoSQL数据库之⼀,Redis是⼀个使⽤ANSI C编写的开源、包含多种数据结
构、⽀持⽹络、基于内存、可选持久性的键值对存储数据库,其具备如下特性:

  • 基于内存运⾏,性能⾼效(每秒可以处理超过 10万次读写操作)
  • ⽀持分布式,理论上可以⽆限扩展
  • key-value存储系统(key是字符串,键有字符串、列表、集合、散列表、有序集合等)
  • 开源的使⽤ANSI C语⾔编写、遵守BSD协议、⽀持⽹络、可基于内存亦可持久化的⽇志型、KeyValue数据库,并提供多种语⾔的API

2.Redis数据类型?应用场景?

2.1String

  • 常⽤命令: set,get,decr,incr,mget 等。
  • String数据结构是简单的key-value类型, value其实不仅可以是String,也可以是数字。 常规keyvalue缓存应⽤; 常规计数:微博数,粉丝数等。

2.2Hash

  • 常⽤命令: hget,hset,hgetall 等。
  • hash 是⼀个 string 类型的 field 和 value 的映射表, hash 特别适合⽤于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 ⽐如我们可以 hash 数据结构来存储⽤户信息,商品信息等。

2.3List

  • 常⽤命令: lpush,rpush,lpop,rpop,lrange等
  • list 就是链表, Redis list 的应⽤场景⾮常多,也是Redis最重要的数据结构之⼀,⽐如微博的关注列表,粉丝列表,消息列表等功能都可以⽤Redis的 list 结构来实现。
  • Redis list 的实现为⼀个双向链表,即可以⽀持反向查找和遍历,更⽅便操作,不过带来了部分额外的内存开销。
  • 另外可以通过 lrange 命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分⻚查询,这个很棒的⼀个功能,基于 redis 实现简单的⾼性能分⻚,可以做类似微博那种下拉不断分⻚的东⻄(⼀⻚⼀⻚的往下⾛),性能⾼。

2.4 Set

  • 常⽤命令: sadd,spop,smembers,sunion 等

  • set 对外提供的功能与list类似是⼀个列表的功能,特殊之处在于 set 是可以⾃动排重的。

  • 当你需要存储⼀个列表数据,⼜不希望出现重复数据时, set是⼀个很好的选择,并且set提供了判断某个成员是否在⼀个set集合内的重要接⼝,这个也是list所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。

  • ⽐如:在微博应⽤中,可以将⼀个⽤户所有的关注⼈存在⼀个集合中,将其所有粉丝存在⼀个集合。
    Redis可以⾮常⽅便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。

2.5 Sorted Set

  • 常⽤命令: zadd,zrange,zrem,zcard等
  • 和set相⽐, sorted set增加了⼀个权重参数score,使得集合中的元素能够按score进⾏有序排列。举例: 在直播系统中,实时排⾏信息包含直播间在线⽤户列表,各种礼物排⾏榜,弹幕消息(可以理解为按消息维度的消息排⾏榜)等信息,适合使⽤ Redis 中的 Sorted Set 结构进⾏存储。

2.6String 还是 Hash 存储对象数据更好呢?

  • String 存储的是序列化后的对象数据,存放的是整个对象。
  • Hash 是对对象的每个字段单独存储,可以获取部分字段的信息,也可以修改或者添加部分字段,节省⽹络流量。如果对象中某些字段需要经常变动或者经常需要单独查询对象中的个别字段信息, Hash 就⾮常适合
  • String 存储相对来说更加节省内存,缓存相同数量的对象数据, String 消耗的内存约是 Hash 的
    ⼀半。并且,存储具有多层嵌套的对象时也⽅便很多。
  • 如果系统对性能和资源消耗⾮常敏感的话, String 就⾮常适合。

在绝⼤部分情况,我们建议使⽤ String 来存储对象数据即可!

那根据你的介绍,购物⻋信息⽤ String 还是 Hash 存储更好呢?

购物⻋信息建议使⽤ Hash 存储:

  • ⽤户 id 为 key
  • 商品 id 为 field,商品数量为 value

由于购物⻋中的商品频繁修改和变动,这个时候 Hash 就⾮常适合了

3.Redis缓存击穿?解决方案?

3.1缓存击穿概念

对于设置了过期时间的key,缓存在某个时间点过期的时候,恰好这时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把DB压垮。

3.2缓存击穿解决方案

  • 使用互斥锁:当缓存失效时,不立即去load db,先使用如Redis的setnx去设置一个互斥锁,当操作成功返回时再进行load db的操作并回设缓存,否则重试get缓存的方法。
  • 永远不过期:物理不过期,但逻辑过期(后台异步线程去刷新)。缓存雪崩:设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。与缓存击穿的区别:雪崩是很多key,击穿是某一个key缓存。

4.Redis穿透?解决方案?

4.1缓存穿透概念

缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在
缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请
求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。

4.2缓存穿透解决方案

  • 方案一:最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。

  • 方案二:简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴

对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)。
Bitmap: 典型的就是哈希表
缺点是,Bitmap对于每个元素只能记录1bit信息,如果还想完成额外的功能,恐怕只能靠牺牲更多的空间、时间来完成了。

4.3布隆过滤器(推荐)

就是引入了k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过
程。这种算 法由⼀个⼆进制数组和⼀个 Hash 算法组成

  • 优点:是空间效率和查询时间都远远超过一般的算法
  • 缺点:是有一定的误识别率和删除困难。
  • Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。Hash存在一个冲突(碰撞)的问题,用同一个Hash得到的两个URL的值有可能相同。为了减少冲突,我们可以多引入几个Hash,如果通过其中的一个Hash值我们得出某元素不在集合中,那么该元素肯定不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。
  • Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。

5.Redis雪崩?解决方案?

5.1缓存雪崩概念

我们可以简单的理解为:由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。

5.2缓存雪崩解决方案

  • 大多数系统设计者考虑用加锁( 最多的解决方案)或者队列的方式保证来保证不会有大量的线程对数据
    库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。

  • 还有一个简单方案就时讲缓存失效时间分散开。

缓存雪崩的事前事中事后的解决方案如下:

  • 事前:Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃。
  • 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
  • 事后:Redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

6.数据库与Redis数据一致性如何保证?

6.1什么是数据⼀致性问题?

只要使⽤到缓存,⽆论是本地内存做缓存还是使⽤ redis 做缓存,那么就会存在数据同步的问题。

6.2更新缓存⽅案

6.2.1先更新缓存,再更新DB

这个⽅案我们⼀般不考虑。原因是更新缓存成功,更新数据库出现异常了,导致缓存数据与数据库数据完全不⼀致,⽽且很难察觉,因为缓存中的数据⼀直都存在

6.2.2先更新DB,再更新缓存

这个⽅案也我们⼀般不考虑,原因跟第⼀个⼀样,数据库更新成功了,缓存更新失败,同样会出现数据不⼀致问题。这种更新缓存的⽅式还有并发问题。

同时有请求A和请求B进⾏更新操作,那么会出现
(1)线程A更新了数据库
(2)线程B更新了数据库
(3)线程B更新了缓存
(4)线程A更新了缓存

这就出现请求A更新缓存应该⽐请求B更新缓存早才对,但是因为⽹络等原因,B却⽐A更早更新了缓存。这就导致了脏数据,因此不考虑。

6.3删除缓存⽅案

6.3.1先删除缓存,后更新DB

该⽅案也会出问题,具体出现的原因如下。
此时来了两个请求,请求 A(更新操作)和请求 B(查询操作)

  • 请求 A 会先删除 Redis 中的数据,然后去数据库进⾏更新操作;
  • 此时请求 B 看到 Redis 中的数据时空的,会去数据库中查询该值,补录到 Redis 中;
  • 但是此时请求 A 并没有更新成功,或者事务还未提交,请求B去数据库查询得到旧值;

那么这时候就会产⽣数据库和 Redis 数据不⼀致的问题。如何解决呢?其实最简单的解决办法就是延时双删的策略。就是
(1)先淘汰缓存
(2)再写数据库
(3)休眠1秒,再次淘汰缓存
这么做,可以将1秒内所造成的缓存脏数据,再次删除。
那么,这个1秒怎么确定的,具体该休眠多久呢?
针对上⾯的情形,⾃⾏评估⾃⼰的项⽬的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业
务逻辑的耗时基础上,加⼏百ms即可。这么做的⽬的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

6.3.2先更新DB,后删除缓存

这种⽅式,被称为Cache Aside Pattern,读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放⼊缓存,同时返回响应。更新的时候,先更新数据库,然后再删除缓存。这种情况不存在并发问题么 ?依然存在。假设这会有两个请求,⼀个请求A做查询操作,⼀个请求B做更新操作,那么会有如下情形产

(1)缓存刚好失效
(2)请求A查询数据库,得⼀个旧值
(3)请求B将新值写⼊数据库
(4)请求B删除缓存
(5)请求A将查到的旧值写⼊缓存

然⽽,发⽣这种情况的概率⼜有多少呢?
发⽣上述情况有⼀个先天性条件,就是步骤(3)的写数据库操作⽐步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。可是,⼤家想想,数据库的读操作的速度远快于写操作的(不然做读写分离⼲嘛,做读写分离的意义就是因为读操作⽐较快,耗资源少),因此步骤(3)耗时⽐步骤(2)更短,这⼀情形很难出现。⼀定要解决怎么办?如何解决上述并发问题?

  • ⾸先,给缓存设有效时间是⼀种⽅案。
  • 其次,采⽤异步延时删除策略。

但是,更新数据库成功了,但是在删除缓存的阶段出错了没有删除成功怎么办?这个问题,在删除缓存
类的⽅案都是存在的,那么此时再读取缓存的时候每次都是错误的数据了。
此时解决⽅案有两个,⼀是就是利⽤消息队列进⾏删除的补偿。具体的业务逻辑⽤语⾔描述如下:
请添加图片描述

1、请求 A 先对数据库进⾏更新操作
2、在对 Redis 进⾏删除操作的时候发现报错,删除失败
3、此时将Redis 的 key 作为消息体发送到消息队列中
4、系统接收到消息队列发送的消息后
5、再次对 Redis 进⾏删除操作
但是这个⽅案会有⼀个缺点就是会对业务代码造成⼤量的侵⼊,深深的耦合在⼀起,所以这时会有⼀个
优化的⽅案,我们知道对 Mysql 数据库更新操作后再 binlog ⽇志中我们都能够找到相应的操作,那么
我们可以订阅 Mysql 数据库的 binlog ⽇志对缓存进⾏操作。

7.主从数据库不一致如何解决

场景描述,对于主从库,读写分离,如果主从库更新同步有时差,就会导致主从库数据的不一致

  • 忽略这个数据不一致,在数据一致性要求不高的业务下,未必需要时时一致性
  • 强制读主库,使用一个高可用的主库,数据库读写都在主库,添加一个缓存,提升数据读取的性能。
  • 选择性读主库,添加一个缓存,用来记录必须读主库的数据,将哪个库,哪个表,哪个主键,作为缓存的key,设置缓存失效的时间为主从库同步的时间,如果缓存当中有这个数据,直接读取主库,如果缓存当中没有这个主键,就到对应的从库中读取。

8.Redis 过期键删除策略

8.1定时删除

在设置键的过期时间的同时,创建一个定时器 timer). 让定时器在键的过期时间来临时,立即执行对键的删除操作。

8.2惰性删除

放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。

8.3定期删除

每隔一段时间程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。

9.Redis(回收策略)淘汰策略

相关问题: MySQL ⾥有 2000w 数据, Redis 中只存 20w 的数据,如何保证 Redis 中的数据都
是热点数据?

9.1volatile-lru

从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

9.2volatile-ttl

从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

9.volatile-random

从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

9.4allkeys-lru

从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

9.5allkeys-random

从数据集(server.db[i].dict)中任意选择数据淘汰

9.6no-enviction(驱逐)

禁止驱逐数据

注意这里的 6 种机制,volatile 和 allkeys 规定了是对已设置过期时间的数据集淘汰数据还是从全部数据集淘汰数据,后面的 lru、ttl 以及 random 是三种不同的淘汰策略,再加上一种 no-enviction 永不回收的策略。

9.7LRU算法实现

  • 通过双向链表来实现,新数据插入到链表头部;
  • 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
  • 当链表满的时候,将链表尾部的数据丢弃。
  • LinkedHashMap:HashMap和双向链表合二为一即是LinkedHashMap。HashMap是无序的,LinkedHashMap通过维护一个额外的双向链表保证了迭代顺序。该迭代顺序可以是插入顺序(默认),也可以是访问顺序。

10.Redis持久化机制?

10.1RDB(Redis DataBase)

Redis 可以通过创建快照来获得存储在内存⾥⾯的数据在某个时间点上的副本。 Redis 创建快照之
后,可以对快照进⾏备份,可以将快照复制到其他服务器从⽽创建具有相同数据的服务器副本
(Redis 主从结构,主要⽤来提⾼ Redis 性能),还可以将快照留在原地以便重启服务器的时候使
⽤。 内存到硬盘的快照,定期更新。

缺点:耗时,耗性能(fork+io操作),易丢失数据。

快照持久化是 Redis 默认采⽤的持久化⽅式,在 redis.conf 配置⽂件中默认有此下配置:

save 900 1 #在900秒(15分钟)之后,如果⾄少有1个key发⽣变化, Redis就会⾃动触发
bgsave命令创建快照。

save 300 10 #在300秒(5分钟)之后,如果⾄少有10个key发⽣变化, Redis就会⾃动触发
bgsave命令创建快照。

save 60 10000 #在60秒(1分钟)之后,如果⾄少有10000个key发⽣变化, Redis就会⾃动触发
bgsave命令创建快照。

10.2AOF(Append Only File)

将redis所执行过的所有指令都记录下来,在下次redis重启时,只需要执行指令就可以了);写日志。

缺点:体积大,恢复速度慢。

默认情况下 Redis没有开启 AOF(append only file)⽅式的持久化,可以通过 appendonly 参数开启

appendonly yes

在 Redis 的配置⽂件中存在三种不同的 AOF 持久化⽅式,它们分别是:

appendfsync always #每次有数据修改发⽣时都会写⼊AOF⽂件,这样会严重降低Redis的速度
appendfsync everysec #每秒钟同步⼀次,显式地将多个写命令同步到硬盘
appendfsync no #让操作系统决定何时进⾏同步

为了兼顾数据和写⼊性能,⽤户可以考虑 appendfsync everysec 选项 ,让 Redis 每秒同步⼀次 AOF⽂件, Redis 性能⼏乎没受到任何影响。⽽且这样即使出现系统崩溃,⽤户最多只会丢失⼀秒之内产⽣的数据。当硬盘忙于执⾏写⼊操作的时候, Redis 还会优雅的放慢⾃⼰的速度以便适应硬盘的最⼤写⼊速度

11.redis实现分布式锁

11.0为什么需要分布式锁?

随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!

  • 在多线程并发的情况下,可以使用java的synchronized以及Reentrantlock类来保证一个代码块在同一时间只能由一个线程访问。这种方式可以保证在同一个JVM进程内的多个线程同步执行

  • 如果在分布式的集群环境中,就需要使用分布式锁来保证不同节点的线程同步执行

  • 何为分布式锁,就是可以锁住不同JVM进程的锁,可以使用Redis、zookeeper等实现。原理就是,多个JVM都可以或都需要访问这些第三方中间件,这样Redis或zooKeeper就相当于一个管理者,可以对不同的JVM请求进行管理。这样就达到了管理多个JVM中请求的功能了。

11.1加锁

最简单的方法是使用setnx命令。key是锁的唯一标识,按业务来决定命名。比如想要给一种商品的秒杀活动加锁,可以给key命名为 “lock_sale_商品ID” 。而value设置成什么呢?我们可以姑且设置成1。加锁的伪代码如下:

setnx(key,1

当一个线程执行setnx返回1,说明key原本不存在,该线程成功得到了锁;当一个线程执行setnx返回0,说明key已经存在,该线程抢锁失败。

11.2解锁

有加锁就得有解锁。当得到锁的线程执行完任务,需要释放锁,以便其他线程可以进入。释放锁的最简单方式是执行del指令,伪代码如下:

del(key)

释放锁之后,其他线程就可以继续执行setnx命令来获得锁。

11.3锁超时

锁超时是什么意思呢?如果一个得到锁的线程在执行任务的过程中挂掉,来不及显式地释放锁,这块资源将会永远被锁住,别的线程再也别想进来。

所以,setnx的key必须设置一个超时时间,以保证即使没有被显式释放,这把锁也要在一定时间后自动释放。setnx不支持超时参数,所以需要额外的指令,伪代码如下:

expire(key, 30

11.4 业务时间 > 超时时间

业务执行时间>key 的过期时间

假设这样一个场景,业务1拿锁成功并设置过期时间30s,但业务1比较复杂需要花费40s,那么到了30s后业务1的锁就已经失效了,此时业务2抢到了锁也进来执行相应的逻辑,那么此时业务1和业务2都在执行各自的业务逻辑,可能会操作相同的数据资源造成违反资源互斥的现象。问题还没完,又过去了10s,业务1执行完了,理所当然的就去删锁,那么自然就会把业务2手里的锁删掉,业务2一脸懵逼。。。这就造成了锁误删的现象。

先解决锁误删的问题,其实很简单,只需要保证谁拿的锁谁就有资格删就可以了,我们在获取锁的时候设置了一个value,此时就派上了用场,把每个业务的value都设置uuid,最后删锁的时候先去redis获取锁对应的值,如果这个值等于uuid才有资格执行删锁命令。

需要注意的是这里也需要保证原子性,因为去远程redis获取锁对应的值再返回来也是有时间差的,如果业务1去远程获取到锁的value为“1111”,回来的过程中锁过期了,此时业务2拿到了锁开心的去执行它的业务去了,好景不长,业务1回来判断出锁的value是等于自己的uuid,于是又理所当然的把锁删掉了,业务2又一脸懵逼。。。。这里就需要使用lua脚本解锁,lua脚本就是为了保证这两段操作的原子性,解锁脚本内容如下:

if redis.call("get",KEYS[1]) == ARGV[1]
then
    return redis.call("del",KEYS[1])
else
    return 0
end

12说说 Redis 哈希槽的概念?

Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念, Redis 集群 有 16384 个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置 哪个槽, 集群的每个节点负责一部分 hash 槽。

13.Redis 主从架构

单机的 Redis,能够承载的 QPS 大概就在上万到几万不等。对于缓存来说,一般都是用来支撑读高并发的。因此架构做成主从(master-slave)架构,一主多从,主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容,支撑读高并发。

请添加图片描述

14.Redis 主从复制的核心原理

当启动一个 slave node 的时候,它会发送一个 PSYNC 命令给 master node。如果这是 slave node初次连接到 master node,那么会触发一次 full resynchronization 全量复制。此时 master 会启动一个后台线程,开始生成一份 RDB 快照文件,同时还会将从客户端 client 新收到的所有写命令缓存在内存中。 RDB 文件生成完毕后, master 会将这个 RDB发送给 slave,slave 会先写入本地磁盘,然后再从本地磁盘加载到内存中,接着 master 会将内存中缓存的写命令发送到 slave,slave 也会同步这些数据。slave node 如果跟 master node 有网络故障,断开了连接,会自动重连,连接之后 master node 仅会复制给 slave 部分缺少的数据。
请添加图片描述

15.Redis 哨兵集群实现高可用

哨兵的介绍
sentinel,中文名是哨兵。哨兵是 Redis 集群架构中非常重要的一个组件,主要有以下功能:

  • 集群监控:负责监控 Redis master 和 slave 进程是否正常工作。
  • 消息通知:如果某个 Redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
  • 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
  • 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。

哨兵用于实现 Redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同
工作。

  • 故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。
  • 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了

16.缓存更新

除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:
(1)定时去清理过期的缓存;
(2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。
两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡

17.为什么Redis的操作是原子性的,怎么保证原子性的?

对于Redis而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。Redis的操作之所以是原子性的,是因为Redis是单线程的。Redis本身提供的所有API都是原子操作,Redis中的事务其实是要保证批量操作的原子性,MULTI和EXEC。

多个命令在并发中也是原子性的吗?

  • 不一定, 将get和set改成单命令操作,incr 。
  • 使用Redis的事务,或者使用Redis+Lua==的方式实现

18.Redis事务

Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的。Redis会将一个事务中的所有命令序列化,然后按顺序执行。

  • redis 不支持回滚“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。
  • 如果在一个事务中的命令出现错误,那么所有的命令都不会执行;
  • 如果在一个事务中出现运行错误,那么正确的命令会被执行。

18.1MULTI命令

用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。

18.2EXEC

执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被打断时,返回空值 nil 。

18.3DISCARD

通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。

18.4WATCH

WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令

19.redis 的线程模型

redis 内部使⽤⽂件事件处理器 file event handler ,这个⽂件事件处理器是单线程的,所以redis 才叫做单线程的模型。它采⽤ IO 多路复⽤机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进⾏处理。
⽂件事件处理器的结构包含 4 个部分:

  • 多个 socket
  • IO 多路复⽤程序
  • ⽂件事件分派器
  • 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)

多个 socket 可能会并发产⽣不同的操作,每个操作对应不同的⽂件事件,但是 IO 多路复⽤程序会监听多个 socket,会将 socket 产⽣的事件放⼊队列中排队,事件分派器每次从队列中取出⼀个事件,把该事件交给对应的事件处理器进⾏处理。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值