1. 总体要求
Redis主要用于缓存处理,加快读取效率,但在使用过程中需要注意合理的使用,一般存储全局配置数据和一些访问非常频繁的较为静态的数据,另外注意过期时间控制,减少资源的不必要消耗。
Redis–不要触碰边界
Redis的边界
–红色区域代表危险
计算方面:Wildcard、Lua并发、1对N PUBSUB、全局配置/热点,会大量消耗计算资源(高计算消耗);
存储方面:Streaming慢消费、Bigkey等,造成高存储消耗。
网络方面:Keys等扫全表、Huge Batch mget/mset、大Value、Bigkey Range (如hgetall,smembers),造成高网
络消耗。
Redis的边界总结:
- 高并发不等于高吞吐
大 Value 的问题:高速存储并不会有特别大的高吞吐收益,相反会很危险;
- 数据倾斜和算力倾斜
bigKey 的问题:break掉存储的分配律;
热点的问题:本质上是cpu上的分配律不满足;
大 Range 的问题:对NoSQL的慢查询和导致的危害没有足够的重视。
- 存储边界
Lua使用不当造成的成本直线上升;
数据倾斜带来的成本飙升,无法有效利用;
- 对于 Latency 的理解问题(RT高)
存储引擎的 Latency 都是P99 Latency,如:99.99%在1ms以内,99.5%在3ms以内,等;
偶发性时延高是必然的。这个根因在于存储引擎内部的复杂性和熵。
Redis–阿里内部开发规约
每次 redis 操作设计前,请重点关注以下建议
推荐:
- 确定场景,是缓存(cache)还是存储型;
- Cache的使用原则是:“无它也可,有它更强”;
- 永远不要强依赖Cache,它会丢,也会被淘汰;
- 优先设计合理的数据结构和逻辑;
- 设计避免bigKey,就避免了80%的问题;
- Keyspace能分开,就多申请几个Redis实例;
- pubsub不适合做消息分发;
- 尽量避免用lua做事务。
不建议:
- 我的服务对RT很敏感。 >> 低RT能让我的服务运行的更好;
- 我把存储都公用在一个redis里。 >> 区分cache和内存数据库用法,区分应用;
- 我有一个大排行榜/大集合/大链表/消息队列;我觉得服务能力足够了。 >> 尽量拆散,服务能力不够可通过分布式集群版可以打散;
- 我有一个特别大的Value,存在redis里,访问能好些。 >> redis吞吐量有瓶颈。
2. Key名设计
- 【强制】Redis key 名前缀遵循可读性和可管理性,以项目缩写为前缀(防止key冲突),用冒号分隔,比如
项目缩写:模块简码:功能简码
。
正例:
项目级固定段:项目缩写,例:ecp:
模块级固定段:项目缩写:模块简码,例:ecp:device
功能级固定段:项目缩写:模块简码:功能简码,例:ecp:device:heart
- 【推荐】Redis key 命名采用小写结构,不采用驼峰格式,中间利用下滑线
_
做连接。
正例:缩写后 ecp:device:app_auth
- 【强制】Redis key 名前缀遵循简洁性,保证语义的前提下,控制key的长度,允许适当使用英文缩写。
反例:ecp:device:last_heartbeat_time
正例:缩写后 ecp:device:lht
- 【强制】不使用特殊字符(不包括冒号
:
,下划线_
)。
反例:包含空格、换行、单双引号以及其他转义字符
3. Value设计
-
【强制】拒绝 BigKey(防止网卡流量、慢查询),string 类型控制在 10KB 以内,hash、list、set、zset 元素个数不要超过 5000。
-
【强制】选择适合的数据类,合理控制和使用数据结构内存编码优化配置,但也要注意节省内存和性能之间的平衡。
反例:set ecp:device:deviceheart:{product_key}:{device_key} ONLINE
-
【推荐】控制 key 的生命周期,建议使用expire设置过期时间,条件允许可以打散过期时间,防止集中过期。
-
【推荐】冷热数据分离,不要将所有数据全部都放到Redis中。建议根据业务只将高频热数据存储到Redis中【QPS大于5000】,对于低频冷数据可以使用MySQL/ElasticSearch/MongoDB等基于磁盘的存储方式,不仅节省内存成本,而且数据量小在操作时速度更快、效率更高。
-
【推荐】不同的业务数据要分开存储。不要将不相关的业务数据都放到一个Redis实例中,建议新业务申请新的单独实例。因为Redis为单线程处理,独立存储会减少不同业务相互操作的影响,提高请求响应速度;同时也避免单个实例内存数据量膨胀过大,在出现异常情况时可以更快恢复服务。
4. 命令使用
-
【强制】禁止线上使用
keys、flushall、flushdb
等命令,通过 redis 的 rename 机制禁掉命令,或者使用scan
的方式渐进式处理。 -
【推荐】O(N)命令关注N的数量,例如
hgetall、lrange、smembers、zrange、sinter
等并非不能使用,但是需要明确 N 的值。有遍历的需求可以使用hscan、sscan、zscan
代替。 -
【推荐】使用批量操作提高效率,但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
正例:
原生命令:例如mget、mset
。
非原生命令:可以使用pipeline
提高效率。
注意:
a. 原生是原子操作,pipeline是非原子操作。
b. pipeline可以打包不同的命令,原生做不到。
c. pipeline需要客户端和服务端同时支持。
-
【推荐】Redis事务功能较弱,不建议过多使用。Redis 的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个 slot 上(可以使用 hashtag 功能解决)
-
【推荐】必要情况下使用 monitor 命令时,要注意不要长时间使用。
-
【推荐】不建议使用 redis 发布订阅(PUB/SUB)功能,不稳定,不可靠
5. 客户端配置
- 【推荐】客户端创建连接池时需要根据实际情况设置连接数大小,可以参考以下几个因素:
并发量:如果你的应用程序具有高并发的特点,可以增加连接数的值,以确保有足够的连接可供同时使用。例如,如果你预计同时可能有数百个并发请求,可以将连接数设置为几百。
系统负载:考虑到 Redis 服务器的负载情况,确保连接数不超过服务器可以承受的最大连接数。如果你的 Redis 服务器在高负载时响应变慢,可能是由于连接数过多导致的。
资源限制:请记住,每个连接都需要占用一定的内存和网络资源。确保你的系统有足够的资源来支持所设置的连接数值。
根据以上考虑因素,你可以尝试使用一些初始值,并通过基准测试和监控来调整连接数的值,以找到适合你应用程序需求的最佳设置。
需要注意的是,过大的连接数值可能会导致连接资源浪费和系统负担增加,而过小的值可能会导致连接瓶颈和性能下降。因此,根据实际情况进行测试和调整是很重要的。
5.1 Golang 相关
-
【推荐】建议使用 go-redis
github.com/redis/go-redis
-
【推荐】若使用 redigo 创建连接池,请务必正确设置
MaxIdle
参数。如果不设置MaxIdle
参数或将其设置为 0,连接池将不会保留任何空闲连接。这意味着每次需要使用 Redis 连接时,都会创建新的连接。这样可能导致频繁地创建和销毁连接,增加了连接的开销和延迟
func newPool(server, password string) *redis.Pool {
return &redis.Pool{
// 表示连接池中最大的空闲连接数
MaxIdle: 10,
// 表示同时可以从连接池中分配的最大连接数
MaxActive: 100,
// 表示连接在连接池中的空闲超时时间
IdleTimeout: 240 * time.Second,
// 用于创建 Redis 连接
Dial: func() (redis.Conn, error) {
// 未设置 DB,默认使用 DB0,根据实际情况选择 DB
c, err := redis.Dial("tcp", server)
if err != nil {
return nil, err
}
if password != "" {
if _, err := c.Do("AUTH", password); err != nil {
c.Close()
return nil, err
}
}
return c, err
},
// 用于测试连接是否可用
TestOnBorrow: func(c redis.Conn, t time.Time) error {
_, err := c.Do("PING")
return err
},
}
}
- 【推荐】go-redis 创建连接池示例
func NewRedisClient() *redis.Client {
return redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // 如果没有设置密码,可以留空
DB: 0,
PoolSize: 10, // 连接池中最大的连接数
})
}
5.2 JAVA 相关
** 【推荐】 **使用jedis连接池,需要根据业务情况配置以下属性(单实例模式)
redis:
# spring.redis.database: redis使用的数据库索引0-15。默认 0
database: 0
# spring.redis.timeout: redis连接超时时间 默认3000
timeout: 3000
redisTemplate:
# spring.redis.redisTemplate.enableTransactionSupport: 配置redisTemplate事务模式。默认开
enableTransactionSupport: true
# spring.redis.host: reids地址
host: ${REDIS_HOST}
# spring.redis.port: reids端口
port: ${REDIS_PORT}
# spring.redis.password: redis登录密码
password: ${REDIS_PASSWORD}
** 【推荐】 **使用lettuce连接池,需要根据业务情况配置以下属性(单实例模式)
redis:
timeout: 5000
host: ${REDIS_HOST}
port: ${REDIS_PORT}
password: ${REDIS_PASSWORD:}
lettuce:
pool:
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
max-wait: -1
# 连接池中的最大空闲连接 默认 8
max-idle: 8
# 连接池中的最小空闲连接 默认 0
min-idle: 0
** 【推荐】 **使用redisson连接池,需要根据业务情况配置以下属性(单实例模式)
redisson官方配置文档
redisson:
# 连接超时时间
timeout: 10000
# 模式 com.hzsun.micp.cloud.redisson.enums.Model
model: SINGLE
# 密码
password: ${REDIS_PASSWORD}
# 客户端名称
clientName: micp
# 等待加锁超时时间 -1一直等待
attemptTimeout: 10000
singleServerConfig:
database: ${REDIS_DATASOURCE}
# 集群地址
address: ${REDIS_PATH}
# 这个线程池数量被所有RTopic对象监听器,RRemoteService调用者和RExecutorService任务共同共享。默认2
threads: 4
# 这个线程池数量是在一个Redisson实例内,被其创建的所有分布式数据类型和服务,以及底层客户端所一同共享的线程池里保存的线程数量。默认2
nettyThreads: 4
# 传输模式
transportMode: NIO
Redis 规范参考
阿里云 Redis 开发规范:https://developer.aliyun.com/article/531067
HZERO Redis 使用规范:https://open.hand-china.com/hzero-docs/v1.3/zh/docs/development-specification/backent-development-specification/redis/
融合小组制定的 Redis 使用规范:https://tech.hzlinks.net/showdoc/web/#/304/16886