Skip to content
清晨的一缕阳光
返回

Redis 7.x 新特性详解

Redis 7.x 新特性详解

Redis 7.0 于 2022 年发布,带来了众多重要更新。本文将深入解析 Redis 7.x 的新特性,帮助你了解技术演进方向。

一、版本演进

1.1 Redis 版本历史

Redis 版本演进:

Redis 2.6 (2012) -  Lua 脚本、过期时间改进
Redis 3.0 (2015) -  Cluster 集群、哨兵改进
Redis 4.0 (2017) -  混合持久化、异步删除
Redis 5.0 (2018) -  Stream 数据类型、事务改进
Redis 6.0 (2020) -  TLS 加密、ACL 权限、多线程 IO
Redis 7.0 (2022) -  Functions、ACL 改进、性能优化
Redis 8.0 (计划) -  向量搜索、AI 功能

1.2 Redis 7.0 核心特性

Redis 7.0 新特性总览:

┌─────────────────────────────────────┐
│ 1. Redis Functions                   │
│    - 替代 Lua 脚本                   │
│    - 更好的可维护性                  │
│    - 支持共享库                      │
├─────────────────────────────────────┤
│ 2. ACL 权限改进                      │
│    - 更细粒度的权限控制              │
│    - 支持通道权限                    │
├─────────────────────────────────────┤
│ 3. 性能优化                          │
│    - 命令执行效率提升                │
│    - 内存使用优化                    │
├─────────────────────────────────────┤
│ 4. 新命令                            │
│    - LMOVE, BLMOVE                  │
│    - ZMPOP, BZMPOP                  │
│    - EXPIRETIME                     │
├─────────────────────────────────────┤
│ 5. 可观测性增强                      │
│    - 更好的监控指标                  │
│    - 慢查询改进                      │
└─────────────────────────────────────┘

二、Redis Functions

2.1 什么是 Functions

Functions 是 Redis 7.0 引入的新特性

Functions vs Lua:

┌─────────────────────────────────────┐
│ 特性        │ Lua     │ Functions │
├─────────────────────────────────────┤
│ 代码共享    │ 不支持   │ 支持      │
│ 版本管理    │ 无       │ 有        │
│ 调试        │ 困难     │ 容易      │
│ 内存隔离    │ 无       │ 有        │
│ 超时控制    │ 有限     │ 完善      │
└─────────────────────────────────────┘

2.2 Function 基础使用

-- 定义 Function 库
-- library.lua

#!lua name=mylib

-- 函数 1:字符串处理
redis.register_function('str_upper', function(keys, args)
    local key = keys[1]
    local value = redis.call('GET', key)
    if value then
        return string.upper(value)
    end
    return nil
end)

-- 函数 2:计数器(带最大值限制)
redis.register_function('incr_with_max', function(keys, args)
    local key = keys[1]
    local max = tonumber(args[1])
    
    local current = redis.call('GET', key)
    if current and tonumber(current) >= max then
        return nil
    end
    
    return redis.call('INCR', key)
end)

-- 函数 3:批量操作
redis.register_function('mget_json', function(keys, args)
    local result = {}
    for i, key in ipairs(keys) do
        result[i] = redis.call('GET', key)
    end
    return cjson.encode(result)
end)

加载 Function 库

# 加载 Function 库
redis-cli -x FUNCTION LOAD < library.lua

# 查看已加载的库
redis-cli FUNCTION LIST

# 查看库的详细信息
redis-cli FUNCTION LIST WITHCODE

# 删除库
redis-cli FUNCTION DELETE mylib

# 更新库(重新加载)
redis-cli FUNCTION LOAD REPLACE < library.lua

调用 Function

# 调用函数
redis-cli FCALL mylib.str_upper 1 user:1001:name

# 带参数调用
redis-cli FCALL mylib.incr_with_max 1 counter:views 1000

# 批量调用
redis-cli FCALL mylib.mget_json 3 key1 key2 key3

2.3 Java 使用 Functions

import redis.clients.jedis.Jedis;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class RedisFunctionsExample {
    private Jedis jedis;
    
    public RedisFunctionsExample(Jedis jedis) {
        this.jedis = jedis;
    }
    
    /**
     * 加载 Function 库
     */
    public void loadLibrary(String libraryPath) throws IOException {
        String code = new String(Files.readAllBytes(Paths.get(libraryPath)));
        jedis.functionLoad(code, true); // true 表示替换已存在的库
    }
    
    /**
     * 调用 Function
     */
    public Object fcall(String functionName, java.util.List<String> keys, 
                       java.util.List<String> args) {
        return jedis.fcall(functionName, keys, args);
    }
    
    /**
     * 示例:字符串处理
     */
    public String strUpper(String key) {
        Object result = fcall("mylib.str_upper", 
                             java.util.Collections.singletonList(key),
                             java.util.Collections.emptyList());
        return (String) result;
    }
    
    /**
     * 示例:带限制的计数器
     */
    public Long incrWithMax(String key, long max) {
        Object result = fcall("mylib.incr_with_max",
                             java.util.Collections.singletonList(key),
                             java.util.Collections.singletonList(String.valueOf(max)));
        return (Long) result;
    }
    
    /**
     * 示例:批量获取 JSON
     */
    public String mgetJson(java.util.List<String> keys) {
        Object result = fcall("mylib.mget_json",
                             keys,
                             java.util.Collections.emptyList());
        return (String) result;
    }
    
    /**
     * 删除库
     */
    public void deleteLibrary(String libraryName) {
        jedis.functionDelete(libraryName);
    }
    
    /**
     * 列出所有库
     */
    public void listLibraries() {
        jedis.functionList().forEach(lib -> {
            System.out.println("库名:" + lib.getLibraryName());
            System.out.println("引擎:" + lib.getEngine());
            System.out.println("函数数量:" + lib.getFunctions().size());
        });
    }
}

2.4 Function 最佳实践

#!lua name=utils

-- 最佳实践 1:错误处理
redis.register_function('safe_get', function(keys, args)
    local key = keys[1]
    local ok, result = pcall(function()
        return redis.call('GET', key)
    end)
    
    if not ok then
        redis.log(redis.LOG_WARNING, "Error getting key: " .. key)
        return nil
    end
    
    return result
end)

-- 最佳实践 2:参数验证
redis.register_function('safe_set', function(keys, args)
    local key = keys[1]
    local value = args[1]
    local ttl = args[2]
    
    -- 验证参数
    if not value or value == "" then
        error("Value cannot be empty")
    end
    
    if ttl and (tonumber(ttl) <= 0 or tonumber(ttl) > 86400) then
        error("TTL must be between 1 and 86400 seconds")
    end
    
    redis.call('SET', key, value)
    
    if ttl then
        redis.call('EXPIRE', key, tonumber(ttl))
    end
    
    return "OK"
end)

-- 最佳实践 3:使用 KEYS 和 ARGS
-- 避免硬编码 key,提高可复用性
redis.register_function('user_profile_get', function(keys, args)
    local user_key = keys[1]  -- 传入 user:{id}:profile
    local field = args[1]
    
    return redis.call('HGET', user_key, field)
end)

三、ACL 权限改进

3.1 ACL 基础

Redis 6.0 引入 ACL,7.0 增强

# 创建用户
ACL SETUSER username on >password ~pattern +command

# 示例:创建只读用户
ACL SETUSER readonly on >readpass ~* +@read

# 示例:创建特定 key 权限用户
ACL SETUSER appuser on >apppass ~app:* +@write +@read

# 示例:创建管理员用户
ACL SETUSER admin on >adminpass ~* +@all

3.2 通道权限(Redis 7.0 新增)

#  Pub/Sub 通道权限
ACL SETUSER pubsubuser on >pubpass &channel:* +PUBLISH +SUBSCRIBE

# 限制只能访问特定通道
ACL SETUSER newsuser on >newspass &news:* +PUBLISH +SUBSCRIBE

# 测试通道权限
redis-cli -a newspass SUBSCRIBE news:sports
# 如果尝试订阅其他通道,会被拒绝

3.3 ACL 配置文件

# redis.conf ACL 配置

# 默认用户(保持兼容性)
user default on nopass ~* +@all

# 只读用户
user readonly on >readonlypass ~* +@read -@dangerous

# 应用用户
user appuser on >apppass ~app:* +@write +@read +INCR +DECR

# 监控用户(只能执行 INFO 命令)
user monitoruser on >monitorpass ~* +INFO +PING +CLIENT

# 备份用户
user backupuser on >backuppass ~* +BGSAVE +LASTSAVE +INFO

# 保存 ACL 到文件
acl-save

# 从文件加载 ACL
acl-load

3.4 Java 使用 ACL

import redis.clients.jedis.Jedis;
import redis.clients.jedis.auth.Protocol;

public class ACLExample {
    
    /**
     * 创建用户
     */
    public void createUser(Jedis jedis, String username, String password) {
        // 创建只读用户
        jedis.aclSetUser(username, 
            "on",
            ">" + password,
            "~*",
            "+@read",
            "-@dangerous"
        );
    }
    
    /**
     * 使用 ACL 用户连接
     */
    public Jedis connectWithACL(String host, int port, 
                               String username, String password) {
        // Redis 6.0+ 支持用户名密码
        return new Jedis(host, port, 
            Protocol.DEFAULT_TIMEOUT,
            username,  // 用户名
            password   // 密码
        );
    }
    
    /**
     * 检查用户权限
     */
    public void checkPermissions(Jedis jedis, String username) {
        // 获取用户信息
        var userInfo = jedis.aclGetUser(username);
        System.out.println("用户:" + userInfo.get(0));
        System.out.println("标志:" + userInfo.get(1));
        System.out.println("密码:" + userInfo.get(2));
        System.out.println("命令:" + userInfo.get(3));
        System.out.println("Keys:" + userInfo.get(4));
        System.out.println("通道:" + userInfo.get(5));
    }
    
    /**
     * 删除用户
     */
    public void deleteUser(Jedis jedis, String username) {
        jedis.aclDelUser(username);
    }
    
    /**
     * 列出所有用户
     */
    public void listUsers(Jedis jedis) {
        jedis.aclList().forEach(user -> System.out.println(user));
    }
}

四、性能优化

4.1 命令执行优化

Redis 7.0 性能改进:

1. 命令执行效率提升
   - 减少锁竞争
   - 优化内存分配
   - 改进数据结构

2. 集群性能提升
   - 减少网络往返
   - 优化槽位计算
   - 改进故障检测

3. 持久化优化
   - RDB 压缩改进
   - AOF 重写优化
   - 减少 fork 阻塞

性能对比

# Redis 6.0 vs Redis 7.0

# SET 性能
# Redis 6.0: ~100,000 ops/sec
# Redis 7.0: ~120,000 ops/sec (+20%)

# GET 性能
# Redis 6.0: ~150,000 ops/sec
# Redis 7.0: ~180,000 ops/sec (+20%)

# 集群性能
# Redis 6.0: ~80,000 ops/sec
# Redis 7.0: ~100,000 ops/sec (+25%)

4.2 内存优化

Redis 7.0 内存优化:

1. 主动内存碎片整理
   - 自动整理内存碎片
   - 减少内存浪费

2. 改进的淘汰算法
   - LFU 算法优化
   - 更智能的淘汰策略

3. 压缩优化
   - List 压缩改进
   - Hash 压缩优化

配置示例

# 主动内存碎片整理
activedefrag yes
active-defrag-threshold-lower 10
active-defrag-threshold-upper 100
active-defrag-cycle-min 1
active-defrag-cycle-max 25

# 内存淘汰策略
maxmemory-policy noeviction
maxmemory-samples 10

五、新命令

5.1 List 命令

# LMOVE - 从列表一端移动元素到另一端
LMOVE source destination LEFT RIGHT

# BLMOVE - 阻塞式 LMOVE
BLMOVE source destination LEFT RIGHT 5.0

# 示例:任务队列
LPUSH queue:pending task1 task2 task3
LMOVE queue:pending queue:processing LEFT RIGHT

5.2 ZSet 命令

# ZMPOP - 从多个有序集合中弹出元素
ZMPOP 2 zset1 zset2 MIN COUNT 1

# BZMPOP - 阻塞式 ZMPOP
BZMPOP 5.0 2 zset1 zset2 MIN COUNT 1

# 示例:多队列消费
BZMPOP 0 3 queue:high queue:medium queue:low MIN COUNT 1

5.3 Key 命令

# EXPIRETIME - 获取 key 的过期时间(绝对时间戳)
EXPIRETIME key

# 返回:过期时间戳(秒)
#      -1: key 存在但无过期时间
#      -2: key 不存在

# PEXPIRETIME - 毫秒级过期时间
PEXPIRETIME key

# 示例
SET key value EX 3600
EXPIRETIME key
# 返回:1725530400(过期时间戳)

六、可观测性增强

6.1 监控指标改进

# INFO 命令增强
INFO everything

# 新增指标
INFO modules      # 模块信息
INFO memory       # 详细内存信息
INFO clients      # 客户端详细信息

6.2 慢查询改进

# 慢查询配置
slowlog-log-slower-than 10000  # 10ms
slowlog-max-len 128

# 新增:慢查询包含更多信息
SLOWLOG GET 10

# 输出包含:
# - 命令执行时间
# - 客户端信息
# - 执行时间戳

6.3 延迟监控

# 延迟监控
redis-cli --latency

# 延迟历史
redis-cli --latency-history -i 1

# 延迟分布
redis-cli --latency-dist

七、升级建议

7.1 升级步骤

# 1. 备份数据
redis-cli BGSAVE
cp /var/lib/redis/dump.rdb /backup/dump.rdb

# 2. 停止 Redis
redis-cli shutdown

# 3. 备份配置文件
cp /etc/redis/redis.conf /etc/redis/redis.conf.bak

# 4. 安装 Redis 7.0
tar -xzf redis-7.0.0.tar.gz
cd redis-7.0.0
make && make install

# 5. 更新配置(如有必要)
# 检查不兼容的配置项

# 6. 启动 Redis 7.0
redis-server /etc/redis/redis.conf

# 7. 验证启动
redis-cli ping
redis-cli info server

# 8. 验证数据
redis-cli dbsize

7.2 兼容性检查

# 检查不兼容的命令
redis-cli ACL CAT

# 检查 Lua 脚本兼容性
# Redis 7.0 的 Functions 与 Lua 脚本可以共存
# 建议逐步迁移到 Functions

# 检查客户端兼容性
# 确保 Jedis/Lettuce 等客户端支持 Redis 7.0

八、总结

8.1 Redis 7.0 核心优势

  1. Functions

    • 更好的代码组织
    • 支持共享库
    • 易于维护
  2. ACL 增强

    • 更细粒度权限
    • 通道权限控制
    • 安全性提升
  3. 性能优化

    • 命令执行更快
    • 内存使用更优
    • 集群性能提升
  4. 可观测性

    • 监控指标更丰富
    • 调试更便捷

8.2 升级建议


参考资料


分享这篇文章到:

上一篇文章
Prompt 优化自动化
下一篇文章
MySQL 内存参数配置与调优