您觉得本文档还缺少什么内容?可以自己补充~
redis 配置
bc:
cache:
type: REDIS
redis:
ip: 10.10.20.233
port: 6379
# redis 的密码,没有配置密码的改成单引号的空字符串: ''
password: ''
database: 0
spring:
cache:
type: GENERIC
redis:
host: ${bc.redis.ip}
password: ${bc.redis.password}
port: ${bc.redis.port}
database: ${bc.redis.database}
修改redis缓存的序列化类型
bc:
cache:
serializerType: ProtoStuff # 可选值: ProtoStuff JDK JACK_SON
redis 是否缓存null 值
bc:
cache:
cacheNullVal: true
缓存的key不就是一个字符串吗,为啥要给每个key单独封装成一个类 (XxxCacheKeyBuilder)?
- 问题1: 缓存key冲突,不能识别不同模块、不同业务的缓存(如相同的用户id和商品id不做任何修改,直接作为Redis的key,就可能会导致key冲突)
- 问题2: 张三存储用户缓存时,使用了
user:1
作为key存储, 李四操作用户缓存时,由于手误使用了usr:1
来操作同一个用户缓存。王五来接手项目时,以为没人用过缓存,又使用userinfo:1
来操作用户缓存。 - 问题3: 不同的开发人员水平参差不齐,有些人甚至重来没用过缓存,不知道如何命名key。 张三的key以
:
作为分割符、 李四用_
、王五用驼峰。 - 问题4:素质差的开发,直接就使用 魔法数,素质高的开发,会将key抽成 常量。
- 问题5: 张三用
user:1
来存储用户对象, 李四却用user:1
来存储用户的姓名。 (XxxCacheKeyBuilder 并没有解决此问题,欢迎留言讨论解决方案) - 问题6: 过期时间到底设置为多少?
- 其他问题...
为了解决以上问题, 所以必须封装出一套缓存key的生成器,隐藏缓存key的生成细节,让开发者拿来即用,传入动态参数,即可返回全平台统一规则的 key。 所以,本系统封装的CacheKeyBuilder封装了以下功能:
- 保证key的唯一性,体现了可靠性。
- 统一的命名规则,体现了可读性。
- 每个CacheKeyBuilder表示一种业务类型的缓存,体现了单一职责。
- 隐藏key的字符串拼接细节, 减少出错概率。 体现了可靠性。
- 统一设置过期时间
当然,本系统采取了一个key封装成一个对象的方式,来解决上述问题。 如果你觉得太重,可以使用一个方法来封装一个key也行。
- key命名风格 【推荐】 Redis key命名需具有可读性以及可管理性,不该使用含义不清的key以及特别长的key名; 【强制】以英文字母开头,命名中只能出现小写字母、数字、英文点号(.)和英文半角冒号(:); 【强制】不要包含特殊字符,如下划线、空格、换行、单双引号以及其他转义字符;
- 命名规范
【强制】命名规范:[租户编码:][服务模块名:]业务类型[:业务字段][:value类型][:业务值]
1)租户编码: 可选。 用来区分不同租户数据缓存。 如: 内置租户用0000。
2)服务模块名:可选。 用来区分不同服务或功能模块的缓存。 如: 仅在权限服务使用和仅在消息服务使用的缓存分别用authority和msg来区分,多个服务共用的缓存可以不设置该段,或者设置为common。
3) 业务类型: 必填。 用来区分不同业务类型的数据缓存。 通常设置为表名。 同一key有多个业务类型时, 使用英文半角点号 (.)分割,用来表示一个完整的语义。 如user.activity 表示存储用户和活动、 user表示存储用户如:用户表、应用表分别用 user、application来区分。
4) 业务字段: 可选。用来区分业务值是那个字段。 通常设置为字段名。 同一key有多个业务类型时,业务字段对应多个业务类型。使用英文半角点号 (.)分割,用来表示一个完整的语义。 如业务类型为
user.activity
表示存储用户和活动, 则应该用id.id
跟user.activity
对应,表示key中包user的id 和 activity的id 5) value类型: 可选。 用来区分value。 Redis key命名包含key所代表的value类型,以提高可读性。 如: obj 表示value是对象,number表示value是数字, string 表示value是字符串。 6) 业务值 :可选。 用来区分同一业务类型的不同行的数据缓存。如:用户表id为1的数据和id为2的,分别是指该值为1和2。
示例:
注意: => 左边表示缓存的完整key , => 右边表示缓存存储的值
- tenant:Id:1:obj => {"id:":1, "name:"租户1"} 表示:租户表(tenant)的租户id(id)为 1 的数据,存储(obj)类型的值 ({"id:":1, "name:"租户1"})
- 0000:authority:user:Id:1:obj => {"id:":1, "name:"张三"} 表示:租户(0000),权限服务(authority)中用户表(user)的用户id(id)为 1 的数据,存储(obj)类型的值 ({"id:":1, "name:"张三"})
- 0000:authority:user:account:zhangsan:obj => {"id:":1, "name:"张三"} 表示:租户(0000),权限服务(authority)中用户表(user)的用户账号(account)为 zhangsan 的数据,存储(obj)类型的值 ({"id:":1, "name:"张三"})
- key 设计 【强制】:拒绝bigkey(防止网卡流量、慢查询)。 String类型控制在10KB以内,Hash、List、Set、ZSet元素个数不要超过5000。 【强制】禁止大key。 大key数据存⼊Redis,除了带来极大的内存占用外,在并发高时,很容易就会将网卡流量占满,进而造成整个服务器上的所有服务不可用。虽然Redis支持512MB大小的string,但是假设1mb的string大key,每秒重复写入10次,就会导致写入网络IO达10MB; (1) 读写大key会导致超时严重,网卡流量占满,甚至阻塞服务,更甚者导致宕机风险。 (2) 如果删除大key,DEL命令可能阻塞Redis进程数十秒,使得其他请求阻塞,对应用程序和Redis集群可用性造成严重的影响。 (3) 每个key不要超过10Kb。
代码中如何直接使用缓存
- 继承带缓存的父类 SuperCacheServiceImpl 、SuperCacheService、SuperCacheController 后, 操作单体时,即可使用共用的缓存。 子类继承SuperCacheServiceImpl 后,需要实现 cacheKeyBuilder 方法,用于构造缓存的前缀。 如:
public class UserServiceImpl extends SuperCacheServiceImpl<UserMapper, User> implements UserService {
@Override
protected CacheKeyBuilder cacheKeyBuilder() {
return new UserCacheKeyBuilder();
}
}
public class UserCacheKeyBuilder implements CacheKeyBuilder {
@Override
public String getPrefix() {
return "USER";
}
@Override
public Duration getExpire() {
return Duration.ofHours(24);
}
}
实现 cacheKeyBuilder 方法后,调用UserService的getById、updateById、removeById等方法时,会自动存取或淘汰key-value类型的用户缓存, 缓存的key格式为: {TENANT}:USER:{id}
, 储存的value为user对象。
如何设置缓存的有效期
- 永不过期
@Override
public Duration getExpire() {
return null;
}
- 指定过期时间
@Override
public Duration getExpire() {
return Duration.ofHours(24); // 24 小时候过期
}
如何自定义使用缓存?
- 注入CacheOps 或 CachePlusOps
@Resouce
private CacheOps cacheOps; // 普通功能, key - value 存取
@Resouce
private CachePlusOps cachePlusOps; // 增强功能,包含redis的高级api
@Resouce
RedisOps redisOps; // 增强功能,包含redis的大部分 api ,若你的项目大量需要使用redis ,且不用考虑支持其他缓存,可以直接使用RedisOps
- 创建CacheKeyBuilder
public class UserCacheKeyBuilder implements CacheKeyBuilder {
@Override
public String getPrefix() {
return "USER"; // 缓存的前缀,用以区分不同类型的缓存, 必须跟其他类型保持唯一!!!
}
@Override
public Duration getExpire() {
return Duration.ofHours(24); // 缓存过期时间, 返回 null 则永不过期
}
}
- 使用
User user = userMapper.selectById(123);
CacheKey cacheKey = new UserCacheKeyBuilder ().key(user.getId());
cacheOps.set(cacheKey, user); // 设置
// 查询
User cacheUser = cacheOps.get(cacheKey);
// 淘汰
cacheOps.del(cacheKey);
cachePlusOps.keys('*user*'); // 模糊搜索key
cachePlusOps.scanUnlink('*user*'); // 模糊淘汰key
// 参考源码, redisOps 支持 hash set list zset 等api
内存建议
- 控制key的长度
- 避免存储bigkey
- 选择合适的数据类型
- key设置过期时间
- 实例设置maxmemory
- 数据插锁后写入Redis
性能建议
- 避免存储bigkey
- 4.0+ 版本开启lazy-free
- 不使用复杂度过高的命令
- O(N) 命令关注N的大小
- 关注DEL时间复杂度
- 批量命令代替单个命令
- 推荐使用Pipeline
- 避免集中过期key
- 使用长连接操作 Redis
- 只使用db0
- 读请求量大,使用读写分离
- 写请求量大,使用分片集群
- 不开AOF或AOF配置没秒刷盘
- 非虚拟机部署Redis
- 关闭操作系统内存大页
可靠性建议
- 按业务线部署实例
- 部署主从集群防止数据丢失
- 关注主从复制参数
- 部署哨兵实现故障自动切换
资源规划建议
- 保证机器有足够的CPU/内存/带宽/磁盘资源
- 主库机器预留一半的内存资源
- 实例内存控制在10G以下
监控建议
- CPU/内存/带宽/磁盘不足预警
- slowlog 过多监控工预警
- 避免频繁短链接采集INFO信息
- 重点关注expired_keys/evicted_keys/latest_fork_usec
Redis 安全建议
- 不要把 Redis 部署在公网可访问的服务器上
- 部署时不使用默认端口 6379
- 以普通用户启动 Redis 进程,禁止 root 用户启动
- 限制 Redis 配置文件的目录访问权限
- 推荐开启密码认证
- 禁用/重命名危险命令(KEYS/FLUSHALL/FLUSHDB/CONFIG/EVAL)
日常运维建议
- 禁用 (KEYS/FLUSHALL/FLUSHDB/CONFIG/EVAL)
- 扫描线上实例,设置休眠时间
- 慎用monitor命令
- 从库必须设置read-only
- 合理配置timeout 和 tcp-keepalive 参数