多大的 key 算大 ?
关于 Redis 中多大的 key 算大,这个貌似没有统一的定义。下面是摘自几篇博客中的关于大 key 的定义:
定义一:[1]
- 单个简单的 key 存储的 value 很大;
Hash
,Set
,ZSet
,List
中存储过多的元素(以万为单位);- 一个集群存储了上亿的 key , key 本身过多也带来了更多的空间占用;
在测试环境(Redis 是单机版)使用 RENAME
功能时是好的,到了生产环境(阿里云的 Redis 集群版)报了如下错误:
ERR 'RENAME' command keys must in same slot.
channel: [id: 0x31b56a88, L:/10.0.3.34:46962 - R:r-xxxxxxxxxxxxxxxxxx.redis.rds.aliyuncs.com/xxx.xxx.xxx.xxx:6379]
command: (RENAME),
promise: java.util.concurrent.CompletableFuture@1e13ae01[Not completed, 1 dependents],
params: [[99, 108, 111, 99, 107, 45, 105, 110, 58, 102, ...], [99, 108, 111, 99, 107, 45, 105, 110, 58, 102, ...]];
nested exception is org.redisson.client.RedisException: ERR 'RENAME' command keys must in same slot.
channel: [id: 0x31b56a88, L:/10.0.3.34:46962 - R:r-xxxxxxxxxxxxxxxxxx.redis.rds.aliyuncs.com/xxx.xxx.xxx.xxx:6379]
command: (RENAME),
promise: java.util.concurrent.CompletableFuture@1e13ae01[Not completed, 1 dependents],
params: [[99, 108, 111, 99, 107, 45, 105, 110, 58, 102, ...], [99, 108, 111, 99, 107, 45, 105, 110, 58, 102, ...]]
使用阿里开源的 redis-shake 工具同步 Redis 数据(这个开源工具貌似暂时仅支持单向同步,我记得阿里云提供的工具里貌似是支持双向同步的)。
这里是同步( sync )的示例,另外还支持从备份文件恢复( restore )数据。
问题现象
-
后端服务获取用户令牌信息时有几率获取不到。
这个处理是在Filter
中执行的,在所有业务处理之前。
采用StringRedisTemplate
操作 Redis 缓存,令牌是个String
类型的缓存。
令牌缓存的 KEY 在 Redis 中是一直存在的,远没到过期时间。 -
获取不到令牌的请求,响应也很快,并不是由于 Redis 超时导致的。
-
使用同一台 Redis(db 不同)的其它服务一切都是正常的,从没出现过类似问题。
-
线下的测试环境使用的自建 Redis,并没有出现过这个问题。
-
出问题服务的容器镜像是 9 天前构建的。
-
服务重启之后会恢复正常,但不确定多长时间之后又会再次出现。
重启后可能一天内都是好的,也可能几分钟之后就又出问题。 -
与此同时,Redis 性能监控中的命中率图表会出现明显的波动,与服务出问题的时间基本一致。
命中率波动非常大,正常情况下一般在 95% 左右,但出问题时可能会降到 20% 以下。
spring-boot-starter-data-redis 默认仅支持配置一个 redis 服务(spring.redis.xxx)。若要配置多个,则需要手动添加相关的配置代码。 spring-boot-with-multi-redis 就是一个多 redis 的 spring-boot 示例,不过是基于 1.4.0.RELEASE 版的,部分配置方法在新版本中已经没有了。
使用 redisTemplate 尝试通过单次访问 Redis 获取多个数据时,使用了 multi 和 exec 方法。但在运行时报了如下错误:
io.lettuce.core.RedisCommandExecutionException: ERR EXEC without MULTI
代码如下:
redisTemplate.multi();
for (String key : keys) {
hmget(key);
}
List<Object> result = redisTemplate.exec();
return result;
Key
-
DEL key
该命令用于在 key 存在时删除 key。 -
DUMP key
序列化给定 key,并返回被序列化的值。 -
EXISTS key
检查给定 key 是否存在。 -
EXPIRE key seconds
为给定 key 设置过期时间,以秒计。 -
EXPIREAT key timestamp
EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置过期时间。不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳 (unix timestamp)。 -
PEXPIRE key milliseconds
设置 key 的过期时间以毫秒计。 -
PEXPIREAT key milliseconds-timestamp
设置 key 过期时间的时间戳 (unix timestamp) 以毫秒计 -
KEYS pattern
查找所有符合给定模式 ( pattern) 的 key。 -
MOVE key db
将当前数据库的 key 移动到给定的数据库 db 当中。 -
PERSIST key
移除 key 的过期时间,key 将持久保持。 -
PTTL key
以毫秒为单位返回 key 的剩余的过期时间。 -
TTL key
以秒为单位,返回给定 key 的剩余生存时间 (TTL, time to live)。 -
RANDOMKEY
从当前数据库中随机返回一个 key。 -
RENAME key newkey
修改 key 的名称 -
RENAMENX key newkey
仅当 newkey 不存在时,将 key 改名为 newkey。 -
SCAN cursor [MATCH pattern] [COUNT count]
迭代数据库中的数据库键。 -
TYPE key
返回 key 所储存的值的类型。
想确认一下 INCR
命令是不是原子性的,所以写了段代码试了一下。
安装所需包
额外安装了一个 Args 包用来解析命令行参数,具体文档参考 这里。
Install-Package StackExchange.Redis -Version 2.0.601
Install-Package Args -Version 1.2.1
在 Redis 数据结构 中简单介绍了 Redis 的 5 种数据结构及常用命令。
其中的示例是在命令行窗口执行的。
下面的代码则是 .NET Core 中通过 StackExchange.Redis 包实现的相同功能的示例。
其中最重要的一点区别就是很多命令(如 删除)在命令行中返回的是删除的元素数,而在 StackExchange.Redis 中返回则是 bool
类型。这点需要注意。
Redis 可以存储键与 5 种不同的数据结构类型之间的映射:
STRING
(字符串)LIST
(列表)SET
(集合)HASH
(散列)ZSET
(有序集合)
结构类型 | 结构存储的值 | 结构的读写能力 |
---|---|---|
STRING |
可以是字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作; 对整数和浮点数执行自增(increment)或者自减(decrement)操作 |
LIST |
一个链表,链表上的每个节点都包含了一个字符串 | 从链表的两端推入或者弹出元素; 根据偏移量对链表进行修剪(trim); 读取单个或者多个元素; 根据值查找或者移除元素 |
SET |
包含字符串的无序收集器(unordered collection),并且被包含的每个字符串都是独一无二、各不相同的 | 添加、获取、移除单个元素; 检查一个元素是否存在于集合中; 计算交集、并集、差集; 从集合里面随机获取元素 |
HASH |
包含键值对的无序散列表 | 添加、获取、移除单个键值对; 获取所有键值对 |
ZSET |
字符串成员(member)与浮点数分支(score)之间的有序映射,元素的排列顺序由分支的大小决定 | 添加、获取、移除单个元素; 根据分支范围(range)或者成员来获取元素 |
在 https://redis.io/clients 可以查看常用的 Redis 客户端。
CSRedis
Redis Desktop Manager:Open source cross-platform Redis Desktop Manager based on Qt 5
GitHub 下载地址:https://github.com/uglide/RedisDesktopManager/releases
运维切换了新的 Redis 架构之后,偶尔会报 No more data 或 Zero length respose 的错误。
架构
客户端 C# 使用的 ServiceStack.Redis 3.9.60.0 包,通过连接池 PooledRedisClientManager
管理连接;
Redis 是一主多从;
中间通过 HAproxy 代理,实现 LB;
异常信息
客户端
第二次跳进这个坑了。不同的 Redis 客户端保存和读取数据的方式有些不一样的地方。
之前是在做 APP 的后台接口,用的 SpringBoot 的 Redis 包,和之前.Net 项目中使用的 ServiceStack.Redis 包保存到 Redis 服务器用的值会不同。最常用的 string
就不一样,ServiceStack.Redis 会在字符串的两头各加一个双引号,还有 Guid
,除了会加双引号之外,还会去除中间的半角横线。
这次则是在 .Net Core 项目中使用的 CSRedisCore 包,现象和 SpringBoot 中类似。
下面是几个常用类型的测试结果。