2021-04-17

面向全球的应用的系统架构

Views: 5799 | Add Comments

某些产品是面向全球用户的, 所以会在全球多个机房部署业务进程(Service)和数据库(Database). 这带来了所谓的数据一致性问题. 以用户加好友功能作为例子:

用户 A 在中, 在 App 中向用户 B 发送了好友申请. 用户 B 在美国, 打开 App 刷新, 没有看到有任何未处理的好友申请…

这是一个非常典型的例子. 我们仔细分析一下问题出在哪.

首先, 用户 B 刷新 App, 没有看到任何好友申请, 算是一个问题吗? 看不到好友申请, 本身不算问题, 但是, 如果 B 通过某些渠道认为自己应该有新的好友申请, 那就算是一个问题了. 比如, 用户 A 私下打电话告知 B, 说”我刚添加你为好友了, 麻烦你通过一下.” 或者 App 有推送功能, 用户 B 收到了 App 推送.

所以系统的架构如图:

a

我们对着这个架构图, 看看能怎么解决问题. 图中标注了角色间的接口调用(黑线)和数据流(蓝线), 以及网络延迟, 接口调用采用请求响应模式(也即收到响应时即表示操作已完成), 数据流是异步传输模式(发送方不等待接收方的确认).

打电话, App 推送, 数据库复制这3个数据流, 是独立的3个信道, 通信延迟主要受光速限制. 用户 A 发起了1次”申请加好友”这个操作, 将在3条信道上产生3条通信消息: 用户 A 私下打电话告知用户 B 他做了什么, 部署在中国的业务进程向 svc_cn 用户 B 的手机推送了一个通知, 以及部署在中国的数据库 db_cn 向部署在美国的数据库 db_us 推送了一条 binlog.

为了让这个交互满足因果关系(也即无 bug), 必须保证数据库复制不发生在打电话和 App 推送之后.

用户 A 的打电话行为, 是基于他看到了 App 给他一个反馈, 让他相信他已经发出了好友申请. 所以, 只要我们在数据库复制之后再给用户反馈, 就能让打电话这个行为发生在后.

方法1: 我们可以把数据库复制改为”同步复制”, 这样, “保存好友申请”操作将耗时 501ms. 但是, 我们不希望所有的数据库写操作都是”同步复制”的, 因为这会带”慢”的问题和可用性问题. 所以, 数据库可以增加同步原语, 让 svc_cn 决定哪些操作应该等待, 哪些操作不需要等待.

方法2: 我们可以修改 svc_cn, 不断地轮询 db_us, 确保数据已经从中国复制到了美国. 这种方法增加了 svc_cn 的工作量.

方法3: 我们可以修改 svc_cn, 但不是去轮询 db_us, 而是简单地 sleep 1 秒, 然后再返回响应给用户. 从概率上, 能保证打电话行为发生在数据库复制之后, 因为经验上数据库复制延迟不会慢于1秒. 当然, 这只是概率上的保证. 具体技术细节, 不一定是 sleep 1秒, 也可以引入延时消息队列.

方法4: 和方法3类似, 不过, 是在用户端进行等待, 例如 App 上转菊花主动多转1秒钟.

方法3和方法4等待的时间不好把握, 需要在用户体验和概率上取舍.

总结

面向全球用户的系统, 永远不可能消除光速带来的通信延迟成本. 无论产品设计人员还是技术研发人员, 都必须对产品和接口不断地细化, 将需要进行长距离通信的功能点的范围缩小到最小, 才能在用户体验和正确性两方面取得平衡.

Related posts:

  1. 生产者消费者模型中生产者的速度快于消费者时所产生的问题及其解决方法讨论
  2. Ideawu.P2P API 简介
  3. LevelDB 写操作出现停顿的问题分析
  4. C# 中实现 FIFO 缓冲区–ArrayBuffer
  5. 单启动多个mysql实例mysqld_multi配置
Posted by ideawu at 2021-04-17 18:25:59

Leave a Comment