最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

在Java中整合Redis的詳細(xì)步驟

 更新時(shí)間:2025年11月06日 09:17:08   作者:祈禱蒼天賜我java之術(shù)  
這篇文章詳細(xì)介紹了在Java中整合Redis的過程,包括Redis的基礎(chǔ)認(rèn)知、環(huán)境搭建、Jedis和Redisson客戶端的使用、SpringBoot整合Redis以及Redis的進(jìn)階知識(shí),感興趣的朋友跟隨小編一起看看吧

一、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ì):

  1. 線程安全:內(nèi)置連接池管理,徹底解決Jedis線程安全問題
  2. 豐富特性:支持分布式鎖(可重入鎖、公平鎖、聯(lián)鎖等)、分布式集合(Set、Map、List)、延遲隊(duì)列、布隆過濾器等20+種分布式對(duì)象
  3. 協(xié)議支持:完整支持Redis 5.0+的所有命令和數(shù)據(jù)類型
  4. 容錯(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í)踐建議

  1. 連接池配置:根據(jù)QPS調(diào)整連接數(shù)(建議公式:最大連接數(shù) = QPS × 平均響應(yīng)時(shí)間(秒))
  2. 超時(shí)設(shè)置:網(wǎng)絡(luò)不穩(wěn)定的環(huán)境適當(dāng)增大超時(shí)時(shí)間
  3. 資源釋放:應(yīng)用關(guān)閉時(shí)調(diào)用shutdown()釋放資源
  4. 配置分離:生產(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)處理死鎖問題。

核心特性
  1. 可重入性:同一線程可以多次獲取同一把鎖
  2. 自動(dòng)續(xù)期:通過watchdog機(jī)制自動(dòng)延長(zhǎng)鎖持有時(shí)間
  3. 公平鎖支持:可配置為公平鎖模式
  4. 鎖等待超時(shí):避免線程無限期等待
  5. 鎖釋放保障:通過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í)踐
  1. 鎖命名規(guī)范:使用業(yè)務(wù)相關(guān)的有意義的名稱,如lock:order:{orderId}
  2. 鎖超時(shí)設(shè)置:leaseTime應(yīng)大于預(yù)估的業(yè)務(wù)處理時(shí)間
  3. 異常處理:確保在finally塊中釋放鎖
  4. 避免長(zhǎng)時(shí)間持有鎖:鎖持有時(shí)間應(yīng)盡量短
  5. 重試機(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ù)雜性。

核心特性
  1. 精確延時(shí):支持毫秒級(jí)延遲精度
  2. 持久化存儲(chǔ):消息不會(huì)因服務(wù)重啟而丟失
  3. 高可用:基于Redis集群保證可靠性
  4. 多消費(fèi)者支持:多個(gè)服務(wù)實(shí)例可以同時(shí)消費(fèi)
  5. 消息順序保障:按延遲時(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í)踐
  1. 消息設(shè)計(jì):消息內(nèi)容應(yīng)包含足夠業(yè)務(wù)信息(如訂單ID)
  2. 異常處理:消費(fèi)者需處理各種異常情況
  3. 冪等性:消息處理應(yīng)保證冪等,防止重復(fù)消費(fèi)
  4. 監(jiān)控報(bào)警:對(duì)消費(fèi)延遲和堆積情況監(jiān)控
  5. 資源釋放:正確關(guān)閉延遲隊(duì)列實(shí)例
性能優(yōu)化建議
  1. 批量消費(fèi):可適當(dāng)批量獲取消息減少網(wǎng)絡(luò)開銷
  2. 分區(qū)設(shè)計(jì):大流量場(chǎng)景可按業(yè)務(wù)ID分多個(gè)隊(duì)列
  3. 消費(fèi)并行度:根據(jù)業(yè)務(wù)需求調(diào)整消費(fèi)者數(shù)量
  4. 消息壓縮:大消息可考慮壓縮后再存儲(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>

說明:

  1. spring-boot-starter-data-redis 是 Spring Boot 提供的 Redis 啟動(dòng)器,自動(dòng)配置了 Redis 相關(guān)的 Bean。
  2. 默認(rèn)使用 Lettuce 客戶端,如需切換為 Jedis,需排除 Lettuce 并添加 Jedis 依賴。
  3. 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)詳解:

  1. hostport:Redis 服務(wù)器地址和端口,必填項(xiàng)。
  2. password:如果 Redis 設(shè)置了密碼,需配置此項(xiàng)。
  3. database:Redis 默認(rèn)有 16 個(gè)數(shù)據(jù)庫(0-15),可通過此配置選擇使用的數(shù)據(jù)庫。
  4. timeout:連接 Redis 的超時(shí)時(shí)間,建議設(shè)置為 5000ms 以上。
  5. lettuce.pool:Lettuce 連接池配置,適用于高并發(fā)場(chǎng)景:
    • max-active:最大連接數(shù),根據(jù)業(yè)務(wù)量調(diào)整。
    • max-idlemin-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)境建議:

  1. 使用連接池以提高性能。
  2. 密碼和敏感信息建議通過環(huán)境變量或配置中心管理。
  3. 對(duì)于集群或哨兵模式,需額外配置。

5.2 核心 API:RedisTemplate 與 StringRedisTemplate

Spring Data Redis 提供了兩種主要的模板類用于操作 Redis,它們各自適用于不同的使用場(chǎng)景:

StringRedisTemplate

StringRedisTemplate 是專門用于操作 String 類型數(shù)據(jù)的模板類,具有以下特點(diǎn):

  1. 序列化方式:默認(rèn)使用 StringRedisSerializer,所有鍵和值都以 UTF-8 編碼的字符串形式存儲(chǔ)
  2. 優(yōu)勢(shì)
    • 完全避免中文亂碼問題
    • 存儲(chǔ)的數(shù)據(jù)可直接在 Redis CLI 中查看
    • 性能較高,適用于簡(jiǎn)單字符串操作
  3. 限制
    • 只能操作字符串類型的數(shù)據(jù)
    • 無法直接存儲(chǔ) Java 對(duì)象

RedisTemplate

RedisTemplate 是通用模板類,支持所有 Redis 數(shù)據(jù)結(jié)構(gòu),但需要注意:

  1. 默認(rèn)序列化問題
    • 默認(rèn)使用 JdkSerializationRedisSerializer
    • 會(huì)導(dǎo)致存儲(chǔ)的數(shù)據(jù)帶有"xac\xed\x00\x05t\x00"等序列化前綴
    • 存儲(chǔ)的數(shù)據(jù)在 Redis CLI 中不可讀
  2. 適用場(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;
    }
}

配置說明:

  1. 鍵序列化使用 StringRedisSerializer,保證鍵的可讀性
  2. 值序列化使用 Jackson,支持復(fù)雜對(duì)象存儲(chǔ)
  3. 必須調(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不存在";
    }
}

操作說明:

  1. 對(duì)象存儲(chǔ)會(huì)自動(dòng)使用配置的 Jackson 序列化器
  2. 獲取時(shí)會(huì)自動(dòng)反序列化為 Java 對(duì)象
  3. 建議使用"類型: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)

  1. 確保項(xiàng)目中已引入spring-boot-starter-cachespring-boot-starter-data-redis依賴
  2. 在application.properties/yml中配置Redis連接信息
  3. 默認(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ì)象存入緩存
    }
}

使用建議

  1. 方法返回值必須是需要緩存的對(duì)象
  2. 確保key與查詢方法@Cacheable的key一致
  3. 適用于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("清空用戶緩存");
    }
}

刪除策略選擇

  1. 單條刪除:使用key指定要?jiǎng)h除的緩存項(xiàng)
  2. 批量刪除:設(shè)置allEntries=true清空整個(gè)緩存區(qū)域
  3. 可在方法執(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)景

  1. 需要同時(shí)操作多個(gè)緩存區(qū)域
  2. 需要同時(shí)執(zhí)行put和evict操作
  3. 復(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 工作流程

  1. 命令執(zhí)行后寫入 AOF 緩沖區(qū)
  2. 根據(jù)配置的刷盤策略將緩沖區(qū)內(nèi)容同步到磁盤
  3. 定期進(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ī)制

  1. 原理:創(chuàng)建一個(gè)新的 AOF 文件,包含重建當(dāng)前數(shù)據(jù)集所需的最少命令集
  2. 觸發(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 從為例)

  1. 準(zhǔn)備 3 臺(tái) Redis 服務(wù)器(假設(shè)IP為192.168.1.100-102)
  2. 配置 Master(192.168.1.100): 確保 redis.conf 中有以下配置:
    # 如果需要密碼認(rèn)證
    requirepass your_password
  3. 配置 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
  4. 驗(yàn)證主從狀態(tài):
    • 在 Master 上執(zhí)行info replication查看 Slave 連接信息
    • 在 Slave 上執(zhí)行info replication查看復(fù)制狀態(tài)

主從復(fù)制過程

  1. Slave 啟動(dòng)后向 Master 發(fā)送 SYNC 命令
  2. Master 執(zhí)行 BGSAVE 生成 RDB 文件并發(fā)送給 Slave
  3. Master 將生成 RDB 期間的新命令緩存起來,之后發(fā)送給 Slave
  4. 之后 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)移。

哨兵核心功能

  1. 監(jiān)控:持續(xù)檢查 Master 和 Slave 是否正常運(yùn)行
  2. 通知:當(dāng)監(jiān)控的 Redis 實(shí)例出現(xiàn)問題時(shí),可以通過API通知管理員
  3. 自動(dòng)故障轉(zhuǎn)移:當(dāng) Master 不可用時(shí),自動(dòng)選舉新的 Master
  4. 配置提供者:為客戶端提供最新的 Master 地址

配置步驟(3個(gè)哨兵節(jié)點(diǎn))

  1. 準(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
  2. 啟動(dòng)哨兵:
    redis-sentinel /path/to/sentinel.conf
  3. 驗(yàn)證哨兵狀態(tài):
    redis-cli -p 26379 info sentinel

故障轉(zhuǎn)移流程

  1. 哨兵檢測(cè)到 Master 不可達(dá)(超過 down-after-milliseconds 時(shí)間)
  2. 哨兵集群選舉領(lǐng)導(dǎo)者哨兵(需要至少 quorum 數(shù)量的哨兵同意)
  3. 領(lǐng)導(dǎo)者哨兵從 Slave 中選擇新的 Master(基于優(yōu)先級(jí)、復(fù)制偏移量等)
  4. 將其他 Slave 重新配置為復(fù)制新的 Master
  5. 通知客戶端配置變更

客戶端訪問哨兵模式的最佳實(shí)踐

  1. 客戶端應(yīng)連接哨兵獲取當(dāng)前 Master 地址
  2. 客戶端應(yīng)訂閱哨兵的+switch-master事件處理主從切換
  3. 建議使用支持哨兵的 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從集群)

  1. 準(zhǔn)備6臺(tái)服務(wù)器(192.168.1.100-105),確保端口6379和16379開放
  2. 每個(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
  3. 啟動(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
  4. 驗(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ù)分片原理

  1. Redis 使用 CRC16(key) mod 16384 計(jì)算 key 所屬的槽位
  2. 每個(gè)節(jié)點(diǎn)負(fù)責(zé)一部分連續(xù)的槽位
  3. 客戶端可以緩存槽位到節(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ò)展操作

  1. 添加新節(jié)點(diǎn):
    redis-cli --cluster add-node new_host:new_port existing_host:existing_port
  2. 遷移槽位:
    redis-cli --cluster reshard host:port
  3. 刪除節(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ì)原則

  1. 使用簡(jiǎn)潔但有意義的鍵名,如user:1000:profile
  2. 避免使用大鍵(如包含百萬元素的hash)
  3. 為鍵設(shè)置合理的過期時(shí)間

命令優(yōu)化

  1. 使用批量操作命令:
    • MSET替代多個(gè)SET
    • HMGET替代多個(gè)HGET
  2. 使用管道(pipeline)減少網(wǎng)絡(luò)往返:
    pipeline = redis.pipeline()
    pipeline.set('key1', 'value1')
    pipeline.set('key2', 'value2')
    pipeline.execute()
  3. 避免阻塞命令:
    • 避免在生產(chǎn)環(huán)境使用KEYS,改用SCAN
    • 謹(jǐn)慎使用FLUSHALL/FLUSHDB

客戶端優(yōu)化

  1. 使用連接池管理連接
  2. 合理設(shè)置連接池大小
  3. 實(shí)現(xiàn)客戶端緩存(Redis 6.0+的客戶端緩存功能)

監(jiān)控與調(diào)優(yōu)

  1. 定期檢查info命令輸出
  2. 監(jiān)控慢查詢(slowlog get
  3. 使用redis-benchmark進(jìn)行性能測(cè)試
  4. 考慮使用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)控

    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
  • 教你如何在 javadoc 輸出<> 符號(hào)

    教你如何在 javadoc 輸出<> 符號(hào)

    在 javadoc 輸出 <> 兩個(gè)符號(hào),直接使用會(huì)提示錯(cuò)誤,今天通過本文教大家如何在 javadoc 輸出<> 符號(hào),需要的朋友可以參考下
    2023-05-05
  • 新版IDEA使用Spring Initializr創(chuàng)建工程的兩種方法

    新版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輸出過程解析

    這篇文章主要介紹了在Java中將List轉(zhuǎn)換為String輸出過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-09-09
  • 使用Jackson 處理 null 或者 空字符串

    使用Jackson 處理 null 或者 空字符串

    這篇文章主要介紹了使用Jackson 處理 null 或者 空字符串,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Java處理多API請(qǐng)求的方法詳解

    Java處理多API請(qǐng)求的方法詳解

    Java?中的并發(fā)是指語言并行運(yùn)行多個(gè)線程的能力,允許同時(shí)執(zhí)行多個(gè)任務(wù),
    2023-10-10
  • Java源碼解析HashMap成員變量

    Java源碼解析HashMap成員變量

    今天小編就為大家分享一篇關(guān)于Java源碼解析HashMap成員變量,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • 用SpringBoot框架來接收multipart/form-data文件方式

    用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ò)誤和正確演示

    這篇文章主要介紹了Java之HashMap.values()轉(zhuǎn)List時(shí)的錯(cuò)誤和正確演示,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • Springboot 自定義線程池的參數(shù)配置最優(yōu)小結(jié)

    Springboot 自定義線程池的參數(shù)配置最優(yōu)小結(jié)

    在SpringBoot中配置自定義線程池時(shí),參數(shù)的設(shè)置需要根據(jù)具體的應(yīng)用場(chǎng)景、系統(tǒng)資源和業(yè)務(wù)需求來調(diào)整,本文就來詳細(xì)的介紹一下,感興趣的可以了解一下
    2025-10-10

最新評(píng)論

安图县| 凤庆县| 汪清县| 吕梁市| 申扎县| 肃北| 丰宁| 任丘市| 肇庆市| 方山县| 西盟| 宜君县| 文登市| 丹凤县| 瑞丽市| 高唐县| 革吉县| 凉山| 封开县| 壤塘县| 上林县| 宣恩县| 潮州市| 登封市| 巴中市| 镇江市| 东光县| 永新县| 宁都县| 万年县| 穆棱市| 祁东县| 阿城市| 乌苏市| 新泰市| 皋兰县| 宝清县| 丹凤县| 休宁县| 绥芬河市| 体育|