Skip to content

最佳实践

BigKey

介绍

在 Redis 中,BigKey 指的是占用较大内存空间的键(key)及其关联的值(value)。BigKey 的存在可能对 Redis 实例的性能和稳定性产生负面影响。以下是 BigKey 的一些主要影响和处理方法:

BigKey 的影响

  1. 内存占用:
    • BigKey 会占用大量内存,可能导致内存不足,影响 Redis 实例的稳定性和性能。
  2. 网络开销:
    • 当客户端请求 BigKey 时,传输大量数据会增加网络开销,可能导致网络延迟增加。
  3. 持久化性能:
    • 如果启用了 RDB 或 AOF 持久化,BigKey 会增加快照生成和追加日志的时间,影响持久化性能。
  4. 阻塞操作:
    • 对 BigKey 进行删除、修改等操作可能会消耗较长时间,阻塞其他命令的执行。

识别 BigKey

要识别 BigKey,可以使用以下方法:

  1. 使用 Redis CLI:
    • 使用 redis-cli --bigkeys 命令可以扫描 Redis 实例,找出内存占用较大的键。
    • 例如:redis-cli --bigkeys -h <host> -p <port> -a <password>
  2. 手动监控:
    • 通过 INFO memory 命令查看内存使用情况,并结合 KEYSSCAN 命令查找占用内存较多的键。
  3. 第三方工具:
    • 使用一些 Redis 管理工具(如 Redis Insight)来监控和分析内存使用情况。

处理 BigKey

  1. 数据拆分:
    • 将大的数据结构拆分成多个小的数据结构。例如,将一个大列表拆分成多个小列表。
  2. 使用合适的数据类型:
    • 根据数据的访问模式和使用场景,选择合适的数据类型。例如,使用哈希表(hash)代替字符串(string)存储复杂对象。
  3. 定期清理:
    • 定期清理不再需要的数据,避免内存浪费。
  4. 内存优化:
    • 使用压缩算法(如 LZF 压缩)来减少数据的存储大小。Redis 4.0 及以上版本支持字符串值的压缩。
  5. 分片:
    • 在数据量非常大时,可以考虑使用 Redis Cluster 进行分片,将数据分散到多个节点上。

注意事项

  • 监控和预警:建立监控机制,及时发现和处理 BigKey。
  • 逐步迁移:在处理 BigKey 时,避免一次性操作导致服务中断,可以采用逐步迁移的策略。
  • 备份和恢复:在进行 BigKey 处理前,确保有可靠的数据备份,以便在出现问题时能够恢复数据。

通过合理识别和处理 BigKey,可以优化 Redis 实例的性能和稳定性,提高系统的整体可靠性。

list、hash、string、stream、set、zset 分别多大才是 BigKey 呢?

在 Redis 中,BigKey 的定义并不是绝对的,它取决于具体的使用场景和性能需求。不过,通常可以根据键值对占用的内存大小或集合中元素的数量来判断一个键是否为 BigKey。以下是对 list、hash、string、stream、set、zset 这六种数据类型何时被视为 BigKey 的一般性指导:

  1. String:
    • 当一个 String 类型的键的值超过一定大小(如 10 KB 或 512 KB,具体阈值可能因 Redis 版本和使用场景而异)时,可以被认为是 BigKey。不过,通常建议将单个 String 值的大小保持在较小的范围内,以避免性能问题。
  2. List:
    • 当一个 List 类型的键包含的元素数量过多(如超过 5000 个元素)时,可能会被视为 BigKey。List 的大小会影响遍历、插入和删除操作的性能。
  3. Hash:
    • 当一个 Hash 类型的键包含的字段数量过多(如超过 1000 个字段)时,可能会被视为 BigKey。Hash 的大小会影响获取、更新和删除字段的性能。
  4. Set:
    • 与 List 类似,当一个 Set 类型的键包含的元素数量过多(如超过 5000 个元素)时,可能会被视为 BigKey。Set 的大小会影响添加、删除和检查成员是否存在等操作的性能。
  5. Zset(Sorted Set):
    • 当一个 Zset 类型的键包含的元素数量过多(如超过一定数量,具体阈值可能因使用场景而异)时,或者当每个元素的数据量也很大时,可能会被视为 BigKey。Zset 的大小会影响排序、范围查询和成员得分更新等操作的性能。
  6. Stream:
    • Stream 是 Redis 5.0 引入的一种新数据类型,用于处理消息队列等场景。对于 Stream 类型的键,当消息数量过多或单个消息的大小过大时,可能会被视为 BigKey。不过,由于 Stream 的设计初衷就是处理大量消息,因此其性能通常比 List 和 Set 更好。然而,如果单个 Stream 的大小超过了 Redis 实例的内存容量,仍然会导致性能问题。

需要注意的是,以上阈值仅供参考,并非绝对标准。在实际应用中,应该根据具体的业务需求和性能要求来判断一个键是否为 BigKey。此外,随着 Redis 版本的更新和性能优化,这些阈值也可能会发生变化。

为了识别和处理 BigKey,可以使用 Redis 提供的命令和工具,如 redis-cli --bigkeysMEMORY USAGE 命令等。同时,也可以考虑使用第三方监控和管理工具来帮助识别和处理 BigKey。在处理 BigKey 时,应该采取逐步迁移、数据拆分等策略,以避免对业务造成过大影响。

如何找出 BigKey?

使用如下命令找出 BigKey

bash
redis-cli -a 123456 --bigkeys
  • 命令缺陷为只能返回最大的 BigKey,无法查看第二大的 Bigkey。
  • 基于以上命令的缺陷,通过使用 scan、hscan、sscan、zscan、strlen、llen 等命令自己编程实现 BigKey 扫描逻辑。

删除 BigKey

BigKey 内存占用较多,删除这样的 Key 有需要耗费很长时间,导致 Redis 主线程阻塞,引发一系列问题。

Redis 3.0 及以下版本,如果是集合类型,则遍历 BigKey 的元素,先逐个删除子元素,最后删除 BigKey

Redis 4.0 以后提供了异步删除的命令 unlink

BigKey JMH 测试结果

详细的 JMH 测试代码请参考示例https://gitee.com/dexterleslie/demonstration/tree/master/demo-redis/demo-redis-best-practice/src/test/java/com/future/demo/bigkey

String 类型

Benchmark               (maximumLength)   Mode  Cnt      Score       Error  Units
StringBigKeyTests.test                2  thrpt    3  30676.244 ± 18848.722  ops/s
StringBigKeyTests.test             1024  thrpt    3  25338.918 ± 39026.075  ops/s
StringBigKeyTests.test             2048  thrpt    3  23785.739 ± 21596.389  ops/s
StringBigKeyTests.test            10240  thrpt    3  23661.158 ± 16452.213  ops/s
StringBigKeyTests.test            20480  thrpt    3  21388.855 ±   321.566  ops/s
StringBigKeyTests.test            65536  thrpt    3  12374.254 ± 10068.321  ops/s
StringBigKeyTests.test           524288  thrpt    3   3111.132 ±  1319.691  ops/s

Set 类型

Benchmark                        (maximumLength)   Mode  Cnt      Score       Error  Units
SetBigKeyTests.testIsMember                    2  thrpt    3  26493.929 ± 22549.625  ops/s
SetBigKeyTests.testIsMember                 1024  thrpt    3  26546.998 ± 28815.818  ops/s
SetBigKeyTests.testIsMember                 2048  thrpt    3  28287.106 ±  6447.242  ops/s
SetBigKeyTests.testIsMember                10240  thrpt    3  23985.139 ± 10724.481  ops/s
SetBigKeyTests.testIsMember                20480  thrpt    3  23300.399 ± 11831.280  ops/s
SetBigKeyTests.testIsMember               262144  thrpt    3  23961.680 ± 13024.500  ops/s
SetBigKeyTests.testRemoveAndAdd                2  thrpt    3  24671.942 ± 12454.298  ops/s
SetBigKeyTests.testRemoveAndAdd             1024  thrpt    3  25847.028 ± 24236.979  ops/s
SetBigKeyTests.testRemoveAndAdd             2048  thrpt    3  21153.702 ± 16835.358  ops/s
SetBigKeyTests.testRemoveAndAdd            10240  thrpt    3  19934.883 ±  6267.995  ops/s
SetBigKeyTests.testRemoveAndAdd            20480  thrpt    3  20963.721 ±  8080.921  ops/s
SetBigKeyTests.testRemoveAndAdd           262144  thrpt    3  21732.251 ± 17444.307  ops/s

选择合适的数据类型

存储用户对象案例

结论:使用 hash 类型存储用户数据,内存占用小,便于局部字段获取或更新。

详细的测试代码请参考示例https://gitee.com/dexterleslie/demonstration/blob/master/demo-redis/demo-redis-best-practice/src/test/java/com/future/demo/appropriate/datatype/UserDataStorageTests.java

使用三种方式存储 10w 用户数据:

  • 使用 string 类型存储

    代码如下:

    java
    // 使用 string 类型存储 10w 个用户
    @Test
    public void testStoringByUsingStringDataType() {
        Objects.requireNonNull(this.redisTemplate.getConnectionFactory()).getConnection().flushDb();
    
        for (int i = 0; i < 100000; i++) {
            int finalI = i;
            this.redisTemplate.opsForValue().multiSet(new HashMap<String, String>() {{
                this.put("user:id:" + finalI, String.valueOf(finalI));
                this.put("user:name:" + finalI, "name" + finalI);
                this.put("user:age:" + finalI, String.valueOf(finalI + 1));
            }});
        }
    }
    • 存储 10w 用户使用内存为 22.74M。
  • 使用 string类型+JSON 存储

    代码如下:

    java
    // 使用 string类型+JSON 存储 10w 个用户
    @Test
    public void testStoringByUsingStringDataTypeAndJSONString() throws JsonProcessingException {
        Objects.requireNonNull(this.redisTemplate.getConnectionFactory()).getConnection().flushDb();
    
        for (int i = 0; i < 100000; i++) {
            UserBean userBean = new UserBean((long) i, "name" + i, i);
            String JSON = this.objectMapper.writeValueAsString(userBean);
            this.redisTemplate.opsForValue().set("user:" + i, JSON);
        }
    }
    • 存储 10w 用户使用内存为 11.73M。
  • 使用 hash 类型存储

    代码如下:

    java
    // 使用 hash 类型存储 10w 个用户
    @Test
    public void testStoringByUsingHashDataType() {
        Objects.requireNonNull(this.redisTemplate.getConnectionFactory()).getConnection().flushDb();
    
        for (int i = 0; i < 100000; i++) {
            int finalI = i;
            this.redisTemplate.opsForHash().putAll("user:" + i, new HashMap<String, String>() {{
                this.put("id", String.valueOf(finalI));
                this.put("name", "name" + finalI);
                this.put("age", String.valueOf(finalI + 1));
            }});
        }
    }
    • 存储 10w 用户使用内存为 11.73M。

增量键值存储案例

结论:使用 hash 分片类型存储占用较少的内存。

详细的测试代码请参考示例https://gitee.com/dexterleslie/demonstration/blob/master/demo-redis/demo-redis-best-practice/src/test/java/com/future/demo/appropriate/datatype/IncrementalKeyValueStorageTests.java

  • 使用 string 类型存储

    代码如下:

    java
    // 使用 string 类型存储 10w 个增量键值
    @Test
    public void testStoringByUsingStringDataType() {
        Objects.requireNonNull(this.redisTemplate.getConnectionFactory()).getConnection().flushDb();
    
        for (int i = 0; i < 100000; i++) {
            this.redisTemplate.opsForValue().set("id:" + i, "value" + i);
        }
    }
    • 存储 10w 增量键值使用内存为 8.67M。
  • 使用 hash 类型存储

    代码如下:

    java
    // 使用 hash 类型存储 10w 个增量键值
    @Test
    public void testStoringByUsingHashDataType() {
        Objects.requireNonNull(this.redisTemplate.getConnectionFactory()).getConnection().flushDb();
    
        String hashKey = "test:hash";
        for (int i = 0; i < 100000; i++) {
            this.redisTemplate.opsForHash().put(hashKey, "id:" + i, "value" + i);
        }
    }
    • 存储 10w 增量键值使用内存为 7.15M。
  • 使用 hash 分片类型存储

    代码如下:

    java
    // 使用 hash分片 存储 10w 个增量键值
    @Test
    public void testStoringByUsingHashDataTypeWithSlicing() throws JsonProcessingException {
        Objects.requireNonNull(this.redisTemplate.getConnectionFactory()).getConnection().flushDb();
    
        Map<String, String> map = new HashMap<>();
        for (int i = 0; i < 100000; i++) {
            map.put("id:" + (i % 100), "value" + i);
    
            if ((i + 1) % 100 == 0) {
                String hashKey = "test:hash:" + (i / 100);
                this.redisTemplate.opsForHash().putAll(hashKey, map);
                map = new HashMap<>();
            }
        }
    }
    • 存储 10w 增量键值使用内存为 2.83M。

批量执行命令

批量执行命令方式

在 Redis 中,批量执行命令可以通过多种方式实现,具体取决于你的使用场景和工具。以下是几种常见的方法:

1. 使用 Redis 管道(Pipeline)

Redis 管道允许你在一次网络往返中发送多个命令,从而显著提高性能。大多数 Redis 客户端库都支持管道功能。

示例(Python):

python
import redis
 
# 连接到 Redis 服务器
r = redis.Redis(host='localhost', port=6379, db=0)
 
# 创建一个管道
pipe = r.pipeline()
 
# 批量执行命令
pipe.set('key1', 'value1')
pipe.set('key2', 'value2')
pipe.get('key1')
pipe.get('key2')
 
# 执行所有命令
results = pipe.execute()
 
print(results)  # 输出: [True, True, b'value1', b'value2']

2. 使用 MULTIEXEC 命令

Redis 提供了事务功能,通过 MULTI 命令开始一个事务,然后使用 EXEC 命令执行所有在事务中排队的命令。事务中的所有命令在 EXEC 命令执行时才会被发送到服务器。

示例(Redis CLI):

sh
redis-cli

然后在 Redis CLI 中:

redis
MULTI
SET key1 value1
SET key2 value2
GET key1
GET key2
EXEC

输出:

1) OK
2) OK
3) "value1"
4) "value2"

示例(Python):

python
import redis
 
# 连接到 Redis 服务器
r = redis.Redis(host='localhost', port=6379, db=0)
 
# 开始事务
with r.pipeline() as pipe:
    while pipe.watch('key1', 'key2'):  # 监视键的变化
        try:
            pipe.multi()  # 标记事务的开始
            pipe.set('key1', 'value1')
            pipe.set('key2', 'value2')
            pipe.get('key1')
            pipe.get('key2')
            pipe.execute()  # 执行事务
            break
        except redis.WatchError:
            # 如果键在监视期间被修改,则重新尝试事务
            continue
 
# 你可以在这里处理结果,但在这个例子中,结果已经在事务中处理了

3. 使用脚本(Lua)

Redis 支持使用 Lua 脚本执行多个命令,并且这些命令作为一个原子操作执行。

示例(Redis CLI):

sh
redis-cli --eval your_script.lua key1 key2 , arg1 arg2

your_script.lua 内容:

lua
redis.call('SET', KEYS[1], ARGV[1])
redis.call('SET', KEYS[2], ARGV[2])
return {redis.call('GET', KEYS[1]), redis.call('GET', KEYS[2])}

示例(Python):

python
import redis
 
# 连接到 Redis 服务器
r = redis.Redis(host='localhost', port=6379, db=0)
 
# Lua 脚本
lua_script = """
redis.call('SET', KEYS[1], ARGV[1])
redis.call('SET', KEYS[2], ARGV[2])
return {redis.call('GET', KEYS[1]), redis.call('GET', KEYS[2])}
"""
 
# 执行 Lua 脚本
result = r.eval(lua_script, 2, 'key1', 'key2', 'value1', 'value2')
 
print(result)  # 输出: [b'value1', b'value2']

总结

  • 管道(Pipeline):适用于不需要原子性的批量命令执行,可以显著提高性能。
  • 事务(MULTI/EXEC):适用于需要原子性的批量命令执行。
  • Lua 脚本:适用于复杂的、需要原子性的批量命令执行,并且可以在服务器端执行逻辑。

选择哪种方法取决于你的具体需求和使用场景。

Pipeline

介绍

Redis Pipeline(管道)是一种批量执行命令的技术,它允许客户端在不等待服务器响应的情况下,一次性发送多个命令到Redis服务器。以下是关于Redis Pipeline的详细解释:

一、Redis Pipeline的定义与原理

  1. 定义: Redis Pipeline是一种提高命令批量执行效率的机制。通过将多个命令打包成一个请求发送到服务器,并在一次网络通信中接收多个命令的响应,从而减少了网络通信的开销,提高了命令执行的效率。
  2. 原理: 在Redis中,Pipeline通过客户端将多个命令打包成一个请求发送到服务器。服务器端会顺序执行管道中的多个命令,并将结果按照命令顺序打包成一个响应返回给客户端。这种方式避免了在每个命令之间等待往返延迟(RTT),从而提高了执行效率。

二、Redis Pipeline的特点与优势

  1. 特点:
    • 批量执行:允许客户端一次性发送多个命令到服务器。
    • 异步通信:客户端发送多个命令到服务器后,可以继续执行其他操作,而不必等待命令执行完成。
    • 原子性:管道中的多个命令在服务器端是原子性执行的(但需要注意,Pipeline本身并不保证跨命令的原子性,如果需要跨命令的原子性,应使用Redis事务或Lua脚本)。
  2. 优势:
    • 减少网络通信开销:由于Pipeline允许一次性发送多个命令并一次性接收响应,减少了网络通信的开销。
    • 提高命令执行效率:通过批量执行多个命令,可以减少服务器端的命令处理开销。
    • 适用于高并发场景:在高并发场景下,Pipeline能够显著提高Redis的响应速度和吞吐量。

三、Redis Pipeline的使用方法

  1. 开启Pipeline模式: 在客户端中开启Pipeline模式,通常是通过获取一个Pipeline对象来实现的。
  2. 添加命令到Pipeline: 将需要执行的Redis命令添加到Pipeline对象中。这些命令会在后续一次性发送给服务器。
  3. 执行Pipeline中的命令: 通过调用Pipeline对象的相应方法(如sync()或syncAndReturnAll())来执行所有添加的命令,并接收服务器的响应。

四、Redis Pipeline的实际应用场景

  1. 数据导入导出: 将数据从Redis中导出到其他存储介质,或者从其他存储介质导入到Redis中。Pipeline可以用于批量导入导出数据,提高数据迁移、备份恢复、数据同步等操作的效率。
  2. 数据处理: 对Redis中的数据进行批量处理、转换、过滤等操作。Pipeline可以用于批量处理数据,减少网络通信开销,提高数据处理的效率。
  3. 批量操作: 对Redis中的多个键进行批量操作,如设置多个键的值、删除多个键等。Pipeline可以用于批量执行多个操作,提高操作的执行效率。

五、使用Redis Pipeline的注意事项

  1. 命令数量限制: 虽然发送的命令数量不会被严格限制,但输入缓存区(即命令的最大存储体积)通常为1GB。当发送的命令超过此限制时,命令不会被执行,并且会被Redis服务器端断开连接。
  2. 网络阻塞: 如果Pipeline的数据过多,可能会导致客户端的等待时间过长,进而造成网络阻塞。因此,在使用Pipeline时,需要合理控制命令的数量和大小。
  3. 错误处理: 在批量执行命令时,单个命令的错误处理可能变得复杂。需要逐一检查每个命令的执行结果,以确保所有命令都正确执行。
  4. 客户端支持: 不同的Redis客户端对Pipeline的支持程度可能不同。在使用Pipeline之前,需要确认所选客户端库是否支持该功能,并了解其具体实现和限制。

综上所述,Redis Pipeline是一种非常有用的批量执行命令的技术,能够显著提高Redis的性能和可扩展性。在使用时,需要注意合理控制命令的数量和大小,以及处理可能出现的错误情况。

RedisTemplate 调用 pipeline

https://blog.csdn.net/yzh_1346983557/article/details/119837981

注意:在 Redis Cluster 模式中,RedisTemplate 调用 pipeline 底层会自动计算 Key 的 slot,不需要手动计算。

详细用法请参考示例https://gitee.com/dexterleslie/demonstration/tree/master/demo-redis/redistemplate/redistemplate-pipeline

java
@SpringBootTest(classes = {Application.class})
@Slf4j
public class ApplicationTests {

    @Autowired
    StringRedisTemplate redisTemplate;

    // 测试 pipeline
    // https://blog.csdn.net/yzh_1346983557/article/details/119837981
    @Test
    public void testPipeline() {
        Objects.requireNonNull(this.redisTemplate.getConnectionFactory()).getConnection().flushDb();

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        Map<String, String> map = new HashMap<>();
        for (int i = 0; i < 100000; i++) {
            String key = "key" + i;
            String value = "value" + i;
            map.put(key, value);

            if ((i + 1) % 1000 == 0) {
                Map<String, String> finalMap = map;
                this.redisTemplate.executePipelined(new SessionCallback<String>() {
                    @Override
                    public <K, V> String execute(RedisOperations<K, V> operations) throws DataAccessException {
                        RedisOperations<String, String> redisOperations = (RedisOperations<String, String>) operations;
                        for (String key : finalMap.keySet()) {
                            redisOperations.opsForValue().set(key, finalMap.get(key));
                        }

                        // 返回null即可,因为返回值会被管道的返回值覆盖,外层取不到这里的返回值
                        return null;
                    }
                });
                map = new HashMap<>();
            }
        }

        stopWatch.stop();
        log.debug("共耗时:" + stopWatch.getTotalTimeSeconds() + "秒");

        Assertions.assertEquals(100000, this.redisTemplate.getConnectionFactory().getConnection().dbSize());
    }
}

性能对比

详细用法请参考示例https://gitee.com/dexterleslie/demonstration/blob/master/demo-redis/demo-redis-best-practice/src/test/java/com/future/demo/batch/BatchExecutionTests.java

不使用批量执行

  • Cluster 模式共耗时:101.90398503秒
  • Standalone 模式共耗时:84.048900913秒

使用 mset 批量执行

  • Cluster 模式共耗时:7.966348218秒
  • Standalone 模式共耗时:1.109627399秒

使用 pipeline 批量执行

  • Cluster 模式共耗时:11.126043612秒
  • Standalone 模式共耗时:4.93598898秒

慢查询

介绍

Redis慢查询是指当Redis命令的执行时间超过预设的阈值时,该命令被视为慢查询,其相关信息会被记录以便后续分析和优化。以下是对Redis慢查询的详细解释:

一、慢查询的定义与配置

  1. 定义:在Redis中,慢查询是指那些执行时间超过预设阈值的命令。Redis会将这些命令的执行时间、命令详情等信息记录下来,以便开发者或运维人员进行分析和优化。
  2. 配置参数
    • slowlog-log-slower-than:设置慢查询的阈值,单位为微秒(1秒=1000毫秒=1000000微秒)。默认值为10000微秒(即10毫秒)。当命令的执行时间超过这个阈值时,Redis会将其视为慢查询并记录。
    • slowlog-max-len:设置慢查询日志列表的最大长度。当慢查询日志的数量超过这个值时,Redis会删除最早的慢查询记录,以保持日志列表的大小在预设范围内。

二、慢查询的排查方法

  1. 使用命令行工具:Redis提供了多个命令行工具用于查询Redis的性能参数和状态。其中,INFO命令可以查看Redis的各种信息,包括CPU使用率、内存使用情况、当前连接数和慢查询数等;SLOWLOG命令可以查看Redis的慢查询日志,包括慢查询的执行时间、命令和参数等。
  2. 查看Redis日志:Redis会记录所有的命令请求和响应日志。通过查看Redis日志,可以排查慢查询问题。可以通过修改Redis的配置文件来配置日志级别和记录方式等。
  3. 使用监控工具:常用的监控工具包括Redis监控工具和第三方监控工具等。这些工具可以实时监视Redis的各种性能指标和状态,更方便地排查慢查询问题。

三、慢查询的优化方法

  1. 优化数据结构:Redis支持多种数据结构,每种数据结构有不同的性能特点。根据应用场景选择合适的数据结构,可以极大地提高Redis的读写性能。
  2. 使用批量操作:在Redis中,批量操作是一种高效的操作方式,可以减少网络开销和Redis的负载压力。通过使用MGETMSETHMSET等批量命令操作,可以有效减少慢查询的出现。
  3. 使用管道操作:管道操作是Redis的一种高级特性,可以将多个命令打包在一起发送到Redis服务器,减少请求和响应的时间延迟。通过使用Redis管道操作,可以极大地提高Redis的读写性能和吞吐量。
  4. 调整持久化策略:Redis提供了RDB和AOF两种持久化策略。如果持久化策略配置不当,可能会导致Redis性能下降。可以考虑调整持久化策略的参数,如aofwritebehindappendfsync等,以提高Redis的性能。
  5. 升级硬件资源:Redis的性能受到硬件资源的限制,如CPU、内存、磁盘等。可以考虑升级硬件设备或优化硬件资源的配置,以提高Redis的查询性能。

四、注意事项

  1. 慢查询日志的持久化:因为慢查询的信息是被记录到了Redis中的一个列表中,并且是先进先出的。所以当Redis中的慢查询过多时,曾经记录的慢查询信息则会被删除。为了保证慢查询信息不会丢失,可以不定期地执行slowlog get命令将信息持久化存储。
  2. 慢查询阈值的设置:在大并发场景下,可能需要将slowlog-log-slower-than参数调整为更小的值(如1毫秒),以便更敏感地捕捉到性能瓶颈。

综上所述,Redis慢查询是Redis性能调优中的重要一环。通过合理配置慢查询参数、使用命令行工具、查看日志、使用监控工具进行排查,并采取有效的优化措施,可以显著提高Redis的查询性能和稳定性。

怎么找

设置slowlog-log-slower-than参数,设置慢查询的阈值,单位为微秒(1秒=1000毫秒=1000000微秒)。默认值为10000微秒(即10毫秒)。当命令的执行时间超过这个阈值时,Redis会将其视为慢查询并记录。

设置slowlog-max-len参数,设置慢查询日志列表的最大长度。当慢查询日志的数量超过这个值时,Redis会删除最早的慢查询记录,以保持日志列表的大小在预设范围内。

查看 slowlog 记录数

bash
slowlog len

获取 2 条 slowlog 日志

bash
slowlog get 2
  • slowlog get 2 命令输出结果如下:

    bash
    127.0.0.1:6379> slowlog get 2
    1) 1) (integer) 0
       2) (integer) 1737693919
       3) (integer) 23341
       4) 1) "keys"
          2) "*"
       5) "127.0.0.1:53390"
       6) ""

    这段Redis命令的输出是关于Redis慢查询日志的查询结果。让我们一步步解析这个输出:

    1. slowlog get 2:这个命令是用来获取Redis慢查询日志中的最后两条记录。slowlog是Redis中用于记录执行时间超过预设阈值的命令的日志,而get 2表示获取最近的两条记录。
    2. 输出结果是一个列表,包含两条慢查询记录。这里只展示了一条(因为格式限制,通常会有两个这样的结构),让我们看看这一条记录的具体内容:
      • 1) (integer) 0:这是慢查询日志的唯一标识符(ID),每次慢查询被记录时,都会分配一个唯一的ID。
      • 2) (integer) 1737693919:这是记录慢查询的时间戳,表示自Unix纪元(1970年1月1日)以来的秒数。你可以使用这个时间来查找具体的日期和时间。
      • 3) (integer) 23341:这是执行该命令所花费的微秒数(百万分之一秒)。在这个例子中,命令执行了大约23.341毫秒。
      • 4) 1) "keys" 2) "*":这是被记录为慢查询的命令及其参数。在这个例子中,keys "*"命令被记录为慢查询。keys命令用于查找所有符合给定模式的键,而"*"表示匹配所有键。需要注意的是,keys命令在生产环境中通常不建议使用,因为它可能会导致性能问题,特别是在键数量很多的情况下。
      • 5) "127.0.0.1:53390":这是执行该命令的客户端的IP地址和端口号。在这个例子中,命令是从本地机器(IP地址为127.0.0.1)上的53390端口发起的。
      • 6) "":这通常用于显示额外的信息,但在这个例子中它是空的。

    综上所述,这条慢查询日志记录了一个从本地客户端发起的keys "*"命令,该命令执行了大约23.341毫秒,被记录为慢查询。如果你频繁看到类似的慢查询日志,可能需要考虑优化你的Redis命令使用,比如避免使用keys命令来查找键,或者增加Redis服务器的性能。

清空慢查询日志

bash
slowlog reset

安全配置

禁用指定命令

通过 rename-command 禁用 keys、flushall、flushdb、config set 等命令,在 redis.conf 配置文件中添加下面配置:

properties
rename-command flushall ""

集群模式部分插槽不可用仍然对外提供服务

cluster-require-full-coverage 参数

Redis的cluster-require-full-coverage配置参数用于决定Redis Cluster在部分槽(slot)不可用的情况下是否仍然对外提供服务。

参数解释

  • cluster-require-full-coverage的默认值为yes。当设置为yes时,表示Redis Cluster需要所有的槽都正常工作才能对外提供服务。如果任何一个槽异常,整个集群将不对外提供服务。
  • 当设置为no时,表示即使部分槽不可用,Redis Cluster仍然可以对外提供服务。但需要注意的是,当尝试访问那些不可用的槽时,操作将会失败。

应用场景与考虑

  1. 高可用性要求
    • 如果应用对Redis的高可用性有严格要求,希望在任何情况下都能保证服务的连续性,那么可以考虑将cluster-require-full-coverage设置为no。这样,即使部分节点或槽出现故障,Redis Cluster仍然可以响应其他正常的请求。
  2. 数据完整性要求
    • 如果应用对数据完整性有较高要求,希望在所有槽都正常工作的情况下才提供服务,那么可以将cluster-require-full-coverage保持为默认的yes。这样,在任何一个槽异常时,整个集群都会停止服务,从而避免数据不一致或丢失的风险。

注意事项

  • 在设置cluster-require-full-coverageno时,需要特别注意监控Redis Cluster的状态,及时发现并处理那些不可用的槽,以确保服务的整体稳定性和可靠性。
  • 在进行集群扩容或缩容等操作时,也需要考虑这个参数的设置,以避免因操作不当而导致的服务中断或数据丢失等问题。

综上所述,cluster-require-full-coverage参数的设置需要根据应用的具体需求和场景来决定。在追求高可用性和数据完整性之间找到平衡点,是确保Redis Cluster稳定运行的关键。

场景模拟

使用https://gitee.com/dexterleslie/demonstration/tree/master/demo-redis/demo-redis-best-practice示例启动 redis 集群

redis.conf 添加如下配置:

properties
cluster-require-full-coverage no

关闭 node1 及其 slave 节点

bash
redis-cli -h 127.0.0.1 -p 6380 shutdown
redis-cli -h 127.0.0.1 -p 6383 shutdown

登录 node2 节点并执行一系列 set 命令

bash
redis-cli -c -p 6381

set 1 ""
set 2 ""
set 3 ""
set 4 ""
set 5 ""
  • 部分命令执行失败,大部分命令依旧正常执行。

HotKey 问题

https://www.cnblogs.com/wzh2010/p/17904849.html

介绍

Redis中的HotKey问题,即热点Key问题,是指某些Redis key由于频繁的访问或操作,导致Redis实例负载过重,影响整个Redis集群或实例的性能。以下是对Redis HotKey问题的详细分析:

一、热点Key的形成原因

热点Key的形成原因多种多样,主要包括以下几点:

  1. 数据访问集中:应用的某些数据访问非常集中,例如某个特别热门的商品信息或活动数据,这些数据对应的key就可能成为热点Key。
  2. 用户行为模式:用户的行为模式也可能导致某些key成为热点,如秒杀活动、热点新闻等读多写少的场景。
  3. 请求分片集中:某些情况下,由于请求分片集中,导致固定名称的key或Hash落入同一台服务器,瞬间访问量极大,超过机器瓶颈,从而产生热点Key问题。

二、热点Key带来的问题

热点Key问题会给Redis系统带来一系列负面影响,主要包括:

  1. 单节点负载过高:热点Key会导致Redis单个节点的CPU和内存负载过高,影响系统的稳定性和性能。
  2. 请求延迟增加:由于大量请求集中到某一个Key上,访问速度变慢,请求超时的情况增加,用户体验下降。
  3. 集群不均衡:热点Key导致的高流量可能导致集群内节点负载不均,影响整体性能。
  4. 数据丢失风险:如果热点Key所在的节点实例状态异常或崩溃,可能导致数据丢失或访问失败。

三、热点Key的定位方法

为了有效地解决热点Key问题,首先需要准确地定位热点Key。以下是一些常用的定位方法:

  1. 使用MONITOR命令:MONITOR命令可以实时跟踪Redis的所有请求,并输出正在执行的命令和key。通过分析高频率访问的key,可以定位到热点Key。但需要注意,MONITOR命令会对Redis性能产生影响,最好在开发环境或非高峰时段使用。
  2. 查看慢查询日志:Redis的SLOWLOG命令用于记录执行时间超过指定阈值的慢查询。热点Key一般会导致慢查询,可以通过慢查询日志来间接找到热点Key。
  3. 使用redis-cli --bigkeys命令:虽然该命令主要用于定位大key,但有时访问频繁的大key也会成为热点Key。结合该命令,可以找到潜在的热点Key。
  4. 使用监控工具:一些Redis客户端监控工具(如RedisInsight和Redis-Stat)提供了性能监控和热点Key检测功能,能够帮助开发者更直观地定位热点Key问题。

四、热点Key的解决方案

定位到热点Key后,需要采取相应的解决方案来减轻其对Redis系统的影响。以下是一些常用的解决方案:

  1. Redis集群扩容:增加分片副本,均衡读流量。通过水平拆分数据,使每个Redis实例处理不同的key,从而减轻单个实例的负载。
  2. 使用二级缓存:在客户端或应用服务器层面使用本地缓存(如JVM本地缓存),将热点Key的值缓存到本地内存中。当再次访问该Key时,先从本地缓存中获取,如果本地缓存中没有,再从Redis中获取。这样可以减少对Redis的访问次数,降低Redis的负载。
  3. 数据分片:将数据分散存储到多个Redis实例中,通过一定的算法(如哈希取模、一致性哈希等)将热点Key分配到不同的实例上。这样可以避免单个Redis实例承受过高的负载。
  4. 使用内存数据库:考虑使用内存数据库(如Memcached)等来缓解Redis的热点Key压力,将一部分热点Key数据迁移到内存数据库中。
  5. 限流降级:对于特别热的Key,可以考虑在应用层做限流或降级处理,减少对这些热点Key的访问频率,从而减轻Redis的压力。
  6. 优化业务代码:优化业务代码,减少对热点Key的频繁访问。例如,通过批量操作、异步处理等方式减少对热点Key的单次访问量。
  7. 调整Redis配置:根据实际需求调整Redis的配置,增加maxmemory限制、使用适当的淘汰策略(如LRU、LFU等)等。

综上所述,解决Redis HotKey问题需要综合考虑多种方法,并根据实际应用场景选择最合适的解决方案。同时,也需要不断监控和优化系统性能,以确保Redis能够稳定高效地运行。

数据倾斜问题

介绍

Redis数据倾斜是指在Redis集群或实例中,数据分布不均匀,导致某些实例或节点上的数据量或访问量远超其他实例或节点,进而造成性能瓶颈、资源耗尽甚至系统崩溃的问题。以下是对Redis数据倾斜的详细分析:

一、数据倾斜的成因

  1. BigKey:
    • 当某个key的值非常大(如string类型的数据量巨大,或集合类型的元素数量极多)时,会占用大量内存和IO资源,导致处理该key的实例负载过高。
    • BigKey的访问和删除操作通常会阻塞IO线程,影响其他请求的处理速度。
  2. Slot分配不均:
    • 在Redis集群中,数据会按照CRC算法的计算值对Slot(逻辑槽)取模,然后分配到不同的实例上。
    • 如果Slot分配不均,会导致大量数据被集中到某个或某些实例上,造成数据倾斜。
  3. HashTag:
    • Redis集群支持使用“{}”来指定哈希标签,使得带有相同标签的所有键都落在同一个节点上。
    • 如果不合理使用HashTag,会导致大量数据被映射到同一个Slot中,进而被分配到同一个实例上,引发数据倾斜。
  4. 热点数据:
    • 某些数据由于访问频率极高,成为热点数据。
    • 热点数据通常会导致所在实例的负载过高,进而造成数据访问倾斜。

二、数据倾斜的影响

  1. 性能下降:
    • 数据倾斜会导致某些实例的负载过高,处理速度变慢,响应时间延长。
  2. 资源耗尽:
    • 负载过高的实例可能会耗尽内存、CPU等资源,导致系统崩溃。
  3. 集群不均衡:
    • 数据倾斜会破坏集群的负载均衡,使得某些实例过载,而其他实例则相对空闲。
  4. 数据丢失风险:
    • 负载过高的实例在崩溃时可能会导致数据丢失,对业务造成严重影响。

三、数据倾斜的解决方案

  1. 避免BigKey:
    • 在业务层生成数据时,避免将过多的数据保存在同一个键值对中。
    • 对于集合类型的BigKey,可以将其拆分成多个小集合,分散保存在不同的实例上。
  2. 均衡Slot分配:
    • 在分配Slot时,应确保数据能够均匀分布到不同的实例上。
    • 如果已经存在数据倾斜问题,可以使用Redis集群提供的重分片命令来手动调整key分布。
  3. 谨慎使用HashTag:
    • 根据实际需求合理使用HashTag,避免大量数据被映射到同一个实例上。
    • 如果必须使用HashTag,应确保HashTag的值能够均匀分布到不同的Slot中。
  4. 处理热点数据:
    • 对于热点数据,可以采取多副本策略,将其分散到不同的实例上,以减轻单个实例的负载。
    • 也可以考虑使用本地缓存(如JVM本地缓存)来缓存热点数据,减少对Redis的访问次数。
  5. 应用层面做负载均衡:
    • 在应用层面上使用更复杂的逻辑来决定key应该存储在哪个节点上,以获得更均匀的数据分布。
  6. 增加节点数:
    • 通过增加更多的节点然后重新均衡key分布来解决数据倾斜问题。
  7. 监控和预警:
    • 持续监控Redis集群的状态,包括每个节点的负载、内存使用等指标。
    • 设立阈值预警机制,及时发现并解决数据倾斜问题。

综上所述,Redis数据倾斜是一个需要高度重视的问题。通过避免BigKey、均衡Slot分配、谨慎使用HashTag、处理热点数据、应用层面做负载均衡、增加节点数以及监控和预警等措施,可以有效地解决和预防数据倾斜问题,确保Redis集群的稳定性和性能。