跳至主要內容
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • 蛋疼的 Redis 和需求…我想问下各位都是怎么实践 Redis 和 Mysql 数据一致性的或者怎么骂回去?
未分類
29 12 月 2020

蛋疼的 Redis 和需求…我想问下各位都是怎么实践 Redis 和 Mysql 数据一致性的或者怎么骂回去?

蛋疼的 Redis 和需求…我想问下各位都是怎么实践 Redis 和 Mysql 数据一致性的或者怎么骂回去?

資深大佬 : Aruforce 6

1.目前基本技术栈

  1. SpringMVC
  2. Mybatis
  3. Redis

2.基本代码实践思路

  1. 数据更新完全在 Mysql 事务之中 不会读取更新缓存
  2. 现在 Redis 只是用来做接口缓存(数据有有效期,Key 根据 API 入口参数建立,value 就是 Mysql 查询的结果)

3. 目前提出的需求及存在的问题

  1. Mysql 数据更新之后 要求全部的接口必须和数据库一致
  2. 现在的接口返回的数据有很多连表查询,所以 mybatis 全局缓存并不好使。。同一个 namespace 的连表查询结果在表数据更新后缓存会被删除但是其他的 namespace 不行。。所以不好使
  3. 老板不懂开发
  4. 如果能做给的实践也不算长

4.我该怎么处理?

  1. 无法完成缓存强一致性,完成不了需求。。这个不太好。。不过有好的说服的理由的话我也试试
  2. 统计在任何一个表数据更新之后需要删除关联查询接口的缓存? 这个工作量太大了
  3. 或者各位给我们一个好的 Redis 缓存实践? Redis 做接口缓存自我感觉也不太好

我倾向与 1 和 3
一个是不做。。
一个是 能争取实践或者我能学点东西。。。

2 太他么的蛋疼了

大佬有話說 (71)

  • 資深大佬 : EminemW

    弄个定时任务主动刷不就好了,如果是手动修改数据的,就弄个刷缓存按钮

  • 資深大佬 : jones2000

    redis 存储的时候增加查询的数据库表名, 数据更新了, 把 redis 里面涉及到这个表的缓存全部删了.

  • 主 資深大佬 : Aruforce

    @EminemW
    你走的还是 2 这个思路..不是不行 就是工作量大…
    而且 以后还或有新的连表查询加入….
    老代码还要跟着改…

  • 資深大佬 : ningmengmao

    spring cache 应该可以吧

  • 主 資深大佬 : Aruforce

    @jones2000 这相当于一个把 关联数据的删除不和老业务代码合在一起了 可以做成一个切面逻辑只用把变更的表名弄出来就行然后直接扫描 redis key 然后删除…这应该算是 2 的变种

  • 資深大佬 : sagaxu

    先撤掉 redis,mysql 内存加大点,看看性能是不是真的扛不住

  • 主 資深大佬 : Aruforce

    @sagaxu mysql 调整过。。模拟线上流量 压测过了不行… 如果不对接口拦截的服务全是超时和 500 傻的

  • 資深大佬 : cz5424

    在 db 层做 hook,查询优先命中缓存,修改删除缓存(没写过 java

  • 資深大佬 : cz5424

    多表查询要拆分查询,这个比较麻烦

  • 資深大佬 : k9982874

    根据经验 redis 缓存做到接口级别,记录级别做不了。你做的再好,一个批量条件 update 或者 delete 就废了。
    除非你把每次 update 受到影响的记录都重新查出来写入 redis,不过这本身就是个性能问题。

    同等下大佬给个解答。

  • 資深大佬 : sadfQED2

    我司方案,所有接口数据直接读 redis 。

    有一个 worker 监听 mysql binlog,根据 mysql binlog 生成 redis 中的新数据

  • 資深大佬 : sagaxu

    @Aruforce 强一致性基本不太现实,无论是 write back 还是 write through,都有一定的 delay,扫描一次 key 可能就要几十秒了,并不比超时更快。

  • 主 資深大佬 : Aruforce

    @cz5424 你说的这个就是
    > 可以做成一个切面逻辑只用把变更的表名弄出来就行然后直接扫描 redis key 然后删除.。。
    这一段… 基本原理

  • 主 資深大佬 : Aruforce

    @sadfQED2 binlog 转到 redis 也是有延时的。。。而且对以前的接口都要改。。。不现实。。。

  • 資深大佬 : sampeng

    什么毛病,自己实现的技术方案满足不了需求不去想办法解决,而是选择怼回去,跳槽,消极怠工?新一代程序员这么不堪了?

  • 資深大佬 : des

    不用连表查询,在内存中进行连接呢?

  • 主 資深大佬 : Aruforce

    @sadfQED2 而且连表查询 会十分胃疼

  • 主 資深大佬 : Aruforce

    @des 不可能的。。。已经有很多连表查询了 这些不可能有时间重写

  • 主 資深大佬 : Aruforce

    @sagaxu 如果没有 更好的实短时间完成的方案的话。。。我先就先推降低缓存有效期了。。。再不行就是做 mapper 的 hook 再不行就禁止写连表查询。。把连表操作转到内存里去了

  • 主 資深大佬 : Aruforce

    @sampeng 你要是能给解决方案就给。。。别一上来的就这指责别人的态度。。就像个老娘们似的

  • 資深大佬 : sampeng

    因为做的时候就没考虑高一致性的高并发。高并发和事务是相对互斥的实现方案。要想达到完美,需要对事务和缓存结构做非常好的设计。一看你说拿 api 的 key 做缓存就知道再项目开始就没设计缓存架构。这是技术债务,找老板要时间填坑或者自己加班填坑。

    我司有一个服务就是这么实现的,当然没有事务,但是性能差的发指。20 台机器吃 30 万日活。然后有互联网经验的做了数据级别缓存后,3 台即可。还是为了高可用…不然一台都够了。

    程序员的价值是解决问题,不用问别人,你自己是有方案的。一个一个试,这才是最有价值的地方。搞成了,这个坑你再不会进去,也有极其丰富的经验。问别人,谁能手把手教你呢

  • 資深大佬 : sampeng

    @Aruforce 方案就是不要连表,redis 做分布式事务。所有子查询都是 redis 缓存,所有写入先写 redis 。工作量肯定很大,你不会选

  • 資深大佬 : gadsavesme

    一般就是两种解决方案啊,延时双删,还有就是订阅 mysql 的 binlog 。不过极限情况下不同步肯定会有,要强一致那就加锁呗,数据同步过程中就等待好了,这个时间就毫秒级,应该也不大会影响。

  • 資深大佬 : micean

    调整缓存:主键 = 行数据
    调整接口:查询数据库返回需要数据行的主键,再从缓存中取回行数据

    等数据库扛不住再说

  • 資深大佬 : dawniii

    @sampeng 总感觉很多极限情况下,会有问题。比如 redis 写成功了,mysql 还没 commit 呢,机器挂了,redis 不会自动回滚吧,这时候 redis 的就是脏数据了,感觉总是要牺牲点什么。

  • 資深大佬 : sampeng

    @dawniii 我刚说了,取消数据库事务实现。全部靠程序控制。

  • 資深大佬 : dawniii

    @sampeng 不管怎么实现,都需要面临双写的情况吧。极限情况可能会出现,其中一个成功,另一个失败。然后再弄个分布式的事务去重试或者回滚(逆操作)吗?但是在你程序回滚的过程中,确实有脏数据了。

  • 資深大佬 : dawniii

    @dawniii 这算是最终一致性?

  • 資深大佬 : dswyzx

    一直以为 redis 的缓存用法是缓存热点数据,妹想到现在都是把 redis 当 mysql 用了

  • 資深大佬 : JasonLaw

    “Turning the database inside out with Apache Samza” by Martin Kleppmann – YouTube

  • 資深大佬 : kkkkkrua

    如果是用的 spring cache 组件的话,可以在更新的时候删除 cache

  • 資深大佬 : caryqy

    看你的第一个需求需要 Mysql 数据更新之后 要求全部的接口必须和数据库一致

    更新 mysql 时先把 redis 中此类数据锁住,此时进来的请求都等待,mysql 更新完之后更新 redis 中数据完成之后删除 redis 锁, 请求返回最新的缓存数据,如果此期间 mysql 或 redis 任何一端挂掉了 /服务挂掉了,请求端得到错误响应 /超时之后需要重新请求, 数据抄送一份到 kafka 中每天的定时任务去校验当天 /时数据

  • 資深大佬 : caryqy

    希望别一顿操作之后和这个类似 https://www.v2ex.com/t/735360

  • 資深大佬 : Jrue0011

    @dawniii 我感觉他的意思应该是写的话不操作数据库只写缓存,通过某种方式将缓存的变化异步更新到数据库

  • 資深大佬 : LJ2010

    什么是缓存? 重点缓解存储的压力, 而不是存储本身,既然是缓存必然存在数据不一致时差的问题,而且缓存目的是把热数据降温,而不是存储所有数据,换个角度就没必要纠结数据一致性的问题,什么样的数据需要一致性?我的观点涉及钱的需要,其他的不需要,所以没必要纠结数据瞬时的一致性,所以你一定要坚持 观点 1,否则就骂街:)

  • 資深大佬 : DoubleShut

    读 binlog 进行同步

  • 資深大佬 : AA5DE3F034ACCB9E

    不可能三角吗

  • 資深大佬 : pengliu

    要求数据实时就不要加缓存,加了缓存肯定会有数据不一致,根据业务需求再定技术方案

  • 資深大佬 : liudaolunhuibl

    @sadfQED2 想到过这种方案,但是如果是连表的缓存怎么办呢?貌似 binlog 都是单表的

  • 資深大佬 : hhyyd

    不是很明白主的问题。spring 的 cache 组件,通过 cacheEnable 和 cacheEvict 注解可以很方便的控制 redis 缓存啊。 麻烦的地方是,查询涉及到多张表,修改每张表的时候都需要删除查询时涉及到的 key,可能需要你设置一组合理的 key,来解决这个问题。

  • 資深大佬 : lijialong1313

    读写 redis,然后用 worker 丢到数据库里。

  • 資深大佬 : moonblog

    这不就是 spring redis cache 做的事情吗
    service 层,几个注解解决的事
    query 后(由于是 service 层,是否 joint 表随意),缓存到 redis
    update 后,evict redis cache

  • 資深大佬 : dawniii

    @Jrue0011 有可能是的。这种方法,感觉不是后台全场景都那么适用吧,后台一般有各种事务和查询条件。。。如果不是全数据都在 redis 里,在后台查询的时候也挺恶心,热的从 redis 查,冷的从数据库查?

  • 資深大佬 : Jrue0011

    mybatis 缓存是什么情况,你们是用 redis 实现 mybatis 的二级缓存吗?如果上 mybatis 本地缓存的话,一个服务器的修改也没法清除另一个服务器上的 mybatis 缓存啊

  • 資深大佬 : securityCoding

    @dswyzx 233 ,就是一个缓存策略问题 .
    cache aside , write/read through 最大区别是 write through 会代理数据操作 , 业务层面直接跟缓存组件交互.

  • 資深大佬 : mtrec

    cap,cache aside pattern
    读 缓存有直接用 没有就查数据库然后更新缓存
    写 写完 db 清对应的缓存

  • 資深大佬 : yzbythesea

    说实话 QPS 没上 10,真的不用 redis 缓存。

  • 資深大佬 : Vegetable

    @sadfQED2 这个读写分离可以的

  • 資深大佬 : laminux29

    上面一堆人还没搞清楚原因就给建议..

    1.Mysql 支持事务但性能不够,Redis 性能够但不支持事务。

    2.Redis 性能之所以够用,本质是因为相对于 Mysql,Redis 砍掉了数据安全与事务功能,这样全跑在内存里,又不要考虑事务,速度不快才怪。

    3.题主的需求:Mysql 数据更新之后,要求 Redis 必须和数据库一致,本质上是要给 Redis 增加事务,还要让 Redis 接受 Mysql 的控制,这是不现实的。

    ================
    几种方案:

    1.Mysql 数据只做新增,不查不改不删,然后推送到 Redis,Redis 做只查,然后允许 Mysql 与 Redis 存在短期内的不一致。这是大厂,包括谷歌的标准玩法。

    2.有钱能增加机器,并且业务支持并行写入或并行读取,则可以根据业务,把系统设计为对并行写入优化但会增加读取时间,或者设计为对并行读取优化但会增加写入时间。

    3.非常有钱,直接上 Oracle 最新版,支持内存表,虽然没 Redis 快,但比 mysql 快得多,还支持事务。

  • 資深大佬 : sadfQED2

    @liudaolunhuibl 1.并不是缓存表的数据,而是像主那样缓存接口数据,每次表更新重新掉接口拿
    2.我们没有连表的操作

  • 資深大佬 : sadfQED2

    @Aruforce 你连 binlog 转 redis 这点延时都忍受不了,那你就不能考虑缓存了啊

  • 資深大佬 : libook

    写程序难题 Top2:起名和维护缓存。

    把缓存和数据库的操作封装在一起,对业务功能仅提供统一的接口,接口被调用后内部管理缓存和数据库的操作。

    基本思路就是有读的话直接去 Redis 里读,有写的话先删缓存,再写数据库。

    可以在写入的时候使用事务,确保写入的时候数据库里的值没有被其他应用实例修改,如果遇到了就尝试二段提交。

    考虑到缓存刚刚被删除就有可能有读请求进来,为了确保一致性,可以在删缓存之前在数据库记录中加一个锁(类似排他锁),等更新完再解锁。

    数据库层级的事务在引入微服务思想和各种中间件、数据库之后,会有较大的局限性,此时就要更多考虑分布式事务方案。

  • 資深大佬 : corningsun

    难点是什么时候删除。
    表和接口不多的话,维护所有表和查询接口的关联关系。
    然后哪张表更新了,就删除对应查询接口的缓存即可。

  • 資深大佬 : kaneg

    你既要 mysql 的实时存储,还要 redis 的高速缓存,这是鱼翅和熊掌不可兼得的。
    除非你的接口非常简单和少量,明确知道哪个操作会造成缓存和数据库不一致,然后让其失效。
    理想要落地必然要做取舍,否则追求的结果必然是镜中花,水中月。

  • 資深大佬 : vindurriel

    读取全部走 redis key 分两种:列表和单行,其中列表的 values 能对应到单行的 keys,对应数据库每一行的 pk

    写入单行:在 mysql transaction 的最后一步写 redis 如果写入不成功 db 回滚 redis 不是集群的话就强一致了

    写入列表:batch 或者 stream 进行联表查询 只能最终一致

  • 資深大佬 : wangritian

    赞同 21 、29 、35L
    缓存是用在数据层的,而不是接口层,你自己也感觉到了
    其次也不能滥用,像复杂条件查询或是 join 查询等等,应该去优化索引和提高 mysql 硬件配置,然后考虑分表
    不建议你在错误的方案下解决问题

  • 資深大佬 : black11black

    @sadfQED2 所以一次 redis 请求还要附带一个 binlog 检查操作?感觉有点本末倒置啊

  • 資深大佬 : YouLMAO

    删除 Redis,MySQL 换成 cloud spanner

  • 資深大佬 : gosansam

    @laminux29 请问数据的更新是通过 binlog 完成的吗?

  • 資深大佬 : seth19960929

    我觉得你可以找一个支持 tag 方式的缓存库(类型 laravel 的 tags cache)
    然后你继续在 API 层面做缓存.
    针对 API 写好 tag, 比如 API 这样:
    接口 1 依赖 users, points 表, 就给这个接口增加 users, points 标签(用 model 的名字更好反射)
    接口 2 依赖 points 表, 给这个接口打上 points 标签

    当 users 表发生变化, 直接清空 users tag 的接口 1
    当 points 表发生变化, 直接清空 points tag 的接口 1, 2

  • 資深大佬 : cuiweieee

    修改数据用 mq 异步通知,mq 消费之后刷 redis

  • 資深大佬 : RedBeanIce

    @hhyyd #38 请问一个问题,spring cache 可以插入不是 string 类型的吗,,因为我看到底层调用的是 sring,不知道是否可以插入 list set hash zset

  • 資深大佬 : iceneet

    感觉用 redis 缓存要求强一致性有点怪

  • 資深大佬 : ZSeptember

    缓存和强一致冲突。

  • 資深大佬 : v2orz

    换一个内存数据库代替 mysql,去掉 redis

  • 資深大佬 : zhangfeiwudi

    我最近在做这一块

  • 資深大佬 : zhangfeiwudi

    @zhangfeiwudi 主要就是做个触发式缓存,db 更新了 发 q 或者监听胖消息通知 cache , 然后所有 cache 收口到一个团队来吐出 比如说我们有 innerapi 接口

  • 資深大佬 : xiaotanyu13

    把读多写少被连表又多的数据常驻 redis,放弃 @cache,自己写代码维护这类数据;
    连表查的数据的话就看情况了,如果要查的数据能从 redis 中取到,就可以少一次连表查询(指的是部分数据直接取 redis 中的数据就够了),然后剩余的数据从数据库查询
    更新的话 先更新 mysql,再更新 redis,如果 redis 更新失败,直接抛异常 让 mysql 回滚

  • 資深大佬 : sakasaka

    mysql 性能没有这么不堪,加入缓存必然会有一致性问题,使用各种监听器同步也会增加系统复杂度,如果业务真的对性能和一致性要求高,建议改用别的 nosql 数据库,这个需要结合具体业务。

  • 資深大佬 : linoder

    要不 …… 了解下 TiDB ?

  • 資深大佬 : dawniii

    @vindurriel
    @xiaotanyu13

    写入单行:在 mysql transaction 的最后一步写 redis 如果写入不成功 db 回滚 redis 不是集群的话就强一致了

    ================================================================================

    先更新 mysql,再更新 redis,如果 redis 更新失败,直接抛异常 让 mysql 回滚

    ==========================================================

    极限情况下,redis 成功,mysql 还没 commit 挂了 – –

文章導覽

上一篇文章
下一篇文章

AD

其他操作

  • 登入
  • 訂閱網站內容的資訊提供
  • 訂閱留言的資訊提供
  • WordPress.org 台灣繁體中文

51la

4563博客

全新的繁體中文 WordPress 網站
返回頂端
本站採用 WordPress 建置 | 佈景主題採用 GretaThemes 所設計的 Memory
4563博客
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?
在這裡新增小工具