分布式事务相关


分布式事务问题

背景

  • 积分支付,需要查询用户积分;
  • 商品扣减库存;
  • 保存订单;

这些一定要保证一致性;

解决办法有

  • 基于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缓存穿透

现象:

  1. 请求查询缓存,缓存不存在。
  2. 查询数据库,数据库也不存在这条数据,空则不写入缓存。
  3. 若相同查询条件查询了多次,则数据库压力大。

解决办法

  1. 数据库查询为空,也写入缓存一个空值,设置上过期时间。>>这个比较好<<
  2. 加分布式锁,同时只能有一个请求查询数据库。
  3. 限流,只能少量数据查询数据库。
  4. 布隆过滤器,能迅速判断一个元素是否在集合里。二进制,空间小速度快。不存在就直接返回null就可以了。

redis缓存雪崩

现象

  缓存都会存在雪崩的现象,很多的缓存key,过期时间大面积失效,同时请求都会打到数据库上。

防护措施(不能做到完全解决)

  1. 热点数据,永不过期
  2. 多缓存结合,长短缓存
    1. 请求redis
    2. 请求memcache,这个过期时间更久
  3. 过期时间错开,加一个失效时间随机数
  4. 开发时可以使用第三方redis,提高可靠性。
    1. 阿里云

数据库和缓存双写一致性方案解析

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