前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >在微服务中保证数据一致性

在微服务中保证数据一致性

作者头像
超级大猪
发布2019-11-21 21:27:50
7670
发布2019-11-21 21:27:50
举报
文章被收录于专栏:大猪的笔记大猪的笔记

在单个进程中,数据库一般提供了数据一致性的保证。但如果数据分布到了不同的服务中,数据为单个服务所私有。问题会更为复杂。

提现示例

比如用户需要提现,简化流程如下: 1. 提现服务收到用户请求提现1元 2. 提现服务插入一条本地数据,记录提现请求 3. 提现服务向money服务发起数据请求,对该用户扣款一元

代码如下:

代码语言:javascript
复制
# 预先插入数据
withdraw_log = WithDrawLog()
withdraw_log.Uid = uid
withdraw_log.Money = str(money)
withdraw_log.FlowNo = flow_no
withdraw_log.Aliname = username
withdraw_log.Aliaccount = useraccount
withdraw_log.Type = withdraw_type
wid = await withdraw_log.save()

ret = await request_api('money_svc', uid, str(money))
if ret['err'] != 0:
    return Exception('账户没有足够的余额'), 1

然而因为网络延时,request_api并不能总是成功。此时,提现服务记录了提现,而用户没有扣款。数据将产生不一致。

解决数据不一致

目前市面上有很多分布式事务框架,可以维护分布式事务的acid。但是我们这个小应用,引入这样的框架就太重量了。 要保持数据一致,首先要有以下几个保证: 1. api必须有幂等性(说白了,同样数据调用一次api和调用N次没什么影响) 2. 服务内部的实现保证acid。否则可靠性就无从谈起。 3. 对异常设计补偿是一种较为简单的方式

改造money_svc

money_svc不具有幂等性,对它进行改造最简单的方式是,加一个唯一时间戳(flow_no)。每次调用都进行检查,如果库中有这个flow_no,则拒绝这次请求。

代码语言:javascript
复制
# request_api('money_svc',flow_no, uid, str(money))
# money_svc实现
ret = await db.select('money_flow', flow_no=args.flow_no)
if ret:
    return Exception("这笔交易已存在"), 1
...

设计补偿

  1. 提现服务在用户申请提现时,生成一个流水号,这个流水号贯穿整个业务。并且,设置一个中间状态predo
  2. 当业务完成,将该笔提现的状态设置为最终状态done
  3. 补偿服务定时取出状态为predo的订单,对它们之前的流水号进行重试。由于money_svc有幂等性,重试并不会造成副作用。

withdraw服务

代码语言:javascript
复制
flow_no = get_rnd_flow_no()
withdraw_log = WithDrawLog()
withdraw_log.Uid = uid
withdraw_log.Money = str(money)
withdraw_log.FlowNo = flow_no
withdraw_log.Aliname = username
withdraw_log.Aliaccount = useraccount
withdraw_log.Type = withdraw_type
withdraw_log.FlowNo = flow_no
withdraw_log.Status = 'predo'
wid = await withdraw_log.save()

ret = await request_api('money_svc',flow_no, uid, str(money))
if ret['err'] != 0:
    return Exception('账户没有足够的余额'), 1

withdraw_log.Status = 'done'
await withdraw_log.update()

补偿服务

代码语言:javascript
复制
while 1:
    withdraw_log = await db.select('withdraw', Status='predo')
    flow_no = withdraw_log.FlowNo
    # 大胆放心的重试
    ret = await request_api('money_svc',flow_no, uid, str(money))
    withdraw_log.Status = 'done'
    await withdraw_log.update()

抽象

如果业务过于复杂,则需要考虑对其进行抽象。大概流程: 1. 业务调用 服务1, 服务2, 服务3,并注册事件监控 2. 事件监控发现 服务1,服务3 成功,服务2失败 3. 对业务调用补偿服务,补偿的方式可以是撤销服务1,服务3,也可以是对服务2进行重试。

其实很简单,目前异常大大下降。就酱。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-03-17 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 提现示例
  • 解决数据不一致
    • 改造money_svc
      • 设计补偿
      • 抽象
      相关产品与服务
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档