日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

Docker中搭建redis分片集群,搭建redis哨兵结构,实现springboot中对redis分片集群、哨兵结构的访问,Redis缓存雪崩、缓存击穿处理(非关系型数据库技术课程 第十二周)

發(fā)布時間:2023/12/31 数据库 24 豆豆

文章目錄

  • 一、要求:
  • 二、知識總結(jié)
    • 緩存雪崩
      • 解決方案
    • docker中redis分片集群搭建
      • 配置好配置文件
        • redis-6380.conf
        • redis-6381.conf
        • redis-6382.conf
        • redis-6383.conf
        • redis-6384.conf
        • redis-6385.conf
      • 將conf_cluster文件夾移動到對應(yīng)位置
      • docker中運行對應(yīng)的redis容器
      • docker中創(chuàng)建分片集群
    • springboot項目訪問redis分片集群
    • 緩存擊穿
      • 什么是緩存擊穿
      • 解決方案
      • 互斥鎖機制
        • 互斥鎖原理
          • (1) 利用互斥鎖處理緩存擊穿問題的邏輯流程
          • (2) 使用redis 的setnx 命令來實現(xiàn)
        • 互斥鎖代碼實現(xiàn)
    • docker中redis哨兵結(jié)構(gòu)搭建
      • 關(guān)于哨兵結(jié)構(gòu)
      • 配置配置文件
        • redis_6390.conf
        • redis_6391.conf
        • redis_6392.conf
        • redis_26390.conf
        • redis_26391.conf
        • redis_26392.conf
      • 把配置文件文件夾移動到對應(yīng)位置
      • docker中創(chuàng)建主從結(jié)構(gòu)對應(yīng)容器
        • 啟動主節(jié)點(redis_master_6390)
        • 啟動從節(jié)點1(redis_slave1_6391)
        • 啟動從節(jié)點2(redis_slave2_6392)
        • 查詢主從結(jié)構(gòu)是否成功
      • docker中創(chuàng)建哨兵結(jié)構(gòu)對應(yīng)容器
        • 啟動哨兵節(jié)點1(redis_sentinel1_26390)
        • 啟動哨兵節(jié)點2(redis_sentinel2_26391)
        • 啟動哨兵節(jié)點3(redis_sentinel3_26392)
        • 查詢哨兵結(jié)構(gòu)是否搭建成功
    • springboot項目訪問redis哨兵集群
      • 配置文件配置
      • 配置主從讀寫分離(可選)
  • 三、經(jīng)驗總結(jié)和報錯處理
    • 關(guān)于docker啟動容器后秒退問題處理
    • 關(guān)于云服務(wù)器端口開放問題
    • 關(guān)于哨兵結(jié)構(gòu)與分片集群
  • 四、相關(guān)代碼
    • application.yml
      • 訪問分片集群版
      • 訪問哨兵結(jié)構(gòu)版:
    • com.example.service.impl.StudentServiceImpl
  • 五、運行結(jié)果
    • 1.搭建redis分片集群,并實現(xiàn)springboot對集群的訪問
    • 2.搭建redis哨兵集群,并實現(xiàn)springboot對哨兵集群的訪問

一、要求:

  • 搭建redis分片集群,并實現(xiàn)springboot對分片集群的訪問;
  • 搭建redis哨兵模式,并實現(xiàn)springboot對哨兵集群的訪問;

二、知識總結(jié)

緩存雪崩

解決方案

常用的緩存雪崩的解決方案包括:

  • 給不同的 Key 的 TTL 添加隨機值
  • 利用 Redis 集群提高服務(wù)的可用性
  • 給緩存業(yè)務(wù)添加降級限流策略
  • 給業(yè)務(wù)添加多級緩存

上一講(非關(guān)系型數(shù)據(jù)庫技術(shù)課程 第十一周作業(yè)(SpringBoot項目中使用Redis作為數(shù)據(jù)緩存,Redis的緩存機制,數(shù)據(jù)一致性、緩存穿透和緩存雪崩等問題的處理))中
提供了 給不同的 Key 的 TTL 添加隨機值 方案解決緩存雪崩問題的實現(xiàn),本講中將實現(xiàn)搭建redis分片集群來解決緩存雪崩問題。

docker中redis分片集群搭建

配置好配置文件

將配置文件和data文件夾都放在一個"conf_cluster"文件夾中,如圖:

data文件夾中要先新建6380-6385對應(yīng)的空文件夾:

開始配置文件的配置:

注意
配置文件內(nèi)容中cluster-announce-ip要根據(jù)自己主機的實際ip地址進行配置(下面的配置文件中類似120.25.223.26都要改為自己對應(yīng)主機的ip)

windows系統(tǒng)電腦作為主機可以在cmd命令行窗口中輸入ipconfig查看ip地址
云服務(wù)器中搭建則需要設(shè)置服務(wù)器外網(wǎng)ip

redis-6380.conf

port 6380cluster-enabled yes cluster-config-file nodes-6380.confcluster-node-timeout 5000cluster-announce-ip 120.25.223.26 cluster-announce-port 6380 cluster-announce-bus-port 16380 appendonly yes

redis-6381.conf

port 6381cluster-enabled yes cluster-config-file nodes-6381.confcluster-node-timeout 5000 appendonly yes cluster-announce-ip 120.25.223.26 cluster-announce-port 6381 cluster-announce-bus-port 16381

redis-6382.conf

port 6382cluster-enabled yes cluster-config-file nodes-6382.confcluster-node-timeout 5000 appendonly yes cluster-announce-ip 120.25.223.26 cluster-announce-port 6382 cluster-announce-bus-port 16382

redis-6383.conf

port 6383cluster-enabled yes cluster-config-file nodes-6383.confcluster-node-timeout 5000 appendonly yes cluster-announce-ip 120.25.223.26 cluster-announce-port 6383 cluster-announce-bus-port 16383

redis-6384.conf

port 6384cluster-enabled yes cluster-config-file nodes-6384.confcluster-node-timeout 5000 appendonly yes cluster-announce-ip 120.25.223.26 cluster-announce-port 6384 cluster-announce-bus-port 16384

redis-6385.conf

port 6385cluster-enabled yes cluster-config-file nodes-6385.confcluster-node-timeout 5000 appendonly yes cluster-announce-ip 120.25.223.26 cluster-announce-port 6385 cluster-announce-bus-port 16385

將conf_cluster文件夾移動到對應(yīng)位置

這里要把conf_cluster文件夾移動到待會docker運行容器時對應(yīng)的掛載目錄,比如我將該文件夾移動到主機(即“宿主機”)的/mydata/路徑中,則后面運行docker容器時的掛載目錄就如下配置:

-v /mydata/conf_cluster/data/6380:/data -v /mydata/conf_cluster/redis-6380.conf:/etc/redis/redis-6380.conf

這里的掛載目錄要按自己實際路徑進行配置

docker中運行對應(yīng)的redis容器

創(chuàng)建各redis容器的命令
redis_6380:

docker run -id --name redis_6380 -p 6380:6380 -p 16380:16380 --privileged=true -v /mydata/conf_cluster/data/6380:/data -v /mydata/conf_cluster/redis-6380.conf:/etc/redis/redis-6380.conf redis redis-server /etc/redis/redis-6380.conf

redis_6381:

docker run -id --name redis_6381 -p 6381:6381 -p 16381:16381 --privileged=true -v /mydata/conf_cluster/data/6381:/data -v /mydata/conf_cluster/redis-6381.conf:/etc/redis/redis-6381.conf redis redis-server /etc/redis/redis-6381.conf

redis_6382:

docker run -id --name redis_6382 -p 6382:6382 -p 16382:16382 --privileged=true -v /mydata/conf_cluster/data/6382:/data -v /mydata/conf_cluster/redis-6382.conf:/etc/redis/redis-6382.conf redis redis-server /etc/redis/redis-6382.conf

redis_6383:

docker run -id --name redis_6383 -p 6383:6383 -p 16383:16383 --privileged=true -v /mydata/conf_cluster/data/6383:/data -v /mydata/conf_cluster/redis-6383.conf:/etc/redis/redis-6383.conf redis redis-server /etc/redis/redis-6383.conf

redis_6384:

docker run -id --name redis_6384 -p 6384:6384 -p 16384:16384 --privileged=true -v /mydata/conf_cluster/data/6384:/data -v /mydata/conf_cluster/redis-6384.conf:/etc/redis/redis-6384.conf redis redis-server /etc/redis/redis-6384.conf

redis_6385:

docker run -id --name redis_6385 -p 6385:6385 -p 16385:16385 --privileged=true -v /mydata/conf_cluster/data/6385:/data -v /mydata/conf_cluster/redis-6385.conf:/etc/redis/redis-6385.conf redis redis-server /etc/redis/redis-6385.conf

docker中創(chuàng)建分片集群

先加入到某個容器中:

docker exec -it redis_6380 /bin/bash

運行以下命令創(chuàng)建集群:

注意:
這里的“120.25.223.26”也要根據(jù)自己主機的ip進行修改

redis-cli --cluster create 120.25.223.26:6380 120.25.223.26:6381 120.25.223.26:6382 120.25.223.26:6383 120.25.223.26:6384 120.25.223.26:6385 --cluster-replicas 1

創(chuàng)建好集群后開啟一個redis客戶端查看集群狀態(tài):
注意要以加“-c”進入到集群中

redis-cli -c -p 6380

查看集群狀態(tài):

cluster info

cluster_state顯示ok則證明集群正常運行

查看節(jié)點信息:

cluster nodes


可以看到該分片集群為三主三從結(jié)構(gòu)

也可以進行一些redis操作查看分片集群是否搭建成功

springboot項目訪問redis分片集群

項目在上一講的代碼基礎(chǔ)上進行修改
配置文件application.yml中關(guān)于訪問redis分片集群的配置:
注意nodes中對應(yīng)的主機名要根據(jù)自己實際主機ip修改

spring:redis:# 分片集群配置cluster:nodes:- 120.25.223.26:6380- 120.25.223.26:6381- 120.25.223.26:6382- 120.25.223.26:6383- 120.25.223.26:6384- 120.25.223.26:6385max-redirects: 5lettuce:pool:max-active: 10max-idle: 10min-idle: 0max-wait: 1000

緩存擊穿

什么是緩存擊穿

緩存擊穿問題也叫 熱點 Key 問題,就是一個被高并發(fā)訪問并且緩存重建業(yè)務(wù)較復(fù)雜的 key 突然失效了,無數(shù)的請求訪問會在 瞬間給數(shù)據(jù)庫帶來巨大的沖擊。
緩存擊穿問題的特點包括:

  • redis 服務(wù)正常,沒有出現(xiàn)大量 key 過期現(xiàn)象
  • 熱點 key 過期,并且緩存重建較復(fù)雜
  • 高并發(fā)訪問熱點 key
  • 數(shù)據(jù)庫訪問壓力瞬時劇增
    具體如下圖所示:

解決方案

常用緩存擊穿問題的解決方案包括:

  • 預(yù)先設(shè)置熱門數(shù)據(jù):在 redis 高峰訪問之前,把一些熱門數(shù)據(jù)提前存入到 redis里面,加大這些熱門數(shù)據(jù) key 的時長
  • 實時調(diào)整:現(xiàn)場監(jiān)控哪些數(shù)據(jù)熱門,實時調(diào)整 key 的過期時長
  • 使用 互斥鎖:類似于悲觀鎖
  • 邏輯過期:類似于樂觀鎖

互斥鎖機制

互斥鎖原理

(1) 利用互斥鎖處理緩存擊穿問題的邏輯流程

(2) 使用redis 的setnx 命令來實現(xiàn)
  • 命令格式:setnx key value
  • 作用:Setnx( SET if Not eXists) 命令在指定的 key 不存在時,為 key設(shè)置指定的值;
  • 返回值:設(shè)置成功,返回 1 。 設(shè)置失敗,返回 0 。

  • 加鎖:該命令執(zhí)行返回 1 表示申請鎖成功,返回 0 表示申請鎖失敗;
  • 釋放鎖:del key ,刪除 key 就表示釋放了鎖。

互斥鎖代碼實現(xiàn)

package com.example.service.impl;import cn.hutool.core.util.BooleanUtil; import com.example.mapper.UserMapper; import com.example.pojo.User; import com.example.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource; import java.io.IOException; import java.util.List; import java.util.concurrent.TimeUnit;/*** description:出來緩存擊穿問題,需用的方法*方法 1:queryWithLock()--處理緩存擊穿問題*方法 2: addlock()--申請鎖*方法 3:unlock()--釋放鎖* author :hj* date: 2022/11/10*/ @Service public class UserServiceImpl implements UserService {@Resource// @Autowiredprivate UserMapper userMapper;@Autowiredprivate RedisTemplate redisTemplate;//根據(jù)id查詢user,調(diào)用queryWithLock()public User findUserById(Long id) throws IOException {//熱點數(shù)據(jù),使用queryWithLock方法User user =queryWithLock(id);return user;}//1.處理緩存擊穿問題public User queryWithLock(Long id) throws IOException {String key="user:"+id;//1.首先查看Redis緩存中是否有數(shù)據(jù)User user =getUserByRedis(id);//2.如果 Redis 中有該用戶,則直接返回if (user !=null){System.out.println("Redis緩存中查詢到此用戶");return user;}System.out.println("Redis緩存中沒有此用戶");String lockKey = "lock:user:"+id;// 3.Redis中沒有,表示查詢未命中,則需進行加鎖和緩存重建(查詢mysql)try {//3.1獲取鎖boolean isLock = addlock(lockKey);//3.2 判斷鎖是否獲取成功. 這里判斷加鎖失敗,則休眠,再次執(zhí)行該方法if(!isLock){Thread.sleep(50);//休眠20毫秒后,再次執(zhí)行該方法,遞歸調(diào)用,重新查詢redisreturn queryWithLock(id);}System.out.println("Redis申請鎖成功!");//3.3 如果成功加上了鎖,要再次查詢 redis 緩存是否有該數(shù)據(jù),// 因為可能其他應(yīng)用已重建了該數(shù)據(jù)的緩存if (getUserByRedis(id)!= null){System.out.println("再次查詢時,Redis緩存中查詢到此用戶");return user;}// 4. 這里表示,兩次查詢 Redis,都沒有查詢到數(shù)據(jù)未命中,則要到mysql中查詢,// 如果mysql中也沒有,則將空對象寫入redisuser=userMapper.findUserById(id);//模擬緩存重建延遲了Thread.sleep(200);//數(shù)據(jù)庫里也沒有,redis中也沒有if(user==null){System.out.println("Mysql中也沒有此用戶");User u=new User();u.setId(id);saveToRedis(u);}else{System.out.println("Mysql中查詢到此用戶");saveToRedis(user);}} catch (InterruptedException e) {e.printStackTrace();}finally {unlock(lockKey);}return user;}//2.加鎖private boolean addlock(String key){Boolean flag= redisTemplate.opsForValue().setIfAbsent(key,"1",10,TimeUnit.SECONDS);return BooleanUtil.isTrue(flag) ;}//3.釋放鎖private void unlock(String key){redisTemplate.delete(key);}//處理:緩存穿透問題public User queryWithPassThrough(Long id) throws IOException {//1.查看Redis緩存中是否有數(shù)據(jù)User user =getUserByRedis(id);//2.如果Redis中有該用戶,則返回if (user !=null){System.out.println("Redis緩存中查詢到此用戶");return user;}// 3.Redis中沒有,則到mysql中查詢,// 如果mysql中也沒有,則將空對象寫入redisSystem.out.println("Redis緩存中沒有此用戶");user=userMapper.findUserById(id);if(user==null){System.out.println("Mysql中也沒有此用戶");User u=new User();u.setId(id);saveToRedis(u);}else{System.out.println("Mysql中查詢到此用戶");saveToRedis(user);}return user;}//從redis中查詢Userpublic User getUserByRedis(Long id){String key="user:"+id;if (redisTemplate.hasKey(key)){String name=(String) redisTemplate.opsForHash().get(key,"name");String pwd= (String) redisTemplate.opsForHash().get(key,"pwd");User user=new User();user.setId(id);user.setUsername(name);user.setPassword(pwd);// System.out.print(user);return user;}return null;}//保存User信息到Redis,使用hash類型public void saveToRedis(User user) {//設(shè)置key: user:IDString key="user:"+user.getId();//各字段的值都存入RedisredisTemplate.opsForHash().put(key,"name",user.getUsername());redisTemplate.opsForHash().put(key,"pwd",user.getPassword());//修改 1:設(shè)置key的過期時間為6分鐘redisTemplate.expire(key,360, TimeUnit.SECONDS);}//根據(jù)id修改用戶信息@Transactional //修改3:開啟事務(wù)public String updateUserById(User user) {Long id = user.getId();if (id == null) {return "用戶id不能為空";}//修改2. 先更新mysql數(shù)據(jù)庫userMapper.updateUserById(user);//修改2. 后刪除緩存String key="user:"+id;redisTemplate.delete(key);return "更新成功";}//查詢用戶public List<User> getAllUser() {return userMapper.getAllUserMap();}public int addUser(User user) {return userMapper.addUser(user);}}

docker中redis哨兵結(jié)構(gòu)搭建

關(guān)于哨兵結(jié)構(gòu)

要注意分片集群是自帶故障處理機制的,因此分片集群是不需要配置哨兵集群的,因此以下在搭建哨兵集群之前先搭建了一個一主二從的主從結(jié)構(gòu)
而搭建的三個哨兵節(jié)點構(gòu)成哨兵集群監(jiān)視主從結(jié)構(gòu)的主節(jié)點
哨兵結(jié)構(gòu)如圖:

配置配置文件

以下配置的哨兵結(jié)構(gòu)為:

6390 主節(jié)點 6391 從節(jié)點 6392 從節(jié)點26390 哨兵節(jié)點1 26391 哨兵節(jié)點2 26392 哨兵節(jié)點3

同樣可以先把配置文件都放在同一個文件夾中

redis_6390.conf

port 6390

redis_6391.conf

注意以下配置文件的“120.25.223.26”要根據(jù)自己主機ip進行修改

port 6391 slaveof 120.25.223.26 6390

redis_6392.conf

port 6392 slaveof 120.25.223.26 6390

redis_26390.conf

port 26390# 讓sentinel服務(wù)后臺運行(docker的話需要設(shè)置為no,非docker運行設(shè)置為yes, 因為docker有個-d屬性就是讓在后臺運行的) daemonize no #Sentinel去監(jiān)視一個名為mymaster的主redis實例 # 投票數(shù)設(shè)置為2代表哨兵集群中兩個或以上哨兵節(jié)點判定主節(jié)點主觀下線則判定該節(jié)點下線 sentinel monitor mymaster 120.25.223.26 6390 2

redis_26391.conf

port 26391# 讓sentinel服務(wù)后臺運行(docker的話需要設(shè)置為no,非docker運行設(shè)置為yes, 因為docker有個-d屬性就是讓在后臺運行的) daemonize no #Sentinel去監(jiān)視一個名為mymaster的主redis實例 sentinel monitor mymaster 120.25.223.26 6390 2

redis_26392.conf

port 26392# 讓sentinel服務(wù)后臺運行(docker的話需要設(shè)置為no,非docker運行設(shè)置為yes, 因為docker有個-d屬性就是讓在后臺運行的) daemonize no #Sentinel去監(jiān)視一個名為mymaster的主redis實例 sentinel monitor mymaster 120.25.223.26 6390 2

把配置文件文件夾移動到對應(yīng)位置

注意文件夾中要先創(chuàng)建一個data空目錄用于docker運行主節(jié)點容器時data目錄的掛載,如圖所示:

conf_sentinel文件夾用于存放相關(guān)的配置文件:

docker中創(chuàng)建主從結(jié)構(gòu)對應(yīng)容器

啟動主節(jié)點(redis_master_6390)

注意這里掛載目錄要根據(jù)實際路徑修改

docker run -id --name redis_master_6390 -p 6390:6390 --privileged=true -v /mydata/redis_master_slave/data:/data -v /mydata/redis_master_slave/conf_sentinel/redis_6390.conf:/etc/redis/redis_6390.conf redis redis-server /etc/redis/redis_6390.conf

啟動從節(jié)點1(redis_slave1_6391)

docker run -id --name redis_slave1_6391 -p 6391:6391 --privileged=true -v /mydata/redis_master_slave/conf_sentinel/redis_6391.conf:/etc/redis/redis_6391.conf redis redis-server /etc/redis/redis_6391.conf

啟動從節(jié)點2(redis_slave2_6392)

docker run -id --name redis_slave2_6392 -p 6392:6392 --privileged=true -v /mydata/redis_master_slave/conf_sentinel/redis_6392.conf:/etc/redis/redis_6392.conf redis redis-server /etc/redis/redis_6392.conf

查詢主從結(jié)構(gòu)是否成功

進入到主節(jié)點容器中:

docker exec -it redis_master_6390 /bin/bash

redis-cli連接:

redis-cli -p 6390

查看主從結(jié)構(gòu)信息:

info replication

docker中創(chuàng)建哨兵結(jié)構(gòu)對應(yīng)容器

docker創(chuàng)建哨兵結(jié)構(gòu)時若遇到容器閃退問題可以參考下面“三、經(jīng)驗總結(jié)和報錯處理”中的解決方法

啟動哨兵節(jié)點1(redis_sentinel1_26390)

docker run --privileged=true -d --name redis_sentinel1_26390 -p 26390:26390 -v /mydata/redis_master_slave/conf_sentinel/redis_26390.conf:/etc/redis/redis_26390.conf redis redis-sentinel /etc/redis/redis_26390.conf

啟動哨兵節(jié)點2(redis_sentinel2_26391)

docker run --privileged=true -d --name redis_sentinel2_26391 -p 26391:26391 -v /mydata/redis_master_slave/conf_sentinel/redis_26391.conf:/etc/redis/redis_26391.conf redis redis-sentinel /etc/redis/redis_26391.conf

啟動哨兵節(jié)點3(redis_sentinel3_26392)

docker run --privileged=true -d --name redis_sentinel3_26392 -p 26392:26392 -v /mydata/redis_master_slave/conf_sentinel/redis_26392.conf:/etc/redis/redis_26392.conf redis redis-sentinel /etc/redis/redis_26392.conf

查詢哨兵結(jié)構(gòu)是否搭建成功

進入哨兵結(jié)點容器

docker exec -it redis_sentinel1_26390 /bin/bash

redis-cli客戶端連接

redis-cli -p 26390

查看哨兵結(jié)點信息:

info sentinel

springboot項目訪問redis哨兵集群

配置文件配置

在配置文件application.yml中做以下配置:
注意配置中的“master”對應(yīng)為在哨兵結(jié)點配置文件中配置的主節(jié)點名稱

# 哨兵結(jié)構(gòu)配置(一主二從 3 哨兵結(jié)點)# 結(jié)構(gòu)# 6390 主結(jié)點# 6391 從結(jié)點# 6392 從結(jié)點# 26390 哨兵1# 26391 哨兵2# 26392 哨兵3 spring:redis:sentinel:master: mymasternodes:- 120.25.223.26:26390- 120.25.223.26:26391- 120.25.223.26:26392

要注意springboot項目application.yml中不能同時配置分片集群與哨兵結(jié)構(gòu),不然項目運行時會報錯

配置主從讀寫分離(可選)

這個貌似不配也能正常使用

在springboot啟動類中配置以下內(nèi)容:

// 主從讀寫分離配置@Beanpublic LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);}

三、經(jīng)驗總結(jié)和報錯處理

關(guān)于docker啟動容器后秒退問題處理

在docker運行哨兵節(jié)點過程中,一輸入完docker run命令后查看docker ps -a發(fā)現(xiàn)容器已經(jīng)下線,為什么會秒退呢?
使用docker logs 容器名查看秒退容器的日志,發(fā)現(xiàn)日志中為如下內(nèi)容:

“1:X 18 Nov 2022 15:22:49.036 # Sentinel config file /etc/redis/redis_26390.conf is not writable: Permission denied. Exiting...”

從日志內(nèi)容可以看到顯示的是conf文件is not writable,Permission denied,于是就上網(wǎng)搜了一下,但網(wǎng)上各種方法都沒用,還是閃退,我不李姐
但最后搞了挺久還是搞好了
可以從以下幾個方面排查錯誤:
1、網(wǎng)上有的說是權(quán)限問題,需要在run命令中使用--privileged=true
已經(jīng)加了還是閃退…
2、有的說要在哨兵結(jié)點配置文件(如redis_26390.conf)中加入以下內(nèi)容

# 讓sentinel服務(wù)后臺運行(docker的話需要設(shè)置為no,非docker運行設(shè)置為yes, 因為docker有個-d屬性就是讓在后臺運行的) daemonize no

加了還是沒用…
3、有的說是selinux的問題
使用getenforce查看selinux狀態(tài)

狀態(tài)是disabled證明沒開啟,也不是這個原因

以上三種方法都沒法解決
以下兩種方法對我的閃退管用
4、docker run命令中使用redis redis-sentinel 配置文件代替redis redis-server 配置文件 --sentinel
如在運行哨兵結(jié)點26390時命令為:

docker run --privileged=true -d --name redis_sentinel1_26390 -p 26390:26390 -v /mydata/redis_master_slave/conf_sentinel/redis_26390.conf:/etc/redis/redis_26390.conf redis redis-sentinel /etc/redis/redis_26390.conf

而不是

docker run --privileged=true -d --name redis_sentinel1_26390 -p 26390:26390 -v /mydata/redis_master_slave/conf_sentinel/redis_26390.conf:/etc/redis/redis_26390.conf redis redis-server /etc/redis/redis_26390.conf --sentinel

5、要注意docker run命令中端口映射要與配置文件中配置相對應(yīng)!!
老師命令參考里的docker run命令和對應(yīng)的配置文件中端口配置是不一樣的!!
例如
哨兵結(jié)點26390的配置文件(.conf)中配置的端口為port 26390,代表該節(jié)點會在docker容器中26390端口啟動,而docker run命令中-p端口映射左邊的端口代表宿主機的端口,右邊的端口代表docker容器里面的端口,因此右邊的端口要與配置文件(.conf)中配置的端口(如:port 26390)一致!!
如:
哨兵結(jié)點26390配置文件中配置的端口為port 26390,則對應(yīng)的docker run命令中的端口映射就要為docker run -p 26390:26390左邊的端口不一定的26390,但右邊的一定要與配置文件中配置的端口(即26390)對應(yīng)!!

關(guān)于云服務(wù)器端口開放問題

當(dāng)使用客戶端遠程連接云服務(wù)器主機端口時,要注意對應(yīng)的端口應(yīng)開放,不然會連接不上
關(guān)于端口開放參考:阿里云輕量應(yīng)用服務(wù)器配置安裝運行時的端口開放問題

關(guān)于哨兵結(jié)構(gòu)與分片集群

注意分片集群自帶故障處理機制,因此不需要在分片集群中再配置哨兵集群
而且springboot中配置文件關(guān)于哨兵結(jié)構(gòu)的配置貌似只能配一個master
因此配置哨兵集群前配置了一主二從的主從結(jié)構(gòu)

四、相關(guān)代碼

項目功能代碼與上一講中代碼(傳送門)相同
只有配置文件application.yml中有所修改

application.yml

訪問分片集群版

spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/myschool?serverTimezone=Hongkong?characterEncoding=utf8&serverTimezone=GMT%2B8username: rootpassword: pwdredis:# 分片集群配置cluster:nodes:- 120.25.223.26:6380- 120.25.223.26:6381- 120.25.223.26:6382- 120.25.223.26:6383- 120.25.223.26:6384- 120.25.223.26:6385max-redirects: 5lettuce:pool:max-active: 10max-idle: 10min-idle: 0max-wait: 1000# 哨兵結(jié)構(gòu)配置(一主二從 3 哨兵結(jié)點)# 結(jié)構(gòu)# 6390 主結(jié)點# 6391 從結(jié)點# 6392 從結(jié)點# 26390 哨兵1# 26391 哨兵2# 26392 哨兵3# sentinel: # master: mymaster # nodes: # - 120.25.223.26:26390 # - 120.25.223.26:26391 # - 120.25.223.26:26392mybatis:mapper-locations: classpath:com/exmaple/mapper/*.xml #指定sql配置文件的位置type-aliases-package: com.example.pojo #指定實體類所在的包名configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #輸出SQL命令

訪問哨兵結(jié)構(gòu)版:

spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/myschool?serverTimezone=Hongkong?characterEncoding=utf8&serverTimezone=GMT%2B8username: rootpassword: pwdredis:# 分片集群配置 # cluster: # nodes: # - 120.25.223.26:6380 # - 120.25.223.26:6381 # - 120.25.223.26:6382 # - 120.25.223.26:6383 # - 120.25.223.26:6384 # - 120.25.223.26:6385 # max-redirects: 5 # lettuce: # pool: # max-active: 10 # max-idle: 10 # min-idle: 0 # max-wait: 1000# 哨兵結(jié)構(gòu)配置(一主二從 3 哨兵結(jié)點)# 結(jié)構(gòu)# 6390 主結(jié)點# 6391 從結(jié)點# 6392 從結(jié)點# 26390 哨兵1# 26391 哨兵2# 26392 哨兵3sentinel:master: mymasternodes:- 120.25.223.26:26390- 120.25.223.26:26391- 120.25.223.26:26392mybatis:mapper-locations: classpath:com/exmaple/mapper/*.xml #指定sql配置文件的位置type-aliases-package: com.example.pojo #指定實體類所在的包名configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #輸出SQL命令

com.example.service.impl.StudentServiceImpl

添加對緩存擊穿問題的處理

package com.example.service.impl;import cn.hutool.core.util.BooleanUtil; import com.example.mapper.StudentMapper; import com.example.pojo.Student; import com.example.pojo.User; import com.example.service.StudentService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.TimeUnit;/*** @projectName: week11_redis_ * @package: com.example.service.impl* @className: StudentServiceImpl* @author: GCT* @description:* 數(shù)據(jù)一致性處理:* 1.數(shù)據(jù)寫入redis時,設(shè)置key的超時時間,* 2.修改數(shù)據(jù)時,先修改mysql,再刪除redis緩存* 3.開啟事務(wù):保證正確事務(wù)的提交** 緩存穿透和緩存雪崩處理方案:* 緩存穿透處理:如果mysql中也沒有,則將空對象寫入redis進行緩存* 緩存雪崩處理 :為存入Redis數(shù)據(jù)庫進行緩存的鍵值對創(chuàng)建一個隨機的Key的有效期** * description:處理緩存擊穿問題,需用的方法* *方法 1:queryWithLock()--處理緩存擊穿問題* *方法 2: addlock()--申請鎖* *方法 3:unlock()--釋放鎖* @date: 2022/11/11 20:39* @version: 1.0*/ @Service public class StudentServiceImpl implements StudentService { // @Resource@Autowiredprivate StudentMapper studentMapper;@Autowiredprivate RedisTemplate redisTemplate;//根據(jù)id查詢user,調(diào)用queryWithLock()public Student findStudentById(Long id) { // 處理緩存擊穿問題//熱點數(shù)據(jù),使用queryWithLock方法Student student =queryWithLock(id);return student;}//1.處理緩存擊穿問題public Student queryWithLock(Long id){String key="student:"+id;//1.首先查看Redis緩存中是否有數(shù)據(jù)Student student =getStudentByRedis(id);//2.如果 Redis 中有該用戶,則直接返回if (student !=null){System.out.println("Redis緩存中查詢到此學(xué)生");return student;}System.out.println("Redis緩存中沒有此學(xué)生");String lockKey = "lock:student:"+id;// 3.Redis中沒有,表示查詢未命中,則需進行加鎖和緩存重建(查詢mysql)try {//3.1獲取鎖boolean isLock = addlock(lockKey);//3.2 判斷鎖是否獲取成功. 這里判斷加鎖失敗,則休眠,再次執(zhí)行該方法if(!isLock){Thread.sleep(50);//休眠20毫秒后,再次執(zhí)行該方法,遞歸調(diào)用,重新查詢redisreturn queryWithLock(id);}System.out.println("Redis申請鎖成功!");//3.3 如果成功加上了鎖,要再次查詢 redis 緩存是否有該數(shù)據(jù),// 因為可能其他應(yīng)用已重建了該數(shù)據(jù)的緩存if (getStudentByRedis(id)!= null){System.out.println("再次查詢時,Redis緩存中查詢到此學(xué)生");return student;}// 4. 這里表示,兩次查詢 Redis,都沒有查詢到數(shù)據(jù)未命中,則要到mysql中查詢,// 如果mysql中也沒有,則將空對象寫入redisstudent=studentMapper.findStudentById(id);//模擬緩存重建延遲了Thread.sleep(200);//數(shù)據(jù)庫里也沒有,redis中也沒有if(student==null){System.out.println("Mysql中也沒有此學(xué)生");Student s=new Student();s.setId(id);saveToRedis(s);}else{System.out.println("Mysql中查詢到此學(xué)生");saveToRedis(student);}} catch (InterruptedException e) {e.printStackTrace();}finally {unlock(lockKey);}return student;}//2.加鎖private boolean addlock(String key){Boolean flag= redisTemplate.opsForValue().setIfAbsent(key,"1",10,TimeUnit.SECONDS);return BooleanUtil.isTrue(flag) ;}//3.釋放鎖private void unlock(String key){redisTemplate.delete(key);}//根據(jù)id查詢學(xué)生信息//處理:緩存穿透問題public Student queryWithPassThrough(Long id){//1.查看Redis緩存中是否有數(shù)據(jù)Student student =getStudentByRedis(id);//2.如果Redis中有該學(xué)生,則返回if (student !=null){System.out.println("Redis緩存中查詢到此學(xué)生");return student;}// 3.Redis中沒有,則到mysql中查詢,// 緩存穿透處理:如果mysql中也沒有,則將空對象寫入redisSystem.out.println("Redis緩存中沒有此學(xué)生");student = studentMapper.findStudentById(id);if(student==null){System.out.println("Mysql中也沒有此學(xué)生");Student s = new Student();s.setId(id);saveToRedis(s);}else{System.out.println("Mysql中查詢到此學(xué)生");saveToRedis(student);}return student;}// 根據(jù)傳入的id數(shù)據(jù)查找出一個或多個學(xué)生信息/*** @param ids:* @return List<Student>* @author GCT* @description 根據(jù)傳入的id數(shù)據(jù)查找出一個或多個學(xué)生信息* @date 2022/11/12 11:30*/public List<Student> findStudentByIds(Long[] ids){List<Student> studentList = new ArrayList<Student>();for (Long id:ids){ // 遍歷ids數(shù)組,使用findStudentById(id)將 // 返回的Student類型數(shù)據(jù)添加到studentList集合中studentList.add(findStudentById(id));}return studentList;}//根據(jù)id修改用戶信息@Transactional //修改3:開啟事務(wù)public String updateStudentById(Student student) {Long id = student.getId();if (id == null) {return "學(xué)生id不能為空";}//修改2. 先更新mysql數(shù)據(jù)庫studentMapper.updateStudentById(student);//修改2. 后刪除緩存String key="student:"+id;redisTemplate.delete(key);return "更新成功";}//保存Student信息到Redis,使用hash類型public void saveToRedis(Student student) {//設(shè)置key: student:IDString key="student:"+student.getId();//各字段的值都存入RedisredisTemplate.opsForHash().put(key,"sname",student.getSname()+"");redisTemplate.opsForHash().put(key,"dept",student.getDept()+"");redisTemplate.opsForHash().put(key,"age",student.getAge()); //!!! Age為Int類型不用+“”//修改 1:設(shè)置key的過期時間為6分鐘 // redisTemplate.expire(key,360, TimeUnit.SECONDS);//緩存雪崩修改 :創(chuàng)建一個隨機的KEY 的有效期int expiredTime=360+new Random().nextInt(100);System.out.println("過期時間: "+expiredTime);redisTemplate.expire(key,expiredTime, TimeUnit.SECONDS);}//從redis中查詢Studentpublic Student getStudentByRedis(Long id){String key="student:"+id;if (redisTemplate.hasKey(key)){String sname=(String) redisTemplate.opsForHash().get(key,"sname");String dept= (String) redisTemplate.opsForHash().get(key,"dept");int age = (Integer)redisTemplate.opsForHash().get(key,"age");Student student = new Student();student.setId(id);student.setSname(sname);student.setDept(dept);student.setAge(age);return student;}return null;}//查詢用戶public List<Student> getAllStudent() {return studentMapper.getAllStudentMap();}/*** @param student:* @return int* @author GCT* @description* 緩存穿透處理時對不存在的學(xué)生創(chuàng)建了* 對應(yīng)id的空對象存入緩存,因此在新增學(xué)生信息時加個判斷,* 判斷新增的學(xué)生id是否存在于Redis緩存中,若存在,則刪去對應(yīng)緩存* @date 2022/11/12 11:45*/@Transactional //開啟事務(wù)public int addStudent(Student student) {//先在mysql數(shù)據(jù)庫新增數(shù)據(jù)int i = studentMapper.addStudent(student);Long studentId = student.getId();Student studentByRedis = getStudentByRedis(studentId);//后判斷,若在緩存中存在對應(yīng)信息則刪除緩存if (studentByRedis!=null){String key="student:"+studentId;redisTemplate.delete(key);//若存在對應(yīng)的對象,則刪除緩存}System.out.println("id: "+studentId);return i;}// 根據(jù)id刪除學(xué)生/*** @param id:* @return int* @author GCT* @description 根據(jù)id刪除學(xué)生* 使用事務(wù)* 先刪除Mysql數(shù)據(jù)庫內(nèi)信息* 再刪除redis數(shù)據(jù)庫內(nèi)信息* @date 2022/11/11 21:30*/@Transactional //開啟事務(wù)public String deleteStudentById(Long id){if (id == null) {return "學(xué)生id不能為空!";}//先更新mysql數(shù)據(jù)庫studentMapper.deleteStudentById(id);//后刪除緩存String key="student:"+id;redisTemplate.delete(key);return "成功刪除id為"+id+"的學(xué)生!";}}

五、運行結(jié)果

1.搭建redis分片集群,并實現(xiàn)springboot對集群的訪問

(1)驗證redis分片集群搭建成功的截圖:

cluster info查看集群狀態(tài):

可見集群運行正常

cluster nodes查看各節(jié)點信息:

可見redis分片集群搭建成功,集群中有三個主節(jié)點,三個從節(jié)點,其中6380、6383、6385端口對應(yīng)節(jié)點為分片集群的主節(jié)點,6381、6382、6384端口對應(yīng)節(jié)點為分片集群的從節(jié)點

(2)SpringBoot訪問redis分片集群的application.yml的修改代碼截圖:
SpringBoot項目配置文件application.yml中對于訪問redis分片集群的配置:

(3)SpringBoot成功訪問redis分片集群的結(jié)果截圖:
使用接口調(diào)試工具調(diào)用根據(jù)id查詢學(xué)生信息接口前查看集群中鍵值對信息可見此時在
redis中沒有相關(guān)的鍵值對

啟動springboot項目,使用接口調(diào)試工具調(diào)用根據(jù)id查詢學(xué)生信息接口:

可見成功查詢到了id為108的學(xué)生信息,此時查看redis集群中鍵值對信息可以看到對應(yīng)的學(xué)生信息
成功保存到redis分片集群中

后臺打印輸出:

2.搭建redis哨兵集群,并實現(xiàn)springboot對哨兵集群的訪問

(1)驗證redis哨兵集群搭建成功的截圖:
哨兵集群結(jié)構(gòu):
先搭建了一主二從的主從結(jié)構(gòu),并搭建了有三個哨兵結(jié)點的哨兵集群監(jiān)視主從結(jié)構(gòu)中的主節(jié)點
其中,主從結(jié)構(gòu)中6390端口對應(yīng)的結(jié)點為主節(jié)點,6391,6392端口對應(yīng)的節(jié)點為主節(jié)點6390
的從節(jié)點,哨兵集群中有26390,26391,26392端口對應(yīng)的三個節(jié)點作為哨兵結(jié)點,監(jiān)視主從
結(jié)構(gòu)中6390端口對應(yīng)的主節(jié)點
在主節(jié)點6390中執(zhí)行info replication查詢主從結(jié)構(gòu)是否構(gòu)建成功:

可見主從結(jié)構(gòu)搭建成功。

在哨兵節(jié)點26390中執(zhí)行 info sentinel查詢哨兵結(jié)構(gòu)是否搭建成功:

可見哨兵結(jié)構(gòu)搭建成功

(2)SpringBoot訪問redis哨兵集群的application.yml的修改代碼截圖:
SpringBoot項目配置文件application.yml中對于訪問redis哨兵結(jié)構(gòu)的配置:

(3)SpringBoot成功訪問redis哨兵集群的結(jié)果截圖:
使用接口調(diào)試工具調(diào)用根據(jù)id查詢學(xué)生信息接口前查看集群中鍵值對信息可見此時在
redis中沒有相關(guān)的鍵值對


啟動springboot項目,使用接口調(diào)試工具調(diào)用根據(jù)id查詢學(xué)生信息接口:

可見成功查詢到了id為2的學(xué)生信息,此時查看redis6390結(jié)點中鍵值對信息可以看到對應(yīng)的學(xué)生信息
成功保存到redis中

此時進入到從節(jié)點6391中,可見從節(jié)點中也成功保存了id為2的學(xué)生信息:

后臺打印輸出:

總結(jié)

以上是生活随笔為你收集整理的Docker中搭建redis分片集群,搭建redis哨兵结构,实现springboot中对redis分片集群、哨兵结构的访问,Redis缓存雪崩、缓存击穿处理(非关系型数据库技术课程 第十二周)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。