Redis 主从模式安装配置开发Idea 实战
规划
Centos7
1、创建目录:
mkdir 638{5,6,7}
[root@machine138 redis-stack]# mkdir masterSlave [root@machine138 redis-stack]# cd masterSlave/ [root@machine138 masterSlave]# vi redis.conf [root@machine138 masterSlave]# rm -rf 638{5,6,7} [root@machine138 masterSlave]# mkdir 638{5,6,7} [root@machine138 masterSlave]# cd .. [root@machine138 redis-stack]# echo 6385 6386 6387 |xargs -t -n 1 cp masterSlave/redis.conf cp masterSlave/redis.conf 6385 cp masterSlave/redis.conf 6386 cp masterSlave/redis.conf 6387 [root@machine138 redis-stack]#
redis.conf(这是最简单的主从模式的 redis.conf配置文件内容。)
port 6385 daemonize no appendonly no
dir ./
protected-mode no
bind 0.0.0.0
tcp-backlog 511 #没有这个会报错:redis Non blocking connect for SYNC fired the event
批量修改配置文件内容
[root@machine138 redis-stack]# sed -i -e 's/6385/6385/g' -e 's/dir .\//dir \/opt\/module\/redis-stack\/6385\//g' 6385/redis.conf
[root@machine138 redis-stack]# sed -i -e 's/6385/6386/g' -e 's/dir .\//dir \/opt\/module\/redis-stack\/6386\//g' 6386/redis.conf
[root@machine138 redis-stack]# sed -i -e 's/6385/6387/g' -e 's/dir .\//dir \/opt\/module\/redis-stack\/6387\//g' 6387/redis.conf
[root@machine138 redis-stack]# cat 6387/redis.conf port 6387 daemonize no appendonly no dir /opt/module/redis-stack/6387/ [root@machine138 redis-stack]# cat 6386/redis.conf port 6386 daemonize no appendonly no dir /opt/module/redis-stack/6386/ [root@machine138 redis-stack]#
在每个配置文件追加声明IP配置
sed -i '1a replica-announce-ip 192.168.1.138' 6385/redis.conf sed -i '1a replica-announce-ip 192.168.1.138' 6386/redis.conf sed -i '1a replica-announce-ip 192.168.1.138' 6387/redis.conf 或者整合上面三个命令 printf '%s\n' 6385 6386 6387 | xargs -I{} -t sed -i '1a replica-announce-ip 192.168.1.138' {}/redis.conf
xargs命令插曲
[root@machine138 redis-stack]# xargs ls #回车 6386#输入要ls 的目录 回车 6385.conf redis.conf #回车 就可以看到目录的内容了 [root@machine138 redis-stack]#
-p 确认要执行的命令 使用xargs命令之后,由于存在着参数转换的过程,所以要对执行的命令进行确认。 -p 参数就是用来对要执行的命令进行确认的 $ echo "dir1 dir2 dir3" | xargs -p mkdir 执行之后会在终端显示出要执行的命令,然后是让用户确认,输入 y (大小写均可)才会继续执行命令。
-t 显示要执行的命令 -t 选项是用来显示要执行的命令的,和-p选项不同的是,它不需要用户进行确认 $ echo "dir1 dir2 dir3" | xargs -t mkdir 会直接显示 mkdir dir1 dir2 dir3
$ find . -name "*.txt" | xargs grep "abc" 上面命令找出所有 TXT 文件以后,对每个文件搜索一次是否包含字符串abc。
[root@machine138 redis-stack]# find -name *.conf ./6379/6379.conf ./6379/nodes-6379.conf ./6380/6380.conf ./6380/nodes-6380.conf ./etc/redis-stack-service.conf ./etc/redis-stack.conf ./6385/6385.conf ./6385/redis.conf ./6386/6385.conf ./6386/redis.conf ./6387/6385.conf ./6387/redis.conf ./masterSlave/redis.conf [root@machine138 redis-stack]# xargs -n 1 find -name *.conf ./6379/6379.conf ./6379/nodes-6379.conf ./6380/6380.conf ./6380/nodes-6380.conf ./etc/redis-stack-service.conf ./etc/redis-stack.conf ./6385/6385.conf ./6385/redis.conf ./6386/6385.conf ./6386/redis.conf ./6387/6385.conf ./6387/redis.conf ./masterSlave/redis.conf
-n参数指定每次将多少项,作为命令行参数。 $ xargs -n 1 find -name 上面命令指定将每一项(-n 1)标准输入作为命令行参数,分别执行一次命令(find -name)。
-I 选项 如果xargs要将命令行参数传给多个命令,可以使用-I参数。 -I 指定每一项命令行参数的替代字符串。 $ cat jiyik.txt jiyik_one jiyik_two jiyik_three $ cat jiyik.txt | xargs -I file sh -c 'echo file; mkdir file' jiyik_one jiyik_two jiyik_three 查看当前目录下已经生成了三个目录 $ ls jiyik_one jiyik_three jiyik_two 上面代码中,jiyik.txt是一个包含三行的文本文件。我们希望对每一项命令行参数,执行两个命令(echo和mkdir),使用-I file表示file是命令行参数的替代字符串。执行命令时,具体的参数会替代掉echo file; mkdir file里面的两个file。
--max-procs 参数 xargs默认只用一个进程执行命令。如果命令要执行多次,必须等上一次执行完,才能执行下一次。 --max-procs参数指定同时用多少个进程并行执行命令。--max-procs 2表示同时最多使用两个进程,--max-procs 0表示不限制进程数。 $ docker ps -q | xargs -n 1 --max-procs 0 docker kill
例如;
printf '%s\n' 6385 6386 6387 | xargs -I{} -t sed -i '1a protected-mode no' {}/redis.conf #第一行append protected-mode no
printf '%s\n' 6385 6386 6387 | xargs -I{} -t sed -i '1a bind 0.0.0.0' {}/redis.conf #第一行append bind 0.0.0.0
printf '%s\n' 6385 6386 6387 | xargs -I{} -t sed -i -e 's/master_sync_in_progress 1/tcp-backlog 511/g' {}/redis.conf #替换 master_sync_in_progress 1 为 tcp-backlog 511
批量操作xshell
在打开的窗口 右键鼠标 复制会话
需要几个复制几个
然后 进行窗口排布
可以对每个窗口 右键 重命名
进行同步控制
如果相对某个窗口进行单独控制,只要把窗口右上角的 on-->OFF
配置主从(master ,salve)
两种方式:
1、修改配置文件 方式
slaveof <masterIP> <masterPORT>
2、命令行方式:
用redis-cli 链接redis server ,在客户端中执行 slaveof <masterIP> <masterPORT> 把当前连接的redis server 设置为 slave
现在把 6385 作为master ,让 6386 6387 作为 6385 的slave
同样的操作完成 6387 slaveof 6385
注意:命令 :replicaof <masteriIP> <masterPORT> 与 slaveof 相同
登录master 查看信息
[root@machine138 redis-stack]# bin/redis-cli -p 6385 127.0.0.1:6385> info replication # Replication role:master connected_slaves:2 slave0:ip=192.168.1.138,port=6386,state=online,offset=882,lag=1 slave1:ip=192.168.1.138,port=6387,state=online,offset=882,lag=1 master_failover_state:no-failover master_replid:8fe2fe374bfb887c4dd2a54b4b4a06fdb2fc30e9 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:882 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:882 127.0.0.1:6385>
登陆到slave 看 info relication
127.0.0.1:6386> info replication # Replication role:slave master_host:192.168.1.138 master_port:6385 master_link_status:up master_last_io_seconds_ago:0 master_sync_in_progress:0 slave_read_repl_offset:1429 slave_repl_offset:1429 slave_priority:100 slave_read_only:1 replica_announced:1 connected_slaves:0 master_failover_state:no-failover master_replid:8fe2fe374bfb887c4dd2a54b4b4a06fdb2fc30e9 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:1429 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:1429 127.0.0.1:6386>
6387
[root@machine138 redis-stack]# bin/redis-cli -p 6387 127.0.0.1:6387> info replication # Replication role:slave master_host:192.168.1.138 master_port:6385 master_link_status:up master_last_io_seconds_ago:6 master_sync_in_progress:0 slave_read_repl_offset:1611 slave_repl_offset:1611 slave_priority:100 slave_read_only:1 replica_announced:1 connected_slaves:0 master_failover_state:no-failover master_replid:8fe2fe374bfb887c4dd2a54b4b4a06fdb2fc30e9 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:1611 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:519 repl_backlog_histlen:1093 127.0.0.1:6387>
测试:
主从
1、读写分离,
2、数据同步
127.0.0.1:6385> set number 123 OK 127.0.0.1:6385> [root@machine138 redis-stack]# bin/redis-cli -p 6386 127.0.0.1:6386> ping PONG 127.0.0.1:6386> get number "123" 127.0.0.1:6386> set name lihui (error) READONLY You can't write against a read only replica. 127.0.0.1:6386>
主从数据同步机制
主从模式优化:
1、无写磁盘同步
# With slow disks and fast (large bandwidth) networks, diskless replication
# works better.
repl-diskless-sync yes
2、尽快修复故障的slave
3、如果slave节点过多可以使用 master-->slave1-->slave2 模式,替代 master-->slave1,master-->slave2
在idea中利用第三方可以完成对 master slave 进行读 写操作 redisson
POM.XML
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.19.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
创建@Bean
@Bean public Redisson redisson() { String masterIP=""; masterIP="192.168.1.138:6385"; Set<String> slavelist=new HashSet<>(); slavelist.add("redis://192.168.1.138:6386"); slavelist.add("redis://192.168.1.138:6387"); Config config=new Config(); //此为单机 模式 //config.useSingleServer().setAddress("redis://"+masterIP).setDatabase(0).setPassword("123456"); config.useMasterSlaveServers().setDatabase(0).setMasterAddress("redis://"+masterIP).setSlaveAddresses(slavelist); return (Redisson) Redisson.create(config); }
yml
注意:这里的 host,和 port 要与 上面 的Bean 中master 一样,共同指向 master redis !!!!
redis: # sentinel: # master: mymaster # nodes: # - 192.168.1.138:27001 # - 192.168.1.138:27002 # - 192.168.1.138:27003 host: 192.168.1.138 # 192.168.1.136 port: 6385 #6479 #password: 123456 lettuce: pool: max-active: 10 max-idle: 10 min-idle: 1 time-between-eviction-runs: 10s
controller:
@RestController @RequestMapping("/stock") public class RedisLockTest { @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private Redisson redisson; @RequestMapping("init") public Result addStock(@RequestParam("count") String count) { String tipmsg = ""; String stockkey = "stock:101"; synchronized (this) { //synchronized 单线程锁 不能解决分布式并发问题 if (StrUtil.isBlank(count)) count = "1000"; stringRedisTemplate.opsForValue().set(stockkey, count); String redisStock = stringRedisTemplate.opsForValue().get(stockkey); return Result.ok("库存初始化成功:" + redisStock); } } @RequestMapping("/substock") public Result substock() { String tipmsg = ""; String stockkey = "stock:101"; String stockLockkey = "stockLock:101"; String clientThreadID = UUID.randomUUID().toString().replace("-", "");//用于标识当前进程,redis 锁删除时判断是当前进程的锁 //synchronized (this){ //synchronized 单线程锁 不能解决分布式并发问题 // ctrl+alt +L 格式化代码 //利用 redisson 工具 创建一个锁对象 RLock redissonLock = redisson.getLock(stockLockkey); redissonLock.lock();//默认30s 每隔10秒检测一下主线程没有完成则续命锁 tryLockInnerAsync lua scripts try { //redis 分布式锁实现 //设置有效期 /* boolean isredisLock = stringRedisTemplate.opsForValue().setIfAbsent(stockLockkey,"stockLockValue", 10, TimeUnit.SECONDS);//redis setnx(key,value) if (!isredisLock) { return Result.fail(stockLockkey + "获取失败。"); }*/ String stock = stringRedisTemplate.opsForValue().get(stockkey); System.out.println(stockkey + "执行了。"); if (stock == "null" || StrUtil.isBlank(stock))//isEmpty() 只是对字符串长度为 0 进行判断,没有对null 进行判断,所以isEmpty()可能 有空指针的情况 { tipmsg = "当前库存记录不存在。key:" + stockkey; //return Result.fail(tipmsg); } else { int stockInt = Integer.parseInt(stock);//redis.get(stockkey) if (stockInt > 0) { int realStock = stockInt - 1; //redis 字符 串操作 所以key value都必须是string stringRedisTemplate.opsForValue().set(stockkey, StrUtil.toString(realStock));//jedis.set(key,value) tipmsg = "库存扣减成功,剩余库存:" + realStock; //return Result.ok(tipmsg); } else { tipmsg = "库存不足:" + stockkey; //return Result.fail(tipmsg); } } return Result.ok(tipmsg); } finally { //try catch 解决异常导致的死锁问题 /* if(clientThreadID.equals(stringRedisTemplate.opsForValue().get(stockLockkey))) {//判断是否为自已的锁 //删除redis 分步式锁 //TODO 但是此时如果锁的有效期 失效了。。此时再另有一个进程重新进行了加锁,那么此时删除的锁已经是别的锁了,因不自已的锁已经到期自动释放了 //TODO 这个问题可以通过 锁续命来解决 就是在主线程之外增加一个分线程,只要主线程不结束 就自动完成锁有效期的延长 redisson已经封装好了 stringRedisTemplate.delete(stockLockkey); }*/ redissonLock.unlock();//解锁 } } }
Idea日志
3.4.3 2023-03-01 16:41:27.860 INFO 53820 --- [ main] org.redisson.Version : Redisson 3.19.3 2023-03-01 16:41:28.321 INFO 53820 --- [isson-netty-4-6] o.r.c.pool.MasterPubSubConnectionPool : 1 connections initialized for 192.168.1.138/192.168.1.138:6387 2023-03-01 16:41:28.358 INFO 53820 --- [sson-netty-4-19] o.r.c.pool.MasterConnectionPool : 24 connections initialized for 192.168.1.138/192.168.1.138:6387 2023-03-01 16:41:28.364 INFO 53820 --- [isson-netty-4-2] o.r.c.pool.PubSubConnectionPool : 1 connections initialized for 192.168.1.138/192.168.1.138:6386 2023-03-01 16:41:28.364 INFO 53820 --- [isson-netty-4-3] o.r.c.pool.PubSubConnectionPool : 1 connections initialized for 192.168.1.138/192.168.1.138:6385 2023-03-01 16:41:28.394 INFO 53820 --- [sson-netty-4-22] o.r.connection.pool.SlaveConnectionPool : 24 connections initialized for 192.168.1.138/192.168.1.138:6386 2023-03-01 16:41:28.401 INFO 53820 --- [sson-netty-4-25] o.r.connection.pool.SlaveConnectionPool : 24 connections initialized for 192.168.1.138/192.168.1.138:6385 2023-03-01 16:41:29.177 INFO 53820 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8081 (http) with context path '' 2023-03-01 16:41:29.186 INFO 53820 --- [ main] com.hmdp.HmDianPingApplication : Started HmDianPingApplication in 5.764 seconds (JVM running for 6.94)
运行效果
全网最简单的主从配置
port 6386 tcp-backlog 511 bind 0.0.0.0 protected-mode no replica-announce-ip 192.168.1.138 daemonize no appendonly no dir /opt/module/redis-stack/6386/
结束