四 Java連接Redis
Jedis連接Redis,Lettuce連接Redis
4.1 Jedis連接Redis
1、創建maven項目
2、導入需要的依賴包
<dependencies> <!--1、Jedis依賴包--> <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency> <!--2、Junit測試--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> </dependency> <!--3、Lombok依賴包--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency> </dependencies>
3、測試
public class Demo1 { @Test public void set(){ //1、連接Redis Jedis jedis = new Jedis("127.0.0.1",6379); //2、操作Redis - redis的命令是什麼jedis對應的方法就是什麼 jedis.set("name","zhangsan"); //3、釋放資源 jedis.close(); } @Test public void get(){ //1、連接Redis Jedis jedis = new Jedis("127.0.0.1",6379); //2、操作Redis - redis的命令是什麼jedis對應的方法就是什麼 String value = jedis.get("name"); System.out.println(value); //3、釋放資源 jedis.close(); } }
4.2 Jedis如何存儲一個對象到Redis
準備一個User實體類
@Data @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { private Long id; private String name; private Date birthday; }
導入spring-context依賴
<!--4、導入spring-context--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.20.RELEASE</version> </dependency>
創建Demo測試類,編寫內容
public class Demo2 { //存儲對象 -- 以byte[]形式存儲在redis中 @Test public void setByteArray(){ //1、連接redis服務 Jedis jedis = new Jedis("127.0.0.1",6379); //2.1 準備key(String) - value(User) String key = "user"; User user = new User(1L,"張三",new Date()); //2.2 將key和value轉換爲byte[] byte[] byteKey = SerializationUtils.serialize(key); //user對象序列化和反序列化,需要在User類實現Serializable接口 byte[] byteValue = SerializationUtils.serialize(user); //2.3 將key和value存儲到redis jedis.set(byteKey,byteValue); //3、釋放資源 jedis.close(); } @Test public void getByteArray(){ //1、連接redis服務 Jedis jedis = new Jedis("127.0.0.1",6379); //2.1 準備key(String) String key = "user"; //2.2 將key轉換爲byte[] byte[] byteKey = SerializationUtils.serialize(key); //2.3 獲取value byte[] byteValue = jedis.get(byteKey); //2.4 將value反序列化爲user對象 User user2 = (User)SerializationUtils.deserialize(byteValue); System.out.println(user2); //3、釋放資源 jedis.close(); } }
4.3 Jedis如何存儲一個對象到Redis,以String的形式存儲
導入一個fastjson依賴
<!--5、導入fastjson--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.71</version> </dependency>
編寫測試類
public class Demo3 { //存儲的對象,以String形式 @Test public void setString(){ //1、連接redis Jedis jedis = new Jedis("127.0.0.1",6379); //2.1 準備key(String) - value(User) String stringKey = "stringUser"; User value = new User(2L,"李四",new Date()); //2.2 使用fastjson將value格式化爲json字符串 String stringVlue = JSON.toJSONString(value); //2.3 存儲到redis中 jedis.set(stringKey,stringVlue); //3關閉連接 jedis.close(); } @Test public void getString(){ //1、連接redis Jedis jedis = new Jedis("127.0.0.1",6379); //2.1 準備key String stringKey = "stringUser"; //2.2 去redis中查詢value String stringValue =jedis.get(stringKey); //2.3 將value反序列化爲User User user = JSON.parseObject(stringValue,User.class); System.out.println(user); //3關閉連接 jedis.close(); } }
4.4 Jedis連接池的操作
@Test public void pool2(){ //1、創建連接池的配置信息 GenericObjectPoolConfig config = new GenericObjectPoolConfig(); //連接池中最大的活躍數 config.setMaxTotal(100); //最大空閒數 config.setMaxIdle(10); //最大空閒數 config.setMinIdle(5); //當連接池空了之後,多久沒獲取到jedis對象就超時,單位毫秒 config.setMaxWaitMillis(3000); //2、創建連接池 JedisPool pool = new JedisPool(config,"127.0.0.1",6379); //3、獲取jedis Jedis jedis = pool.getResource(); //4、操作 String value = jedis.get("stringUser"); System.out.println(value); //6、釋放連接 jedis.close(); }
4.5 Redis的管道操作
因爲在操作Redis的時候,執行一個命令需要先發送請求到Redis服務器,這個過程需要經歷網絡延遲,Redis還需要給客戶端一個響應。
如果我需要一次性執行很多個命令,上述的方式效率很低,可以通過Redis的管道,先將命令放到客戶端的一個pipeline中,之後一次性的將全部命令發送到Redis服務器,Redis服務一次性的將全部的返回結果響應給客戶端。
//Redis的管道操作 @Test public void pipeline(){ //1、創建連接 JedisPool pool = new JedisPool("127.0.0.1",6379); long start = System.currentTimeMillis(); //2、獲取一個連接對象 Jedis jedis = pool.getResource(); // //3、執行incr - 10000次 // for (int i = 0; i < 50000; i++) { // jedis.incr("pp"); // } // //4、釋放資源 // jedis.close(); //------------------ //3、創建管道 Pipeline pipeline = jedis.pipelined(); //4、執行incr - 10000次放到管道中 for (int i = 0; i < 50000; i++) { pipeline.incr("qq"); } pipeline.syncAndReturnAll(); //5、釋放資源 jedis.close(); long end = System.currentTimeMillis(); System.out.println(end-start); }
五 Redis其他配置及集羣
修改yaml文件,以方便後期修改Redis配置信息
#指定本 yml 依從的 compose 哪個版本制定的 version: '3.1' #定義服務 services: #定義一個服務 redis: #指定鏡像 image: daocloud.io/library/redis:5.0.7 #容器總是從新啓動 restart: always #容器名稱 container_name: redis #添加環境變量。指定時區 environment: - TZ=Asia/Shanghai #添加端口映射 ports: - 6379:6379 #將主機當前目錄的conf目錄下的redis.conf掛載到容器裏的/usr/local/redis.conf。 volumes: - ./conf/redis.conf:/usr/local/redis.conf #覆蓋容器啓動的默認命令。 command: ["redis-server","/usr/local/redis.conf"]
執行docker命令:
#停止和刪除容器、網絡、卷、鏡像。先停止和刪除之前的redis容器 docker-compose down #重新創建容器並後臺啓動 docker-compose up -d
5.1 Redis的AUTH
方式一:通過修改Redis的配置文件,實現Redis的密碼校驗
#在./conf/redis.conf裏面添加如下配置 requirepass 密碼 然後重啓redis容器:docker-compose restart
三種客戶端的連接方式
redis-cli:在輸入正常命令之前,先輸入auth密碼即可
圖形化界面:在連接Redis的信息中添加上驗證的密碼
Jedis客戶端:
第一種:jedis.auth(password);(不推薦)
第二種:使用JedisPool的方式
public JedisPool(GenericObjectPoolConfig poolConfig, String host, int port, int timeout, String password)
方式二:在不修改redis.conf文件的前提下,在第一次連接redis時,輸入命令:Config set requirepass 密碼,後續再次操作redis時,需要先AUTH做一次校驗。(不推薦這種方式)重啓之後密碼就失效了。
5.2 Redis的事務
Redis的事務:一次事務,該成功的成功,該失敗的失敗。
先開啓事務,執行一系列的命令,但是密碼不會立即執行,會被放在一個隊列中,如果你執行事務,那麼這個隊列中的命令全部執行,如果取消事務,一個隊列中的命令全部作廢。
開啓事務:multi
輸入要執行的命令,命令被放入到一個隊列中
執行事務:exec
取消事務:discard
Redis的事務想發揮功能,需要配合watch監聽機制
在開啓事務之前,先通過watch命令監聽一個或者多個key,在開啓事務之後,如果有其他客戶端修改了我監聽的key,事務會自動取消。
如果執行了事務或者取消了事務,watch監聽自動消除,一般不需要手動執行unwatch釋放監聽。
5.3 Redis持久化機制
RDB方式-默認
RDB是Redis默認的持久化機制
RDB持久化文件,速度比較快,而且存儲的是一個二進制的文件,傳輸起來很方便。
RDB持久化的時機:
save 900 1 #在900秒內,有1個key改變,就執行RDB持久化 save 300 10 #在300秒內,有10個key改變,就執行RDB持久化 save 60 10000 #在60秒內,有10000個key改變,就執行RDB持久化
RDB無法保證數據的絕對安全
#RDB主要配置項 #持久化時機:在900秒內,有1個key改變,就執行RDB持久化 save 900 1 #持久化時機:在300秒內,有10個key改變,就執行RDB持久化 save 300 10 #持久化時機:在60秒內,有10000個key改變,就執行RDB持久化 save 60 10000 #開啓RDB持久化的壓縮 rdbcompression yes #RDB持久化文件的名稱 dbfilename dump.rdb
AOF方式
AOF持久化機制默認是關閉的,Redis官方推薦同時開啓RDB和AOF持久化,更安全,避免數據丟失。在aof無法使用的時候,再用rdb的備份文件做替補恢復
AOF持久化的速度相對RDB較慢,存儲的是一個文本文件,時間久了文件會比較大,傳輸困難
AOF持久化機制:
#每執行一個寫操作,立即持久化到AOF文件中,性能比較低
appendfsync always
#每秒執行一次持久化 appendfsync everysec #會根據你的操作系統不同,環境的不同,在一定時間執行一次持久化
appendfsync no
AOF相對RDB更安全,推薦同時開啓AOF和RDB。
#AOF主要配置項 #代表開啓AOF持久化 appendonly yes #AOF文件的名稱 appendfilename "redis.aof" #AOF持久化執行的時機 #每執行一個寫操作,立即持久化到AOF文件中,性能比較低 appendfsync always #每秒執行一次持久化 appendfsync everysec #會根據你的操作系統不同,環境的不同,在一定時間執行一次持久化 appendfsync no
同時開啓RDB和AOF的注意事項:
如果同時開啓了AOF和RDB持久化,那麼Redis宕機重啓之後,需要加載一個持久化文件,優先選擇AOF文件。
如果先開啓了RDB,然後之後開啓AOF,如果RDB執行了持久化,那麼RDB文件中的內容會被AOF覆蓋掉。
5.4 Redis主從架構
單機版Redis存在讀寫瓶頸的問題
docker-compose.yml文件:
#指定本 yml 依從的 compose 哪個版本制定的 version: '3.1' #定義服務 services: #定義一個服務 redis1: #指定鏡像 image: daocloud.io/library/redis:5.0.7 #容器總是從新啓動 restart: always #容器名稱 container_name: redis1 #添加環境變量。指定時區 environment: - TZ=Asia/Shanghai #添加端口映射 ports: - 7001:6379 #將主機當前目錄的conf目錄下的redis.conf掛載到容器裏的/usr/local/redis.conf。 volumes: - ./conf/redis1.conf:/usr/local/redis.conf #覆蓋容器啓動的默認命令。 command: ["redis-server","/usr/local/redis.conf"] redis2: #指定鏡像 image: daocloud.io/library/redis:5.0.7 #容器總是從新啓動 restart: always #容器名稱 container_name: redis2 #添加環境變量。指定時區 environment: - TZ=Asia/Shanghai #添加端口映射 ports: - 7002:6379 #將主機當前目錄的conf目錄下的redis.conf掛載到容器裏的/usr/local/redis.conf。 volumes: - ./conf/redis2.conf:/usr/local/redis.conf #配置鏈接,redis1容器別名master links: - redis1:master #覆蓋容器啓動的默認命令。 command: ["redis-server","/usr/local/redis.conf"] redis3: #指定鏡像 image: daocloud.io/library/redis:5.0.7 #容器總是從新啓動 restart: always #容器名稱 container_name: redis3 #添加環境變量。指定時區 environment: - TZ=Asia/Shanghai #添加端口映射 ports: - 7003:6379 #將主機當前目錄的conf目錄下的redis.conf掛載到容器裏的/usr/local/redis.conf。 volumes: - ./conf/redis3.conf:/usr/local/redis.conf links: - redis1:master #覆蓋容器啓動的默認命令。 command: ["redis-server","/usr/local/redis.conf"]
#redis2和redis3從節點配置 replicaof <masterip> <masterport> replicaof master 6379
具體操作步驟
-
在/opt目錄下面創建工作目錄
mkdir docker_redis_master_salve
-
vi docker-compose.yml
複製上面配置信息到yml
-
在docker_redis_master_salve下創建conf目錄
-
touch redis1.conf
-
touch redis2.conf
-
touch redis3.conf
-
向redis2.conf和redis3.conf中添加配置:replicaof master 6379
5.5 哨兵
哨兵可以幫助我們解決主從架構中的單點故障問題
修改docker-compose.yml,爲了可以在容器內部使用哨兵的配置
#指定本 yml 依從的 compose 哪個版本制定的 version: '3.1' #定義服務 services: #定義一個服務 redis1: #指定鏡像 image: daocloud.io/library/redis:5.0.7 #容器總是從新啓動 restart: always #容器名稱 container_name: redis1 #添加環境變量。指定時區 environment: - TZ=Asia/Shanghai #添加端口映射 ports: - 7001:6379 #將主機當前目錄的conf目錄下的redis.conf掛載到容器裏的/usr/local/redis.conf。 volumes: - ./conf/redis1.conf:/usr/local/redis.conf - ./conf/sentinel1.conf:/data/sentinel.conf #添加的內容 #覆蓋容器啓動的默認命令。 command: ["redis-server","/usr/local/redis.conf"] redis2: #指定鏡像 image: daocloud.io/library/redis:5.0.7 #容器總是從新啓動 restart: always #容器名稱 container_name: redis2 #添加環境變量。指定時區 environment: - TZ=Asia/Shanghai #添加端口映射 ports: - 7002:6379 #將主機當前目錄的conf目錄下的redis.conf掛載到容器裏的/usr/local/redis.conf。 volumes: - ./conf/redis2.conf:/usr/local/redis.conf - ./conf/sentinel2.conf:/data/sentinel.conf #添加的內容 #配置鏈接,redis1容器別名master links: - redis1:master #覆蓋容器啓動的默認命令。 command: ["redis-server","/usr/local/redis.conf"] redis3: #指定鏡像 image: daocloud.io/library/redis:5.0.7 #容器總是從新啓動 restart: always #容器名稱 container_name: redis3 #添加環境變量。指定時區 environment: - TZ=Asia/Shanghai #添加端口映射 ports: - 7003:6379 #將主機當前目錄的conf目錄下的redis.conf掛載到容器裏的/usr/local/redis.conf。 volumes: - ./conf/redis3.conf:/usr/local/redis.conf - ./conf/sentinel3.conf:/data/sentinel.conf #添加的內容 links: - redis1:master #覆蓋容器啓動的默認命令。 command: ["redis-server","/usr/local/redis.conf"]
準備哨兵的配置文件,並且在容器內部手動啓動哨兵即可
哨兵基本配置:
#哨兵需要後臺啓動 daemonize yes #指定Master節點的ip和端口(主) 哨兵 監視 主節節點 主節點IP/名稱 端口 2個從節點 sentinel monitor master localhost 6379 2 #指定Master節點的ip和端口(從) sentinel monitor mymaster 127.0.0.1 6379 2 sentinel monitor master master 6379 2 #哨兵每隔多久監聽一次redis架構,默認爲3秒,這裏設置1秒好看效果 sentinel down-after-milliseconds master 10000
./conf/sentinel1.conf
#哨兵需要後臺啓動 daemonize yes #指定Master節點的ip和端口(主) 哨兵 監視 主節節點 主節點IP/名稱 端口 2個從節點 sentinel monitor master localhost 6379 2 #哨兵每隔多久監聽一次redis架構,默認爲3秒,這裏設置1秒好看效果 sentinel down-after-milliseconds master 10000
./conf/sentinel2.conf
#哨兵需要後臺啓動 daemonize yes #指定Master節點的ip和端口(從) sentinel monitor mymaster 127.0.0.1 6379 2 sentinel monitor master master 6379 2 #哨兵每隔多久監聽一次redis架構,默認爲3秒,這裏設置1秒好看效果 sentinel down-after-milliseconds master 10000
./conf/sentinel3.conf
#哨兵需要後臺啓動 daemonize yes #指定Master節點的ip和端口(從) sentinel monitor mymaster 127.0.0.1 6379 2 sentinel monitor master master 6379 2 #哨兵每隔多久監聽一次redis架構,默認爲3秒,這裏設置1秒好看效果 sentinel down-after-milliseconds master 10000
修改docker-compose.yml和增加sentinel的配置文件之後重新構建容器。
docker-compose down docker-compose up -d
在Redis容器內部啓動sentinel即可,三個容器分別進入啓動。
redis-sentinel sentinel.conf
啓動成功之後,如果我們down掉redis1這個容器,集羣會自動選舉redis2或者redis3爲Master.
5.6 Redis集羣
Redis集羣在保證主從加哨兵的基本功能之外,還能提升Redis存儲數據的能力。
搭建集羣
#docker-compose.yml version: "3.1" services: redis1: image: daocloud.io/library/redis:5.0.7 #容器總是從新啓動 restart: always container_name: redis1 environment: - TZ=Asia/Shanghai ports: - 7001:7001 - 17001:17001 volumes: - ./conf/redis1.conf:/usr/local/redis/redis.conf command: ["redis-server","/usr/local/redis/redis.conf"] redis2: image: daocloud.io/library/redis:5.0.7 #容器總是從新啓動 restart: always container_name: redis2 environment: - TZ=Asia/Shanghai ports: - 7002:7002 - 17002:17002 volumes: - ./conf/redis2.conf:/usr/local/redis/redis.conf command: ["redis-server","/usr/local/redis/redis.conf"] redis3: image: daocloud.io/library/redis:5.0.7 #容器總是從新啓動 restart: always container_name: redis3 environment: - TZ=Asia/Shanghai ports: - 7003:7003 - 17003:17003 volumes: - ./conf/redis3.conf:/usr/local/redis/redis.conf command: ["redis-server","/usr/local/redis/redis.conf"] redis4: image: daocloud.io/library/redis:5.0.7 #容器總是從新啓動 restart: always container_name: redis4 environment: - TZ=Asia/Shanghai ports: - 7004:7004 - 17004:17004 volumes: - ./conf/redis4.conf:/usr/local/redis/redis.conf command: ["redis-server","/usr/local/redis/redis.conf"] redis5: image: daocloud.io/library/redis:5.0.7 #容器總是從新啓動 restart: always container_name: redis5 environment: - TZ=Asia/Shanghai ports: - 7005:7005 - 17005:17005 volumes: - ./conf/redis5.conf:/usr/local/redis/redis.conf command: ["redis-server","/usr/local/redis/redis.conf"] redis6: image: daocloud.io/library/redis:5.0.7 #容器總是從新啓動 restart: always container_name: redis6 environment: - TZ=Asia/Shanghai ports: - 7006:7006 - 17006:17006 volumes: - ./conf/redis6.conf:/usr/local/redis/redis.conf command: ["redis-server","/usr/local/redis/redis.conf"]
#redis.conf # 指定redis的端口號 port 7001 #開啓redis集羣 cluster-enabled yes # 集羣信息的文件 cluster-config-file nodes-7001.conf # 集羣的對外ip地址 cluster-announce-ip 192.168.102.11 # 集羣的對外端口號 cluster-announce-port 7001 #集羣的總線端口號 cluster-announce-bus-port 17001
啓動6個Redis的節點。
隨便跳轉到一個容器內部,使用redis-cli管理集羣,他會自動分配好主從節點以及hash槽
redis-cli --cluster create 192.168.102.11:7001 192.168.102.11:7002 192.168.102.11:7003 192.168.102.11:7004 192.168.102.11:7005 192.168.102.11:7006 --cluster-replicas 1
測試
使用redis-cli -h 192.168.102.11 -p 7001 連接指定一個redis節點,此時set key可能設置不進去,因爲通過計算key應該在另外的節點。如果需要在客戶端連接,但是set數據能跳轉到其他節點set,連接命令需要加-c,如下
redis-cli -h 192.168.102.11 -p 7001 -c
5.7 Java連接Redis集羣
使用JedisCluster對象連接Redis集羣
public class Demo5 { public void clusterTest(){ //創建Set<HostAndPort> Set<HostAndPort> nodes = new HashSet<>(); nodes.add(new HostAndPort("192.168.102.11",7001)); nodes.add(new HostAndPort("192.168.102.11",7002)); nodes.add(new HostAndPort("192.168.102.11",7003)); nodes.add(new HostAndPort("192.168.102.11",7004)); nodes.add(new HostAndPort("192.168.102.11",7005)); nodes.add(new HostAndPort("192.168.102.11",7006)); //創建jedisCluster集羣對象 JedisCluster jedisCluster = new JedisCluster(nodes); String value = jedisCluster.get("a"); System.out.println(value); } }
六 Redis常見問題
6.1 key的生存時間到了,Redis會立即刪除嗎?
不會立即刪除
定期刪除:
Redis每隔一段時間就會去查看Redis設置了過期時間的key,會在大概100ms的間隔中默認查看3個key.
惰性刪除
當去查詢一個已經過了生存時間的key時,Redis會先查看當前key的生存時間是否已經到了,直接刪除當前key,並且給用戶返回一個空值。
6.2 Redis的淘汰機制
在Redis內存已經滿的時候,添加一個新的數據,就會執行淘汰策略。
volatile-lru:在內存不足時,Redis會在設置了過期時間的key中淘汰掉一個最近最少使用的key
allkeys-lru:在內存不足時,Redis會在全部的key中淘汰掉一個最近最少使用的key
volatile-lfu:在內存不足時,Redis會在設置了過期時間的key中淘汰掉一個最近最少頻次使用的key
allkeys-lfu:在內存不足時,Redis會在全部的key中淘汰掉一個最近最少頻次使用的key
volatile-random:在內存不足時,Redis會在設置了過期時間的key中隨機淘汰掉一個key
allkeys-random:在內存不足時,Redis會在全部的key中隨機淘汰掉一個key
volatile-ttl:在內存不足時,Redis會在設置了過期時間的key中隨機淘汰掉一個剩餘生存時間最少的key
noeviction:(默認):在內存不足時,直接報錯
指定淘汰機制的方式:maxmemory-policy noeviction(具體策略)
設置Redis最大內存:maxmemory <bytes>
6.3 緩存的常見問題
緩存穿透
問題出現的原因:查詢的數據,Redis中沒有,數據庫中也沒有。如何解決?
-
根據Id查詢時,如果id是自增的,將id的最大值放到Redis中,在查詢數據庫之前,直接比較一下id.
-
如果id不是整形的,可以將全部id放到set中,在用戶查詢之前,去set中查看一些是否有這個id.
-
獲取客戶端的ip地址,可以將ip的訪問添加限制。
-
將訪問的key直接在Redis中緩存一個空值,下次訪問的時候可直接查redis放回空值
-
根據緩存數據Key的設計規則,將不符合規則的key採用布隆過濾器進行過濾
緩存擊穿
問題出現的原因:緩存中的熱點數據,突然到期了,造成大量的請求都去訪問數據庫,造成數據庫宕機
-
在訪問緩存中沒有的時候,添加一個鎖,讓幾個請求去訪問數據庫,避免數據庫宕機
-
去掉熱點數據的生存時間
緩存雪崩
問題出現的原因:當大量緩存同時到期時,最終大量的同時去訪問數據庫,導致數據庫宕機
-
將緩存中的數據設置不同的生存時間,例如設置爲30~60分鐘的要給隨機時間
緩存傾斜
問題出現的原因:熱點數據放在一個Reids節點上,導致Redis節點無法承受住大量的請求,最終導致Redis宕機。
-
擴展主從架構,搭建多個從節點,緩解Redis的壓力
-
可以在Tomcat中做JVM緩存,在查詢Redis之前,先去查詢Tomcat中的緩存。
好了,本次Redis學習就到這裏了。相關的示例代碼已上傳碼雲,學習文檔也已放百度雲,倉庫地址和文檔地址可關注"良辰"公衆號,回覆"學無止境"或者"redis"獲取
學無止境,關注我,我們一起進步。如果覺得文章還可以,點個贊,點個在看唄,謝謝~我們下期見。