Redis实践

Redis实践

缓存key命名

缓存key的构建,统一放在一个类里面,key需要有前,后缀,前缀使用系统的标识,后缀使用版本号。
后缀的版本号建议在配置中心进行配置。

@Component
public class CacheKeyBuilder {
// 值形如 demo
@Value("${cache.demo.prefix}")
private String prefix;
// 值形如 v1, v2
@Value("${cache.demo.suffix}")
private String suffix;

public String buildUserInfoKey(String userId) {
    return Joiner.on(":").join(prefix, userId, suffix);
}
}

优势:
1、缓存key统一管理,清晰明确
2、当有共用缓存时,不会发生冲突
3、当需要批量淘汰key时,只需要增长后缀版本号

数据需要设置过期时间

集合数据类型,里面的field无法直接设置过期时间

public void addCase() {
	stringRedisTemplate.opsForValue().set("stringKey", "stringValue", 5, TimeUnit.MINUTES);

// 操作集合类,可以使用lua脚本同时赋值和设置过期时间
}

批量读/写

1、同时操作多个key时,建议使用multi get 或者 pipeline功能,但是要注意一次发送的命令的数量,如果数据巨大依然会造成阻塞,可以根据业务场景进行测试,找到合适的数量。
Note:在Redis cluster模式下,pipeline不被支持

// 批量获取
public ~<T>~ List~<T>~ multiGetCase(List~<String>~ keys, Class~<T>~ returnType) {
List<String> result = stringRedisTemplate.opsForValue().multiGet(keys);
if (CollectionUtils.isEmpty(result)) {
    return new ArrayList<>(0);
}

List<T> data = new ArrayList<>(result.size());
for (String res : result) {
    data.add(JSON.parseObject(res, returnType));
}

return data;
}

// pipeline 使用
public List~<Object>~ pipelineCase() {
RedisSerializer keySerializer = stringRedisTemplate.getKeySerializer();
List<Object> List = stringRedisTemplate.executePipelined(new RedisCallback<Long>() {
    @Nullable
    @Override
    public Long doInRedis(RedisConnection connection) throws DataAccessException {
        // 调用与否均可
//            connection.openPipeline();
        for (int i = 0; i < 1000; i++) {
            String key = "123" + i;
            connection.get(keySerializer.serialize(key));
        }
            //依靠代理的返回,此处应该固定返回null
        return null;
    }
});

return List;
}

2、针对大集合,有些操作需要谨慎使用,会耗时很长,阻塞Redis。

// 批量获取
public ~<T>~ List~<T>~ multiGetCase(List~<String>~ keys, Class~<T>~ returnType) {
List<String> result = stringRedisTemplate.opsForValue().multiGet(keys);
if (CollectionUtils.isEmpty(result)) {
    return new ArrayList<>(0);
}

List<T> data = new ArrayList<>(result.size());
for (String res : result) {
    data.add(JSON.parseObject(res, returnType));
}

return data;
}

// pipeline 使用
public List~<Object>~ pipelineCase() {
RedisSerializer keySerializer = stringRedisTemplate.getKeySerializer();
List<Object> List = stringRedisTemplate.executePipelined(new RedisCallback<Long>() {
    @Nullable
    @Override
    public Long doInRedis(RedisConnection connection) throws DataAccessException {
        // 调用与否均可
//            connection.openPipeline();
        for (int i = 0; i < 1000; i++) {
            String key = "123" + i;
            connection.get(keySerializer.serialize(key));
        }
            //依靠代理的返回,此处应该固定返回null
        return null;
    }
});

return List;
}

2、针对大集合,有些操作需要谨慎使用,会耗时很长,阻塞Redis。

删除操作

针对大object,使用unlink进行删除,避免阻塞.

private StringRedisTemplate stringRedisTemplate;

public void delBigObject(String cacheKey) {
stringRedisTemplate.unlink(cacheKey);
}

案例:

1、统计在线人数案例
背景:使用zset存储用户的心跳请求时间,来统计在线人数。
优化前:同步写入zset,并计算zcount,获取数量
优化后:
写操作:异步+时间窗口,每经过一个时间窗口,异步写入redis
读操作:有调度任务定期查询zset的长度,写入一个专门的key,业务读这个key,时间复杂度降为O(1)。
2、通用热key优化案例:
提供热点key的监控机制,发现热点key时,自动在缓存SDK内形成本地缓存。数据更新时,需要下发更新通知,清除本地缓存,待后续把key打热。
京东开源热key探测(JD-hotkey)中间件单机qps 2万提升至35万实录 - 武伟峰的个人空间 - OSCHINA - 中文开源技术交流社区
3、缓存和数据库操作的顺序,需要保证一致性,前提都需要设置过期时间
a、简单场景方案1:
如后台类操作,可以同时操作缓存和数据库
b、cache aside
先操作数据库,在删缓存。 只有在删缓存失败的时候,会导致读到旧数据,删除缓存失败概率较低。
c、方案2:异步双删
删除缓存,更新数据库,异步删除缓存(可以在加上失败重试)
d、方案3:读写分离
读操作就读缓存
写操作就操作数据库
监听binlog的变更,准实时淘汰key(避免上述方案2对业务代码的大量侵入)
e、方案3:读写分离2
读操作就读缓存
写操作就操作数据库
监听binlog的变更,准实时更新缓存,重要的场景下,在加上定期的任务去校对缓存和数据库(此时需要注意对同一条记录的操作的顺序)
4、使用RedisTemplate的注意事项
一些没有直接提供相关api的操作,不建议通过RedisTemplate直接获取connection进行一些操作(不手动释放链接会导致连接池耗尽),如果确实使用该方式,一定要使用RedisConnectionUtil对链接进行释放
RedisConnection redisConnection = stringRedisTemplate.getConnectionFactory().getConnection();
// do xxx
RedisConnectionUtils.releaseConnection(redisConnection, stringRedisTemplate.getConnectionFactory(), /按实际情况传值/false);

建议的方式是使用execute方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值