在Java中整合Redis的詳細(xì)步驟
一、Redis 基礎(chǔ)認(rèn)知:為什么選擇 Redis?
1.1 Redis 的核心特性
高性能
- 內(nèi)存存儲(chǔ)機(jī)制:Redis 將數(shù)據(jù)完全存儲(chǔ)在內(nèi)存中,數(shù)據(jù)讀寫操作直接在內(nèi)存中進(jìn)行,避免了磁盤I/O的瓶頸。內(nèi)存讀寫速度通常是磁盤的100倍以上(內(nèi)存訪問約100ns,SSD約100μs)。
- 單線程模型:采用單線程處理命令請(qǐng)求,避免了多線程上下文切換和鎖競(jìng)爭(zhēng)的開銷,同時(shí)通過I/O多路復(fù)用技術(shù)(如epoll)實(shí)現(xiàn)高并發(fā)處理。
- 性能基準(zhǔn):在標(biāo)準(zhǔn)服務(wù)器配置(如8核CPU,16GB內(nèi)存)下,Redis單節(jié)點(diǎn)可達(dá)到:
- 讀操作(QPS):10萬-15萬次/秒
- 寫操作(QPS):8萬-12萬次/秒
- 特別優(yōu)化的場(chǎng)景下甚至可達(dá)20萬+ QPS
豐富的數(shù)據(jù)結(jié)構(gòu)
- String:最基本類型,最大512MB,常用于緩存簡(jiǎn)單數(shù)據(jù)或計(jì)數(shù)器
- Hash:鍵值對(duì)集合,適合存儲(chǔ)對(duì)象(如用戶信息:user:1001 {name:"張三",age:28})
- List:雙向鏈表,可實(shí)現(xiàn)消息隊(duì)列(LPUSH+BRPOP)或最新N條記錄
- Set:無序集合,支持交并差運(yùn)算,適合標(biāo)簽系統(tǒng)
- Sorted Set:帶權(quán)重的有序集合,可用于排行榜(ZADD game:score 100 "player1")
- Bitmap:位圖操作,適合簽到統(tǒng)計(jì)(SETBIT sign:202301 100 1)
- HyperLogLog:基數(shù)統(tǒng)計(jì),誤差率0.81%,適合UV統(tǒng)計(jì)(PFADD uv:20230101 "user1")
持久化機(jī)制
- RDB(快照):
- 定時(shí)將內(nèi)存數(shù)據(jù)生成二進(jìn)制快照文件(dump.rdb)
- 配置示例:save 900 1(900秒內(nèi)至少1次修改觸發(fā)保存)
- 優(yōu)點(diǎn):文件緊湊,恢復(fù)速度快
- 缺點(diǎn):可能丟失最后一次快照后的數(shù)據(jù)
- AOF(Append Only File):
- 記錄所有寫操作命令(類似MySQL的binlog)
- 提供三種同步策略:
- always:每次寫操作都同步
- everysec:每秒同步(默認(rèn))
- no:由操作系統(tǒng)決定
- 優(yōu)點(diǎn):數(shù)據(jù)安全性高,最多丟失1秒數(shù)據(jù)
- 缺點(diǎn):文件較大,恢復(fù)速度較慢
- 混合模式:Redis 4.0+支持RDB+AOF混合持久化,結(jié)合兩者優(yōu)勢(shì)
高可用與分布式
- 主從復(fù)制:
- 一個(gè)主節(jié)點(diǎn)(Master)可配置多個(gè)從節(jié)點(diǎn)(Slave)
- 從節(jié)點(diǎn)異步復(fù)制主節(jié)點(diǎn)數(shù)據(jù)
- 讀寫分離:寫操作走主節(jié)點(diǎn),讀操作可分散到從節(jié)點(diǎn)
- 哨兵模式(Sentinel):
- 由2個(gè)以上哨兵節(jié)點(diǎn)監(jiān)控Redis集群狀態(tài)
- 自動(dòng)故障轉(zhuǎn)移:主節(jié)點(diǎn)宕機(jī)時(shí),自動(dòng)將從節(jié)點(diǎn)提升為主節(jié)點(diǎn)
- 提供配置中心和通知機(jī)制
- Redis Cluster:
- 官方分布式解決方案(Redis 3.0+)
- 數(shù)據(jù)分片存儲(chǔ):16384個(gè)哈希槽分配到不同節(jié)點(diǎn)
- 節(jié)點(diǎn)間使用Gossip協(xié)議通信
- 支持自動(dòng)故障轉(zhuǎn)移和請(qǐng)求重定向
多語言支持
- Java:Jedis、Lettuce、Redisson
- Python:redis-py
- Go:go-redis
- Node.js:ioredis
- 所有客戶端庫均遵循Redis協(xié)議規(guī)范,支持連接池、管道等高級(jí)特性
1.2 Redis 在 Java 項(xiàng)目中的典型應(yīng)用場(chǎng)景
緩存熱點(diǎn)數(shù)據(jù)
- 實(shí)現(xiàn)方式:
// Spring Cache示例
@Cacheable(value="products", key="#productId")
public Product getProduct(String productId) {
return productDao.findById(productId);
}
- 緩存策略:
- 緩存穿透:布隆過濾器或空值緩存
- 緩存雪崩:隨機(jī)過期時(shí)間
- 緩存擊穿:互斥鎖更新
分布式鎖
- 原生實(shí)現(xiàn):
// SETNX實(shí)現(xiàn)
String lockKey = "order_lock_"+orderId;
String clientId = UUID.randomUUID().toString();
try {
Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
if(result) {
// 執(zhí)行業(yè)務(wù)邏輯
} else {
// 獲取鎖失敗
}
} finally {
// 使用Lua腳本保證原子性
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(lockKey), clientId);
}
Redisson實(shí)現(xiàn):
RLock lock = redissonClient.getLock("order_lock_"+orderId);
try {
// 嘗試加鎖,最多等待100秒,上鎖后30秒自動(dòng)解鎖
if(lock.tryLock(100, 30, TimeUnit.SECONDS)) {
// 執(zhí)行業(yè)務(wù)邏輯
}
} finally {
lock.unlock();
}
計(jì)數(shù)器與限流
計(jì)數(shù)器示例:
// 文章閱讀量計(jì)數(shù)
redisTemplate.opsForValue().increment("article:read:count:"+articleId);
// 獲取閱讀量
Long count = redisTemplate.opsForValue().increment("article:read:count:"+articleId, 0);限流實(shí)現(xiàn)(滑動(dòng)窗口算法):
public boolean isAllowed(String key, int maxCount, int period) {
long now = System.currentTimeMillis();
long windowStart = now - period * 1000;
// 刪除舊時(shí)間戳
redisTemplate.opsForZSet().removeRangeByScore(key, 0, windowStart);
// 獲取當(dāng)前請(qǐng)求數(shù)
long count = redisTemplate.opsForZSet().zCard(key);
if(count < maxCount) {
// 添加當(dāng)前請(qǐng)求
redisTemplate.opsForZSet().add(key, String.valueOf(now), now);
return true;
}
return false;
}消息隊(duì)列
List實(shí)現(xiàn)簡(jiǎn)單隊(duì)列:
// 生產(chǎn)者
redisTemplate.opsForList().leftPush("queue:order", orderJson);
// 消費(fèi)者
String orderJson = redisTemplate.opsForList().rightPop("queue:order", 30, TimeUnit.SECONDS);Redis Stream實(shí)現(xiàn)(Redis 5.0+):
// 創(chuàng)建消費(fèi)者組
redisTemplate.opsForStream().createGroup("order_stream", "order_group");
// 生產(chǎn)者
Map<String, String> message = new HashMap<>();
message.put("orderId", "1001");
message.put("amount", "99.9");
redisTemplate.opsForStream().add("order_stream", message);
// 消費(fèi)者
List<MapRecord<String, String, String>> records = redisTemplate.opsForStream()
.read(Consumer.from("order_group", "consumer1"),
StreamReadOptions.empty().count(1).block(Duration.ofSeconds(1)),
StreamOffset.create("order_stream", ReadOffset.lastConsumed()));分布式Session
Spring Session集成:
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency># application.properties spring.session.store-type=redis spring.session.timeout=1800 # 30分鐘
// 自動(dòng)將Session存儲(chǔ)到Redis
@RestController
@SessionAttributes("user")
public class UserController {
@PostMapping("/login")
public String login(@RequestParam String username, Model model) {
model.addAttribute("user", username);
return "login success";
}
}- Session共享效果:
- 用戶登錄后,Session信息存儲(chǔ)在Redis
- 同一用戶的請(qǐng)求可路由到集群中任意節(jié)點(diǎn)
- 各服務(wù)通過統(tǒng)一Session ID獲取用戶狀態(tài)
- 默認(rèn)使用Jackson序列化Session對(duì)象
二、環(huán)境搭建:Redis 服務(wù)與 Java 開發(fā)環(huán)境準(zhǔn)備
2.1 Redis 服務(wù)端部署(以 Linux 為例)
步驟 1:下載并解壓 Redis
下載 Redis(版本可根據(jù)需求選擇,此處以 6.2.6 為例)
# 下載 Redis 源碼包 wget https://download.redis.io/releases/redis-6.2.6.tar.gz # 驗(yàn)證下載完整性(可選) wget https://download.redis.io/releases/redis-6.2.6.tar.gz.asc gpg --verify redis-6.2.6.tar.gz.asc # 解壓 Redis 源碼包 tar -zxvf redis-6.2.6.tar.gz # 進(jìn)入解壓后的目錄 cd redis-6.2.6
注意事項(xiàng):
- 建議使用穩(wěn)定版本(stable)而非開發(fā)版本
- 下載前可訪問 https://download.redis.io/releases/ 查看最新版本
- 對(duì)于生產(chǎn)環(huán)境,建議使用 Redis 6.x 及以上版本以支持多線程處理
步驟 2:編譯與安裝
# 安裝編譯依賴(CentOS/RHEL) yum install -y gcc make tcl # 或 Ubuntu/Debian apt-get install -y build-essential tcl # 編譯 Redis(此過程可能需要幾分鐘) make # 運(yùn)行測(cè)試(可選,但推薦) make test # 安裝 Redis 到系統(tǒng)目錄 make install
安裝結(jié)果驗(yàn)證:
- 默認(rèn)安裝路徑:/usr/local/bin
- 檢查是否安裝成功:
ls -l /usr/local/bin/redis-*
步驟 3:配置并啟動(dòng) Redis
基本配置管理:
# 創(chuàng)建 Redis 配置目錄 mkdir -p /etc/redis # 復(fù)制配置文件模板 cp redis.conf /etc/redis/redis.conf # 創(chuàng)建數(shù)據(jù)存儲(chǔ)目錄 mkdir -p /var/lib/redis
關(guān)鍵配置文件修改(/etc/redis/redis.conf):
# 使用 vim 或其他編輯器修改配置文件 vim /etc/redis/redis.conf
需要修改的重要配置項(xiàng):
網(wǎng)絡(luò)配置:
# 允許遠(yuǎn)程訪問(注釋掉或修改為) # bind 127.0.0.1 bind 0.0.0.0
安全配置:
# 關(guān)閉保護(hù)模式 protected-mode no # 設(shè)置密碼(生產(chǎn)環(huán)境必須) requirepass your_strong_password
運(yùn)行模式:
# 以守護(hù)進(jìn)程方式運(yùn)行 daemonize yes
日志配置:
# 指定日志文件路徑 logfile "/var/log/redis/redis-server.log"
數(shù)據(jù)持久化(根據(jù)需求配置):
# RDB 持久化配置 save 900 1 save 300 10 save 60 10000 # AOF 持久化配置(可選) appendonly yes appendfsync everysec
啟動(dòng) Redis 服務(wù):
# 創(chuàng)建日志目錄 mkdir -p /var/log/redis chown -R redis:redis /var/log/redis # 啟動(dòng) Redis 服務(wù) redis-server /etc/redis/redis.conf # 驗(yàn)證服務(wù)狀態(tài) ps aux | grep redis netstat -tulnp | grep 6379
連接測(cè)試:
# 本地連接測(cè)試 redis-cli -h 127.0.0.1 -p 6379 -a your_password ping # 遠(yuǎn)程連接測(cè)試(從其他服務(wù)器) redis-cli -h your_server_ip -p 6379 -a your_password ping
設(shè)置開機(jī)自啟(可選):
# 創(chuàng)建 systemd 服務(wù)文件 vim /etc/systemd/system/redis.service
添加以下內(nèi)容:
[Unit] Description=Redis In-Memory Data Store After=network.target [Service] User=redis Group=redis ExecStart=/usr/local/bin/redis-server /etc/redis/redis.conf ExecStop=/usr/local/bin/redis-cli shutdown Restart=always [Install] WantedBy=multi-user.target
然后執(zhí)行:
# 重新加載 systemd systemctl daemon-reload # 啟用開機(jī)自啟 systemctl enable redis # 啟動(dòng)服務(wù) systemctl start redis # 檢查狀態(tài) systemctl status redis
2.2 Java 開發(fā)環(huán)境配置
客戶端選擇說明
Java 操作 Redis 主要有兩種主流客戶端:
- Jedis:
- 官方推薦的輕量級(jí)客戶端
- 直接操作 Redis 命令
- 支持連接池
- 適合簡(jiǎn)單場(chǎng)景和性能要求高的場(chǎng)景
- Redisson:
- 基于 Netty 實(shí)現(xiàn)
- 提供分布式對(duì)象和服務(wù)
- 內(nèi)置分布式鎖、原子操作等高級(jí)功能
- 適合復(fù)雜分布式場(chǎng)景
2.2.1 Maven 依賴配置
Jedis 依賴配置(推薦使用連接池):
<!-- Jedis 核心依賴 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
<!-- 連接池依賴(必須) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
<!-- 可選:JSON 序列化支持 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.0</version>
</dependency>Redisson 依賴配置:
<!-- Redisson 核心依賴 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.17.6</version>
</dependency>
<!-- 可選:配置支持(如 JSON/YAML 配置文件) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>版本選擇建議:
- 生產(chǎn)環(huán)境建議使用穩(wěn)定版本
- 可訪問 Maven 中央倉庫(https://mvnrepository.com/)查詢最新版本
- 注意版本兼容性:
- Jedis 4.x 支持 Redis 6.x
- Redisson 3.x 支持 Redis 5.x+
開發(fā)環(huán)境驗(yàn)證
創(chuàng)建簡(jiǎn)單的測(cè)試類驗(yàn)證環(huán)境是否配置成功:
import redis.clients.jedis.Jedis;
public class RedisTest {
public static void main(String[] args) {
// 創(chuàng)建連接
Jedis jedis = new Jedis("localhost", 6379);
jedis.auth("your_password"); // 如果有密碼
// 測(cè)試連接
System.out.println("連接狀態(tài):" + jedis.ping());
// 基本操作
jedis.set("test_key", "Hello Redis");
System.out.println("獲取值:" + jedis.get("test_key"));
// 關(guān)閉連接
jedis.close();
}
}IDE 配置建議
- IntelliJ IDEA:
- 確保 Maven 項(xiàng)目正確導(dǎo)入依賴
- 安裝 Redis 插件(如 "Redis" 插件)方便調(diào)試
- Eclipse:
- 使用 M2Eclipse 插件管理依賴
- 可安裝 Redis 客戶端插件(如 "RedisClipse")
生產(chǎn)環(huán)境注意事項(xiàng)
- 連接池配置:
- 必須使用連接池管理連接
- 推薦配置參數(shù):
JedisPoolConfig poolConfig = new JedisPoolConfig(); poolConfig.setMaxTotal(128); // 最大連接數(shù) poolConfig.setMaxIdle(32); // 最大空閑連接 poolConfig.setMinIdle(8); // 最小空閑連接 poolConfig.setTestOnBorrow(true); // 獲取連接時(shí)測(cè)試
- 安全建議:
- 不要將密碼硬編碼在代碼中
- 使用配置中心或環(huán)境變量管理敏感信息
- 啟用 SSL/TLS 加密傳輸(Redis 6+支持)
- 性能優(yōu)化:
- 批量操作使用 pipeline
- 大 value 考慮分片存儲(chǔ)
- 合理選擇序列化方式
三、Jedis 客戶端:Java 操作 Redis 的基礎(chǔ)實(shí)現(xiàn)
3.1 基本使用:?jiǎn)螌?shí)例連接
步驟 1:創(chuàng)建 Jedis 實(shí)例并連接 Redis
import redis.clients.jedis.Jedis;
public class JedisBasicDemo {
public static void main(String[] args) {
// 1. 創(chuàng)建Jedis實(shí)例(參數(shù)說明:Redis地址、端口、連接超時(shí)時(shí)間(毫秒)、密碼)
// 注意:如果Redis服務(wù)器在同一臺(tái)機(jī)器,地址可以是"localhost"
Jedis jedis = new Jedis("192.168.1.100", 6379, 5000);
try {
// 2. 驗(yàn)證密碼(若Redis未設(shè)置密碼,可跳過此步驟)
// 如果密碼錯(cuò)誤,會(huì)拋出redis.clients.jedis.exceptions.JedisDataException
jedis.auth("your_password");
// 3. 測(cè)試連接
// ping()返回"PONG"表示連接正常
System.out.println("Redis連接狀態(tài):" + jedis.ping()); // 期望輸出:PONG
// 4. 操作String類型數(shù)據(jù)
jedis.set("username", "zhangsan"); // 存入數(shù)據(jù),默認(rèn)永不過期
// 帶過期時(shí)間的設(shè)置(單位秒)
jedis.setex("temp_token", 3600, "abcd1234");
String username = jedis.get("username"); // 獲取數(shù)據(jù)
System.out.println("獲取到的用戶名:" + username); // 輸出:zhangsan
// 5. 操作Hash類型數(shù)據(jù)
// 存儲(chǔ)用戶信息
jedis.hset("user:1001", "name", "lisi");
jedis.hset("user:1001", "age", "25");
jedis.hset("user:1001", "gender", "male");
// 獲取Hash中所有字段和值(返回Map<String,String>)
System.out.println("用戶1001信息:" + jedis.hgetAll("user:1001"));
// 輸出:{name=lisi, age=25, gender=male}
// 獲取單個(gè)字段
System.out.println("用戶年齡:" + jedis.hget("user:1001", "age")); // 輸出:25
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6. 關(guān)閉連接(重要!避免連接泄漏)
if (jedis != null) {
jedis.close();
}
}
}
}注意事項(xiàng)
- 線程安全性:
- Jedis實(shí)例是非線程安全的,每個(gè)線程應(yīng)該使用自己的Jedis實(shí)例
- 在高并發(fā)場(chǎng)景中直接使用單例連接會(huì)導(dǎo)致線程安全問題
- 性能考量:
- 每次操作都需要?jiǎng)?chuàng)建和關(guān)閉TCP連接,頻繁操作時(shí)會(huì)產(chǎn)生較大性能開銷
- 實(shí)測(cè)表明,在100次連續(xù)操作中,單實(shí)例連接方式耗時(shí)是連接池方式的10倍以上
- 實(shí)際應(yīng)用建議:
- 開發(fā)環(huán)境或簡(jiǎn)單腳本可以使用單實(shí)例連接
- 生產(chǎn)環(huán)境必須使用連接池(JedisPool)管理連接
- 對(duì)于長(zhǎng)時(shí)間運(yùn)行的任務(wù),建議使用try-with-resources確保連接關(guān)閉
- 異常處理:
- 網(wǎng)絡(luò)中斷時(shí)可能拋出JedisConnectionException
- 認(rèn)證失敗拋出JedisDataException
- 建議對(duì)關(guān)鍵操作添加重試機(jī)制
3.2 進(jìn)階使用:Jedis 連接池
步驟 1:配置 Jedis 連接池
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class JedisPoolDemo {
// 初始化Jedis連接池(使用靜態(tài)代碼塊確保只初始化一次)
private static JedisPool jedisPool;
static {
// 1. 配置連接池參數(shù)
GenericObjectPoolConfig<Jedis> poolConfig = new GenericObjectPoolConfig<>();
// 關(guān)鍵參數(shù)配置(根據(jù)實(shí)際業(yè)務(wù)需求調(diào)整)
poolConfig.setMaxTotal(100); // 最大連接數(shù)(根據(jù)Redis服務(wù)器配置調(diào)整)
poolConfig.setMaxIdle(20); // 最大空閑連接數(shù)(建議設(shè)為MaxTotal的20%-50%)
poolConfig.setMinIdle(5); // 最小空閑連接數(shù)(保持一定數(shù)量的熱連接)
poolConfig.setBlockWhenExhausted(true); // 連接耗盡時(shí)是否阻塞等待(建議true)
poolConfig.setMaxWaitMillis(3000); // 獲取連接最大等待時(shí)間(毫秒)
poolConfig.setTestOnBorrow(true); // 獲取連接時(shí)是否測(cè)試有效性(建議true,但會(huì)有性能損耗)
poolConfig.setTestOnReturn(false); // 歸還連接時(shí)是否測(cè)試(建議false)
poolConfig.setTestWhileIdle(true); // 空閑時(shí)是否測(cè)試(建議true)
// 2. 初始化連接池
String redisHost = "192.168.1.100";
int redisPort = 6379;
int timeout = 5000; // 連接超時(shí)時(shí)間(毫秒)
String password = "your_password";
jedisPool = new JedisPool(
poolConfig,
redisHost,
redisPort,
timeout,
password
);
}
// 從連接池獲取Jedis實(shí)例
public static Jedis getJedis() {
return jedisPool.getResource();
}
// 測(cè)試連接池使用
public static void main(String[] args) {
// 使用try-with-resources語法自動(dòng)管理連接
try (Jedis jedis = getJedis()) {
// 1. 操作String類型
jedis.set("product:1001", "iPhone 13");
System.out.println("商品1001名稱:" + jedis.get("product:1001"));
// 2. 操作List類型(模擬消息隊(duì)列)
jedis.lpush("message:queue", "msg1", "msg2", "msg3"); // 左推(入隊(duì))
// 右拉(出隊(duì)),0表示無限期阻塞等待
String msg = jedis.brpop(0, "message:queue").get(1);
System.out.println("消費(fèi)的消息:" + msg);
// 3. 批量操作(使用pipeline提升性能)
long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
jedis.set("key_" + i, "value_" + i);
}
long end = System.currentTimeMillis();
System.out.println("普通操作耗時(shí):" + (end - start) + "ms");
} catch (Exception e) {
e.printStackTrace();
}
}
}核心優(yōu)勢(shì)
- 性能優(yōu)化:
- 連接復(fù)用減少TCP三次握手開銷
- 實(shí)測(cè)在1000次操作中,連接池方式比單實(shí)例快8-10倍
- 支持Pipeline批量操作,進(jìn)一步提升吞吐量
- 資源管理:
- 自動(dòng)維護(hù)連接池中的活躍連接數(shù)
- 可配置連接最大存活時(shí)間(maxAge)避免長(zhǎng)時(shí)間使用的連接出現(xiàn)問題
- 通過evictor線程定期檢測(cè)和清理無效連接
- 生產(chǎn)環(huán)境配置建議:
// 典型生產(chǎn)環(huán)境配置 poolConfig.setMaxTotal(200); // 根據(jù)QPS估算,一般(平均QPS*平均耗時(shí)(ms))/1000 poolConfig.setMaxIdle(50); // 根據(jù)業(yè)務(wù)波動(dòng)情況調(diào)整 poolConfig.setMinIdle(10); // 保持一定熱連接 poolConfig.setMaxWaitMillis(1000); // 根據(jù)系統(tǒng)容忍度設(shè)置 poolConfig.setTestOnBorrow(true); // 確保獲取的連接可用 poolConfig.setTimeBetweenEvictionRunsMillis(30000); // 30秒檢測(cè)一次
- 監(jiān)控指標(biāo):
- 可通過JMX監(jiān)控連接池狀態(tài)
- 關(guān)鍵指標(biāo):活動(dòng)連接數(shù)、空閑連接數(shù)、等待獲取連接的線程數(shù)等
- 最佳實(shí)踐:
- 每個(gè)應(yīng)用使用獨(dú)立的連接池
- 根據(jù)業(yè)務(wù)類型(讀寫比例、命令復(fù)雜度)配置不同連接池
- 定期監(jiān)控連接池狀態(tài),及時(shí)調(diào)整參數(shù)
3.3 Jedis 操作核心數(shù)據(jù)結(jié)構(gòu)
(1)Set 類型(無序、唯一)
try (Jedis jedis = JedisPoolDemo.getJedis()) {
// 1. 添加元素(自動(dòng)去重)
jedis.sadd("tags:java", "spring", "mybatis", "redis");
jedis.sadd("tags:java", "spring"); // 重復(fù)元素不會(huì)被添加
// 2. 獲取所有元素(無序)
System.out.println("Java相關(guān)標(biāo)簽:" + jedis.smembers("tags:java"));
// 輸出可能是:[redis, spring, mybatis]
// 3. 判斷元素是否存在
System.out.println("是否包含spring:" + jedis.sismember("tags:java", "spring"));
// 輸出:true
// 4. 移除元素
jedis.srem("tags:java", "mybatis");
// 5. 計(jì)算集合大小
System.out.println("標(biāo)簽數(shù)量:" + jedis.scard("tags:java")); // 輸出:2
// 6. 集合運(yùn)算
jedis.sadd("tags:backend", "spring", "django", "redis");
// 交集
System.out.println("交集:" + jedis.sinter("tags:java", "tags:backend"));
// 輸出:[spring, redis]
// 并集
System.out.println("并集:" + jedis.sunion("tags:java", "tags:backend"));
// 輸出:[spring, redis, django]
// 差集(tags:java有而tags:backend沒有的)
System.out.println("差集:" + jedis.sdiff("tags:java", "tags:backend"));
// 輸出:[]
}(2)Sorted Set 類型(有序、唯一,基于分?jǐn)?shù)排序)
try (Jedis jedis = JedisPoolDemo.getJedis()) {
// 1. 添加元素(如果成員已存在,則更新分?jǐn)?shù))
jedis.zadd("rank:java", 95, "zhangsan");
jedis.zadd("rank:java", 88, "lisi");
jedis.zadd("rank:java", 92, "wangwu");
jedis.zadd("rank:java", 90, "lisi"); // 更新lisi的分?jǐn)?shù)
// 2. 按分?jǐn)?shù)升序獲?。?到-1表示全部)
System.out.println("升序排名:" + jedis.zrange("rank:java", 0, -1));
// 輸出:[lisi, wangwu, zhangsan]
// 3. 按分?jǐn)?shù)降序獲?。◣Х?jǐn)?shù))
Set<Tuple> results = jedis.zrevrangeWithScores("rank:java", 0, -1);
results.forEach(tuple ->
System.out.println(tuple.getElement() + ":" + tuple.getScore())
);
// 輸出:
// zhangsan:95.0
// wangwu:92.0
// lisi:90.0
// 4. 獲取分?jǐn)?shù)
System.out.println("lisi的分?jǐn)?shù):" + jedis.zscore("rank:java", "lisi"));
// 輸出:90.0
// 5. 獲取排名(從0開始)
System.out.println("zhangsan的降序排名:" + jedis.zrevrank("rank:java", "zhangsan"));
// 輸出:0
// 6. 范圍查詢(80<=score<=90)
System.out.println("80-90分的學(xué)生:" +
jedis.zrangeByScoreWithScores("rank:java", 80, 90));
// 輸出:[lisi:90.0, wangwu:92.0](注意包含邊界)
// 7. 原子操作:增加分?jǐn)?shù)
jedis.zincrby("rank:java", 5, "lisi"); // lisi分?jǐn)?shù)+5
System.out.println("新分?jǐn)?shù):" + jedis.zscore("rank:java", "lisi"));
// 輸出:95.0
}(3)其他數(shù)據(jù)結(jié)構(gòu)操作
// Geo地理位置操作
try (Jedis jedis = JedisPoolDemo.getJedis()) {
// 添加地理位置(經(jīng)度、緯度、名稱)
jedis.geoadd("cities", 116.404, 39.915, "beijing");
jedis.geoadd("cities", 121.474, 31.230, "shanghai");
// 計(jì)算兩地距離(單位:km)
Double dist = jedis.geodist("cities", "beijing", "shanghai", GeoUnit.KM);
System.out.println("北京到上海距離:" + dist + "km");
// 查找附近位置
List<GeoRadiusResponse> nearby = jedis.georadiusByMember(
"cities",
"beijing",
1000,
GeoUnit.KM
);
nearby.forEach(r -> System.out.println(r.getMemberByString()));
}
// HyperLogLog基數(shù)統(tǒng)計(jì)
try (Jedis jedis = JedisPoolDemo.getJedis()) {
// 添加元素(用于統(tǒng)計(jì)不重復(fù)元素?cái)?shù)量)
for (int i = 0; i < 10000; i++) {
jedis.pfadd("visitors", "user_" + i);
jedis.pfadd("visitors", "user_" + (i % 5000)); // 50%重復(fù)
}
// 獲取基數(shù)估計(jì)值
System.out.println("獨(dú)立訪客數(shù):" + jedis.pfcount("visitors"));
// 輸出約7500(誤差率約0.81%)
}四、Redisson 客戶端:Java 操作 Redis 的高級(jí)實(shí)現(xiàn)
Redisson是基于Jedis的封裝,不僅提供了更簡(jiǎn)潔易用的API,還實(shí)現(xiàn)了大量Redis官方推薦的分布式特性。相比原生Jedis,Redisson具有以下核心優(yōu)勢(shì):
- 線程安全:內(nèi)置連接池管理,徹底解決Jedis線程安全問題
- 豐富特性:支持分布式鎖(可重入鎖、公平鎖、聯(lián)鎖等)、分布式集合(Set、Map、List)、延遲隊(duì)列、布隆過濾器等20+種分布式對(duì)象
- 協(xié)議支持:完整支持Redis 5.0+的所有命令和數(shù)據(jù)類型
- 容錯(cuò)機(jī)制:自動(dòng)重連、故障轉(zhuǎn)移、讀寫分離等企業(yè)級(jí)特性
4.1 Redisson客戶端初始化
Redisson支持多種Redis部署模式,每種模式的配置方式如下:
單點(diǎn)模式配置示例
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
/**
* Redisson客戶端工廠類(推薦單例模式)
* 典型應(yīng)用場(chǎng)景:Spring Bean管理、靜態(tài)工具類等
*/
public class RedissonClientFactory {
// 使用volatile保證多線程可見性
private static volatile RedissonClient redissonClient;
// 雙重檢查鎖實(shí)現(xiàn)線程安全初始化
public static RedissonClient getRedissonClient() {
if (redissonClient == null) {
synchronized (RedissonClientFactory.class) {
if (redissonClient == null) {
// 1. 創(chuàng)建配置構(gòu)建器
Config config = new Config();
// 2. 配置單點(diǎn)模式(生產(chǎn)環(huán)境建議使用YAML/JSON配置文件)
config.useSingleServer()
.setAddress("redis://192.168.1.100:6379") // 支持rediss://協(xié)議(SSL加密)
.setPassword("your_password") // 密碼特殊字符需URL編碼
.setDatabase(0) // 默認(rèn)DB索引
.setConnectionPoolSize(100) // 最大連接數(shù)(默認(rèn)64)
.setConnectionMinimumIdleSize(10) // 最小空閑連接(默認(rèn)10)
.setConnectTimeout(5000) // 連接超時(shí)(毫秒)
.setIdleConnectionTimeout(30000) // 空閑連接超時(shí)
.setRetryAttempts(3) // 命令重試次數(shù)
.setRetryInterval(1000); // 重試間隔
// 3. 創(chuàng)建客戶端實(shí)例(實(shí)際會(huì)啟動(dòng)連接池和定時(shí)任務(wù))
redissonClient = Redisson.create(config);
}
}
}
return redissonClient;
}
// 優(yōu)雅關(guān)閉方法
public static void shutdown() {
if (redissonClient != null) {
redissonClient.shutdown();
}
}
}其他部署模式配置示例
哨兵模式
config.useSentinelServers()
.addSentinelAddress("redis://sentinel1:26379")
.addSentinelAddress("redis://sentinel2:26379")
.setMasterName("mymaster");
集群模式
config.useClusterServers()
.addNodeAddress("redis://node1:6379")
.addNodeAddress("redis://node2:6379")
.setScanInterval(2000); // 集群狀態(tài)掃描間隔
最佳實(shí)踐建議
- 連接池配置:根據(jù)QPS調(diào)整連接數(shù)(建議公式:最大連接數(shù) = QPS × 平均響應(yīng)時(shí)間(秒))
- 超時(shí)設(shè)置:網(wǎng)絡(luò)不穩(wěn)定的環(huán)境適當(dāng)增大超時(shí)時(shí)間
- 資源釋放:應(yīng)用關(guān)閉時(shí)調(diào)用
shutdown()釋放資源 - 配置分離:生產(chǎn)環(huán)境建議使用外部配置文件(JSON/YAML格式)
4.2 Redisson 操作核心數(shù)據(jù)結(jié)構(gòu)
Redisson 是一個(gè)基于 Redis 的 Java 客戶端,它通過 RedissonClient 提供了對(duì)應(yīng) Redis 數(shù)據(jù)結(jié)構(gòu)的封裝類,使得操作 Redis 更加面向?qū)ο蠛秃?jiǎn)潔。Redisson 支持 Redis 的所有核心數(shù)據(jù)結(jié)構(gòu),包括 String、Hash、List、Set 和 Sorted Set 等,每種數(shù)據(jù)結(jié)構(gòu)都有對(duì)應(yīng)的 Java 接口實(shí)現(xiàn)。
(1)String 類型(RBucket)
RBucket 是 Redisson 對(duì) Redis String 類型的封裝,提供了一系列操作字符串的方法。RBucket 支持設(shè)置過期時(shí)間,適用于緩存場(chǎng)景。
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
public class RedissonStringDemo {
public static void main(String[] args) {
// 獲取 RedissonClient 實(shí)例
RedissonClient redissonClient = RedissonClientFactory.getRedissonClient();
try {
// 獲取 RBucket 對(duì)象(對(duì)應(yīng) Redis 的 String 類型)
RBucket<String> usernameBucket = redissonClient.getBucket("username");
// 存入數(shù)據(jù),并設(shè)置過期時(shí)間為 3600 秒(1小時(shí))
usernameBucket.set("zhangsan", 3600);
// 獲取數(shù)據(jù)
String username = usernameBucket.get();
System.out.println("用戶名:" + username); // 輸出:zhangsan
// 判斷 key 是否存在
System.out.println("是否存在:" + usernameBucket.isExists()); // 輸出:true
// 刪除數(shù)據(jù)
usernameBucket.delete();
System.out.println("刪除后是否存在:" + usernameBucket.isExists()); // 輸出:false
} finally {
// 關(guān)閉客戶端(若為長(zhǎng)期運(yùn)行的服務(wù),無需頻繁關(guān)閉)
redissonClient.shutdown();
}
}
}(2)Hash 類型(RMap)
RMap 是 Redisson 對(duì) Redis Hash 類型的封裝,提供類似 Java Map 的操作方法,適用于存儲(chǔ)對(duì)象或結(jié)構(gòu)化數(shù)據(jù)。
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
public class RedissonHashDemo {
public static void main(String[] args) {
RedissonClient redissonClient = RedissonClientFactory.getRedissonClient();
try {
// 獲取 RMap 對(duì)象(對(duì)應(yīng) Redis 的 Hash 類型)
RMap<String, String> userMap = redissonClient.getMap("user:1001");
// 存入數(shù)據(jù)
userMap.put("name", "lisi");
userMap.put("age", "25");
userMap.put("gender", "male");
// 獲取數(shù)據(jù)
System.out.println("用戶姓名:" + userMap.get("name")); // 輸出:lisi
System.out.println("用戶所有信息:" + userMap); // 輸出:{name=lisi, age=25, gender=male}
// 刪除字段
userMap.remove("gender");
System.out.println("刪除 gender 后的信息:" + userMap); // 輸出:{name=lisi, age=25}
// 判斷字段是否存在
System.out.println("是否包含 age:" + userMap.containsKey("age")); // 輸出:true
} finally {
// 關(guān)閉客戶端
redissonClient.shutdown();
}
}
}(3)List 類型(RList)
RList 是 Redisson 對(duì) Redis List 類型的封裝,支持雙向添加、刪除和獲取元素,適用于隊(duì)列或棧的場(chǎng)景。
import org.redisson.api.RList;
import org.redisson.api.RedissonClient;
public class RedissonListDemo {
public static void main(String[] args) {
RedissonClient redissonClient = RedissonClientFactory.getRedissonClient();
try {
// 獲取 RList 對(duì)象(對(duì)應(yīng) Redis 的 List 類型)
RList<String> messageList = redissonClient.getList("message:queue");
// 添加元素(右添加,等同于 RPUSH)
messageList.add("msg1");
// 左添加(等同于 LPUSH)
messageList.add(0, "msg0");
// 獲取指定索引元素
System.out.println("索引 0 的元素:" + messageList.get(0)); // 輸出:msg0
// 獲取所有元素
System.out.println("所有消息:" + messageList); // 輸出:[msg0, msg1]
// 移除并返回最后一個(gè)元素(等同于 RPOP)
String lastMsg = messageList.remove(messageList.size() - 1);
System.out.println("移除的最后一條消息:" + lastMsg); // 輸出:msg1
// 獲取列表長(zhǎng)度
System.out.println("列表長(zhǎng)度:" + messageList.size()); // 輸出:1
} finally {
redissonClient.shutdown();
}
}
}(4)Set 類型(RSet)
RSet 是 Redisson 對(duì) Redis Set 類型的封裝,支持元素的添加、刪除和集合運(yùn)算,適用于標(biāo)簽或唯一值存儲(chǔ)場(chǎng)景。
import org.redisson.api.RSet;
import org.redisson.api.RedissonClient;
public class RedissonSetDemo {
public static void main(String[] args) {
RedissonClient redissonClient = RedissonClientFactory.getRedissonClient();
try {
// 獲取 RSet 對(duì)象(對(duì)應(yīng) Redis 的 Set 類型)
RSet<String> tagSet = redissonClient.getSet("tags:java");
// 添加元素
tagSet.add("spring");
tagSet.add("mybatis");
tagSet.add("redis");
// 判斷元素是否存在
System.out.println("是否包含 spring:" + tagSet.contains("spring")); // 輸出:true
// 獲取所有元素
System.out.println("Java 標(biāo)簽集合:" + tagSet); // 輸出:[spring, mybatis, redis]
// 創(chuàng)建另一個(gè) Set
RSet<String> backendTagSet = redissonClient.getSet("tags:backend");
backendTagSet.add("spring");
backendTagSet.add("django");
backendTagSet.add("redis");
// 計(jì)算并返回交集
System.out.println("Java 與 Backend 標(biāo)簽交集:" + tagSet.retainAll(backendTagSet));
// 輸出:[spring, redis](retainAll 方法會(huì)保留交集元素并返回)
// 獲取集合大小
System.out.println("交集大?。? + tagSet.size()); // 輸出:2
} finally {
redissonClient.shutdown();
}
}
}(5)Sorted Set 類型(RSortedSet)
RSortedSet 是 Redisson 對(duì) Redis Sorted Set 類型的封裝,支持按分?jǐn)?shù)排序和范圍查詢,適用于排行榜或優(yōu)先級(jí)隊(duì)列場(chǎng)景。
import org.redisson.api.RSortedSet;
import org.redisson.api.RedissonClient;
import org.redisson.api.ScoredEntry;
import java.util.Collection;
public class RedissonSortedSetDemo {
public static void main(String[] args) {
RedissonClient redissonClient = RedissonClientFactory.getRedissonClient();
try {
// 獲取 RSortedSet 對(duì)象(對(duì)應(yīng) Redis 的 Sorted Set 類型)
RSortedSet<String> rankSet = redissonClient.getSortedSet("rank:java");
// 添加元素(指定分?jǐn)?shù))
rankSet.add(95, "zhangsan");
rankSet.add(88, "lisi");
rankSet.add(92, "wangwu");
// 按分?jǐn)?shù)升序獲取元素
System.out.println("成績(jī)升序排名:" + rankSet); // 輸出:[lisi, wangwu, zhangsan]
// 按分?jǐn)?shù)降序獲取元素(含分?jǐn)?shù))
Collection<ScoredEntry<String>> scoredEntries = rankSet.entryRangeReversed(0, -1);
for (ScoredEntry<String> entry : scoredEntries) {
System.out.println(entry.getValue() + ":" + entry.getScore());
// 輸出:
// zhangsan:95.0
// wangwu:92.0
// lisi:88.0
}
// 獲取指定元素的排名(降序)
System.out.println("zhangsan 的排名:" + rankSet.revRank("zhangsan")); // 輸出:0
// 獲取分?jǐn)?shù)范圍內(nèi)的元素
Collection<String> range = rankSet.valueRange(90, true, 95, true);
System.out.println("分?jǐn)?shù)在 90~95 之間的學(xué)生:" + range); // 輸出:[wangwu, zhangsan]
} finally {
redissonClient.shutdown();
}
}
}4.3 Redisson 高級(jí)特性:分布式鎖與延遲隊(duì)列
4.3.1 分布式鎖(RLock)
在分布式系統(tǒng)中,多個(gè)服務(wù)實(shí)例可能同時(shí)操作同一資源(如訂單創(chuàng)建、庫存扣減等場(chǎng)景),分布式鎖可避免并發(fā)問題,保證數(shù)據(jù)一致性。Redisson 的RLock實(shí)現(xiàn)了與Java的ReentrantLock類似的可重入鎖特性,且支持自動(dòng)過期釋放機(jī)制,無需手動(dòng)處理死鎖問題。
核心特性
- 可重入性:同一線程可以多次獲取同一把鎖
- 自動(dòng)續(xù)期:通過watchdog機(jī)制自動(dòng)延長(zhǎng)鎖持有時(shí)間
- 公平鎖支持:可配置為公平鎖模式
- 鎖等待超時(shí):避免線程無限期等待
- 鎖釋放保障:通過finally塊確保鎖釋放
典型應(yīng)用場(chǎng)景
- 秒殺系統(tǒng)中的庫存扣減
- 支付系統(tǒng)中的訂單狀態(tài)變更
- 分布式定時(shí)任務(wù)調(diào)度
- 重要數(shù)據(jù)的并發(fā)修改
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.concurrent.TimeUnit;
public class RedissonDistributedLockDemo {
// 鎖的key命名規(guī)范:業(yè)務(wù)類型:業(yè)務(wù)ID(如訂單創(chuàng)建鎖)
private static final String LOCK_KEY = "lock:order:create";
public static void createOrder(RedissonClient redissonClient) {
// 1. 獲取分布式鎖實(shí)例(非阻塞)
RLock lock = redissonClient.getLock(LOCK_KEY);
try {
// 2. 嘗試獲取鎖(參數(shù)說明)
// - waitTime: 獲取鎖的最大等待時(shí)間(建議設(shè)置短于業(yè)務(wù)超時(shí)時(shí)間)
// - leaseTime: 鎖自動(dòng)釋放時(shí)間(業(yè)務(wù)完成前應(yīng)足夠長(zhǎng))
// - TimeUnit: 時(shí)間單位
boolean isLocked = lock.tryLock(3, 30, TimeUnit.SECONDS);
if (isLocked) {
try {
// 3. 加鎖成功,執(zhí)行業(yè)務(wù)邏輯
System.out.println(Thread.currentThread().getName() + "獲取分布式鎖成功,開始創(chuàng)建訂單...");
// 模擬業(yè)務(wù)處理耗時(shí)(如數(shù)據(jù)庫操作、遠(yuǎn)程調(diào)用等)
Thread.sleep(5000);
System.out.println("訂單創(chuàng)建完成!");
} finally {
// 4. 確保業(yè)務(wù)完成后再釋放鎖
if (lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println("分布式鎖已釋放!");
}
}
} else {
// 5. 獲取鎖失敗處理(可記錄日志、返回友好提示或重試)
System.out.println("獲取分布式鎖失敗,請(qǐng)稍后再試!");
// 可拋出特定異?;蚍祷劐e(cuò)誤碼
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("線程被中斷:" + e.getMessage());
} catch (Exception e) {
System.err.println("業(yè)務(wù)處理異常:" + e.getMessage());
}
}
public static void main(String[] args) {
RedissonClient redissonClient = RedissonClientFactory.getRedissonClient();
// 模擬3個(gè)并發(fā)線程嘗試獲取鎖
for (int i = 0; i < 3; i++) {
new Thread(() -> createOrder(redissonClient), "Thread-"+i).start();
}
// 注意:實(shí)際應(yīng)用中RedissonClient應(yīng)該是單例長(zhǎng)期存在
// redissonClient.shutdown();
}
}最佳實(shí)踐
- 鎖命名規(guī)范:使用業(yè)務(wù)相關(guān)的有意義的名稱,如
lock:order:{orderId} - 鎖超時(shí)設(shè)置:leaseTime應(yīng)大于預(yù)估的業(yè)務(wù)處理時(shí)間
- 異常處理:確保在finally塊中釋放鎖
- 避免長(zhǎng)時(shí)間持有鎖:鎖持有時(shí)間應(yīng)盡量短
- 重試機(jī)制:獲取鎖失敗時(shí)應(yīng)有合理的重試策略
4.3.2 延遲隊(duì)列(RDelayedQueue)
延遲隊(duì)列可實(shí)現(xiàn)"消息延遲一段時(shí)間后再被消費(fèi)"的場(chǎng)景,如訂單超時(shí)未支付自動(dòng)取消、定時(shí)任務(wù)觸發(fā)、預(yù)約提醒等。Redisson的RDelayedQueue基于Redis的Sorted Set實(shí)現(xiàn),提供了簡(jiǎn)單易用的API,避免了自行實(shí)現(xiàn)延遲隊(duì)列的復(fù)雜性。
核心特性
- 精確延時(shí):支持毫秒級(jí)延遲精度
- 持久化存儲(chǔ):消息不會(huì)因服務(wù)重啟而丟失
- 高可用:基于Redis集群保證可靠性
- 多消費(fèi)者支持:多個(gè)服務(wù)實(shí)例可以同時(shí)消費(fèi)
- 消息順序保障:按延遲時(shí)間順序消費(fèi)消息
典型應(yīng)用場(chǎng)景
- 電商訂單超時(shí)自動(dòng)取消(30分鐘未支付)
- 會(huì)議開始前15分鐘提醒
- 延時(shí)任務(wù)調(diào)度
- 紅包24小時(shí)未領(lǐng)取自動(dòng)退回
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import java.util.concurrent.TimeUnit;
public class RedissonDelayedQueueDemo {
// 延遲隊(duì)列命名規(guī)范:業(yè)務(wù)類型:業(yè)務(wù)目的
private static final String DELAY_QUEUE_KEY = "delay:queue:order";
private static final String BLOCKING_QUEUE_KEY = "blocking:queue:order";
/**
* 生產(chǎn)者:添加延遲消息
* @param redissonClient Redisson客戶端
* @param message 消息內(nèi)容(建議包含業(yè)務(wù)ID)
* @param delayTime 延遲時(shí)間(單位:秒)
*/
public static void produceDelayMessage(RedissonClient redissonClient, String message, long delayTime) {
// 1. 獲取阻塞隊(duì)列(底層實(shí)際存儲(chǔ)結(jié)構(gòu))
RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue(BLOCKING_QUEUE_KEY);
// 2. 創(chuàng)建延遲隊(duì)列(與阻塞隊(duì)列關(guān)聯(lián))
try (RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue)) {
// 3. 添加延遲消息
delayedQueue.offer(message, delayTime, TimeUnit.SECONDS);
System.out.println("[" + System.currentTimeMillis() + "] 添加延遲消息:" + message
+ ",延遲時(shí)間:" + delayTime + "秒");
} // 自動(dòng)關(guān)閉延遲隊(duì)列(Java 7+ try-with-resources)
}
/**
* 消費(fèi)者:持續(xù)消費(fèi)延遲消息
* @param redissonClient Redisson客戶端
*/
public static void consumeDelayMessage(RedissonClient redissonClient) {
// 1. 獲取阻塞隊(duì)列(與生產(chǎn)者使用的相同)
RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue(BLOCKING_QUEUE_KEY);
System.out.println("消費(fèi)者啟動(dòng),等待消費(fèi)延遲消息...");
while (!Thread.currentThread().isInterrupted()) {
try {
// 2. 阻塞獲取消息(當(dāng)有消息到期時(shí)自動(dòng)喚醒)
String message = blockingQueue.take();
// 3. 處理消息
long currentTime = System.currentTimeMillis();
System.out.println("[" + currentTime + "] 消費(fèi)延遲消息:" + message);
// 根據(jù)消息內(nèi)容處理業(yè)務(wù)
if (message != null && message.startsWith("order:")) {
String[] parts = message.split(":");
if (parts.length >= 2) {
String orderId = parts[1];
System.out.println("處理訂單超時(shí):訂單" + orderId + "超時(shí)未支付,已自動(dòng)取消!");
// 實(shí)際業(yè)務(wù)中此處會(huì)調(diào)用訂單服務(wù)取消訂單
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("消費(fèi)者線程被中斷");
break;
} catch (Exception e) {
System.err.println("消費(fèi)消息異常:" + e.getMessage());
// 可添加重試或錯(cuò)誤處理邏輯
}
}
}
public static void main(String[] args) {
RedissonClient redissonClient = RedissonClientFactory.getRedissonClient();
// 啟動(dòng)消費(fèi)者線程(實(shí)際應(yīng)用中通常是獨(dú)立服務(wù))
Thread consumerThread = new Thread(() -> consumeDelayMessage(redissonClient));
consumerThread.setDaemon(true);
consumerThread.start();
// 模擬生產(chǎn)3個(gè)訂單延遲消息
produceDelayMessage(redissonClient, "order:10001", 5); // 5秒后超時(shí)
produceDelayMessage(redissonClient, "order:10002", 10); // 10秒后超時(shí)
produceDelayMessage(redissonClient, "order:10003", 15); // 15秒后超時(shí)
// 保持主線程運(yùn)行(演示用)
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 實(shí)際應(yīng)用中不需要關(guān)閉(長(zhǎng)期運(yùn)行的服務(wù))
// redissonClient.shutdown();
}
}最佳實(shí)踐
- 消息設(shè)計(jì):消息內(nèi)容應(yīng)包含足夠業(yè)務(wù)信息(如訂單ID)
- 異常處理:消費(fèi)者需處理各種異常情況
- 冪等性:消息處理應(yīng)保證冪等,防止重復(fù)消費(fèi)
- 監(jiān)控報(bào)警:對(duì)消費(fèi)延遲和堆積情況監(jiān)控
- 資源釋放:正確關(guān)閉延遲隊(duì)列實(shí)例
性能優(yōu)化建議
- 批量消費(fèi):可適當(dāng)批量獲取消息減少網(wǎng)絡(luò)開銷
- 分區(qū)設(shè)計(jì):大流量場(chǎng)景可按業(yè)務(wù)ID分多個(gè)隊(duì)列
- 消費(fèi)并行度:根據(jù)業(yè)務(wù)需求調(diào)整消費(fèi)者數(shù)量
- 消息壓縮:大消息可考慮壓縮后再存儲(chǔ)
五、Spring Boot 整合 Redis:企業(yè)級(jí)項(xiàng)目實(shí)戰(zhàn)
在 Spring Boot 項(xiàng)目中,可通過 spring-boot-starter-data-redis 快速整合 Redis,簡(jiǎn)化配置和開發(fā)。該 starter 默認(rèn)使用 Spring Data Redis 抽象層,支持 Jedis、Lettuce(Spring Boot 2.x 默認(rèn)客戶端)等底層客戶端。Spring Data Redis 提供了統(tǒng)一的 API 來操作 Redis,支持多種數(shù)據(jù)結(jié)構(gòu)的操作,包括字符串、哈希、列表、集合等。
5.1 環(huán)境配置
5.1.1 添加 Maven 依賴
在 pom.xml 中添加以下依賴:
<!-- Spring Boot Redis Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<!-- 排除默認(rèn)的Lettuce客戶端(若需使用Jedis,可添加此配置) -->
<!-- <exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions> -->
</dependency>
<!-- 若排除Lettuce后,需添加Jedis依賴 -->
<!-- <dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency> -->
<!-- Spring Boot Web Starter(用于演示接口) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>說明:
spring-boot-starter-data-redis是 Spring Boot 提供的 Redis 啟動(dòng)器,自動(dòng)配置了 Redis 相關(guān)的 Bean。- 默認(rèn)使用 Lettuce 客戶端,如需切換為 Jedis,需排除 Lettuce 并添加 Jedis 依賴。
spring-boot-starter-web用于演示 RESTful 接口,實(shí)際項(xiàng)目中根據(jù)需求添加。
5.1.2 配置 application.yml
在 src/main/resources/application.yml 中添加 Redis 相關(guān)配置:
spring:
redis:
host: 192.168.1.100 # Redis服務(wù)地址,生產(chǎn)環(huán)境建議使用域名或內(nèi)網(wǎng)IP
port: 6379 # Redis端口,默認(rèn)6379
password: your_password # Redis密碼(若未設(shè),可省略)
database: 0 # Redis數(shù)據(jù)庫索引(0-15),默認(rèn)0
timeout: 5000ms # 連接超時(shí)時(shí)間
lettuce: # Lettuce客戶端配置(若使用Jedis,需改為jedis配置)
pool: # 連接池配置
max-active: 100 # 最大連接數(shù),默認(rèn)8
max-idle: 20 # 最大空閑連接數(shù),默認(rèn)8
min-idle: 5 # 最小空閑連接數(shù),默認(rèn)0
max-wait: 3000ms # 連接池耗盡時(shí)的最大等待時(shí)間,默認(rèn)-1(無限等待)配置項(xiàng)詳解:
host和port:Redis 服務(wù)器地址和端口,必填項(xiàng)。password:如果 Redis 設(shè)置了密碼,需配置此項(xiàng)。database:Redis 默認(rèn)有 16 個(gè)數(shù)據(jù)庫(0-15),可通過此配置選擇使用的數(shù)據(jù)庫。timeout:連接 Redis 的超時(shí)時(shí)間,建議設(shè)置為 5000ms 以上。lettuce.pool:Lettuce 連接池配置,適用于高并發(fā)場(chǎng)景:max-active:最大連接數(shù),根據(jù)業(yè)務(wù)量調(diào)整。max-idle和min-idle:空閑連接數(shù),避免頻繁創(chuàng)建和銷毀連接。max-wait:獲取連接的最大等待時(shí)間,避免長(zhǎng)時(shí)間阻塞。
Jedis 配置示例(如需切換):
spring:
redis:
host: 192.168.1.100
port: 6379
jedis:
pool:
max-active: 100
max-idle: 20
min-idle: 5
max-wait: 3000ms生產(chǎn)環(huán)境建議:
- 使用連接池以提高性能。
- 密碼和敏感信息建議通過環(huán)境變量或配置中心管理。
- 對(duì)于集群或哨兵模式,需額外配置。
5.2 核心 API:RedisTemplate 與 StringRedisTemplate
Spring Data Redis 提供了兩種主要的模板類用于操作 Redis,它們各自適用于不同的使用場(chǎng)景:
StringRedisTemplate
StringRedisTemplate 是專門用于操作 String 類型數(shù)據(jù)的模板類,具有以下特點(diǎn):
- 序列化方式:默認(rèn)使用 StringRedisSerializer,所有鍵和值都以 UTF-8 編碼的字符串形式存儲(chǔ)
- 優(yōu)勢(shì):
- 完全避免中文亂碼問題
- 存儲(chǔ)的數(shù)據(jù)可直接在 Redis CLI 中查看
- 性能較高,適用于簡(jiǎn)單字符串操作
- 限制:
- 只能操作字符串類型的數(shù)據(jù)
- 無法直接存儲(chǔ) Java 對(duì)象
RedisTemplate
RedisTemplate 是通用模板類,支持所有 Redis 數(shù)據(jù)結(jié)構(gòu),但需要注意:
- 默認(rèn)序列化問題:
- 默認(rèn)使用 JdkSerializationRedisSerializer
- 會(huì)導(dǎo)致存儲(chǔ)的數(shù)據(jù)帶有"xac\xed\x00\x05t\x00"等序列化前綴
- 存儲(chǔ)的數(shù)據(jù)在 Redis CLI 中不可讀
- 適用場(chǎng)景:
- 需要操作復(fù)雜數(shù)據(jù)結(jié)構(gòu)(List, Set, ZSet等)
- 需要直接存儲(chǔ) Java 對(duì)象
- 需要自定義序列化方式
5.2.1 配置 RedisTemplate(自定義序列化)
為優(yōu)化 RedisTemplate 的默認(rèn)行為,推薦配置自定義序列化:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 設(shè)置連接工廠(必需)
redisTemplate.setConnectionFactory(redisConnectionFactory);
// Key序列化配置(String類型)
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
// Value序列化配置(JSON格式)
GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer =
new GenericJackson2JsonRedisSerializer();
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
// 初始化模板(必需)
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}配置說明:
- 鍵序列化使用 StringRedisSerializer,保證鍵的可讀性
- 值序列化使用 Jackson,支持復(fù)雜對(duì)象存儲(chǔ)
- 必須調(diào)用 afterPropertiesSet() 完成初始化
5.2.2 StringRedisTemplate 使用示例
StringRedisTemplate 的常見操作示例:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/redis/string")
public class StringRedisController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
// 存儲(chǔ)String數(shù)據(jù)(帶過期時(shí)間)
@PostMapping("/set")
public String setString(@RequestParam String key,
@RequestParam String value,
@RequestParam(required = false, defaultValue = "3600") long timeout) {
stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
return "存儲(chǔ)成功!Key:" + key + ",Value:" + value;
}
// 獲取String數(shù)據(jù)
@GetMapping("/get/{key}")
public String getString(@PathVariable String key) {
String value = stringRedisTemplate.opsForValue().get(key);
return "獲取到的數(shù)據(jù):Key:" + key + ",Value:" + (value == null ? "無" : value);
}
// 存儲(chǔ)Hash數(shù)據(jù)
@PostMapping("/hash/set")
public String setHash(@RequestParam String key,
@RequestParam Map<String, String> hashMap) {
stringRedisTemplate.opsForHash().putAll(key, hashMap);
return "Hash數(shù)據(jù)存儲(chǔ)成功!Key:" + key;
}
// 獲取Hash數(shù)據(jù)
@GetMapping("/hash/get/{key}")
public Map<Object, Object> getHash(@PathVariable String key) {
return stringRedisTemplate.opsForHash().entries(key);
}
// 設(shè)置過期時(shí)間
@PostMapping("/expire")
public Boolean expireKey(@RequestParam String key,
@RequestParam long timeout) {
return stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
}5.2.3 RedisTemplate 使用示例(操作對(duì)象)
定義實(shí)體類:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String gender;
}對(duì)象操作示例:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/redis/object")
public class ObjectRedisController {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 存儲(chǔ)User對(duì)象
@PostMapping("/user/set")
public String setUser(@RequestBody User user,
@RequestParam(required = false, defaultValue = "86400") long timeout) {
String key = "user:" + user.getId();
redisTemplate.opsForValue().set(key, user, timeout, TimeUnit.SECONDS);
return "User對(duì)象存儲(chǔ)成功!Key:" + key + ",User:" + user;
}
// 獲取User對(duì)象
@GetMapping("/user/get")
public User getUser(@RequestParam Long userId) {
String key = "user:" + userId;
return (User) redisTemplate.opsForValue().get(key);
}
// 刪除User對(duì)象
@PostMapping("/user/delete")
public String deleteUser(@RequestParam Long userId) {
String key = "user:" + userId;
Boolean isDeleted = redisTemplate.delete(key);
return isDeleted ? "User對(duì)象刪除成功!Key:" + key : "刪除失敗,Key不存在";
}
}操作說明:
- 對(duì)象存儲(chǔ)會(huì)自動(dòng)使用配置的 Jackson 序列化器
- 獲取時(shí)會(huì)自動(dòng)反序列化為 Java 對(duì)象
- 建議使用"類型:id"的鍵命名規(guī)范(如"user:123")
5.3 Spring Boot Redis緩存注解:簡(jiǎn)化緩存開發(fā)
Spring Boot提供了@Cacheable、@CachePut、@CacheEvict等緩存注解,通過聲明式的方式快速實(shí)現(xiàn)緩存功能,無需手動(dòng)調(diào)用RedisTemplate。這些注解底層基于Spring Cache抽象,可以與多種緩存實(shí)現(xiàn)(如Redis、EhCache等)無縫集成,大大簡(jiǎn)化了緩存開發(fā)流程。
5.3.1 開啟緩存注解支持
在Spring Boot啟動(dòng)類上添加@EnableCaching注解:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching // 開啟緩存注解支持,自動(dòng)配置CacheManager
public class RedisSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(RedisSpringBootApplication.class, args);
}
}注意事項(xiàng):
- 確保項(xiàng)目中已引入
spring-boot-starter-cache和spring-boot-starter-data-redis依賴 - 在application.properties/yml中配置Redis連接信息
- 默認(rèn)情況下會(huì)自動(dòng)配置RedisCacheManager
5.3.2 常用緩存注解詳解
(1)@Cacheable:查詢緩存
當(dāng)方法被調(diào)用時(shí),先從緩存中查詢數(shù)據(jù)。若緩存存在,則直接返回緩存數(shù)據(jù);若緩存不存在,則執(zhí)行方法邏輯,并將結(jié)果存入緩存。
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class UserService {
/**
* 根據(jù)用戶ID查詢用戶信息
* @param userId 用戶ID
* @return 用戶對(duì)象
*
* 緩存配置說明:
* - value="userCache": 指定緩存名稱(相當(dāng)于Redis中的key前綴)
* - key="#userId": 動(dòng)態(tài)生成緩存key(使用SpEL表達(dá)式獲取方法參數(shù))
* - unless="#result == null": 當(dāng)方法返回null時(shí)不緩存結(jié)果
*/
@Cacheable(value = "userCache", key = "#userId", unless = "#result == null")
public User getUserById(Long userId) {
// 模擬從數(shù)據(jù)庫查詢數(shù)據(jù)(實(shí)際開發(fā)中替換為DAO層調(diào)用)
System.out.println("從數(shù)據(jù)庫查詢用戶,userId:" + userId);
return new User(userId, "張三", 25, "男");
}
}典型應(yīng)用場(chǎng)景:
- 高頻訪問但數(shù)據(jù)變化不頻繁的查詢,如商品詳情、用戶信息等
- 復(fù)雜計(jì)算結(jié)果的緩存,如報(bào)表數(shù)據(jù)、統(tǒng)計(jì)結(jié)果等
(2)@CachePut:更新緩存
執(zhí)行方法邏輯后,將結(jié)果存入緩存(無論緩存是否存在,都會(huì)覆蓋原有緩存),常用于數(shù)據(jù)新增或更新場(chǎng)景。
import org.springframework.cache.annotation.CachePut;
import org.springframework.stereotype.Service;
@Service
public class UserService {
/**
* 保存或更新用戶信息
* @param user 用戶對(duì)象
* @return 更新后的用戶對(duì)象
*
* 緩存配置說明:
* - value="userCache": 緩存名稱
* - key="#user.id": 使用用戶ID作為緩存key
* - unless="#result == null": 更新失敗不緩存
*/
@CachePut(value = "userCache", key = "#user.id", unless = "#result == null")
public User saveOrUpdateUser(User user) {
// 模擬數(shù)據(jù)庫新增/更新操作
System.out.println("數(shù)據(jù)庫新增/更新用戶,user:" + user);
return user; // 將更新后的用戶對(duì)象存入緩存
}
}使用建議:
- 方法返回值必須是需要緩存的對(duì)象
- 確保key與查詢方法@Cacheable的key一致
- 適用于insert和update操作
(3)@CacheEvict:刪除緩存
執(zhí)行方法邏輯后,刪除指定緩存,常用于數(shù)據(jù)刪除場(chǎng)景。
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
@Service
public class UserService {
/**
* 刪除用戶
* @param userId 用戶ID
*
* 緩存配置說明:
* - value="userCache": 緩存名稱
* - key="#userId": 指定要?jiǎng)h除的緩存key
* - allEntries=false: 默認(rèn)只刪除指定key的緩存
*/
@CacheEvict(value = "userCache", key = "#userId", allEntries = false)
public void deleteUser(Long userId) {
// 模擬數(shù)據(jù)庫刪除操作
System.out.println("數(shù)據(jù)庫刪除用戶,userId:" + userId);
}
/**
* 清空用戶緩存
*
* 緩存配置說明:
* - value="userCache": 緩存名稱
* - allEntries=true: 刪除該緩存名稱下的所有緩存
*/
@CacheEvict(value = "userCache", allEntries = true)
public void clearUserCache() {
System.out.println("清空用戶緩存");
}
}刪除策略選擇:
- 單條刪除:使用key指定要?jiǎng)h除的緩存項(xiàng)
- 批量刪除:設(shè)置allEntries=true清空整個(gè)緩存區(qū)域
- 可在方法執(zhí)行前刪除(beforeInvocation=true)
(4)@Caching:組合緩存注解
當(dāng)一個(gè)方法需要同時(shí)使用多個(gè)緩存注解(如同時(shí)更新和刪除緩存)時(shí),可使用@Caching組合注解。
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
@Service
public class UserService {
/**
* 更新用戶信息并清空用戶列表緩存
* @param user 用戶對(duì)象
* @return 更新后的用戶對(duì)象
*/
@Caching(
put = {
// 更新用戶緩存
@CachePut(value = "userCache", key = "#user.id")
},
evict = {
// 刪除用戶列表緩存(假設(shè)存在用戶列表緩存)
@CacheEvict(value = "userListCache", allEntries = true)
}
)
public User updateUserAndClearListCache(User user) {
// 模擬數(shù)據(jù)庫更新操作
System.out.println("更新用戶并清空用戶列表緩存,user:" + user);
return user;
}
}適用場(chǎng)景:
- 需要同時(shí)操作多個(gè)緩存區(qū)域
- 需要同時(shí)執(zhí)行put和evict操作
- 復(fù)雜的緩存策略組合
六、Redis 進(jìn)階:持久化、高可用與性能優(yōu)化
在實(shí)際生產(chǎn)環(huán)境中,僅掌握基礎(chǔ)使用還不夠,還需深入理解 Redis 的持久化機(jī)制、高可用架構(gòu)配置及性能優(yōu)化技巧,確保 Redis 服務(wù)穩(wěn)定高效運(yùn)行。本章將詳細(xì)介紹這些關(guān)鍵內(nèi)容。
6.1 Redis 持久化機(jī)制
Redis 提供兩種持久化方式,可根據(jù)業(yè)務(wù)需求選擇單獨(dú)使用或組合使用,確保數(shù)據(jù)安全性和系統(tǒng)可靠性。
6.1.1 RDB(Redis Database):快照持久化
原理:在指定時(shí)間間隔內(nèi),將 Redis 內(nèi)存中的數(shù)據(jù)生成二進(jìn)制快照文件(.rdb 文件)并保存到磁盤。這種機(jī)制類似于數(shù)據(jù)庫的全量備份。
觸發(fā)方式:
- 手動(dòng)觸發(fā):
save命令:阻塞 Redis 主線程直到快照完成,期間無法處理其他請(qǐng)求(生產(chǎn)環(huán)境不推薦)bgsave命令:Redis 會(huì) fork 一個(gè)子進(jìn)程進(jìn)行快照操作,主進(jìn)程繼續(xù)提供服務(wù)(推薦方式)
- 自動(dòng)觸發(fā):
- 通過 redis.conf 配置文件中的 save 指令設(shè)置觸發(fā)條件
- 當(dāng) Redis 正常關(guān)閉時(shí)(shutdown命令)也會(huì)自動(dòng)執(zhí)行 RDB 持久化
典型配置示例:
# 900秒(15分鐘)內(nèi)至少1個(gè)key變化,觸發(fā)RDB save 900 1 # 300秒(5分鐘)內(nèi)至少10個(gè)key變化,觸發(fā)RDB save 300 10 # 60秒內(nèi)至少10000個(gè)key變化,觸發(fā)RDB save 60 10000 # RDB文件名稱 dbfilename dump.rdb # 工作目錄(保存RDB文件和AOF文件的目錄) dir /var/lib/redis
優(yōu)缺點(diǎn)分析:
- 優(yōu)點(diǎn):
- 快照文件體積小,占用空間少
- 恢復(fù)速度快,特別適合大規(guī)模數(shù)據(jù)恢復(fù)
- 對(duì) Redis 性能影響小,適合定期備份
- 缺點(diǎn):
- 可能丟失最后一次快照后的所有數(shù)據(jù)(如 Redis 異常崩潰時(shí))
- 當(dāng)數(shù)據(jù)量很大時(shí),fork 子進(jìn)程的過程可能耗時(shí)較長(zhǎng),導(dǎo)致短暫的服務(wù)停頓
6.1.2 AOF(Append Only File):日志持久化
原理:將 Redis 的每一條寫命令(如 SET、HSET)以協(xié)議文本格式追加到 AOF 日志文件中,Redis 重啟時(shí)通過重新執(zhí)行日志中的命令來重建數(shù)據(jù)。
AOF 工作流程:
- 命令執(zhí)行后寫入 AOF 緩沖區(qū)
- 根據(jù)配置的刷盤策略將緩沖區(qū)內(nèi)容同步到磁盤
- 定期進(jìn)行 AOF 重寫(rewrite)壓縮文件體積
配置詳解:
# 開啟AOF(默認(rèn)關(guān)閉,需手動(dòng)設(shè)置為yes) appendonly yes # AOF文件名稱 appendfilename "appendonly.aof" # AOF刷盤策略(關(guān)鍵配置) # everysec:每秒刷盤一次(推薦,平衡性能和數(shù)據(jù)安全性) # always:每執(zhí)行一條命令刷盤一次(數(shù)據(jù)零丟失,性能較差) # no:由操作系統(tǒng)決定刷盤時(shí)機(jī)(性能最好,數(shù)據(jù)丟失風(fēng)險(xiǎn)最高) appendfsync everysec # AOF重寫觸發(fā)條件 # 當(dāng)前AOF文件大小比起最后一次重寫時(shí)的大小增長(zhǎng)率達(dá)到100%時(shí)觸發(fā) auto-aof-rewrite-percentage 100 # AOF文件最小重寫大小,避免文件很小就重寫 auto-aof-rewrite-min-size 64mb # 加載AOF時(shí)發(fā)現(xiàn)文件末尾不完整時(shí)的處理方式 aof-load-truncated yes
AOF 重寫機(jī)制:
- 原理:創(chuàng)建一個(gè)新的 AOF 文件,包含重建當(dāng)前數(shù)據(jù)集所需的最少命令集
- 觸發(fā)方式:
- 自動(dòng)觸發(fā):根據(jù) auto-aof-rewrite-percentage 和 auto-aof-rewrite-min-size 配置
- 手動(dòng)觸發(fā):執(zhí)行
BGREWRITEAOF命令
優(yōu)缺點(diǎn)分析:
- 優(yōu)點(diǎn):
- 數(shù)據(jù)安全性高,最多丟失1秒數(shù)據(jù)(everysec配置下)
- AOF 文件可讀性強(qiáng),便于問題排查
- 通過重寫機(jī)制可以壓縮文件大小
- 缺點(diǎn):
- AOF 文件通常比 RDB 文件大
- 恢復(fù)速度比 RDB 慢
- 在高寫入負(fù)載下,AOF 可能影響 Redis 性能
6.1.3 RDB 與 AOF 組合使用
生產(chǎn)環(huán)境中推薦同時(shí)開啟 RDB 和 AOF,充分發(fā)揮兩者的優(yōu)勢(shì):
- 數(shù)據(jù)恢復(fù)策略:
- 日?;謴?fù):優(yōu)先使用 AOF 恢復(fù)(數(shù)據(jù)更完整)
- 大規(guī)模數(shù)據(jù)恢復(fù):若 AOF 文件過大,先使用 RDB 快速恢復(fù)基礎(chǔ)數(shù)據(jù),再通過 AOF 補(bǔ)充后續(xù)數(shù)據(jù)
- 典型配置組合:
- RDB 配置為每天全量備份一次
- AOF 配置為每秒刷盤
- 定期將 RDB 文件和 AOF 文件備份到異地
- 注意事項(xiàng):
- Redis 4.0+ 支持混合持久化(aof-use-rdb-preamble),AOF 文件前半部分是 RDB 格式,后半部分是 AOF 格式,可以結(jié)合兩者優(yōu)點(diǎn)
- 需要監(jiān)控磁盤空間使用情況,避免持久化文件占滿磁盤
6.2 Redis 高可用配置
為避免 Redis 單點(diǎn)故障,需搭建高可用架構(gòu),確保服務(wù)持續(xù)可用。Redis 提供了多種高可用方案,適用于不同場(chǎng)景。
6.2.1 主從復(fù)制(Master-Slave)
原理:將一臺(tái) Redis 服務(wù)器(Master)的數(shù)據(jù)同步到多臺(tái) Redis 服務(wù)器(Slave),實(shí)現(xiàn)數(shù)據(jù)冗余和讀寫分離。
核心特性:
- 異步復(fù)制:Slave 異步從 Master 同步數(shù)據(jù)
- 讀寫分離:Master 處理寫請(qǐng)求,Slave 處理讀請(qǐng)求
- 級(jí)聯(lián)復(fù)制:Slave 可以作為其他 Slave 的 Master
配置步驟(以 1 主 2 從為例):
- 準(zhǔn)備 3 臺(tái) Redis 服務(wù)器(假設(shè)IP為192.168.1.100-102)
- 配置 Master(192.168.1.100): 確保 redis.conf 中有以下配置:
# 如果需要密碼認(rèn)證 requirepass your_password
- 配置 Slave(192.168.1.101 和 192.168.1.102):
# 指定Master的地址和端口 replicaof 192.168.1.100 6379 # 若Master設(shè)置了密碼,需配置認(rèn)證密碼 masterauth your_password # 可選:設(shè)置Slave只讀 replica-read-only yes
- 驗(yàn)證主從狀態(tài):
- 在 Master 上執(zhí)行
info replication查看 Slave 連接信息 - 在 Slave 上執(zhí)行
info replication查看復(fù)制狀態(tài)
- 在 Master 上執(zhí)行
主從復(fù)制過程:
- Slave 啟動(dòng)后向 Master 發(fā)送 SYNC 命令
- Master 執(zhí)行 BGSAVE 生成 RDB 文件并發(fā)送給 Slave
- Master 將生成 RDB 期間的新命令緩存起來,之后發(fā)送給 Slave
- 之后 Master 將所有寫命令異步發(fā)送給 Slave
注意事項(xiàng):
- 復(fù)制延遲問題:網(wǎng)絡(luò)延遲或 Slave 負(fù)載過高可能導(dǎo)致數(shù)據(jù)不一致
- 數(shù)據(jù)過期問題:Redis 的過期策略在主從模式下有特殊處理
- 復(fù)制中斷處理:可通過
replica-serve-stale-data yes配置Slave在斷開連接時(shí)是否繼續(xù)提供服務(wù)
6.2.2 哨兵模式(Sentinel)
原理:在主從復(fù)制的基礎(chǔ)上,增加哨兵進(jìn)程監(jiān)控節(jié)點(diǎn)狀態(tài),實(shí)現(xiàn)自動(dòng)故障轉(zhuǎn)移。
哨兵核心功能:
- 監(jiān)控:持續(xù)檢查 Master 和 Slave 是否正常運(yùn)行
- 通知:當(dāng)監(jiān)控的 Redis 實(shí)例出現(xiàn)問題時(shí),可以通過API通知管理員
- 自動(dòng)故障轉(zhuǎn)移:當(dāng) Master 不可用時(shí),自動(dòng)選舉新的 Master
- 配置提供者:為客戶端提供最新的 Master 地址
配置步驟(3個(gè)哨兵節(jié)點(diǎn)):
- 準(zhǔn)備哨兵配置文件 sentinel.conf:
port 26379 sentinel monitor mymaster 192.168.1.100 6379 2 sentinel auth-pass mymaster your_password sentinel down-after-milliseconds mymaster 30000 sentinel parallel-syncs mymaster 1 sentinel failover-timeout mymaster 180000
- 啟動(dòng)哨兵:
redis-sentinel /path/to/sentinel.conf
- 驗(yàn)證哨兵狀態(tài):
redis-cli -p 26379 info sentinel
故障轉(zhuǎn)移流程:
- 哨兵檢測(cè)到 Master 不可達(dá)(超過 down-after-milliseconds 時(shí)間)
- 哨兵集群選舉領(lǐng)導(dǎo)者哨兵(需要至少 quorum 數(shù)量的哨兵同意)
- 領(lǐng)導(dǎo)者哨兵從 Slave 中選擇新的 Master(基于優(yōu)先級(jí)、復(fù)制偏移量等)
- 將其他 Slave 重新配置為復(fù)制新的 Master
- 通知客戶端配置變更
客戶端訪問哨兵模式的最佳實(shí)踐:
- 客戶端應(yīng)連接哨兵獲取當(dāng)前 Master 地址
- 客戶端應(yīng)訂閱哨兵的
+switch-master事件處理主從切換 - 建議使用支持哨兵的 Redis 客戶端庫(如Jedis、Lettuce)
6.2.3 Redis Cluster(集群)
原理:將數(shù)據(jù)分片存儲(chǔ)到多個(gè)節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)負(fù)責(zé)一部分哈希槽(共16384個(gè)槽),同時(shí)每個(gè)分片可配置主從復(fù)制。
集群特性:
- 數(shù)據(jù)分片:自動(dòng)將數(shù)據(jù)分布到多個(gè)節(jié)點(diǎn)
- 高可用:每個(gè)分片可以配置主從復(fù)制
- 線性擴(kuò)展:可通過增加節(jié)點(diǎn)擴(kuò)展集群容量和性能
- 客戶端路由:客戶端可直接連接正確的節(jié)點(diǎn)
配置步驟(3主3從集群):
- 準(zhǔn)備6臺(tái)服務(wù)器(192.168.1.100-105),確保端口6379和16379開放
- 每個(gè)節(jié)點(diǎn)的 redis.conf 配置:
cluster-enabled yes cluster-config-file nodes-6379.conf cluster-node-timeout 15000 cluster-announce-ip 192.168.1.100 # 每臺(tái)填寫自己的IP cluster-announce-port 6379 cluster-announce-bus-port 16379
- 啟動(dòng)所有節(jié)點(diǎn)后,創(chuàng)建集群:
redis-cli --cluster create \ 192.168.1.100:6379 192.168.1.101:6379 192.168.1.102:6379 \ 192.168.1.103:6379 192.168.1.104:6379 192.168.1.105:6379 \ --cluster-replicas 1
- 驗(yàn)證集群狀態(tài):
redis-cli -c -h 192.168.1.100 -p 6379 cluster info redis-cli -c -h 192.168.1.100 -p 6379 cluster nodes
數(shù)據(jù)分片原理:
- Redis 使用 CRC16(key) mod 16384 計(jì)算 key 所屬的槽位
- 每個(gè)節(jié)點(diǎn)負(fù)責(zé)一部分連續(xù)的槽位
- 客戶端可以緩存槽位到節(jié)點(diǎn)的映射關(guān)系
集群管理命令:
cluster meet:將節(jié)點(diǎn)加入集群cluster addslots:分配槽位給節(jié)點(diǎn)cluster replicate:設(shè)置節(jié)點(diǎn)為從節(jié)點(diǎn)cluster failover:手動(dòng)觸發(fā)故障轉(zhuǎn)移
集群擴(kuò)展操作:
- 添加新節(jié)點(diǎn):
redis-cli --cluster add-node new_host:new_port existing_host:existing_port
- 遷移槽位:
redis-cli --cluster reshard host:port
- 刪除節(jié)點(diǎn):
redis-cli --cluster del-node host:port node_id
6.3 Redis 性能優(yōu)化建議
6.3.1 硬件優(yōu)化
內(nèi)存優(yōu)化:
- 為 Redis 分配足夠的內(nèi)存,避免頻繁交換
- 使用
maxmemory限制 Redis 最大內(nèi)存使用量 - 考慮使用大頁內(nèi)存(transparent huge pages)
CPU優(yōu)化:
- Redis 6.0+ 支持多線程IO(I/O threading),可配置:
io-threads 4 io-threads-do-reads yes
- 在高并發(fā)場(chǎng)景下,可考慮使用多個(gè) Redis 實(shí)例分擔(dān)負(fù)載
磁盤優(yōu)化:
- 使用 SSD 存儲(chǔ)持久化文件
- 確保磁盤有足夠的寫入帶寬
- 對(duì)于 AOF,可以考慮使用
no-appendfsync-on-rewrite yes減少重寫時(shí)的磁盤壓力
6.3.2 配置優(yōu)化
內(nèi)存管理配置:
# 內(nèi)存淘汰策略(當(dāng)內(nèi)存達(dá)到maxmemory時(shí)) maxmemory-policy allkeys-lru # 設(shè)置最大內(nèi)存(例如4GB) maxmemory 4gb # 避免內(nèi)存碎片 activedefrag yes
網(wǎng)絡(luò)配置優(yōu)化:
# 提高TCP連接隊(duì)列大小 tcp-backlog 511 # 客戶端空閑超時(shí)時(shí)間 timeout 0 # 0表示不超時(shí) # 最大客戶端連接數(shù) maxclients 10000
持久化優(yōu)化:
# 禁用持久化(僅用于純緩存場(chǎng)景) save "" appendonly no # 如果使用RDB,適當(dāng)調(diào)整save參數(shù) save 3600 1 # 1小時(shí)內(nèi)至少有1個(gè)變化則保存
6.3.3 開發(fā)優(yōu)化
鍵設(shè)計(jì)原則:
- 使用簡(jiǎn)潔但有意義的鍵名,如
user:1000:profile - 避免使用大鍵(如包含百萬元素的hash)
- 為鍵設(shè)置合理的過期時(shí)間
命令優(yōu)化:
- 使用批量操作命令:
MSET替代多個(gè)SETHMGET替代多個(gè)HGET
- 使用管道(pipeline)減少網(wǎng)絡(luò)往返:
pipeline = redis.pipeline() pipeline.set('key1', 'value1') pipeline.set('key2', 'value2') pipeline.execute() - 避免阻塞命令:
- 避免在生產(chǎn)環(huán)境使用
KEYS,改用SCAN - 謹(jǐn)慎使用
FLUSHALL/FLUSHDB
- 避免在生產(chǎn)環(huán)境使用
客戶端優(yōu)化:
- 使用連接池管理連接
- 合理設(shè)置連接池大小
- 實(shí)現(xiàn)客戶端緩存(Redis 6.0+的客戶端緩存功能)
監(jiān)控與調(diào)優(yōu):
- 定期檢查
info命令輸出 - 監(jiān)控慢查詢(
slowlog get) - 使用
redis-benchmark進(jìn)行性能測(cè)試 - 考慮使用Redis的Lua腳本減少網(wǎng)絡(luò)開銷
到此這篇關(guān)于在Java中整合Redis的詳細(xì)過程的文章就介紹到這了,更多相關(guān)java整合redis內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot配置Actuator組件,實(shí)現(xiàn)系統(tǒng)監(jiān)控
在生產(chǎn)環(huán)境中,需要實(shí)時(shí)或定期監(jiān)控服務(wù)的可用性。Spring Boot的actuator(健康監(jiān)控)功能提供了很多監(jiān)控所需的接口,可以對(duì)應(yīng)用系統(tǒng)進(jìn)行配置查看、相關(guān)功能統(tǒng)計(jì)等。2021-06-06
新版IDEA使用Spring Initializr創(chuàng)建工程的兩種方法
這篇文章主要介紹了新版IDEA使用Spring Initializr創(chuàng)建工程(兩種方法,官方工具和IDEA),文中通過代碼示例和圖文結(jié)合的方式給大家講解的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2024-10-10
在Java中將List轉(zhuǎn)換為String輸出過程解析
這篇文章主要介紹了在Java中將List轉(zhuǎn)換為String輸出過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09
用SpringBoot框架來接收multipart/form-data文件方式
這篇文章主要介紹了用SpringBoot框架來接收multipart/form-data文件方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02
Java之HashMap.values()轉(zhuǎn)List時(shí)的錯(cuò)誤和正確演示
這篇文章主要介紹了Java之HashMap.values()轉(zhuǎn)List時(shí)的錯(cuò)誤和正確演示,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
Springboot 自定義線程池的參數(shù)配置最優(yōu)小結(jié)
在SpringBoot中配置自定義線程池時(shí),參數(shù)的設(shè)置需要根據(jù)具體的應(yīng)用場(chǎng)景、系統(tǒng)資源和業(yè)務(wù)需求來調(diào)整,本文就來詳細(xì)的介紹一下,感興趣的可以了解一下2025-10-10

