目录
2、springboot整合redis(普通+集群版)的过程及常用方法
一、主从复制
一主二从
主机:master 读写
从机:slave 只读
redis.conf配置文件
requirepass 123456
masterauth 123456
docker run -id -p 6379:6379 --name redis7.2 -v /opt/redis/redis.conf:/usr/local/bin/redis.conf -v /usr/local/docker/data:/data redis:7.0.2 redis-server /usr/local/bin/redis.conf
docker run -id -p 6380:6379 --name redis7.22 -v /opt/redis/redis2.conf:/usr/local/bin/redis.conf -v /usr/local/docker/data2:/data redis:7.0.2 redis-server /usr/local/bin/redis.conf
docker run -id -p 6381:6379 --name redis7.23 -v /opt/redis/redis3.conf:/usr/local/bin/redis.conf -v /usr/local/docker/data3:/data redis:7.0.2 redis-server /usr/local/bin/redis.conf
默认情况下,直接启动,三台服务,三台服务都是master主机。
实现主从复制、读写分离
选6379为master,
在6380 和 6381上,分别执行slaveof ip 6379 ,让当前主机为slave从机,让6379为master主机。
测试:
1、防火墙开放端口
firewall-cmd --zone=public --add-port=6379-6381/tcp --permanent
2、云服务器网络安全组放行(腾讯防火墙放行)
6379 6380 5381
3、重启docker容器
systemctl restart docker
4、依次启动三个容器,在从机上守护主机
docker start redis7.2
docker start redis7.22
docker start redis7.23
客户端连接:
redis-cli -h ip -p 6380 -a 123456
slaveof ip 6379
info replication
主从切换(反客为主)
主机故障,从机变主机的过程。
手动切换
在从机上执行
redis-cli -h ip -p 6380 -a 123456
slaveof no one
info replication
哨兵模式
哨兵
哨兵的作用是监控 redis系统的运行状况,他的功能如下:
监控主从数据库是否正常运行
master出现故障时,自动将slave转化为master
多哨兵配置的时候,哨兵之间也会自动监控
多个哨兵可以监控同一个redis
哨兵工作机制
./redis-sentinel sentinel.conf
哨兵进程启动时会读取配置文件的内容,通过sentinel monitor master-name ip port quorum查找到master的ip端口。一个哨兵可以监控多个master数据库,只需要提供多个该配置项即可。
配置文件还定义了与监控相关的参数,比如master多长时间无响应即即判定位为下线。
哨兵启动后,会与要监控的master建立俩条连接:
一条连接用来订阅master的sentinel:hello频道与获取其他监控该master的哨兵节点信息
另一条连接定期向master发送INFO等命令获取master本身的信息
与master建立连接后,哨兵会执行三个操作,这三个操作的发送频率都可以在配置文件中配置:
定期向master和slave发送INFO命令
定期向master和slave的sentinel:hello频道发送自己的信息
定期向master、slave和其他哨兵发送PING命令
这三个操作的意义非常重大,发送INFO命令可以获取当前数据库的相关信息从而实现新节点的自动发现。所以说哨兵只需要配置master数据库信息就可以自动发现其slave信息。获取到slave信息后,哨兵也会与slave建立俩条连接执行监控。通过INFO命令,哨兵可以获取主从数据库的最新信息,并进行相应的操作,比如角色变更等。
接下来哨兵向主从数据库的sentinel:hello频道发送信息与同样监控这些数据库的哨兵共享自己的信息,发送内容为哨兵的ip端口、运行id、配置版本、master名字、master的ip端口还有master的配置版本。这些信息有以下用处:
其他哨兵可以通过该信息判断发送者是否是新发现的哨兵,如果是的话会创建一个到该哨兵的连接用于发送PING命令。
其他哨兵通过该信息可以判断master的版本,如果该版本高于直接记录的版本,将会更新
当实现了自动发现slave和其他哨兵节点后,哨兵就可以通过定期发送PING命令定时监控这些数据库和节点有没有停止服务。发送频率可以配置,但是最长间隔时间为1s,可以通过sentinel down-after-milliseconds mymaster 600设置。
如果被ping的数据库或者节点超时未回复,哨兵认为其主观下线。如果下线的是master,哨兵会向其他哨兵点发送命令询问他们是否也认为该master主观下线,如果达到一定数目(即配置文件中的quorum)投票,哨兵会认为该master已经客观下线,并选举领头的哨兵节点对主从系统发起故障恢复。
如上文所说,哨兵认为master客观下线后,故障恢复的操作需要由选举的领头哨兵执行,选举采用Raft算法:
发现master下线的哨兵节点(我们称他为A)向每个哨兵发送命令,要求对方选自己为领头哨兵
如果目标哨兵节点没有选过其他人,则会同意选举A为领头哨兵
如果有超过一半的哨兵同意选举A为领头,则A当选
如果有多个哨兵节点同时参选领头,此时有可能存在一轮投票无竞选者胜出,此时每个参选的节点等待一个随机时间后再次发起参选请求,进行下一轮投票精选,直至选举出领头哨兵
选出领头哨兵后,领头者开始对进行故障恢复,从出现故障的master的从数据库中挑选一个来当选新的master,选择规则如下:
所有在线的slave中选择优先级最高的,优先级可以通过slave-priority配置
如果有多个最高优先级的slave,则选取复制偏移量最大(即复制越完整)的当选
如果以上条件都一样,选取id最小的slave
挑选出需要继任的slave后,领头哨兵向该数据库发送命令使其升格为master,然后再向其他slave发送命令接受新的master,最后更新数据。将已经停止的旧的master更新为新的master的从数据库,使其恢复服务后以slave的身份继续运行。
二、springboot整合redis
1、添加依赖
<!--redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、添加配置
spring:
redis:
host: 1.94.230.82
port: 6381
password: 123456
database: 0
3、使用模版工具类完成redis的增删改查
package com.hl.mybatis03.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/redis")
public class RedisTest {
@Autowired
private RedisTemplate redisTemplate;
@RequestMapping("/test1")
public Object test1(){
//新增
redisTemplate.opsForValue().set("name", "zhangsan");
//查询
return redisTemplate.opsForValue().get("name");
}
}
4、测试
5、redis 序列化及常用方法
@SpringBootApplication
public class Mybatis03Application {
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // 必须为 String 序列化
return template;
}
package com.hl.mybatis03.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/redis")
public class RedisTest {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/test1")
public Object test1(){
//新增 修改
redisTemplate.opsForValue().set("name", "zhangsan");
redisTemplate.opsForValue().set("age",20);
List list = new ArrayList();
list.add("beijing");
list.add("nanjing");
list.add("bianjing");
redisTemplate.opsForValue().set("list",list);
redisTemplate.opsForValue().increment("age");//自增加1
redisTemplate.opsForValue().increment("age",10); //自增长度 自定义
redisTemplate.opsForValue().append("name"," hello! "); //追加内容
String name = (String)redisTemplate.opsForValue().get("name");
System.out.println(name.length());
redisTemplate.opsForValue().getAndDelete("list");
return null;
}
}
6、springboot+mysql+redis 业务整合
@Service
public class QueenServiceImpl implements QueenService{
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private QueenMapper queenMapper;
@Override
public List<Queen> getQueens() {
Object obj = redisTemplate.opsForValue().get("queen");
List<Queen> list = null;
if(obj !=null ){
list = (List<Queen>)obj;
}else{
list = queenMapper.getQueens();
redisTemplate.opsForValue().set("queen",list);
}
return list;
}
}
三、雪崩、穿透、击穿
1. redis缓存使用流程
使用Redis缓存数据的流程是:
-
数据查询首先进行缓存查询。
-
如果数据存在则直接返回缓存数据。
-
如果数据不存在,就对数据库进行查询,并把查询到的数据放进缓存。
-
如果数据库查询数据为空,则不放进缓存。
2. 缓存穿透
2.1. 概念
缓存穿透是指查询缓存和数据库中都不存在的数据。比如id为-1的数据。
2.2. 解决方案
-
缓存空值:当第一次请求时,数据不存在 Redis 也不存在数据库的时候,设置一个缺省值(比如:None)。当后续再次进行查询则直接返回空值或者缺省值。
//伪代码
public object GetProductListNew(String key) {
//首先查询redis数据
String value = redis.get(key);
//如果redis中没有数据
if(value == null){
//查询数据库中的数据
String dbValue = db.get();
//如果数据库中的数据为null,则设置默认值
if (dbValue == null) {
dbValue = "none";
}
//将数据存储到redis
redis.set(key,dbValue,50000);
//返回
return dbvalue;
}
}else{
//第二次查询数据时,redis有数据,就直接返回
return value;
}
}
-
布隆过滤器:在数据写入数据库的同时将这个 ID 同步到到布隆过滤器中,当请求的 id 不存在布隆过滤器中则说明该请求查询的数据一定没有在数据库中保存,就不要去数据库查询了。
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>
</dependencies>
//伪代码
public class BloomFilterTest {
private static final int capacity = 1000000;
private static final int key = 999998;
private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), capacity);
static {
for (int i = 0; i < capacity; i++) {
bloomFilter.put(i);
}
}
public static void main(String[] args) {
/*返回计算机最精确的时间,单位微妙*/
long start = System.nanoTime();
if (bloomFilter.mightContain(key)) {
System.out.println("成功过滤到" + key);
}
long end = System.nanoTime();
System.out.println("布隆过滤器消耗时间:" + (end - start));
int sum = 0;
for (int i = capacity + 20000; i < capacity + 30000; i++) {
if (bloomFilter.mightContain(i)) {
sum = sum + 1;
}
}
System.out.println("错判率为:" + sum);
}
}
3. 缓存雪崩
3.1. 概念
是指在某一个时间段,redis服务器故障或者redis缓存的数据集中全部过期失效或者大量key过期失效,在缓存集中失效的这个时间段对数据的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力。
3.2. 解决方案
事前:
redis服务器设置高可用(一主二从或者集群3主3从)
-
随机设置key失效时间,避免大量key集体失效。
-
不设置过期时间(不推荐)。
-
用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写。
-
跑定时任务,在缓存失效前刷进新的缓存。
事中:
可以考略多种缓存机制,比如redis缓存+其他缓存机制 spring cache +mysql
事后:
redis崩溃、mysql崩溃,系统崩溃
解决方案:快速恢复系统(重启mysql、重启redis服务 快速恢复数据到内存中 rdb aof机制 、重启java服务)
4. 缓存击穿
4.1. 概念
指一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个完好无损的桶上凿开了一个洞。
4.2. 解决方案
-
预先把热门数据提前存入 Redis 中,并设热门数据的过期时间超大值。
-
使用分布式锁,当发现缓存失效的时候,不是立即从数据库加载数据。而是先获取分布式锁,获取锁成功才执行数据库查询和写数据到缓存的操作,获取锁失败,则说明当前有线程在执行数据库查询操作,当前线程睡眠一段时间在重试。这样只让一个请求去数据库读取数据。
//伪代码
public String get(key) {
String value = redis.get(key);
if (value == null) { //代表缓存值过期
//设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表设置成功
value = db.get(key);
redis.set(key, value, expire_secs);
redis.del(key_mutex);
} else { //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
sleep(50);
get(key); //重试
}
} else {
return value;
}
}
四、集群 三主六从---配置
3主6从
创建redis目录和配置文件 由于这里需要配置6个redis实例的配置文件,所以在这里,可以先创建一个模板配置文件,之后使用代码批量替换的方式来创建每个redis的配置文件。
创建模板文件
创建目录
mkdir -p /data/docker_redis/
vi redis_cluster.conf.template
port ${PORT}
requirepass 123456
masterauth 123456
protected-mode no
daemonize no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 82.157.170.39
cluster-announce-port ${PORT}
cluster-announce-bus-port 1${PORT}
在这里需要讲解2个参数 cluster-announce-port:此端口为redis提供服务端口,用于应用客户端连接 cluster-announce-bus-port:此端口用于redis集群进行故障检测、配置更新、故障转移授权和内部通讯使用,不用于应用客户端连接使用。
创建6个redis实例的数据目录,配置文件目录和配置文件
for port in `seq 8001 8006`; do \
mkdir -p /data/redis_data/${port}/conf \
&& PORT=${port} envsubst < /data/docker_redis/redis_cluster.conf.template > /data/redis_data/${port}/conf/redis.conf \
&& mkdir -p /data/redis_data/${port}/data;\
done
批量创建容器 在这里使用for循环批量创建redis容器,代码如下所示
#防火墙开放端口
sudo firewall-cmd --permanent --add-port=8001-8006/tcp
sudo firewall-cmd --permanent --add-port=18001-18006/tcp#重新加载防火墙使得开放端口生效
sudo firewall-cmd --reload#查看是否开放成功
sudo firewall-cmd --list-ports#在服务器上开放相应端口
for port in $(seq 8001 8006); do \
docker run -id -p ${port}:${port} -p 1${port}:1${port} --restart always --name redis-${port} \
-v /data/redis_data/${port}/conf/redis.conf:/usr/local/etc/redis/redis.conf \
-v /data/redis_data/${port}/data:/data \
redis:7.0.2 redis-server /usr/local/etc/redis/redis.conf; \
done
#纯启动几个容器
for port in $(seq 8001 8006); do \
docker start redis-${port} ;\
done
在这里使用-v参数,让容器的redis配置参数和数据全部使用宿主机的文件目录。这样做的好处是,保证redis的数据不丢失。
查看刚刚创建的redis容器
[root@mysql data]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f4c971ce2d84 redis:7.0.2-buster "docker-entrypoint.s鈥 6 seconds ago Up 5 seconds 0.0.0.0:8006->8006/tcp, 6379/tcp, 0.0.0.0:18006->18006/tcp redis-8006 1b5a8b0a1c93 redis:7.0.2-buster "docker-entrypoint.s鈥 7 seconds ago Up 6 seconds 0.0.0.0:8005->8005/tcp, 6379/tcp, 0.0.0.0:18005->18005/tcp redis-8005 35d8fe01fc71 redis:7.0.2-buster "docker-entrypoint.s鈥 7 seconds ago Up 6 seconds 0.0.0.0:8004->8004/tcp, 6379/tcp, 0.0.0.0:18004->18004/tcp redis-8004 408e5302378a redis:7.0.2-buster "docker-entrypoint.s鈥 8 seconds ago Up 7 seconds 0.0.0.0:8003->8003/tcp, 6379/tcp, 0.0.0.0:18003->18003/tcp redis-8003 c1c23c543233 redis:7.0.2-buster "docker-entrypoint.s鈥 8 seconds ago Up 7 seconds 0.0.0.0:8002->8002/tcp, 6379/tcp, 0.0.0.0:18002->18002/tcp redis-8002 c752fab6c6b8 redis:7.0.2-buster "docker-entrypoint.s鈥 9 seconds ago Up 8 seconds 0.0.0.0:8001->8001/tcp, 6379/tcp, 0.0.0.0:18001->18001/tcp redis-8001
创建redis-cluster集群 6个redis容器创建好之后,选择其中的一个容器进入,进行redis-cluster集群创建
[root@mysql bin]# docker exec -it redis-8001 /bin/bash
root@f4c971ce2d84:~# redis-cli -a 123456 --cluster create 82.157.170.39:8001 82.157.170.39:8002 82.157.170.39:8003 82.157.170.39:8004 82.157.170.39:8005 82.157.170.39:8006 --cluster-replicas 1
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 1.94.230.82:8005 to 1.94.230.82:8001
Adding replica 1.94.230.82:8006 to 1.94.230.82:8002
Adding replica 1.94.230.82:8004 to 1.94.230.82:8003
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: c9e549f4d04d4466d2550d1e027412304099b1f2 1.94.230.82:8001
slots:[0-5460] (5461 slots) master
M: 0956927ee74737d5ff91a1885e77f94d531dab76 1.94.230.82:8002
slots:[5461-10922] (5462 slots) master
M: d6321788b2717b4142390c158ba70ca758f70964 1.94.230.82:8003
slots:[10923-16383] (5461 slots) master
S: 96d2cb55f163ecc13a714ba01d90348c1c3ad02f 1.94.230.82:8004
replicates c9e549f4d04d4466d2550d1e027412304099b1f2
S: 8a771c26a95e82d9a6818e372d7c0226937670ac 1.94.230.82:8005
replicates 0956927ee74737d5ff91a1885e77f94d531dab76
S: aa7c2f6173904973f041b35efc5200359188eb0f 1.94.230.82:8006
replicates d6321788b2717b4142390c158ba70ca758f70964
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
.
>>> Performing Cluster Check (using node 1.94.230.82:8001)
M: c9e549f4d04d4466d2550d1e027412304099b1f2 1.94.230.82:8001
slots:[0-5460] (5461 slots) master
1 additional replica(s)
M: 0956927ee74737d5ff91a1885e77f94d531dab76 1.94.230.82:8002
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: 96d2cb55f163ecc13a714ba01d90348c1c3ad02f 1.94.230.82:8004
slots: (0 slots) slave
replicates c9e549f4d04d4466d2550d1e027412304099b1f2
S: aa7c2f6173904973f041b35efc5200359188eb0f 1.94.230.82:8006
slots: (0 slots) slave
replicates d6321788b2717b4142390c158ba70ca758f70964
S: 8a771c26a95e82d9a6818e372d7c0226937670ac 1.94.230.82:8005
slots: (0 slots) slave
replicates 0956927ee74737d5ff91a1885e77f94d531dab76
M: d6321788b2717b4142390c158ba70ca758f70964 1.94.230.82:8003
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
上述命令执行之后,redis-cluster集群就搭建好了,下面是整个redis-cluster集群的架构图
测试
D:\programmeSoft\redis>redis-cli -h 82.157.170.39 -p 8001 -a 123456 -c
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
82.157.170.39:8001> set s1 v1
-> Redirected to slot [15224] located at 82.157.170.39:8003
OK
82.157.170.39:8003> set s2 v2
-> Redirected to slot [2843] located at 82.157.170.39:8001
OK
82.157.170.39:8001> set s3 v3
-> Redirected to slot [6970] located at 82.157.170.39:8002
OK
82.157.170.39:8002> get s3
"v3"
82.157.170.39:8002> get s1
-> Redirected to slot [15224] located at 82.157.170.39:8003
"v1"
82.157.170.39:8003> get s2
(error) CLUSTERDOWN The cluster is down
82.157.170.39:8003> get s2
-> Redirected to slot [2843] located at 82.157.170.39:8005
"v2"
82.157.170.39:8005> info repliation
82.157.170.39:8005> get s3
-> Redirected to slot [6970] located at 82.157.170.39:8002
"v3"
82.157.170.39:8002> info repliation
82.157.170.39:8002> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=82.157.170.39,port=8006,state=online,offset=346,lag=0
master_failover_state:no-failover
master_replid:71429ed7b1b2d6e86ea87bad2f22887427d2ed8a
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:346
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:346
82.157.170.39:8002> get s2
-> Redirected to slot [2843] located at 82.157.170.39:8005
"v2"
82.157.170.39:8005> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:a1dcdef78f7736baa9b72c95158920d482ecce6a
master_replid2:5171ae1660acd424fbad40555badf369f8ccc5ff
master_repl_offset:234
second_repl_offset:235
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:234
82.157.170.39:8005> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=82.157.170.39,port=8001,state=online,offset=248,lag=0
master_failover_state:no-failover
master_replid:a1dcdef78f7736baa9b72c95158920d482ecce6a
master_replid2:5171ae1660acd424fbad40555badf369f8ccc5ff
master_repl_offset:248
second_repl_offset:235
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:248
82.157.170.39:8005> get s2
"v2"
五、springboot整合redis连接集群
作业
1、掌握redis的高可用实现方式
-
主从复制:一主多从,数据异步复制,实现读写分离和备份,但主节点故障需手动切换。
#查看主从关系 info replication role:slave master_host:<ip> master_port:<port> #接触从节点身份 slaveof no one #将其他节点指向新主节点 slaveof <new_master_ip> <new_master_port>
-
哨兵模式(Sentinel):通过哨兵集群监控主节点,自动选举新主节点,解决主从切换问题,适合中小规模场景。
-
Redis Cluster:数据分片存储,支持多主多从,自动故障转移和水平扩展,适合大规模高并发场景。
2、springboot整合redis(普通+集群版)的过程及常用方法
-
添加依赖:
spring-boot-starter-data-redis
。 -
配置连接:在
application.yml
中设置主机、端口、密码等。 -
-
使用 RedisTemplate:
-
opsForValue()
:操作 String 类型。// 带过期时间 redisTemplate.opsForValue().set("key", "value", 10, TimeUnit.SECONDS); String value = (String) redisTemplate.opsForValue().get("key");
-
opsForHash()
:操作 Hash 类型。redisTemplate.opsForHash().put("user", "name", "Alice"); Map<Object, Object> user = redisTemplate.opsForHash().entries("user");
-
expire()
:设置过期时间。 -
execute()
:支持事务和 Lua 脚本。
-
-
自定义序列化(解决JDK序列化乱码)