赞
踩
目录
四、redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性)
9、面试官:redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性)
7、面试官:数据库有1000万数据,Redis只能缓存20w数据,如何保证Redis中的数据都是热点数据?
九、Redis分布式锁实现原理(setnx,redisson)
11、面试官:好的,那你如何控制Redis实现分布式锁有效时长呢?
12、面试官:好的,redisson实现的分布式锁是可重入的吗?
13、面试官: redisson实现的分布式锁能解决主从一致性的问题吗?
10、 面试官: Redis是单线程的,但是为什么还那么快?
十七、既然Redis那么快,为什么不用它做主数据库,只用它做缓存?
缓存空数据,查询返回的数据为空,仍把这个空结果进行缓存
bitmap(位图):相当于是一个以 (bit) 位为单位的数组,数组中每个单元只能存储二进制数0或1。
布隆过滤器作用:布隆过滤器可以用于检索一个元素是否在一个集合中。
当访问id存在的时候,会根据布隆过滤器的hash函数获取hash值来计算相应的位置并且修改0为1。当查询的时候就会根据生成的hash函数获取hash值判断对应位置是否为1,如果都为1,则可以进入redis缓存查询,否则不可以。
误判: 为空的数据计算的hash值在数组中都为1,但实际上该数据不存在
误判率:数组越小误判率就越大,数组越大误判率就越小,但是同时带来了更多的内存消耗。一般误判率是5%。
Redis的使用场景
什么是缓存穿透,怎么解决
当线程1查询缓存的时候,未命中缓存,然后获取互斥锁成功。此时线程1去查询数据库,将数据写入缓存,最后释放锁。
在线程1查询缓存的时候,未命中缓存,然后线程2去获取互斥锁,获取失败,因为线程1获取到了互斥锁,此时线程2只能休眠一会再重试。当线程1写入缓存成功后并释放锁的同时,线程2重试命中缓存,命中成功直接获取缓存。
缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
那么先删除缓存还是先修改数据库呢? (2.3)
那么为什么要删除两次缓存呢?(4)
那么为什么要延时删除呢?(5)
此时缓存和数据库的值分别为10,10
如下是正常的情况,数据库和缓存的值修改正常
但是一般而言线程是交叉进行的
此时缓存和数据库的值分别为10,20。造成了数据库和缓存不一致情况
此时缓存和数据库的值分别为10,10
如下是正常的情况,数据库和缓存的值修改正常
但是一般而言线程是交叉进行的
此时缓存和数据库的值分别为10,20。造成了数据库和缓存不一致情况
为了保证缓存和数据库的一致性。因为不管是先修改数据库还是先删缓存都会导致数据库和缓存的值不同。而两次删除缓存会避免这种情况发生。但是一般情况下数据库都是主从分离的,所以可能出现主从数据库数据不一致的情况。
一般情况下数据库都是主从分离的,所以可能出现主从数据库数据不一致的情况。为了避免这种情况,就会延迟一会,等待主节点同步到从节点。所以要延时,但是这个延时时间不好控制,在这个过程中依然可能会出现脏数据。所以延时双删只是控制减少了脏数据的出现,但无法避免脏数据的出现。
使用分布式锁来避免数据的不一致性
但是这种普通的读写加锁性能太差,所以可以使用读写锁来处理这种问题。因为缓存一般都是读多写少,所以可以分别使用读锁和写锁来进行加锁
读锁相关代码,注意读锁和写锁的锁名必须一致
写锁相关代码
但是此方法虽然保持了强一致,但是性能不高。
redis做为缓存,mysql的数据如何与redis进行同步呢? (双写一致性)
那你来介绍一下异步的方案(你来介绍一下redisson读写锁的这种方案)
允许延时一致的业务,采用异步通知
强一致性的,采用Redisson提供的读写锁o
在Redis中提供了两种数据持久化的方式:
RDB全称Redis Database Backup file (Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。
主动备份
被动备份
save 300 10: 表示300秒内,有10个key被修改可以执行bgsave
bgsave的子进程执行的时候不会阻塞主进程,不过在开启子进程的时候会阻塞主进程,但是由于时间是纳秒级所以几乎毫无影响。
bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。fork采用的是copy-on-write技术
在linux系统中进程不能直接操作物理内存,需要页表来进行虚拟内存和物理内存直接的映射。主进程通过页表关联到物理内存真正的地址,这样就能对物理内存进行读和写操作了。
当开启RDB的时候,主进程就会fork(将主进程的页表数据复制过去,因此子进程就有了和主进程相同的映射关系了,这样子进程就可以根据页表进行对物理内存中数据的读取了)一个子进程。然后子进程就可以去物理内存读取数据并且写入到磁盘中去,生成新的RDB文件,并且将新的RDB文件覆盖旧的RDB文件中去。
但是子进程在写RDB文件的过程中,主进程可以接受用户的请求来修改内存中的数据。这就导致了读写冲突,甚至可能产生脏数据。为了避免这种问题的发生,fork底层会采用Copy On Write的技术。在fork的过程中,就会将数据标记为Read—Only(只读)模式,任何一个进程只能来读数据不能来写数据。如果主进程进行写数据,那么就会将物理内存中的数据拷贝一份,然后对这个拷贝的数据进读和写,避免了脏写的情况。
AOF全称为Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。
AOF默认是关闭的,需要修改redis.conf配置文件来开启AOF:
AOF的命令记录的频率也可以通过redis.conf文件来配:
因为 AOF 持久化是通过保存被执行的写命令来记录数据库状态的,所以随着服务器运行时间的流逝,AOF 文件中的内容会越来越多,文件的体积也会越来越大。
所以 Redis 服务器就会选择一个时间点创建一个新的 AOF 文件来替代现有的 AOF 文件,新旧两个 AOF 文件所保存的数据库状态相同,但新 AOF 文件不会包含任何浪费空间的冗余命令,所以新 AOF 文件的体积通常会比旧 AOF 文件的体积要小得多
因为是记录命令,AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果。
优势
劣势
优势:
劣势:
Redis对数据设置数据的有效时间,数据过期以后,就需要将数据从内存中删除掉。可以按照不同的规则进行删除,这种删除规则就被称之为数据的删除策略(数据过期策略)。
数据库有1000万数据,Redis只能缓存20w数据,如何保证Redis中的数据都是热点数据?
Redis的内存用完了会发生什么?
数据淘汰策略
平时开发过程中用的比较多的就是allkeys-lru (结合自己的业务场景) ,因为allkeys-lru保存下来的数据一般都是热点数据。
需要结合项目中的业务进行回答,通常情况下,分布式锁使用的场景:
这是它的流程:
正常情况:
线程1查询优惠价,查看库存数量是否充足,如果充足就减库存,否则抛出异常。在线程1执行完后线程2进行同样的流程。
多线程情况:
假设此时库存剩余1
线程1查询库存看到库存为1,查完后切换到线程2查询到库存也是1。然后再切换到线程1取查看库存是否充足,结果充足,然后库存减1为0。再切换到线程2查看库存为1,结果充足,然后减少库存库存为-1。
解决方法:
添加互斥锁
流程如下:
使用setnx加锁情况:
结果发生8080和8081服务器都能查询到库存为1。这是因为setnx是本地锁只能在当前的JVM进行加锁,但是集群部署是多台服务器,每个服务器都有自己的JVM,就导致了数据异常的情况。
使用redisson分布式锁情况:
线程1获取分布式锁之后,其他服务器包括本服务器的线程不能再获取锁,只有当线程1释放锁之后才可以获取锁。
Redis实现分布式锁主要利用Redis的setnx命令。setnx是SET if not exists(如果不存在则SET)的简写。
死锁问题
根据上图流程,线程在获取锁成功以后,在执行业务的时候突然服务器宕机了,但是此刻依旧没有释放锁,导致锁无法释放就会导致死锁。
代码未执行完但锁时间到期了
如果添加了锁的过期时间,会出现业务代码未执行完就会释放锁。这是因为锁的时间难以预估出现代码未执行完出现锁提前释放,无法保证代码的原子性。
注:releaseTime过期时间
当线程1尝试获取锁并且获取锁成功的时候,就会新开一个线程Watch dog,Watch dog会监听锁的情况,每隔releaseTime/3时间会做一次续期,每次续期就会重置过期时间。比如说过期时间为30秒,那么每30/3=10秒进行一次续期,将过期时间重置为30秒。如果业务执行完成的话,就会手动释放锁。释放锁以后watch dog就不需要再进行监听了,因为key已经被删除了。
如果线程2尝试获取锁,但是线程1占用着锁,就会获取锁失败,但是线程2不会终止,而是在有限的次数不断循环,直到获取锁位置。
首先是获取锁,然后是尝试获取锁,根据尝试获取锁的结果判断是否获取锁成功,如果获取锁成功那么就可以执行业务代码。
如下图代码:
在add1()方法中,线程1创建了一把锁名为heimalock。然后在加锁的代码中执行add2()方法。但是add2()方法中也添加了一把锁,也名为heimalock。在redis中根据hash结构来记录线程id和重入次数。如下图为当前的线程id和可重入情况:
key为heimalock,field为当前线程id,value为重入锁的次数。因为当前线程执行了add1()和add()2方法所以拥有两把锁。
当add2()方法释放锁时候,重入次数就会减1,如下图
执行add1()方法中的lock.unlock()方法就会释放锁,如下图
因此可重入锁优点是可以避免死锁,提高了程序的可靠性和效率
一般情况下都会使用redis的主从集群架构,主节点写数据,从节点读数据
此时线程1获取到了锁,对主节点进行写数据,但是突然主节点宕机了导致修改的数据还没有同步到从节点。那么就会开启哨兵模式,将从节点设置为主节点。但会出现一种情况,线程1因为宕机没有释放锁,并且信息没有同步到从节点。所以新节点就会从新设置的主节点获取同一把锁,因此线程2就获取到了和线程1相同的锁。
这时候会出现两个线程获取同一把锁,如果业务还在执行就可能出现脏数据的情况。因为宕机的节点修改后的数据没有同步到先建立的主节点,导致现场2获取的数据不是提交过的数据。
为了解决这种主从一致性的情况,redis提供了另外一种锁(不推荐):
redis分布式锁,是如何实现的?
Redisson实现分布式锁如何合理的控制锁的有效时长?
Redisson的这个锁,可以重入吗?
Redisson锁能解决主从数据一致的问题吗
单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。
注:repl_baklog用来记录除第一次同步以后的数据
流程:
流程:
介绍一下redis的主从同步?
能说一下,主从同步数据的流程?
Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。哨兵的结构和作用如下:
Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令
哨兵选主规则
这是正常情况的redis主从架构
假如此时因为网络的原因 ,将主节点和从节点分开来了。并且主节点是一个分区,其他从节点是另外一个分区。这个时候从节点分区就会从从节点选出一个主节点,然后就会出现两个master主节点,就好像脑裂了一样。
不过老的master没有挂,只是网络出现的问题,客户端还可以去对老的主节点进行写数据。这就是脑裂的现象。
但是老的主节点写入的数据不能同步到新的主节点。
此时网络恢复了,哨兵会将老的master强制降为salve节点。这个savle就会送master中同步数据,就会把自己的数据清空然后同步master的数据。
然后客户端连接新的master,变成正常情况。不过丢失的数据依旧丢失了
解决方法:
怎么保证Redis的高并发高可用
你们使用redis是单点还是集群,哪种集群
redis集群脑裂,该怎么解决呢?
主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:
使用分片集群可以解决上述问题,分片集群特征:
Redis 分片集群引入了哈希槽的概念,Redis 集群有 16384 个哈希槽,每个key通过CRC16 校验后对16384 取模来决定放置哪个槽,集群的每个节点负责一部分hash 槽。
对于每个master,会根据master的数量对哈希槽进行均分。然后对于每个写或者读的key经过CRC16的计算其hash值并取模16384就可以获取所在的集群位置。
redis的分片集群有什么作用
Redis分片集群中数据是怎么存储和读取的?
Redis是纯内存操作,执行速度非常快
采用单线程,避免不必要的上下文切换可竞争条件,多线程还要考虑线程安全问题
使用I/O多路复用模型,非阻塞IO
Linux系统中一个进程使用的内存情况划分两部分: 内核空间、用户空间
Linux系统为了提高lO效率,会在用户空间和内核空间都加入缓冲区
但是会出现两个问题
顾名思义,阻塞IO就是两个阶段都必须阻塞等待:
用户想从内核获取数据:
可以看到,阻塞IO模型中,用户进程在两个阶段都是阻塞状态
顾名思义,非阻塞IO的recvfrom操作会立即返回结果而不是阻塞用户进程。
流程:
可以看到,非阻塞IO模型中,用户进程在第一个阶段是非阻塞,第二个阶段是阻塞状态。虽然是非阻塞,但性能并没有得到提高。而且忙等机制会导致CPU空转,CPU使用率暴增。
过程:
I/0多路复用是利用单个线程来同时监听多个Socket,并在某个Socket可读、可写时得到通知,从而避免无效的等待充分利用CPU资源。不过监听Socket的方式、通知的方式又有多种实现,常见的有:
差异:
Redis通过I/O多路复用来提高网络性能,并且支持各种不同的多路复用实现,并且将这些实现进行封装,提供了统一的高性能事件库。
Redis6.0以后添加的多线程,其中分别为 命令回复处理器和Redis命令转换这里添加了多线程。
流程:
redis通过I/O多路复用来进行对socket的监听,然后将socket分发到对应的处理器,这个过程称为I/O多路复用+事件派发。
当监听到可用的socket的时候就会将通知用户线程,然后用户线程将内核空间的数据拷贝到用户空间。拷贝后用户线程接受其数据,将数据转化为redis命令。选择执行其命令把结果写入缓冲区,然后将缓冲区的信息发送给命令回复处理器。命令回去处理器将结果返回给客户端。
能解释一下I/O多路复用模型?
I/O多路复用
Redis网络模型
Redis(Remote Dictionary Server
)是一个使用 C 语言编写的,高性能非关系型的键值对数据库。与传统数据库不同的是,Redis 的数据是存在内存中的,所以读写速度非常快,被广泛应用于缓存方向。Redis可以将数据写入磁盘中,保证了数据的安全不丢失,而且Redis的操作是原子性的。
虽然Redis非常快,但它还有一些限制,不能完全替代主数据库。所以,使用Redis作为缓存是一种很好的方式,可以提高应用程序的性能,并减少数据库的负载
score
的参数来实现。适用于排行榜和带权重的消息队列等场景。如果达到设置的上限,Redis的写命令会返回错误信息(但是读命令还可以正常返回)。
也可以配置内存淘汰机制,当Redis达到内存上限时会冲刷掉旧的内容。
redis的单线程的。keys指令会导致线程阻塞一段时间,直到执行完毕,服务才能恢复。scan采用渐进式遍历的方式来解决keys命令可能带来的阻塞问题,每次scan命令的时间复杂度是O(1)
,但是要真正实现keys的功能,需要执行多次scan。
scan的缺点:在scan的过程中如果有键的变化(增加、删除、修改),遍历过程可能会有以下问题:新增的键可能没有遍历到,遍历出了重复的键等情况,也就是说scan并不能保证完整的遍历出来所有的键。
当一个key在Redis中已经存在了,但是由于一些误操作使得key过期时间发生了改变,从而导致这个key在应该过期的时间内并没有过期,从而造成内存的占用。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。