1.従来のアプリケーションのトランザクション管理
1.1地方の事情
マイクロサービスでのデータ整合性を紹介する前に、トランザクションの背景を簡単に紹介しましょう。従来のスタンドアロンアプリケーションは、データソースとしてRDBMSを使用します。アプリケーションはトランザクションを開き、CRUDを実行し、トランザクションをコミットまたはロールバックします。これらはすべてローカルトランザクションで発生し、リソースマネージャー(RM)はトランザクションサポートを直接提供します。ローカルトランザクションでは、データの整合性が保証されます。
1.2分散トランザクション
1.2.1 2フェーズコミット(2PC)
アプリケーションが徐々に拡張し、アプリケーションが複数のデータソースを使用すると、ローカルトランザクションはデータ整合性の要件を満たすことができなくなります。複数のデータソースに同時にアクセスするため、トランザクションは複数のデータソース間で管理する必要があり、分散トランザクションが出現しています。最も人気のあるものの1つは2フェーズコミット(2PC)であり、分散トランザクションはトランザクションマネージャー(TM)によって均一に管理されます。
2フェーズのコミットは、準備フェーズとコミットフェーズに分けられます。
ただし、2フェーズコミットはデータ整合性の問題を完全に保証することはできず、同期ブロッキングの問題があるため、3フェーズコミット(3PC)の最適化バージョンが発明されました。
1.2.2 3フェーズ送信(3PC)
ただし、3PCは、ほとんどの場合、データの一貫性のみを保証できます。
2.マイクロサービスでのトランザクション管理
では、分散トランザクション2PCまたは3PCは、マイクロサービスでのトランザクション管理に適していますか?3つの理由から、答えはノーです。
- マイクロサービスはデータに直接アクセスできないため、マイクロサービス間の相互呼び出しは通常、RPC(dubbo)またはHttp API(SpringCloud)を介して行われるため、TMを使用してマイクロサービスのRMを管理することはできなくなります。
- 異なるマイクロサービスで使用されるデータソースのタイプは完全に異なる場合があります。マイクロサービスがNoSQLなどのトランザクションをサポートしないデータベースを使用する場合、トランザクションについて話す方法はありません。
- マイクロサービスで使用されるデータソースがすべてトランザクションをサポートしている場合でも、大規模なトランザクションを使用して多くのマイクロサービスのトランザクションを管理する場合、この大規模なトランザクションのメンテナンス時間は、ローカルトランザクションのメンテナンス時間よりも数桁長くなります。このような長期トランザクションとクロスサービストランザクションは、多くのロックとデータの可用性を引き起こし、システムパフォーマンスに深刻な影響を及ぼします。
従来の分散トランザクションは、マイクロサービスアーキテクチャの下でのトランザクション管理のニーズを満たすことができなくなっていることがわかります。次に、従来のACIDトランザクションは満たすことができないため、マイクロサービスでのトランザクション管理は新しいルールベース理論に従う必要があります。
BASE理論は、eBayのアーキテクトであるDan Pritchettによって提案されました。BASE理論はCAP理論の拡張であり、強力な一貫性を実現できない場合でも、アプリケーションは適切な方法で最終的な一貫性を実現できる必要があるというのが基本的な考え方です。BASEは、基本的に利用可能な、ソフト状態、最終的な一貫性を指します。
基本可用
:分散システムに障害が発生した場合、つまりコアが使用可能であることを確認するために、部分的な可用性が失われることを指します。
软状态
:システムに中間状態を持たせると、中間状態がシステム全体の可用性に影響を与えることはありません。分散ストレージでは、通常、データのコピーが少なくとも3つあります。異なるノード間でコピーの同期を可能にする遅延は、ソフト状態の兆候です。
最终一致性
:最終的な一貫性とは、システム内のすべてのデータコピーが、一定期間後に最終的に一貫した状態に到達できることを意味します。弱い一貫性は強い一貫性の反対であり、究極の一貫性は弱い一貫性の特殊なケースです。
BASEは、マイクロサービスでのトランザクション管理の基本的な要件です。最终一致性
マイクロサービスに基づくトランザクション管理では、強力な一貫性を実現できませんが、最も重要な一貫性を保証する必要があります。では、マイクロサービスでのトランザクション管理の究極の一貫性を確保するためにどのような方法がありますか?実装の原則によれば、イベント通知タイプと報酬タイプの2つの主要なタイプがあります。イベント通知タイプは、信頼できるイベント通知モードとベストエフォートに分けることができます。通知モードと報酬モードは、TCCモードとビジネス報酬モードの2つのタイプに分けることができます。これらの4つのモードはすべて、マイクロサービスの下でデータの究極の一貫性を実現できます。
3.マイクロサービスでデータの一貫性を実現する方法
3.1信頼性の高いイベント通知モード
3.1.1同期イベント
信頼性の高いイベント通知モードの設計コンセプトは理解しやすいです。つまり、メインサービスが完了した後、結果はイベント(通常はメッセージキュー)を介してスレーブサービスに渡され、スレーブサービスはメッセージを受信した後にメッセージを消費してビジネスを完了し、メインサービスとスレーブサービス間のメッセージの一貫性。考えられる最初の最も単純なことは、同期イベント通知です。ビジネス処理とメッセージ送信は同期的に実行されます。実装ロジックについては、以下のコードとシーケンス図を参照してください。
public void trans(){ try { // 1。データベースを操作します boolresult = dao.update(data); //データベースの操作が失敗すると、例外がスローされます // 2.データベースの操作が成功した場合は、メッセージを送信します if(result){ mq.send(data); //メソッドが失敗すると、例外がスローされます } } catch(Exception e){ roolback(); //例外が発生した場合は、ロールバックします }}
上記のロジックはシームレスに見えます。データベース操作が失敗した場合、メッセージを送信せずに直接終了します。メッセージ送信が失敗した場合、データベースはロールバックします。データベース操作が成功し、メッセージが正常に送信された場合、ビジネスは成功し、メッセージはダウンストリームコンシューマーに送信されます。次に、慎重に検討した結果、同期メッセージ通知には実際には2つの欠点があります。
マイクロサービスアーキテクチャでは、ネットワークIOの問題やサーバーのダウンタイムの問題が発生する可能性があります。これらの問題がシーケンス図のステップ7で発生した場合、メッセージの配信後にメインサービスに正常に通知できないか(ネットワークの問題)、送信を続行できません。トランザクション(ダウンタイム)の場合、メインサービスはメッセージ配信が失敗したと見なし、メインサービスビジネスをロールオーバーしますが、実際にはメッセージはスレーブサービスによって消費されているため、メインサービスとスレーブサービスのデータは不整合になります。特定のシナリオは、次の2つのタイミング図で確認できます。
- イベントサービス(この場合はメッセージサービス)はビジネスとの結合が強すぎます。メッセージサービスが利用できない場合、ビジネスは利用できなくなります。イベントサービスは、ビジネスから切り離して独立して非同期で実行するか、ビジネスの実行後にメッセージの送信を試みる必要があります。メッセージの送信に失敗した場合は、非同期送信にダウングレードされます。
3.1.2非同期イベント
3.1.2.1ローカルイベントサービス
3.1.1で説明した同期イベントの問題を解決するために、非同期イベント通知モードが開発されました。ビジネスサービスとイベントサービスが分離され、イベントが非同期で実行されます。別個のイベントサービスにより、イベントの確実な配信が保証されます。
异步事件通知-本地事件服务
当业务执行时,在同一个本地事务中将事件写入本地事件表,同时投递该事件,如果事件投递成功,则将该事件从事件表中删除。如果投递失败,则使用事件服务定时地异步统一处理投递失败的事件,进行重新投递,直到事件被正确投递,并将事件从事件表中删除。这种方式最大可能地保证了事件投递的实效性,并且当第一次投递失败后,也能使用异步事件服务保证事件至少被投递一次。
然而,这种使用本地事件服务保证可靠事件通知的方式也有它的不足之处,那便是业务仍旧与事件服务有一定耦合(第一次同步投递时),更为严重的是,本地事务需要负责额外的事件表的操作,为数据库带来了压力,在高并发的场景,由于每一个业务操作就要产生相应的事件表操作,几乎将数据库的可用吞吐量砍了一半,这无疑是无法接受的。正是因为这样的原因,可靠事件通知模式进一步地发展-外部事件服务出现在了人们的眼中。
3.1.2.2 外部事件服务
外部事件服务在本地事件服务的基础上更进了一步,将事件服务独立出主业务服务,主业务服务不在对事件服务有任何强依赖。
异步事件通知-外部事件服务
业务服务在提交前,向事件服务发送事件,事件服务只记录事件,并不发送。业务服务在提交或回滚后通知事件服务,事件服务发送事件或者删除事件。不用担心业务系统在提交或者会滚后宕机而无法发送确认事件给事件服务,因为事件服务会定时获取所有仍未发送的事件并且向业务系统查询,根据业务系统的返回来决定发送或者删除该事件。
外部事件虽然能够将业务系统和事件系统解耦,但是也带来了额外的工作量:外部事件服务比起本地事件服务来说多了两次网络通信开销(提交前、提交/回滚后),同时也需要业务系统提供单独的查询接口给事件系统用来判断未发送事件的状态。
3.1.2.3 可靠事件通知模式的注意事项
可靠事件模式需要注意的有两点,1. 事件的正确发送; 2. 事件的重复消费。
通过异步消息服务可以确保事件的正确发送,然而事件是有可能重复发送的,那么就需要消费端保证同一条事件不会重复被消费,简而言之就是保证事件消费的幂等性
。
如果事件本身是具备幂等性的状态型事件,如订单状态的通知(已下单、已支付、已发货等),则需要判断事件的顺序。一般通过时间戳来判断,既消费过了新的消息后,当接受到老的消息直接丢弃不予消费。如果无法提供全局时间戳,则应考虑使用全局统一的序列号。
对于不具备幂等性的事件,一般是动作行为事件,如扣款100,存款200,则应该将事件id及事件结果持久化,在消费事件前查询事件id,若已经消费则直接返回执行结果;若是新消息,则执行,并存储执行结果。
3.2 最大努力通知模式
相比可靠事件通知模式,最大努力通知模式就容易理解多了。最大努力通知型的特点是,业务服务在提交事务后,进行有限次数(设置最大次数限制)的消息发送,比如发送三次消息,若三次消息发送都失败,则不予继续发送。所以有可能导致消息的丢失。同时,主业务方需要提供查询接口给从业务服务,用来恢复丢失消息。最大努力通知型对于时效性保证比较差(既可能会出现较长时间的软状态),所以对于数据一致性的时效性要求比较高的系统无法使用。这种模式通常使用在不同业务平台服务或者对于第三方业务服务的通知,如银行通知、商户通知等,这里不再展开。
3.3 业务补偿模式
接下来介绍两种补偿模式,补偿模式比起事件通知模式最大的不同是,补偿模式的上游服务依赖于下游服务的运行结果,而事件通知模式上游服务不依赖于下游服务的运行结果。首先介绍业务补偿模式,业务补偿模式是一种纯补偿模式,其设计理念为,业务在调用的时候正常提交,当一个服务失败的时候,所有其依赖的上游服务都进行业务补偿操作。举个例子,小明从杭州出发,去往美国纽约出差,现在他需要定从杭州去往上海的火车票,以及从上海飞往纽约的飞机票。如果小明成功购买了火车票之后发现那天的飞机票已经售空了,那么与其在上海再多待一天,小明还不如取消去上海的火车票,选择飞往北京再转机纽约,所以小明就取消了去上海的火车票。这个例子中购买杭州到上海的火车票是服务a,购买上海到纽约的飞机票是服务b,业务补偿模式就是在服务b失败的时候,对服务a进行补偿操作,在例子中就是取消杭州到上海的火车票。
补偿模式要求每个服务都提供补偿借口,且这种补偿一般来说是不完全补偿
,既即使进行了补偿操作,那条取消的火车票记录还是一直存在数据库中可以被追踪(一般是有相信的状态字段“已取消”作为标记),毕竟已经提交的线上数据一般是不能进行物理删除的。
业务补偿模式最大的缺点是软状态的时间比较长,既数据一致性的时效性很低,多个服务常常可能处于数据不一致的情况。
3.4 TCC/Try Confirm Cancel模式
TCC模式是一种优化了的业务补偿模式,它可以做到完全补偿
,既进行补偿后不留下补偿的纪录,就好像什么事情都没有发生过一样。同时,TCC的软状态时间很短,原因是因为TCC是一种两阶段型模式(已经忘了两阶段概念的可以回顾一下1.2.1),只有在所有的服务的第一阶段(try)都成功的时候才进行第二阶段确认(Confirm)操作,否则进行补偿(Cancel)操作,而在try阶段是不会进行真正的业务处理的。
TCC模式
TCC模式的具体流程为两个阶段:
- Try,业务服务完成所有的业务检查,预留必需的业务资源
- 如果Try在所有服务中都成功,那么执行Confirm操作,Confirm操作不做任何的业务检查(因为try中已经做过),只是用Try阶段预留的业务资源进行业务处理;否则进行Cancel操作,Cancel操作释放Try阶段预留的业务资源。
这么说可能比较模糊,下面我举一个具体的例子,小明在线从招商银行转账100元到广发银行。这个操作可看作两个服务,服务a从小明的招行账户转出100元,服务b从小明的广发银行帐户汇入100元。
服务a(小明从招行转出100元):
try: update cmb_account set balance=balance-100, freeze=freeze+100 where acc_id=1 and balance>100;
confirm: update cmb_account set freeze=freeze-100 where acc_id=1;
cancel: update cmb_account set balance=balance+100, freeze=freeze-100 where acc_id=1;
服务b(小明往广发银行汇入100元):
try: update cgb_account set freeze=freeze+100 where acc_id=1;
confirm: update cgb_account set balance=balance+100, freeze=freeze-100 where acc_id=1;
cancel: update cgb_account set freeze=freeze-100 where acc_id=1;
具体说明:
a的try阶段,服务做了两件事,1:业务检查,这里是检查小明的帐户里的钱是否多余100元;2:预留资源,将100元从余额中划入冻结资金。
a的confirm阶段,这里不再进行业务检查,因为try阶段已经做过了,同时由于转账已经成功,将冻结资金扣除。
aのキャンセルフェーズでは、予約されたリソースが解放され、資金は100元で凍結され、残高に戻されます。
bの試行段階が実行され、リソースが予約され、100元が凍結されます。
bの確認フェーズでは、試行フェーズで予約したリソースを使用して、100元の凍結資金を残高に転送します。
bのキャンセルフェーズでは、トライフェーズで予約されていたリソースが解放され、凍結された資金から100元が差し引かれます。
上記の簡単な例からわかるように、TCCモデルは純粋なビジネス報酬モデルよりも複雑であるため、各サービスはCofirmとCancelの2つのインターフェイスを実装する必要があります。
3.5まとめ
次の表では、これら4つの一般的に使用されるモードを比較しています。
の種類 | 名前 | リアルタイムのデータ整合性 | 開発費 | アップストリームサービスがダウンストリームサービスの結果に依存するかどうか |
---|---|---|---|---|
通知タイプ | 最大の努力 | 低 | 低 | 依存しない |
通知タイプ | 信頼できるイベント | 高い | 高い | 依存しない |
補償タイプ | 事業報酬 | 低 | 低 | 頼る |
補償タイプ | TCC | 高い | 高い | 頼る |
やっと
ここを見てくれてありがとう、記事は短いです、指摘することを歓迎します;それがうまく書かれていると思うなら、私に親指を上げてください。
また、私の公式アカウントであるプログラマーのマイドンに注目してください。毎日業界情報を更新してください。