查看原文
其他

Redis+Guava,性能炸裂!

点击关注 👉 顶级架构师 2023-09-18
推荐关注
顶级架构师后台回复 1024 有特别礼包


作者:热黄油啤酒
来源:c1n.cn/cV4xo

上一篇:动态上传jar包热部署实战


大家好,我是顶级架构师。


目录
  • 前言

  • 设计示例

  • 后记


前言


我们开发中经常用到 Redis 作为缓存,将高频数据放在 Redis 中能够提高业务性能,降低 MySQL 等关系型数据库压力,甚至一些系统使用 Redis 进行数据持久化,Redis 松散的文档结构非常适合业务系统开发,在精确查询,数据统计业务有着很大的优势。

但是高频数据流处理系统中,Redis 的压力也会很大,同时 I/O 开销才是耗时的主要原因,这时候为了降低 Redis 读写压力我们可以用到本地缓存,Guava 为我们提供了优秀的本地缓存 API,包含了过期策略等等,编码难度低,个人非常推荐。


设计示例


| Redis 懒加载缓存

数据在新增到 MySQL 不进行缓存,在精确查找进行缓存,做到查询即缓存,不查询不缓存。


流程图如下:
代码示例:
// 伪代码示例 Xx代表你的的业务对象 如User Goods等等
public class XxLazyCache {

    @Autowired
    private RedisTemplate<String, Xx> redisTemplate;

    @Autowired
    private XxService xxService;// 你的业务service

    /**
     * 查询 通过查询缓存是否存在驱动缓存加载 建议在前置业务保证id对应数据是绝对存在于数据库中的
     */

    public Xx getXx(int id) {
        // 1.查询缓存里面有没有数据
        Xx xxCache = getXxFromCache(id);
        if(xxCache != null) {
            return xxCache;// 卫语句使代码更有利于阅读
        }
        // 2.查询数据库获取数据 我们假定到业务这一步,传过来的id都在数据库中有对应数据
        Xx xx = xxService.getXxById(id);
        // 3.设置缓存、这一步相当于Redis缓存懒加载,下次再查询此id,则会走缓存
        setXxFromCache(xx);
        return xx;
        }
    }

    /**
     * 对xx数据进行修改或者删除操作 操作数据库成功后 删除缓存
     * 删除请求 - 删除数据库数据 删除缓存
     * 修改请求 - 更新数据库数据 删除缓存 下次在查询时候就会从数据库拉取新的数据到缓存中
     */

    public void deleteXxFromCache(long id) {
        String key = "Xx:" + xx.getId();
        redisTemplate.delete(key);
    }

    private void setXxFromCache(Xx xx) {
        String key = "Xx:" + xx.getId();
        redisTemplate.opsForValue().set(key, xx);
    }

    private Xx getXxFromCache(int id) {
        // 通过缓存前缀拼装唯一主键作为缓存Key 如Xxx信息 就是Xxx:id
        String key = "Xx:" + id;
        return redisTemplate.opsForValue().get(key);
    }

}
// 业务类
public class XxServie {
    @Autowired
    private XxLazyCache xxLazyCache;
    // 查询数据库
    public Xx getXxById(long id) {
        // 省略实现
        return xx;
    }

    public void updateXx(Xx xx) {
        // 更新MySQL数据 省略
        // 删除缓存
        xxLazyCache.deleteXxFromCache(xx.getId());
    }

    public void deleteXx(long id) {
        // 删除MySQL数据 省略
        // 删除缓存
        xxLazyCache.deleteXxFromCache(xx.getId());
    }
}
// 实体类
@Data
public class Xx {
    // 业务主键
    private Long id;
    // ...省略
}


优点如下:

  • 保证最小的缓存量满足精确查询业务,避免冷数据占用宝贵的内存空间
  • 对增删改查业务入侵小、删除即同步
  • 可插拔,对于老系统升级,历史数据无需在启动时初始化缓存
  • 另外,搜索公众号Linux就该这样学后台回复“git书籍”,获取一份惊喜礼包。


缺点如下:

  • 数据量需可控,在无限增长业务场景不适用
  • 在微服务场景不利于全局缓存应用


总结:

  • 空间最小化
  • 满足精确查询场景
  • 总数据量可控推荐使用
  • 微服务场景不适用


| Redis 结合本地缓存

微服务场景下,多个微服务使用一个大缓存,流数据业务下,高频读取缓存对 Redis 压力很大,我们使用本地缓存结合 Redis 缓存使用,降低 Redis 压力,同时本地缓存没有连接开销,性能更优。


牛逼啊!接私活必备的 N 个开源项目!赶快收藏吧

流程图如下:

业务场景:在流处数处理过程中,微服务对多个设备上传的数据进行处理,每个设备有一个 code,流数据的频率高,在消息队列发送过程中使用分区发送,我们需要为设备 code 生成对应的自增号,用自增号对 kafka 中 topic 分区数进行取模。


这样如果有 10000 台设备,自增号就是 0~9999,在取模后就进行分区发送就可以做到每个分区均匀分布。


这个自增号我们使用 redis 的自增数生成,生成后放到 redis 的 hash 结构进行缓存,每次来一个设备,我们就去这个 hash 缓存中取,没有取到就使用自增数生成一个,然后放到 redis 的 hash 缓存中。


这时候每个设备的自增数一经生成是不会再发生改变的,我们就想到使用本地缓存进行优化,避免高频的调用 redis 去获取,降低 redis 压力。扩展:接私活


代码示例:
/**
 * 此缓存演示如何结合redis自增数 hash 本地缓存使用进行设备自增数的生成、缓存、本地缓存
 * 本地缓存使用Guava Cache
 */

public class DeviceIncCache {

    /**
     * 本地缓存
     */

    private Cache<String, Integer> localCache = CacheBuilder.newBuilder()
        .concurrencyLevel(16// 并发级别
        .initialCapacity(1000// 初始容量
        .maximumSize(10000// 缓存最大长度
        .expireAfterAccess(1, TimeUnit.HOURS) // 缓存1小时没被使用就过期
        .build();

    @Autowired
    private RedisTemplate<String, Integer> redisTemplate;

    /**
     * redis自增数缓存的key
     */

    private static final String DEVICE_INC_COUNT = "device_inc_count";

    /**
     * redis设备编码对应自增数的hash缓存key
     */

    private static final String DEVICE_INC_VALUE = "device_inc_value";

    /**
     * 获取设备自增数
     */

    public int getInc(String deviceCode){
        // 1.从本地缓存获取
        Integer inc = localCache.get(deviceCode);
        if(inc != null) {
            return inc;
        }
        // 2.本地缓存未命中,从redis的hash缓存获取
        inc = (Integer)redisTemplate.opsForHash().get(DEVICE_INC_VALUE, deviceCode);
        // 3. redis的hash缓存中没有,说明是新设备,先为设备生成一个自增号
        if(inc == null) {
            inc = redisTemplate.opsForValue().increment(DEVICE_INC_COUNT).intValue;
            // 添加到redis hash缓存
            redisTemplate.opsForHash().put(DEVICE_INC_VALUE, deviceCode, inc);
        }
        // 4.添加到本地缓存
        localCache.put(deviceCode, inc);
        // 4.返回自增数
        return inc;
    }

}


优点如下:

  • redis 保证数据可持久,本地缓存保证超高的读取性能,微服务共用 redis 大缓存的场景能有效降低 redis 压力
  • guava 作为本地缓存,提供了丰富的 api,过期策略,最大容量,保证服务内存可控,冷数据不会长期占据内存空间
  • 服务重启导致的本地缓存清空不会影响业务进行
  • 微服务及分布式场景使用,分布式情况下每个服务实例只会缓存自己接入的那一部分设备的自增号,本地内存空间最优
  • 在示例业务中,自增数满足了分布区发送的均匀分布需求,也可以满足统计设备接入数目的业务场景,一举两得


缺点如下:

  • 增加编码复杂度,不直接
  • 只适用于缓存内容只增不改的场景


总结:

  • 本地缓存空间可控,过期策略优
  • 适用于微服务及分布式场景
  • 缓存内容不能发生改变
  • 性能优


后记


redis 提供了丰富的数据类型及 api,非常适合业务系统开发,统计计数(increment,decrement),标记位(bitmap),松散数据(hash),先进先出、队列式读取(list)。


guava 缓存作为本地缓存,能够高效的读取的同时,提供了大量 api 方便我们控制本地缓存的数据量及冷数据淘汰。


我们充分的学习这些特性能够帮助我们在业务开发中更加轻松灵活,在空间与时间上找到一个平衡点。


欢迎大家进行观点的探讨和碰撞,各抒己见。如果你有疑问,也可以找我沟通和交流。


最后给读者整理了一份BAT大厂面试真题,需要的可扫码回复“面试题”即可获取。


公众号后台回复 架构 或者 架构整洁 有惊喜礼包!顶级架构师交流群

 「顶级架构师」建立了读者架构师交流群,大家可以添加小编微信进行加群。欢迎有想法、乐于分享的朋友们一起交流学习。

扫描添加好友邀你进架构师群,加我时注明姓名+公司+职位】


版权申明:内容来源网络,版权归原作者所有。如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。

猜你还想看

推荐一套开源通用后台管理系统(附源码)

看看人家那 IM 即时通讯系统,那叫一个优雅(附源码)

面试官:生成订单30分钟未支付,则自动取消,该怎么实现?

阿里技术专家:一文教你高效画出技术架构图

16个 Redis 常见使用场景,面试有内容聊啦

面试官问:MySQL的自增 ID 用完了,怎么办?

知名国产论坛,凉了!!!!

Spring Boot性能太差,教你几招轻松搞定

阿里云Redis的规范:键值设计、命令使用、客户端使用、相关工具

一个很酷的全文检索引擎,支持亿级数据,毫秒级查询!

OAuth 2.0 授权认证详解

曾经最有价值的 Gitee 开源项目给你们找来了!

10 种常见的软件架构模式


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存