分布式事务相关
分布式事务问题
背景
- 积分支付,需要查询用户积分;
- 商品扣减库存;
- 保存订单;
这些一定要保证一致性;
解决办法有
- 基于XA协议的两阶段提交
- 事务补偿机制
- 本地消息表+定时任务
- MQ
XA协议
一个事务管理器和多个资源管理器组成
提交分为两阶段
- prepare
- commit
第一阶段
第二阶段
这个需要事务管理器给所有RM发送准备指令和执行指令,效率特别低。
DEMO:
MySql5.7支持XA,尽量使用8.0版本
Mysql连接器,Mysql Connector/J 5.0以上支持
Atomikos做分布式事务,管理分布式数据库
分库分表的组件:MyCat和Sharding-JDBC这两个都已经集成Atomikos了,直接使用就可以。
事务补偿机制
我们现在用的这个,分布式事务处理,就是事务补偿机制的原理。嗯,这个存在的问题就是有可能A扣减了,B也扣减了,但是B扣钱失败了。然后此时A回滚的时候会有问题,嗯,这是一个待解决的一块儿。
在catch里写回滚逻辑。涉及到重试,这块有一些复杂,具体怎么处理才能尽可能一致?
本地消息表+定时任务
这个就是落一个表,定时任务扫表处理
基于MQ
不同公司之间,因为机房不一致,就无法使用MQ来达到消息一致性,可以使用本地消息表,推送信息去处理,例如对接支付宝微信。
消息的重试
rocketmq有messageExt.getReconsumeTimes()可以记录重试次数,可以达到一定次数后进行consume,然后报警,人工处理。
幂等
怎么防止重复下单
表单怎么防止重复提交
接口重复调用,重试,怎么幂等
UPDATE时,使用乐观锁
insert使用版本号,可以是token
混合操作,可以使用分布式锁来锁住业务单号,如果没有业务单号,也是可以用token
UPDATE幂等
列表更新时,可以使用数据的version来做乐观锁,加上数据库的行锁来保证。
insert幂等
有唯一业务号的insert操作,例如下单场景,商品id+用户id ,
分布式锁来保证幂等,保证接口幂等,
这个也可以作为一个唯一索引到数据库,
业务执行后,不进行锁的释放,过期自动释放。
没有这种唯一业务号的就用token作为key来获取分布式锁。
redis缓存穿透
现象:
- 请求查询缓存,缓存不存在。
- 查询数据库,数据库也不存在这条数据,空则不写入缓存。
- 若相同查询条件查询了多次,则数据库压力大。
解决办法
- 数据库查询为空,也写入缓存一个空值,设置上过期时间。>>这个比较好<<
- 加分布式锁,同时只能有一个请求查询数据库。
- 限流,只能少量数据查询数据库。
- 布隆过滤器,能迅速判断一个元素是否在集合里。二进制,空间小速度快。不存在就直接返回null就可以了。
redis缓存雪崩
现象
缓存都会存在雪崩的现象,很多的缓存key,过期时间大面积失效,同时请求都会打到数据库上。
防护措施(不能做到完全解决)
- 热点数据,永不过期
- 多缓存结合,长短缓存
- 请求redis
- 请求memcache,这个过期时间更久
- 过期时间错开,加一个失效时间随机数
- 开发时可以使用第三方redis,提高可靠性。
- 阿里云
数据库和缓存双写一致性方案解析
https://www.cnblogs.com/rjzheng/p/9041659.html#!comments
文章中:
对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存
1.先更新数据库,再更新缓存(这个不好)
并发情况下,更新缓存的线程有可能是老数据的线程,存在脏数据。
写多场景,浪费性能。
计算后写入缓存场景,也浪费性能。
(2和3是争议较大的)
2.先删除缓存,再更新数据库
A删了B马上又写入,不设过期时间不做更新的话就永远不一致。
需要用:延时双删策略
(1)先淘汰缓存
(2)再写数据库(这两步和原来一样)
(3)休眠n秒,再次淘汰缓存(3)的这个n需要根据处理时常判断,读写分离库需要额外时间。
(3)的这个删除也可以异步删除,增加吞吐,但还是存在删除失败的问题。
3.先更新数据库,再删除缓存
2和3都会存在删除失败的问题。提供一个保障重试机制
方案1:发送消息,继续删除key,直至成功;代码侵入多,不好;
方案2:使用binlog订阅方法:在订阅逻辑中使用1,删除不成功后,继续发消息删除。
数据库写成功后会有binlog写入
binlog 具体见:todo 待补充
上述的订阅binlog程序在mysql中有现成的中间件叫canal