日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

逸仙电商Seata企业级落地实践

發(fā)布時(shí)間:2024/9/3 编程问答 64 豆豆
生活随笔 收集整理的這篇文章主要介紹了 逸仙电商Seata企业级落地实践 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
簡(jiǎn)介:本文將會(huì)以逸仙電商的業(yè)務(wù)作為背景, 先介紹一下seata的原理, 并給大家進(jìn)行線(xiàn)上演示, 由淺入深去介紹這款中間件, 以便讀者更加容易去理解 Seata 這個(gè)中間件。

作者 | 張嘉偉(GitHub ID:l81893521)

就職于逸仙電商交易中心;Seata Committer,加入 Seata 社區(qū)已有一年半,見(jiàn)證了從 Fescar 到 Seata 的變更,GA等。


你可能沒(méi)有聽(tīng)說(shuō)過(guò)逸仙電商,但是你的女朋友不可能沒(méi)有聽(tīng)說(shuō)過(guò)它。逸仙電商旗下有完美日記、小奧汀、完子心選等品牌。完美日記作為國(guó)貨美妝界的黑馬用了不到三年時(shí)間,達(dá)到了行業(yè)龍頭企業(yè)通常需要十年以上才能達(dá)到的營(yíng)收規(guī)模。2020 年正式登陸紐約證券交易所,成為第一家在美國(guó)上市的“國(guó)貨美妝品牌”。在快速增長(zhǎng)的業(yè)務(wù)下,系統(tǒng)流量增長(zhǎng)速度越來(lái)越快,服務(wù)數(shù)量不斷增多,調(diào)用鏈路錯(cuò)綜復(fù)雜,數(shù)據(jù)不一致的問(wèn)題日漸顯現(xiàn),為了降低人力成本和系統(tǒng)資源,我們選擇了 Seata。


本文將會(huì)以逸仙電商的業(yè)務(wù)作為背景, 先介紹一下seata的原理, 并給大家進(jìn)行線(xiàn)上演示, 由淺入深去介紹這款中間件, 以便讀者更加容易去理解 Seata 這個(gè)中間件。



1. 問(wèn)題背景


在微服務(wù)的架構(gòu)下,數(shù)據(jù)不一致的產(chǎn)生原因


2. 業(yè)務(wù)介紹


挑選了逸仙電商一些比較簡(jiǎn)單易懂的業(yè)務(wù)作為開(kāi)展背景


3. 原理分析


Seata的實(shí)現(xiàn)原理和故障解決以及部署方案


4. Demo演示


如何在線(xiàn)體驗(yàn)這款中間件,無(wú)需整合和下載任何代碼


數(shù)據(jù)不一致的原因



在微服務(wù)的環(huán)境下,由于調(diào)用鏈路跨越多個(gè)應(yīng)用,甚至跨越多個(gè)數(shù)據(jù)源,數(shù)據(jù)的一致性在普通情況下難以保證,導(dǎo)致數(shù)據(jù)不一致的原因非常多,這里列舉了三個(gè)最常見(jiàn)的原因


  • 業(yè)務(wù)異常一個(gè)服務(wù)鏈路調(diào)用中,如果調(diào)用的過(guò)程出現(xiàn)業(yè)務(wù)異常,產(chǎn)生異常的應(yīng)用獨(dú)立回滾,非異常的應(yīng)用數(shù)據(jù)已經(jīng)持久化到數(shù)據(jù)庫(kù)。
  • 網(wǎng)絡(luò)異常調(diào)用的過(guò)程中,由于網(wǎng)絡(luò)不穩(wěn)定,導(dǎo)致鏈路中斷,部分應(yīng)用業(yè)務(wù)執(zhí)行完成,部分應(yīng)用業(yè)務(wù)未被執(zhí)行。
  • 服務(wù)不可用若服務(wù)不可用,無(wú)法被正常調(diào)用,也會(huì)導(dǎo)致問(wèn)題的產(chǎn)生


  • 這里挑選了逸仙電商業(yè)務(wù)體系里面一個(gè)非常通俗容易理解的調(diào)用方式,并且去掉了多余復(fù)雜的鏈路,方便在閱讀過(guò)程中更加關(guān)注重點(diǎn)。


    在以往如果出現(xiàn)數(shù)據(jù)不一致的問(wèn)題,相信大多數(shù)的解決方案是這樣的


    • 人工補(bǔ)償數(shù)據(jù)
    • 定時(shí)任務(wù)檢查和補(bǔ)償數(shù)據(jù)


    但是這兩種方式的缺點(diǎn)也是顯然意見(jiàn)的,一種是浪費(fèi)大量的人力成本和時(shí)間,另外一種是浪費(fèi)大量的系統(tǒng)資源去檢查數(shù)據(jù)是否一致和額外的人力成本。


    接下來(lái)我會(huì)根據(jù)逸仙在生產(chǎn)上穩(wěn)定運(yùn)行將近一年總結(jié)的經(jīng)驗(yàn)并且盡可能簡(jiǎn)單的去描述Seata是如何保證數(shù)據(jù)一致的。


    原理



    在接觸一項(xiàng)新技術(shù)之前,我們應(yīng)該先從宏觀(guān)的角度去理解它大概包含些什么。在Seata中,它大概分為以下三個(gè)角色。


    • 黃色,Transaction Manager(TM),client端
    • 藍(lán)色,Resource Manager(RM),client端
    • 綠色,Transaction Coordinator(TC),server端


    你可以根據(jù)顏色,名字,縮寫(xiě)甚至客戶(hù)端/服務(wù)端去區(qū)分這三者的關(guān)系,同時(shí)簡(jiǎn)單去理解它們每一個(gè)自身的職責(zé)大概是要干些什么事情,后面的講解我也會(huì)保持一樣的顏色和名字來(lái)區(qū)分它們。



    Seata其中只一個(gè)核心是數(shù)據(jù)源代理,意味著在你執(zhí)行一句Sql語(yǔ)句時(shí),Seata會(huì)幫你在執(zhí)行之前和之后做一些額外的操作,從而保證數(shù)據(jù)的一致性,并且盡可能做到無(wú)感知,讓你使用起來(lái)感覺(jué)非常方便和神奇。這里首先要去理解兩個(gè)知識(shí)點(diǎn)。


    • 前置鏡像(Before Image):保存數(shù)據(jù)變更前的樣子
    • 后置鏡像(After Image):保存數(shù)據(jù)變更后的樣子
    • Undo Log:保存鏡像


    有時(shí)候新項(xiàng)目接入的時(shí)候,有同事會(huì)問(wèn),為什么事務(wù)不生效,如果你也遇到過(guò)同樣的問(wèn)題,那首先要檢查一下自己的數(shù)據(jù)源是否已經(jīng)代理成功。


    當(dāng)執(zhí)行一句Sql時(shí),Seata會(huì)嘗試去獲取這條/批數(shù)據(jù)變更前的內(nèi)容,并保存到前置鏡像中(Insert語(yǔ)句沒(méi)有前置鏡像),然后執(zhí)行業(yè)務(wù)Sql,執(zhí)行完后會(huì)嘗試去獲取這條/批數(shù)據(jù)變更后的內(nèi)容,并保存到后置鏡像中(Delete語(yǔ)句沒(méi)有后置鏡像),之后會(huì)進(jìn)行分支事務(wù)注冊(cè),TC在收到分支事務(wù)注冊(cè)請(qǐng)求時(shí),會(huì)持久化這些分支事務(wù)信息和根據(jù)操作數(shù)據(jù)的主鍵為維度作為全局鎖并持久化,可選持久化方式有


    • file
    • db
    • redis


    在收到TC返回的分支注冊(cè)成功響應(yīng)后,會(huì)把鏡像持久化到應(yīng)用所在的數(shù)據(jù)源的Undo Log表中,最后提交本地事務(wù)。


    以上所有操作都會(huì)保證在同一個(gè)本地事務(wù)中,保證業(yè)務(wù)操作和Undo Log操作的原子性


    一階段



    理解了單個(gè)應(yīng)用的處理流程,再?gòu)囊粋€(gè)完全的調(diào)用鏈路,去看Seata的處理過(guò)程,相信理解起來(lái)會(huì)簡(jiǎn)單很多,


  • 首先一個(gè)使用了@GlobalTransactional的接口被調(diào)用,Seata會(huì)對(duì)其進(jìn)行攔截,攔截的角色我們稱(chēng)之為T(mén)M,這個(gè)時(shí)候會(huì)訪(fǎng)問(wèn)TC開(kāi)啟一個(gè)新的全局事務(wù),TC收到請(qǐng)求后會(huì)生成XID和全局事務(wù)信息并持久化,然后返回XID。
  • 在每一層的調(diào)用鏈路中,XID都必須往下傳遞,然后每一層都經(jīng)過(guò)之前說(shuō)過(guò)的處理邏輯,直到執(zhí)行完成/異常拋出。

  • 直到目前,一階段已經(jīng)執(zhí)行完成。


    另外一個(gè)需要注意的問(wèn)題是,如果發(fā)現(xiàn)事務(wù)不生效,需要檢查XID是否成功往下傳遞


    二階段提交



    如果在整個(gè)調(diào)用鏈路的過(guò)程,沒(méi)有發(fā)生任何異常,那么二階段提交的過(guò)程是非常簡(jiǎn)單而且非常的高效,只有兩步


    • TC清理全局事務(wù)對(duì)應(yīng)的信息
    • RM清理對(duì)應(yīng)Undo Log信息


    二階段回滾



    若調(diào)用過(guò)程中出現(xiàn)異常,會(huì)自動(dòng)觸發(fā)反向回滾


    反向回滾表示,如果調(diào)用鏈路順序?yàn)?A -> B -> C,那么回滾順序?yàn)?C -> B -> A。

    例:A=Insert,B=Update,如果回滾時(shí)不按照反向的順序進(jìn)行回滾,則有可能出現(xiàn)回滾時(shí)先把A刪除了,再更新A,引發(fā)錯(cuò)誤


    在回滾的過(guò)程中有可能會(huì)遇到一種非常極端的情況,回滾到對(duì)應(yīng)的模塊時(shí),找不到對(duì)應(yīng)的Undo Log,這種情況主要發(fā)生在


    • 分支事務(wù)注冊(cè)成功,但是由于網(wǎng)絡(luò)原因收不到成功的響應(yīng),Undo Log未被持久化
    • 同時(shí)全局事務(wù)超時(shí)(超時(shí)時(shí)間可自由配置)觸發(fā)回滾


    這時(shí)候RM會(huì)持久化一個(gè)特殊的Undo Log,狀態(tài)為GlobalFinished。由于這個(gè)全局事務(wù)已經(jīng)回滾,需要防止網(wǎng)絡(luò)恢復(fù)時(shí),未持久化Undo Log的應(yīng)用收到了分支注冊(cè)成功的響應(yīng)和持久化Undo Log,并提交本地最終引發(fā)的數(shù)據(jù)不一致。


    讀已提交


    由于在一階段的時(shí)候,數(shù)據(jù)已經(jīng)保存到數(shù)據(jù)庫(kù)并提交,所以Seata默認(rèn)的隔離級(jí)別為讀未提交,如果需要把隔離級(jí)別提升至讀已提交則需要使用@GlobalLock標(biāo)簽并且在查詢(xún)語(yǔ)句上加上for update


    @GlobalLock @Transactional public PayMoneyDto detail(ProcessOnEventRequestDto processOnEventRequestDto) { return baseMapper.detail(processOnEventRequestDto.getProcessInfoDto().getBusinessKey()) } @Mapper public interface PayMoneyMapper extends BaseMapper<PayMoney> { @Select("select id, name, amount, account, has_repayment, pay_amount from pay_money m where m.business_key = #{businessKey} for update") PayMoneyDto detail(@Param("businessKey") String businessKey); }


    這個(gè)時(shí)候Seata會(huì)對(duì)添加了for update的查詢(xún)語(yǔ)句進(jìn)行代理



    如果一個(gè)全局事務(wù)1正在操作,并且未進(jìn)行二階段提交/回滾的時(shí)候,全局鎖是被全局事務(wù)1鎖持有的,同時(shí)另外一個(gè)全局事務(wù)2嘗試去查詢(xún)相同的數(shù)據(jù),由于查詢(xún)語(yǔ)句被代理,seata會(huì)嘗試去獲取這條數(shù)據(jù)的全局鎖,直到獲取成功/失敗(重試次數(shù)達(dá)到配置值)為止。


    問(wèn)題


    在生產(chǎn)上運(yùn)行接近1年時(shí)間,總體來(lái)說(shuō)遇到的問(wèn)題不算多,解決起來(lái)也比較容易,比如以下這個(gè)問(wèn)題



    經(jīng)過(guò)排查發(fā)現(xiàn),由于Seata會(huì)使用jdbc標(biāo)準(zhǔn)接口嘗試獲取業(yè)務(wù)操作所對(duì)應(yīng)的表結(jié)構(gòu),由于表結(jié)構(gòu)改動(dòng)頻率較少,并且考慮到表結(jié)構(gòu)變更后應(yīng)用會(huì)進(jìn)行重啟,所以會(huì)對(duì)表結(jié)構(gòu)進(jìn)行緩存,如果表結(jié)構(gòu)改動(dòng)后不對(duì)應(yīng)用進(jìn)行重啟,有可能引發(fā)構(gòu)建鏡像時(shí)出現(xiàn)NullPointerException。下面貼出關(guān)鍵代碼


    @Override public TableMeta getTableMeta(final Connection connection, final String tableName, String resourceId) { if (StringUtils.isNullOrEmpty(tableName)) { throw new IllegalArgumentException("TableMeta cannot be fetched without tableName"); } TableMeta tmeta; final String key = getCacheKey(connection, tableName, resourceId); //錯(cuò)誤關(guān)鍵處,嘗試從緩存獲取表結(jié)構(gòu) tmeta = TABLE_META_CACHE.get(key, mappingFunction -> { try { return fetchSchema(connection, tableName); } catch (SQLException e) { LOGGER.error("get table meta of the table `{}` error: {}", tableName, e.getMessage(), e); return null; } }); if (tmeta == null) { throw new ShouldNeverHappenException(String.format("[xid:%s]get table meta failed," + " please check whether the table `%s` exists.", RootContext.getXID(), tableName)); } return tmeta; }


    修改表結(jié)構(gòu),需要對(duì)應(yīng)用進(jìn)行重啟,即可解決此問(wèn)題,非常簡(jiǎn)單


    第二個(gè)遇到的問(wèn)題就是在生產(chǎn)運(yùn)行一段時(shí)間后,發(fā)現(xiàn)branch_table和lock_table存在數(shù)據(jù)殘留,并且根據(jù)xid查詢(xún)global_table沒(méi)有對(duì)應(yīng)的數(shù)據(jù),導(dǎo)致后續(xù)操作相同的數(shù)據(jù)行會(huì)出現(xiàn)獲取全局鎖失敗,并且會(huì)每隔一段時(shí)間小量出現(xiàn)。這個(gè)異常隱藏的比較深,而且在開(kāi)發(fā)環(huán)境和測(cè)試環(huán)境無(wú)法復(fù)現(xiàn),通過(guò)跟蹤源碼和總結(jié)原因發(fā)現(xiàn),是由于開(kāi)啟了Mysql主從,導(dǎo)致提交/回滾時(shí),Seata通過(guò)xid查詢(xún)分支事務(wù)時(shí),數(shù)據(jù)未同步到從庫(kù),導(dǎo)致遺漏了一部分分支事務(wù)數(shù)據(jù)。


    源碼部分


    @Override public GlobalStatus commit(String xid) throws TransactionException { //根據(jù)xid查詢(xún)信息,如果開(kāi)啟主從,會(huì)有可能導(dǎo)致查詢(xún)信息不完整 GlobalSession globalSession = SessionHolder.findGlobalSession(xid); if (globalSession == null) { return GlobalStatus.Finished; } globalSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager()); // just lock changeStatus boolean shouldCommit = SessionHolder.lockAndExecute(globalSession, () -> { // Highlight: Firstly, close the session, then no more branch can be registered. globalSession.closeAndClean(); if (globalSession.getStatus() == GlobalStatus.Begin) { if (globalSession.canBeCommittedAsync()) { globalSession.asyncCommit(); return false; } else { globalSession.changeStatus(GlobalStatus.Committing); return true; } } return false; }); if (shouldCommit) { boolean success = doGlobalCommit(globalSession, false); //If successful and all remaining branches can be committed asynchronously, do async commit. if (success && globalSession.hasBranch() && globalSession.canBeCommittedAsync()) { globalSession.asyncCommit(); return GlobalStatus.Committed; } else { return globalSession.getStatus(); } } else { return globalSession.getStatus() == GlobalStatus.AsyncCommitting ? GlobalStatus.Committed : globalSession.getStatus(); } }


    @Override public GlobalStatus rollback(String xid) throws TransactionException { //根據(jù)xid查詢(xún)信息,如果開(kāi)啟主從,會(huì)有可能導(dǎo)致查詢(xún)信息不完整 GlobalSession globalSession = SessionHolder.findGlobalSession(xid); if (globalSession == null) { return GlobalStatus.Finished; } globalSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager()); // just lock changeStatus boolean shouldRollBack = SessionHolder.lockAndExecute(globalSession, () -> { globalSession.close(); // Highlight: Firstly, close the session, then no more branch can be registered. if (globalSession.getStatus() == GlobalStatus.Begin) { globalSession.changeStatus(GlobalStatus.Rollbacking); return true; } return false; }); if (!shouldRollBack) { return globalSession.getStatus(); } doGlobalRollback(globalSession, false); return globalSession.getStatus(); }


    相信此問(wèn)題會(huì)在支持Raft之后得到完美的解決

    pr: https://github.com/seata/seata/pull/3086

    有興趣的朋友也可以嘗試去review一下代碼


    部署-高可用



    Seata和其他中間件的高可用部署方式差別不大,如圖片所示,確保應(yīng)用服務(wù)和TC訪(fǎng)問(wèn)相同的注冊(cè)中心和配置中心,同時(shí)只需要啟動(dòng)多臺(tái)TC,并將store.mode改為db模式即可完成高可用部署,并選擇合適的注冊(cè)中心和配置中心即可,目前支持的配置中心有


    • nacos
    • consul
    • etcd3
    • eureka
    • redis
    • sofa
    • zookeeper


    可選的配置中心有


    • nacos
    • etcd3
    • consul
    • apollo
    • zk


    部署-單節(jié)點(diǎn)多應(yīng)用



    當(dāng)然也有更加靈活的部署方式,通過(guò)vgoup-mapping(事務(wù)集群),可以做到單節(jié)點(diǎn)多應(yīng)用的隔離,比如A應(yīng)用和B應(yīng)用訪(fǎng)問(wèn)A-Group的兩個(gè)TC,C應(yīng)用和D應(yīng)用訪(fǎng)問(wèn)B-Group的兩個(gè)TC,E應(yīng)用和F應(yīng)用訪(fǎng)問(wèn)C-Group的兩個(gè)TC。


    部署-異地容災(zāi)




    通過(guò)vgoup-mapping也可以做到異地容災(zāi),當(dāng)原有集群出現(xiàn)不可用時(shí),可以通過(guò)變更配置立刻轉(zhuǎn)移到備用的集群上。此處以Nacos作為注冊(cè)中心舉例,TC配置方式如下:


    # 廣州機(jī)房 registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "nacos" loadBalance = "RandomLoadBalance" loadBalanceVirtualNodes = 10 nacos { application = "seata-server" serverAddr = "127.0.0.1:8848" group = "SEATA_GROUP" namespace = "" cluster = "Guangzhou" username = "" password = "" } }


    # 上海機(jī)房 registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "nacos" loadBalance = "RandomLoadBalance" loadBalanceVirtualNodes = 10 nacos { application = "seata-server" serverAddr = "127.0.0.1:8848" group = "SEATA_GROUP" namespace = "" cluster = "Shanghai" username = "" password = "" } }


    Demo


    最后通過(guò)訪(fǎng)問(wèn)阿里云知行動(dòng)手首頁(yè),即可在線(xiàn)快速體驗(yàn)各種各樣的中間件:


    https://start.aliyun.com


    Seata直達(dá)傳送門(mén),無(wú)需下載代碼,在線(xiàn)編譯和部署:


    https://start.aliyun.com/handson/isnEO76f/distributedtransaction

    原文鏈接:https://developer.aliyun.com/article/783527?

    版權(quán)聲明:本文內(nèi)容由阿里云實(shí)名注冊(cè)用戶(hù)自發(fā)貢獻(xiàn),版權(quán)歸原作者所有,阿里云開(kāi)發(fā)者社區(qū)不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。具體規(guī)則請(qǐng)查看《阿里云開(kāi)發(fā)者社區(qū)用戶(hù)服務(wù)協(xié)議》和《阿里云開(kāi)發(fā)者社區(qū)知識(shí)產(chǎn)權(quán)保護(hù)指引》。如果您發(fā)現(xiàn)本社區(qū)中有涉嫌抄襲的內(nèi)容,填寫(xiě)侵權(quán)投訴表單進(jìn)行舉報(bào),一經(jīng)查實(shí),本社區(qū)將立刻刪除涉嫌侵權(quán)內(nèi)容。

    總結(jié)

    以上是生活随笔為你收集整理的逸仙电商Seata企业级落地实践的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。