我倾向与 1 和 3
一个是不做。。
一个是 能争取实践或者我能学点东西。。。
2 太他么的蛋疼了
一个表数据更新之后,把这个表所有的关联查询结果全部清除
问题:
开启mybatis的全局缓存,禁用连表查询,可以屏蔽上面那个方案的问题
问题:
1
EminemW 2020-12-17 12:25:08 +08:00 via iPhone
弄个定时任务主动刷不就好了,如果是手动修改数据的,就弄个刷缓存按钮
|
2
jones2000 2020-12-17 12:26:40 +08:00
redis 存储的时候增加查询的数据库表名, 数据更新了, 把 redis 里面涉及到这个表的缓存全部删了.
|
3
Aruforce OP |
4
ningmengmao 2020-12-17 12:29:08 +08:00 via Android
spring cache 应该可以吧
|
5
Aruforce OP @jones2000 这相当于一个把 关联数据的删除不和老业务代码合在一起了 可以做成一个切面逻辑只用把变更的表名弄出来就行然后直接扫描 redis key 然后删除...这应该算是 2 的变种
|
6
sagaxu 2020-12-17 12:38:37 +08:00 via Android
先撤掉 redis,mysql 内存加大点,看看性能是不是真的扛不住
|
7
Aruforce OP @sagaxu mysql 调整过。。模拟线上流量 压测过了不行... 如果不对接口拦截的服务全是超时和 500 傻的
|
8
cz5424 2020-12-17 12:45:18 +08:00 via iPhone
在 db 层做 hook,查询优先命中缓存,修改删除缓存(没写过 java
|
9
cz5424 2020-12-17 12:46:04 +08:00 via iPhone
多表查询要拆分查询,这个比较麻烦
|
10
k9982874 2020-12-17 12:46:17 +08:00
根据经验 redis 缓存做到接口级别,记录级别做不了。你做的再好,一个批量条件 update 或者 delete 就废了。
除非你把每次 update 受到影响的记录都重新查出来写入 redis,不过这本身就是个性能问题。 同等楼下大佬给个解答。 |
11
sadfQED2 2020-12-17 12:47:24 +08:00 via Android 5
我司方案,所有接口数据直接读 redis 。
有一个 worker 监听 mysql binlog,根据 mysql binlog 生成 redis 中的新数据 |
12
sagaxu 2020-12-17 12:51:17 +08:00 via Android
@Aruforce 强一致性基本不太现实,无论是 write back 还是 write through,都有一定的 delay,扫描一次 key 可能就要几十秒了,并不比超时更快。
|
13
Aruforce OP |
15
sampeng 2020-12-17 12:57:25 +08:00 via iPhone
什么毛病,自己实现的技术方案满足不了需求不去想办法解决,而是选择怼回去,跳槽,消极怠工?新一代程序员这么不堪了?
|
16
des 2020-12-17 12:58:07 +08:00
不用连表查询,在内存中进行连接呢?
|
19
Aruforce OP @sagaxu 如果没有 更好的实短时间完成的方案的话。。。我先就先推降低缓存有效期了。。。再不行就是做 mapper 的 hook 再不行就禁止写连表查询。。把连表操作转到内存里去了
|
21
sampeng 2020-12-17 13:06:03 +08:00 via iPhone 3
因为做的时候就没考虑高一致性的高并发。高并发和事务是相对互斥的实现方案。要想达到完美,需要对事务和缓存结构做非常好的设计。一看你说拿 api 的 key 做缓存就知道再项目开始就没设计缓存架构。这是技术债务,找老板要时间填坑或者自己加班填坑。
我司有一个服务就是这么实现的,当然没有事务,但是性能差的发指。20 台机器吃 30 万日活。然后有互联网经验的做了数据级别缓存后,3 台即可。还是为了高可用…不然一台都够了。 程序员的价值是解决问题,不用问别人,你自己是有方案的。一个一个试,这才是最有价值的地方。搞成了,这个坑你再不会进去,也有极其丰富的经验。问别人,谁能手把手教你呢 |
22
sampeng 2020-12-17 13:15:42 +08:00 via iPhone 1
@Aruforce 方案就是不要连表,redis 做分布式事务。所有子查询都是 redis 缓存,所有写入先写 redis 。工作量肯定很大,你不会选
|
23
gadsavesme 2020-12-17 13:22:21 +08:00 1
一般就是两种解决方案啊,延时双删,还有就是订阅 mysql 的 binlog 。不过极限情况下不同步肯定会有,要强一致那就加锁呗,数据同步过程中就等待好了,这个时间就毫秒级,应该也不大会影响。
|
24
micean 2020-12-17 13:30:22 +08:00
调整缓存:主键 = 行数据
调整接口:查询数据库返回需要数据行的主键,再从缓存中取回行数据 等数据库扛不住再说 |
25
dawniii 2020-12-17 13:35:17 +08:00
@sampeng 总感觉很多极限情况下,会有问题。比如 redis 写成功了,mysql 还没 commit 呢,机器挂了,redis 不会自动回滚吧,这时候 redis 的就是脏数据了,感觉总是要牺牲点什么。
|
27
dawniii 2020-12-17 13:46:43 +08:00
@sampeng 不管怎么实现,都需要面临双写的情况吧。极限情况可能会出现,其中一个成功,另一个失败。然后再弄个分布式的事务去重试或者回滚(逆操作)吗?但是在你程序回滚的过程中,确实有脏数据了。
|
29
dswyzx 2020-12-17 13:54:50 +08:00 2
一直以为 redis 的缓存用法是缓存热点数据,妹想到现在都是把 redis 当 mysql 用了
|
30
JasonLaw 2020-12-17 14:12:03 +08:00
"Turning the database inside out with Apache Samza" by Martin Kleppmann - YouTube
|
31
kkkkkrua 2020-12-17 14:15:45 +08:00
如果是用的 spring cache 组件的话,可以在更新的时候删除 cache
|
32
caryqy 2020-12-17 14:17:02 +08:00
看你的第一个需求需要 Mysql 数据更新之后 要求全部的接口必须和数据库一致
更新 mysql 时先把 redis 中此类数据锁住,此时进来的请求都等待,mysql 更新完之后更新 redis 中数据完成之后删除 redis 锁, 请求返回最新的缓存数据,如果此期间 mysql 或 redis 任何一端挂掉了 /服务挂掉了,请求端得到错误响应 /超时之后需要重新请求, 数据抄送一份到 kafka 中每天的定时任务去校验当天 /时数据 |
33
caryqy 2020-12-17 14:22:45 +08:00
希望别一顿操作之后和这个类似 https://www.v2ex.com/t/735360 🐶
|
35
LJ2010 2020-12-17 14:26:02 +08:00
什么是缓存? 重点缓解存储的压力, 而不是存储本身,既然是缓存必然存在数据不一致时差的问题,而且缓存目的是把热数据降温,而不是存储所有数据,换个角度就没必要纠结数据一致性的问题,什么样的数据需要一致性?我的观点涉及钱的需要,其他的不需要,所以没必要纠结数据瞬时的一致性,所以你一定要坚持 观点 1,否则就骂街:)
|
36
DoubleShut 2020-12-17 14:26:31 +08:00
读 binlog 进行同步
|
37
AA5DE3F034ACCB9E 2020-12-17 14:26:53 +08:00
不可能三角吗
|
38
pengliu 2020-12-17 14:26:53 +08:00
要求数据实时就不要加缓存,加了缓存肯定会有数据不一致,根据业务需求再定技术方案
|
39
liudaolunhuibl 2020-12-17 14:33:40 +08:00
@sadfQED2 想到过这种方案,但是如果是连表的缓存怎么办呢?貌似 binlog 都是单表的
|
40
hhyyd 2020-12-17 14:41:39 +08:00
不是很明白楼主的问题。spring 的 cache 组件,通过 cacheEnable 和 cacheEvict 注解可以很方便的控制 redis 缓存啊。 麻烦的地方是,查询涉及到多张表,修改每张表的时候都需要删除查询时涉及到的 key,可能需要你设置一组合理的 key,来解决这个问题。
|
41
lijialong1313 2020-12-17 14:47:23 +08:00
读写 redis,然后用 worker 丢到数据库里。
|
42
moonblog 2020-12-17 15:20:52 +08:00
这不就是 spring redis cache 做的事情吗
service 层,几个注解解决的事 query 后(由于是 service 层,是否 joint 表随意),缓存到 redis update 后,evict redis cache |
43
dawniii 2020-12-17 15:33:46 +08:00
@Jrue0011 有可能是的。这种方法,感觉不是后台全场景都那么适用吧,后台一般有各种事务和查询条件。。。如果不是全数据都在 redis 里,在后台查询的时候也挺恶心,热的从 redis 查,冷的从数据库查?
|
44
Jrue0011 2020-12-17 16:07:04 +08:00
mybatis 缓存是什么情况,你们是用 redis 实现 mybatis 的二级缓存吗?如果上 mybatis 本地缓存的话,一个服务器的修改也没法清除另一个服务器上的 mybatis 缓存啊
|
45
securityCoding 2020-12-17 16:12:08 +08:00
@dswyzx 233 ,就是一个缓存策略问题 .
cache aside , write/read through 最大区别是 write through 会代理数据操作 , 业务层面直接跟缓存组件交互. |
46
mtrec 2020-12-17 18:02:41 +08:00 via Android
cap,cache aside pattern
读 缓存有直接用 没有就查数据库然后更新缓存 写 写完 db 清对应的缓存 |
47
yzbythesea 2020-12-17 18:10:30 +08:00
说实话 QPS 没上 10,真的不用 redis 缓存。
|
49
laminux29 2020-12-17 18:26:46 +08:00 2
上面一堆人还没搞清楚原因就给建议..
1.Mysql 支持事务但性能不够,Redis 性能够但不支持事务。 2.Redis 性能之所以够用,本质是因为相对于 Mysql,Redis 砍掉了数据安全与事务功能,这样全跑在内存里,又不要考虑事务,速度不快才怪。 3.题主的需求:Mysql 数据更新之后,要求 Redis 必须和数据库一致,本质上是要给 Redis 增加事务,还要让 Redis 接受 Mysql 的控制,这是不现实的。 ================ 几种方案: 1.Mysql 数据只做新增,不查不改不删,然后推送到 Redis,Redis 做只查,然后允许 Mysql 与 Redis 存在短期内的不一致。这是大厂,包括谷歌的标准玩法。 2.有钱能增加机器,并且业务支持并行写入或并行读取,则可以根据业务,把系统设计为对并行写入优化但会增加读取时间,或者设计为对并行读取优化但会增加写入时间。 3.非常有钱,直接上 Oracle 最新版,支持内存表,虽然没 Redis 快,但比 mysql 快得多,还支持事务。 |
50
sadfQED2 2020-12-17 18:50:08 +08:00 via Android
@liudaolunhuibl 1.并不是缓存表的数据,而是像楼主那样缓存接口数据,每次表更新重新掉接口拿
2.我们没有连表的操作 |
51
sadfQED2 2020-12-17 18:52:55 +08:00 via Android 1
@Aruforce 你连 binlog 转 redis 这点延时都忍受不了,那你就不能考虑缓存了啊
|
52
libook 2020-12-17 18:53:54 +08:00
写程序难题 Top2:起名和维护缓存。
把缓存和数据库的操作封装在一起,对业务功能仅提供统一的接口,接口被调用后内部管理缓存和数据库的操作。 基本思路就是有读的话直接去 Redis 里读,有写的话先删缓存,再写数据库。 可以在写入的时候使用事务,确保写入的时候数据库里的值没有被其他应用实例修改,如果遇到了就尝试二段提交。 考虑到缓存刚刚被删除就有可能有读请求进来,为了确保一致性,可以在删缓存之前在数据库记录中加一个锁(类似排他锁),等更新完再解锁。 数据库层级的事务在引入微服务思想和各种中间件、数据库之后,会有较大的局限性,此时就要更多考虑分布式事务方案。 |
53
corningsun 2020-12-17 19:25:25 +08:00 via iPhone
难点是什么时候删除。
表和接口不多的话,维护所有表和查询接口的关联关系。 然后哪张表更新了,就删除对应查询接口的缓存即可。 |
54
kaneg 2020-12-17 21:44:07 +08:00 via iPhone
你既要 mysql 的实时存储,还要 redis 的高速缓存,这是鱼翅和熊掌不可兼得的。
除非你的接口非常简单和少量,明确知道哪个操作会造成缓存和数据库不一致,然后让其失效。 理想要落地必然要做取舍,否则追求的结果必然是镜中花,水中月。 |
55
vindurriel 2020-12-18 01:00:56 +08:00 via iPhone
读取全部走 redis key 分两种:列表和单行,其中列表的 values 能对应到单行的 keys,对应数据库每一行的 pk
写入单行:在 mysql transaction 的最后一步写 redis 如果写入不成功 db 回滚 redis 不是集群的话就强一致了 写入列表:batch 或者 stream 进行联表查询 只能最终一致 |
56
wangritian 2020-12-18 02:03:16 +08:00
赞同 21 、29 、35L
缓存是用在数据层的,而不是接口层,你自己也感觉到了 其次也不能滥用,像复杂条件查询或是 join 查询等等,应该去优化索引和提高 mysql 硬件配置,然后考虑分表 不建议你在错误的方案下解决问题 |
57
black11black 2020-12-18 03:00:56 +08:00 via Android
@sadfQED2 所以一次 redis 请求还要附带一个 binlog 检查操作?感觉有点本末倒置啊
|
58
YouLMAO 2020-12-18 08:06:23 +08:00 via Android
删除 Redis,MySQL 换成 cloud spanner
|
60
seth19960929 2020-12-18 10:16:52 +08:00
我觉得你可以找一个支持 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 |
61
cuiweieee 2020-12-18 10:19:03 +08:00
修改数据用 mq 异步通知,mq 消费之后刷 redis
|
62
RedBeanIce 2020-12-18 10:48:28 +08:00
@hhyyd #38 请问一个问题,spring cache 可以插入不是 string 类型的吗,,因为我看到底层调用的是 sring,不知道是否可以插入 list set hash zset
|
63
iceneet 2020-12-18 11:14:55 +08:00
感觉用 redis 缓存要求强一致性有点怪
|
64
ZSeptember 2020-12-18 11:17:08 +08:00
缓存和强一致冲突。
|
65
v2orz 2020-12-18 11:39:08 +08:00
换一个内存数据库代替 mysql,去掉 redis
|
66
zhangfeiwudi 2020-12-18 12:17:49 +08:00
我最近在做这一块
|
67
zhangfeiwudi 2020-12-18 12:18:49 +08:00
@zhangfeiwudi 主要就是做个触发式缓存,db 更新了 发 q 或者监听胖消息通知 cache , 然后所有 cache 收口到一个团队来吐出 比如说我们有 innerapi 接口
|
68
xiaotanyu13 2020-12-18 14:03:02 +08:00
把读多写少被连表又多的数据常驻 redis,放弃 @cache,自己写代码维护这类数据;
连表查的数据的话就看情况了,如果要查的数据能从 redis 中取到,就可以少一次连表查询(指的是部分数据直接取 redis 中的数据就够了),然后剩余的数据从数据库查询 更新的话 先更新 mysql,再更新 redis,如果 redis 更新失败,直接抛异常 让 mysql 回滚 |
69
sakasaka 2020-12-18 14:05:28 +08:00
mysql 性能没有这么不堪,加入缓存必然会有一致性问题,使用各种监听器同步也会增加系统复杂度,如果业务真的对性能和一致性要求高,建议改用别的 nosql 数据库,这个需要结合具体业务。
|
70
linoder 2020-12-18 15:47:15 +08:00
要不 …… 了解下 TiDB ?
|
71
dawniii 2020-12-18 19:54:39 +08:00
@vindurriel
@xiaotanyu13 写入单行:在 mysql transaction 的最后一步写 redis 如果写入不成功 db 回滚 redis 不是集群的话就强一致了 ================================================================================ 先更新 mysql,再更新 redis,如果 redis 更新失败,直接抛异常 让 mysql 回滚 ========================================================== 极限情况下,redis 成功,mysql 还没 commit 挂了 - - |