V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
fox0001
V2EX  ›  Java

[请教]Spring Boot 使用 Redis,缓存集合数据的疑问

  •  
  •   fox0001 · 2022-08-11 10:12:35 +08:00 via Android · 2639 次点击
    这是一个创建于 860 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Spring Boot 集成 Redis ,使用 @Cacheable 等注解,以 ID 为 key ,可以方便地缓存数据。但是如果要缓存一个集合的数据,同时需要读取或更新其中某条数据的情况下,怎样做才是最近实践?

    比如国家和地区数据,有 ID 、国家和地区名称等,最多 200 多条。这种数据变化频率极低,更新缓存可以忽略。只是有时需要获取所有数据(比如修改地址选国家),有时需要获取一条数据(比如显示地址)。

    方案 1 ,使用 List 对象缓存所有国家。

    取所有国家时,就是从 Redis 获取这个 List 对象。但是取一个国家时,需要或者这个 List 对象,再取出指定 ID 的数据。或者把 List 换成 LinkedHashMap ,提高获取单条数据的效率。但是,为了取一条数据而把所有国家从 Redis 反序列化出来,感觉有点浪费性能,甚至还不如直接从数据库取(这里需要测试对比)。

    方案 2 ,只缓存单条国家数据。

    就是 getCountryById 方法加上 @Cacheable 注解,提高获取单个国家的效率,而且这个使用场景更多。需要获取所有国家的话,直接查数据库。

    方案 3 ,单条国家数据和所有国家数据分开缓存。

    getCountryById 和 findAllCountries 两个方法都加上 @Cacheable 注解,各自的缓存 key 不同。缺点是缓存了两份相同的数据,更新缓存时还得两边都处理。

    方案 4 ,使用 Redis 的 List 或者 Hash 数据类型进行缓存。

    完美支持一次过获取全部数据和只获取单条数据的场景。更新时不用全部更新。缺点是不能使用 @Cacheable 等注解。

    或者有没有更好的方案?

    22 条回复    2022-08-28 10:14:19 +08:00
    Saxton
        1
    Saxton  
       2022-08-11 10:25:34 +08:00
    “感觉有点浪费性能,甚至还不如直接从数据库取”

    内存和磁盘 IO 哪个快?
    hhhhhh123
        2
    hhhhhh123  
       2022-08-11 10:42:15 +08:00
    不懂 java 但是这种场景 你想 o(1) 的话, 可以用 set ,key: '{''国家 1":xx, "国家 2":xx}' 用 json 就好了,才 200+个 key 而已 . 每次使用 取出 key ,然后使用 json 转一下就行。
    fox0001
        3
    fox0001  
    OP
       2022-08-11 10:42:25 +08:00 via Android
    @Saxton #1 因为 Hibernate 开启了二级缓存,所以不会每次都直接查数据库
    LeegoYih
        4
    LeegoYih  
       2022-08-11 10:50:22 +08:00
    具体问题具体分析,像国家这种数据,启动服务实例时,直接存在本地内存也没关系,因为不可能会出现下一秒就多一个或少一个国家情况,当前,为了以防万一可以加一个刷新接口。

    城市也是一样的,可以把热门城市放在本地内存,其他城市可以走缓存或数据库。
    hidemyself
        5
    hidemyself  
       2022-08-11 10:52:17 +08:00
    你们很缺内存吗?这点数据量就是全部 load 到内存,会有多大影响
    simonlu9
        6
    simonlu9  
       2022-08-11 10:56:33 +08:00
    cacheable 比较不好用,不能再同一个类使用,同类调用方法不生效,不能单独设置过期时间,比较坑,只能缓存列表,其他数据结构也用不上
    DeepRedApple
        7
    DeepRedApple  
       2022-08-11 11:01:27 +08:00
    直接使用 guava cache 缓存到内存里面,定时或者 LRU 更新读库就好了
    cheng6563
        8
    cheng6563  
       2022-08-11 11:03:24 +08:00
    Redisson
    vgbhfive
        9
    vgbhfive  
       2022-08-11 11:34:15 +08:00
    数据缓存到 redis 中,服务重启 load 到内存里,更新的时候先更新内存,在更新 redis 缓存。
    qinxi
        10
    qinxi  
       2022-08-11 11:53:10 +08:00
    @simonlu9 #6
    不能再同一个类使用,同类调用方法不生效 这个属于 spring aop 的问题. 调用默认使用的 this 不是 spring 代理的对象, 因此 aop 不生效. 我一般采用自己注入自己(@Lazy), 调用同类方法时用注入的对象调.

    过期时间这个确实不支持. 所以我自己写了一个带过期时间的 Cacheable
    monmon
        11
    monmon  
       2022-08-11 14:05:43 +08:00
    使用 Hash 类型存储单个国家数据,需要获取多个国家时使用 executePipelined 读取
    issakchill
        12
    issakchill  
       2022-08-11 15:40:42 +08:00
    如果是非分布式应用,直接用本地缓存好了
    stonewu
        13
    stonewu  
       2022-08-11 15:50:09 +08:00
    之前工作中使用的是方案 3 ,需要更新数据时,通过 @Caching+@CacheEvict 注解同时清除多个缓存
    lmshl
        14
    lmshl  
       2022-08-11 15:58:54 +08:00
    我选方案 5 ,把国家列表缓存到内存中,性能起码吊打 Redis 100 条街
    leogm9408leo
        15
    leogm9408leo  
       2022-08-11 16:01:59 +08:00
    可枚举的、低频变更的数据,直接加载到内存里好了,存 redis 还要走网络 IO 和序列化
    fox0001
        16
    fox0001  
    OP
       2022-08-11 16:03:07 +08:00 via Android
    @lmshl #14 是的,这是性能最佳。不过要做好更新缓存的接口。
    themostlazyman
        17
    themostlazyman  
       2022-08-11 16:44:37 +08:00
    如果是 web 的话,性能最佳直接放客户端了。单机可以直接放内存,多机还是 redis 吧,方便维护。
    siweipancc
        18
    siweipancc  
       2022-08-12 13:48:26 +08:00 via iPhone
    简单业务用抽象,不然老实用客户端。

    或者自定义抽象,那就等同于维护了一套新的架构。
    zhady009
        19
    zhady009  
       2022-08-12 15:00:57 +08:00
    @Cacheable 有 keyGenerator 和 cacheResolver 来自定义需求
    本地内存更新推荐用 Redisson 里的 RLocalCachedMap
    golangLover
        20
    golangLover  
       2022-08-27 23:33:52 +08:00
    @fox0001 想请问一下你们关于 hibernate 二级缓存的问题,因为我们自己也在用,但有点问题我想不通。先谢谢了。

    主要是我们对客户端的服务上用了二级缓存。问题是这个服务本身是可以被外部修改的(例如管理页面),但是管理页面跟这个服务是分离的。也就是说管理页面的修改其实并不能直接反应到这个服务上。那我们就想可不可以直接清除所有 cache ,但是这样做会有缓存雪崩的问题。问题是这个管理页面其实同时间可能修改颇为多的表,例如更新一个 store 页面,其实可能在更新三个表,那管理页面的清楚 cache 应该如何准确的 evict 掉这三个表的缓存呢。然而我们可能在管理页面可以改掉几十个表的。而且不同服务之间应该属于不同的 session factory, 应该也不能共用吧。请问你们是如何解决掉这个问题的。
    fox0001
        21
    fox0001  
    OP
       2022-08-28 08:27:24 +08:00 via Android
    @golangLover #20 多个服务都是使用 Hibernate 的话,可以配置为指向同一个二级缓存。Hibernate 二级缓存就不用手工清除了。

    如果有的服务使用 Java ,有的使用 Go ,这种情况没遇到过。
    golangLover
        22
    golangLover  
       2022-08-28 10:14:19 +08:00
    @fox0001 好的。谢谢你。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4773 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 05:38 · PVG 13:38 · LAX 21:38 · JFK 00:38
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.