[请教]Spring Boot 使用 Redis,缓存集合数据的疑问 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
fox0001
V2EX    Java

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

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

    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     Solana     1429 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 16:46 PVG 00:46 LAX 09:46 JFK 12:46
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86