在数据库层面,事务是指一组操作,这些操作要么全都被成功执行,要么全都不执行。
数据库事务的四大特性:
- A:Atomic, 原子性。要么全部执行,要么全部不执行;
- C:Consistent, 一致性。事务完成后,所有数据的状态都是一致的。即A账户只要减去了100,B账户则必定加上了100;
- I:Isolation, 隔离性。如果有多个事务并发执行,每个事务作出的修改必须与其他事务隔离;
- D:Duration,持久性。即事务完成后,对数据库数据的修改被持久化存储。
Redis事务是一组命令的集合,一个事务中的所有命令都将被序列化,按照一次性、顺序性、排他性的执行一系列的命令。
Redis事务三大特性:
单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断;
没有隔离级别的概念:队列中的命令没有提交(exec)之前都不会实际的被执行,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”。
不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚;
Redis事务执行的三个阶段
开启:?MULTI 开始一个事务;入队:将多个命令入队到事务中。并不会执行。执行:由 EXEC 命令执行入队的事务;
Multi、Exec、discard
- 事务从输入Multi命令开始,输入的命令都会依次压入命令缓冲队列中,并不会执行,
- 输入Exec后,Redis会将之前的命令缓冲队列中的命令依次执行。
- 组队过程中,可以通过discard来放弃组队。
示例1:正常执行 ?
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 1
QUEUED
127.0.0.1:6379(TX)> get k1?
QUEUED
127.0.0.1:6379(TX)> incr k1
QUEUED
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) "1"
3) (integer) 2
4) "2"
示例2:放弃事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k2 1
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> incr k2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> discard
OK
示例3:全体连坐
命令集合中含有错误的指令(注意是语法错误),均连坐,全部失败。
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k3 1
QUEUED
127.0.0.1:6379(TX)> get k3
QUEUED
127.0.0.1:6379(TX)> set k3
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors
示例4:冤有头,债有主
运行时错误,即非语法错误,正确命令都会执行,错误命令返回错误。
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k4 1
QUEUED
127.0.0.1:6379(TX)> incr k4
QUEUED
127.0.0.1:6379(TX)> get k4
QUEUED
127.0.0.1:6379(TX)> set name zhangsan
QUEUED
127.0.0.1:6379(TX)> incr name
QUEUED
127.0.0.1:6379(TX)> get name
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) (integer) 2
3) "2"
4) OK
5) (error) ERR value is not an integer or out of range
6) "zhangsan"
单一节点的redis容易面临风险。
机器故障。
容量瓶颈。
解决办法?:将原来集中式数据库的数据分别存储到其他多个网络节点上(主从复制)。
什么是主从复制
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点。
主从复制的作用
数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务 ,分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
高可用基石:主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
【1】编写配置文件
新建redis6379.conf?
include /usr/local/redis/redis.conf
pidfile /var/run/redis_6379.pid
port 6379
dbfilename dump6379.rdb
新建redis6380.conf
include /usr/local/redis/redis.conf
pidfile /var/run/redis_6380.pid
port 6380
dbfilename dump6380.rdb
新建redis6381.conf
include /usr/local/redis/redis.conf
pidfile /var/run/redis_6381.pid
port 6381
dbfilename dump6381.rdb
【2】启动三台redis服务器
redis-server ./redis6379.conf
redis-server ./redis6380.conf
redis-server ./redis6381.conf
【3】查看系统进程
[root@localhost redis-conf1]# ps -ef |grep redis
root 122739 1 0 00:18 ? 00:00:00 redis-server 0.0.0.0:6379
root 122877 1 0 00:18 ? 00:00:00 redis-server 0.0.0.0:6380
root 122965 1 0 00:19 ? 00:00:00 redis-server 0.0.0.0:6381
root 123210 3414 0 00:19 pts/0 00:00:00 grep --color=auto redis
【4】查看三台主机运行情况
#打印主从复制的相关信息
redis-cli -p 6379
redis-cli -p 6380
redis-cli -p 6381
127.0.0.1:6379> info replication
127.0.0.1:6380> info replication
127.0.0.1:6381> info replication
【5】配从库不配主库
#slaveof <ip> <port>
127.0.0.1:6380> SLAVEOF 192.168.184.100 6379
OK
【6】在主机上写,在从机上可以读取数据
set k1 v1
1、主从复制可以分为3个阶段
连接建立阶段(即准备阶段)
数据同步阶段
命令传播阶段
2、复制过程大致分为6个过程
1、保存主节点(master)信息。
2、从节点与主节点建立网络连接
从节点(slave)内部通过每秒运行的定时任务维护复制相关逻辑,当定时任务发现存在新的主节点后,会尝试与该节点建立网络连接。从节点建立了一个端口为51234的socket套接字,专门用于接受主节点发送的复制命令。
3、发送ping命令
连接建立成功后从节点发送 ping 请求进行首次通信。成功继续复制流程,失败断开重连。
作用:
- 检测主从之间网络套接字是否可用。
检测主节点当前是否可以接受命令 。
4、权限验证
如果主节点设置了 requirepass 参数,则需要密码验证,从节点必须配置 masterauth 参数保证与主节点相同的密码才能通过验证;如果验证失败复制将终止,从节点重新发起复制流程。
5、同步数据集
主从复制连接正常通信后,对于首次建立复制的场景,主节点会把持有的数据全部发送给从节点,这部分操作是耗时最长的步骤。
6、主从同步策略
主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,slave 在任何时候都可以发起全量同步。
redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。
哨兵概述
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
哨兵作用
- 集群监控,负责监控redis master和slave进程是否正常工作。
- 消息通知,如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
- 故障转移,如果master node挂掉了,master身份会自动转移到slave node上。
- 配置中心,如果故障转移发生了,通知client客户端新的master地址。
【1】编写配置文件
新建sentinel-26379.conf文件
#端口
port 26379
#守护进程运行
daemonize yes
#日志文件
logfile "26379.log"
sentinel monitor mymaster 192.168.8.130 6379 2
参数:
sentinel monitor mymaster 192.168.92.128 6379 2 配置的含义是:该哨兵节点监控192.168.92.128:6379这个主节点,该主节点的名称是mymaster,最后的2的含义与主节点的故障判定有关:至少需要2个哨兵节点同意,才能判定主节点故障并进行故障转移。
新建sentinel-26380.conf文件
#端口
port 26380
#守护进程运行
daemonize yes
#日志文件
logfile "26380.log"
sentinel monitor mymaster 192.168.8.130 6379 2
新建sentinel-26381.conf文件
#端口
port 26381
#守护进程运行
daemonize yes
#日志文件
logfile "26381.log"
sentinel monitor mymaster 192.168.8.130 6379 2
【2】启动哨兵节点
redis-sentinel sentinel-26379.conf
redis-sentinel sentinel-26379.conf
redis-sentinel sentinel-26379.conf
【3】查看哨兵状态
[root@localhost src]redis-cli -p 26379
127.0.0.1:26379>
127.0.0.1:26379>
127.0.0.1:26379> info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.8.130:6379,slaves=2,sentinels=3
【4】测试故障转移,使用kill命令杀掉主节点
[root@localhost redis-conf] ps -ef | grep redis
root 29220 1 0 16:05 ? 00:00:04 redis-server 0.0.0.0:6379
root 29603 1 0 16:05 ? 00:00:03 redis-server 0.0.0.0:6380
root 29871 1 0 16:05 ? 00:00:03 redis-server 0.0.0.0:6381
root 32414 4319 0 16:06 pts/2 00:00:00 redis-cli -p 6380
root 33095 4510 0 16:06 pts/4 00:00:00 redis-cli -p 6381
root 38347 1 0 16:41 ? 00:00:00 redis-sentinel *:26379 [sentinel]
root 38745 1 0 16:41 ? 00:00:00 redis-sentinel *:26380 [sentinel]
root 38948 1 0 16:42 ? 00:00:00 redis-sentinel *:26381 [sentinel]
root 45471 128898 0 16:43 pts/0 00:00:00 grep --color=auto redis
[root@localhost redis-conf]# kill -9 29220
【5】查看哨兵节点信息
127.0.0.1:26379> info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6380,slaves=2,sentinels=3
【6】在新主节点写数据,其他节点读数据
127.0.0.1:6380> set k6 v6
OK
127.0.0.1:6381> get k6
"v6"
分为四个阶段:监控阶段、通知阶段、故障转移阶段、投票阶段。
1、监控阶段
注意:
sentinel(哨兵1)----->向master(主)和slave(从)发起info,拿到全信息。
sentinel(哨兵2)----->向master(主)发起info,知道已经存在的sentinel(哨兵1)的信息,并且连接slave(从)。
sentinel(哨兵2)----->向sentinel(哨兵1)发起subscribe(订阅)。
2、通知阶段
sentinel不断的向master和slave发起通知,收集信息。
3、故障转移阶段
通知阶段sentinel发送的通知没得到master的回应,就会把master标记为SRI_S_DOWN,并且把master的状态发给各个sentinel,其他sentinel听到master挂了,说我不信,我也去看看,并把结果共享给各个sentinel,当有一半的sentinel都认为master挂了的时候,就会把master标记为SRI_0_DOWN。
4、投票阶段
方式: 自己最先接到哪个sentinel的竞选通知就会把票投给它。
剔除一些情况:
不在线的
响应慢的
与原来master断开时间久的
优先级原则
Redis有三种集群模式
主从模式
Sentinel模式
Cluster模式
哨兵模式的缺点
- 进行选举的时候是没有办法去访问Redis的,会存在访问瞬断的情况;
- 哨兵模式,对外只有master节点可以写,slave节点只能用于读。尽管Redis单节点最多支持10W的QPS,但是在电商大促的时候,写数据的压力全部在master上。
- Redis的单节点内存不能设置过大,若数据过大在主从同步将会很慢;在节点启动的时候,时间特别长;
Redis集群是一个由多个主从节点群组成的分布式服务集群,它具有复制、高可用和分片特性。
Redis集群的优点
Redis集群有多个master,可以减小访问瞬断问题的影响。
Redis集群有多个master,可以提供更高的并发量。
Redis集群可以分片存储,这样就可以存储更多的数据。
Redis的集群搭建最少需要3个master节点。
【1】 准备环境
第1台机器: 192.168.8.130 8001端口 8002端口
第2台机器: 192.168.8.131 8001端口 8002端口
第3台机器: 192.168.8.132 8001端口 8002端口
【2】配置集群环境
创建文件夹
mkdir -p /usr/local/redis-cluster/8001 /usr/local/redis-cluster/8002
拷贝配置文件
将redis安装目录下的 redis.conf 文件分别拷贝到8001和8002目录下
cp /usr/local/redis-6.2.6/redis.conf /usr/local/redis-cluster/8001
cp /usr/local/redis-6.2.6/redis.conf /usr/local/redis-cluster/8002
修改redis.conf文件以下内容 8001
# 端口
port 8001
# 后台启动
daemonize yes
# 进程id
pidfile "/var/run/redis_8001.pid"
#指定数据文件存放位置,必须要指定不同的目录位置,不然会丢失数据
dir /usr/local/redis-cluster/8001/
#启动集群模式
cluster-enabled yes
#集群节点信息文件,这里800x最好和port对应上
cluster-config-file nodes-8001.conf
# 节点离线的超时时间
cluster-node-timeout 5000
#去掉bind绑定访问ip信息
#bind 127.0.0.1
#关闭保护模式
protected-mode no
#启动AOF文件
appendonly yes
#如果要设置密码需要增加如下配置:
#设置redis访问密码
#requirepass 123456
#设置集群节点间访问密码,跟上面一致
#masterauth 123456
修改redis.conf文件以下内容 8002
# 端口
port 8002
# 后台启动
daemonize yes
# 进程id
pidfile "/var/run/redis_8002.pid"
#指定数据文件存放位置,必须要指定不同的目录位置,不然会丢失数据
dir /usr/local/redis-cluster/8002/
#启动集群模式
cluster-enabled yes
#集群节点信息文件,这里800x最好和port对应上
cluster-config-file nodes-8002.conf
# 节点离线的超时时间
cluster-node-timeout 5000
#去掉bind绑定访问ip信息
#bind 127.0.0.1
#关闭保护模式
protected-mode no
#启动AOF文件
appendonly yes
#如果要设置密码需要增加如下配置:
#设置redis访问密码
#requirepass 123456
#设置集群节点间访问密码,跟上面一致
#masterauth 123456
将本台机器文件拷贝到另外两台机器上
【3】启动集群
启动服务器
redis-server /usr/local/redis-cluster/8001/redis.conf
redis-server /usr/local/redis-cluster/8002/redis.conf
创建集群
redis-cli -a redis-pw --cluster create --cluster-replicas 1 \
192.168.184.101:8001 192.168.184.101:8002 \
192.168.184.102:8001 192.168.184.102:8002 \
192.168.184.103:8001 192.168.184.103:8002 \
-a:密码
--cluster-replicas 1:表示1个master下挂1个slave; --cluster-replicas 2:表示1个master下挂2个slave。
查看帮助命令
src/redis‐cli --cluster help
create:创建一个集群环境host1:port1 ... hostN:portN
call:可以执行redis命令
add-node:将一个节点添加到集群里,第一个参数为新节点的ip:port,第二个参数为集群中任意一个已经存在的节点的ip:port
del-node:移除一个节点
reshard:重新分片
check:检查集群状态
【4】测试集群
连接任意一个客户端
redis-cli -c -h 192.168.8.130 -p 8001
查看集群的信息
cluster info
Redis Cluster将所有数据划分为16384个slots(槽位),每个节点负责其中一部分槽位。槽位的信息存储于每个节点中。只有master节点会被分配槽位,slave节点不会分配槽位。
槽位定位算法:
Cluster 默认会对 key 值使用 crc16 算法进行 hash 得到一个整数值,然后用这个整数值对 16384 进行取模来得到具体槽位。
HASH_SLOT = CRC16(key) % 16384
命令执行:
set k1 v1
注意:
根据k1计算出的槽值进行切换节点,并存入数据。不在一个slot下的键值,是不能使用mget、mset等多建操作。
可以通过{}来定义组的概念,从而是key中{}内相同内容的键值对放到同一个slot中。
mset k1{test} v1 k2{test} v2 k3{test} v3
故障恢复
查看节点
cluster nodes
杀死Master节点
lsof -i:8001
kill -9 进程号
观察节点信息
【1】添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
【2】编写配置
spring:
redis:
cluster:
nodes:
192.168.8.130:8001 192.168.8.130:8002 192.168.8.131:8001 192.168.8.131:8002 192.168.8.132:8001 192.168.8.132:8002
【3】编写测试
@SpringBootTest
class RedisDemo2ApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
redisTemplate.opsForValue().set("k6","v6");
System.out.println(redisTemplate.opsForValue().get("k6"));
}
}
因为网络问题,导致Redis Master节点跟Redis slave节点和Sentinel集群处于不同的网络分区,此时因为sentinel集群无法感知到master的存在,所以将slave节点提升为master节点。
注意:
此时存在两个不同的master节点,就像一个大脑分裂成了两个。集群脑裂问题中,如果客户端还在基于原来的master节点 继续写入数据,那么新的Master节点将无法同步这些数据,当网络问题解决之后,sentinel集群将原先的Master节点降为slave节点,此时再从新的master中同步数据,将会造成大量的数据丢失。
redis.conf配置参数:
min-replicas-to-write 1
min-replicas-max-lag 5
第一个参数表示最少的slave节点为1个
第二个参数表示数据复制和同步的延迟不能超过5秒
缓存中没有数据,由于缓存冷启动一点数据都没有,如果直接就对外提供服务了,那么并发量上来Mysql就裸奔挂掉了。
新启动的系统没有任何缓存数据,在缓存重建数据的过程中,系统性能和数据库负载都不太好,所以最好是在系统上线之前就把要缓存的热点数据加载到缓存中,这种缓存预加载手段就是缓存预热。
提前给redis中灌入部分数据,再提供服务。但如果数据量非常大,就不可能将所有数据都写入redis,因为数据量太大了,第一是因为耗费的时间太长了,第二根本redis容纳不下所有的数据 。
根据当天的具体访问情况,实时统计出访问频率较高的热数据。然后将热数据写入redis中,如果热数据也比较多,我们得多个服务并行读取数据去写,并行的进行分布式的缓存预热 。
用户不断发起请求获取缓存和数据库中都没有的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。
对空值缓存:如果一个查询返回的数据为空(不管数据是否存在),我们仍然把这个空结果缓存,设置空结果的过期时间会很短,最长不超过5分钟。
布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。
?布隆说不存在一定不存在,布隆说存在你要小心了,它有可能不存在
代码实现
<dependency>
? <groupId>cn.hutool</groupId>
? <artifactId>hutool-all</artifactId>
? <version>5.7.17</version>
</dependency>
// 初始化 注意 构造方法的参数大小10 决定了布隆过滤器BitMap的大小
BitMapBloomFilter filter ?= new BitMapBloomFilter(10);
filter.add("123");
filter.add("abc");
filter.add("ddd");
boolean abc = filter.contains("abc");
System.out.println(abc);
某一个热点 key,在缓存过期的一瞬间,同时有大量的请求打进来,由于此时缓存过期了,所以请求最终都会走到数据库,造成瞬时数据库请求量大、压力骤增,甚至可能打垮数据库。
互斥锁:在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,其他线程直接查询缓存。
热点数据不过期:直接将缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存(不咋靠谱)
public String get(String key) throws InterruptedException {
? ? ? String value = jedis.get(key);
? ? ? // 缓存过期
? ? ? if (value == null){
? ? ? ? ? // 设置3分钟超时,防止删除操作失败的时候 下一次缓存不能load db
? ? ? ? ? Long setnx = jedis.setnx(key +"mutex", "1"); ? ? ? ? ?
jedis.pexpire(key + "mutex", 3 *60);
? ? ? ? ? // 代表设置成功
? ? ? ? ? if (setnx == 1){
? ? ? ? ? ? ? // 数据库查询
? ? ? ? ? ? ? //value = db.get(key);
? ? ? ? ? ? ? //保存缓存
? ? ? ? ? ? ? jedis.setex(key,3*60,"");
? ? ? ? ? ? ? jedis.del(key + "mutex");
? ? ? ? ? ? ? return value;
? ? ? ? ? }else {
? ? ? ? ? ? ? // 这个时候代表同时操作的其他线程已经load db并设置缓存了。 需要重新重新获取缓存
? ? ? ? ? ? ? Thread.sleep(50);
? ? ? ? ? ? ? // 重试
? ? ? ? ? ? ? return get(key);
? ? ? ? ? }
? ? ? }else {
? ? ? ? ? return value;
? ? ? }
}
缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
用二级缓存解决:
先查询本地nginx缓存查询有没有数据,有数据,直接返回;nginx缓存没有数据再去redis分布式缓存查询,如果有将redis缓存同步nginx缓存再返回;如果redis没有,取数据库查询,数据库存在将数据同步到redis并返回。只要nginx的过期时间和redis的过期时间不一样就解决了缓存雪崩问题,并合理使用了本地缓存和分布式缓存。
把表名转换为key前缀,如 tag:
把第二段放置用于区分key的字段,对应msyql中主键的列名,如 user_id
第三段放置主键值,如 2,3,4
第四段写存储的列名
# ? 表名 主键 主键值 存储列名字
set user:user_id:1:name 张三
set user:user_id:1:age 20
拒绝bigkey
防止网卡流量、慢查询,string类型控制在10KB以内,hash、 list、set、zset元素个数不要超过5000。
禁用命令 :禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。
合理使用select :redis的多数据库较弱,使用数字进行区分,很多客户端支持较差, 同时多业务用多数据库实际还是单线程处理,会有干扰。
使用批量操作提高效率(但要注意控制一次批量操作的元素个数 )
原生命令:例如mget、mset。
非原生命令:可以使用pipeline提高效率
不建议过多使用Redis事务功能。Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上。
从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。
三种更新策略
先更新数据库,再更新缓存
先删除缓存,再更新数据库
先更新数据库,再删除缓存
先更新数据库,再更新缓存
这套方案,大家是普遍反对的。为什么呢?
线程安全角度 ,同时有请求A和请求B进行更新操作,那么会出现
(1)线程A更新了数据库
(2)线程B更新了数据库
(3)线程B更新了缓存
(4)线程A更新了缓存
这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑。
先删缓存,再更新数据库
该方案会导致不一致的原因是。同时有一个请求A进行更新操作,另一个请求B进行查询操作。那么会出现如下情形:
(1)请求A进行写操作,删除缓存
(2)请求B查询发现缓存不存在
(3)请求B去数据库查询得到旧值
(4)请求B将旧值写入缓存
(5)请求A将新值写入数据库
注意: 该数据永远都是脏数据。
先更新数据库,再延时删缓存
这种情况存在并发问题吗?
(1)缓存刚好失效
(2)请求A查询数据库,得一个旧值
(3)请求B将新值写入数据库
(4)请求B删除缓存
(5)请求A将查到的旧值写入缓存
发生这种情况的概率又有多少?
发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。可是,大家想想,数据库的读操作的速度远快于写操作的,因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。
【1】简述redis事务?
- redsi的事务和关系型数据库事务解决的问题不一样,原则也不一样,没有acid原则。
- redis事务保证命令按照队列顺序执行问题,所以redis事务分为两个阶段,第一个阶段是命令入队,第二个阶段是命令执行。
- 如果队列中命令都证正确,那么按照入队顺序执行,如果队列中命令语法错误,队列所有命名都不执行,如果队列命令语法没错,但是执行失败,只有该命令本身不会执行,其他命令都会执行。
redis事务命令 ?
multi 开启事务
exec 执行事务
discard 取消事务
【2】简述一下Redis的主从复制模式
Redis集群模式分三种
主从模式
哨兵模式
集群模式
主从模式从一定程度上解决单机Redis的并发问题和可用性问题。但是主从模式问题很大,主节点挂了,需要手动将从节点切换为主节点,所以实际开发很少用到。然后Redis主从复制是哨兵机制和集群机制的根本,所以理解主从模式很重要。
主从模式原理如下: ?
从节点保存主节点(master)信息。
从节点(slave)内部通过每秒运行的定时任务维护复制相关逻辑,当定时任务发现存在新的主节点后,会尝试与该节点建立网络连接。
从节点与主节点建立网络连接 。
发送ping命令 :连接建立成功后从节点发送 ping 请求进行首次通信。
权限验证:如果主节点设置了 requirepass 参数,则需要密码验证,从节点必须配置 masterauth 参数保证与主节点相同的密码才能通过验证;如果验证失败复制将终止,从节点重新发起复制流程。
主从复制连接正常通信后,对于首次建立复制的场景,主节点会把持有的数据全部发送给从节点,这部分操作是耗时最长的步骤。
主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。 redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。
【3】简述一下哨兵模式
哨兵模式主要解决主从复制master节点挂掉之后的,切换从节点为主节点问题,哨兵模式通过监控主从复制主节点和从节点完成自动切换主节点功能。
原理如下:
四个阶段:监控阶段、通知阶段、故障转移阶段、投票阶段。
【4】简述以下Redis集群模式
集群模式至少需要6个redis服务器,集群模式就是n个主从复制模式,但是需要根据插槽机制分配每个节点存取数据范围。来解决单个主从redis不能存取数据过大问题,因为会降低从节点复制速度。
原理如下:
Redis Cluster将所有数据划分为16384个slots(槽位),每个节点负责其中一部分槽位。槽位的信息存储于每个节点中。只有master节点会被分配槽位,slave节点不会分配槽位。
?【5】Redis脑裂问题解决?
Redis主从模式下,由于主节点挂了,从节点新选出主节点,由于网络情况,挂掉主节点没有降级为从节点,导致有两个主节点可以写数据。导致挂掉的主节点的数据无法同步而丢失问题。
解决办法:
min-replicas-to-write 1
min-replicas-max-lag 5
【6】如何解决缓存冷启动问题?
- 冷启动是指服务器缓存中没有数据直接启动,访问量有比较大,mysql直接裸机运行。
- 通过缓存预热解决,通过storm实时计算出热点数据,然后定时将热点数据写入缓存。
?【7】如何解决缓存穿透问题?
- 查询一个redis和mysql肯定都不存在的数据是缓存穿透。
- 去数据库查不存在在redis存null,并且设置过期时间5分钟。
- 或者用布隆过器解决(布隆过滤器是一个bitMap数组,它说不存在的元素一定不存在,他说存在的未必存在。)
?【8】如何解决缓存击穿问题?
- 某一个热点 key,在缓存过期的一瞬间,同时有大量的请求打进来,由于此时缓存过期了,所以请求最终都会走到数据库,造成瞬时数据库请求量大、压力骤增,甚至可能打垮数据库。
- 从造成问题的原因去解决, 在热点Key失效的一刹那保证只有一个请求过来就可以了。这种需求肯定用锁解决,理论上可以用同步锁解决,但这个不靠谱,锁只能锁一个进程,但是微服务是多个服务是多个进程,根本不起作用。所以用分布式锁解决。 ?
【9】如何解决缓存雪崩问题? ?
- 大量缓存同时失效,数据库被击垮。
- 解决思路不让缓存同时失效,比如加给缓存过期时间加随机数,但是当缓存数据足够大时,这个效果不那么明显了,可以通过二级缓存技术实现。