終於跑通分散式事務框架tcc-transaction的示例專案
1、背景
前段時間在看專案程式碼的時候,發現有些介面的流程比較長,在各個服務裡面都有通過資料庫事務保證資料的一致性,但是在上游的controller層並沒有對一致性做保證。
網上查了下,還沒找到基於Go開源的比較成熟的分散式事務框架。
於是,準備看看之前隔壁部門大佬寫的tcc-transaction,這是一個基於tcc思想實現的分散式事務框架。
tcc分別程式碼Try,Confirm和Cancel。
Try: 嘗試執行業務
- 完成所有業務檢查(一致性)
- 預留必須業務資源(準隔離性)
Confirm: 確認執行業務
- 真正執行業務
- 不作任何業務檢查
- 只使用Try階段預留的業務資源
- Confirm操作滿足冪等性
Cancel: 取消執行業務
- 釋放Try階段預留的業務資源
- Cancel操作滿足冪等性
要了解其實現原理,第一步就是跑通專案自帶的示例,即tcc-transaction-tutorial-sample部分的程式碼。
今天主要介紹在跑通tcc-transaction-tutorial-sample過程中遇到的各種坑。
2、依賴環境
- Java
- Maven
- Git
- MySQL
- Redis
- Zookeeper
- Intellij IDEA
原始碼地址:https://github.com/changmingxie/tcc-transaction
我自己Fork了一份(配置改動已提交):https://github.com/DMinerJackie/tcc-transaction
3、踩坑歷程
踩坑準備
第一步:克隆程式碼
使用"git clone https://github.com/DMinerJackie/tcc-transaction"命令下載程式碼
第二步:匯入程式碼並執行資料庫指令碼
程式碼匯入Intellij IDEA。
執行tcc-transaction-http-sample/src/main/dbscripts 下的資料庫指令碼。
第三步:修改配置檔案
主要修改的是資料庫配置引數。拿tcc-transaction-dubbo-sample舉例,需要修改的檔案有
tcc-transaction/tcc-transaction-tutorial-sample/tcc-transaction-dubbo-sample/tcc-transaction-dubbo-capital/src/main/resources/tccjdbc.properties
tcc-transaction/tcc-transaction-tutorial-sample/tcc-transaction-dubbo-sample/tcc-transaction-dubbo-redpacket/src/main/resources/tccjdbc.properties
tcc-transaction/tcc-transaction-tutorial-sample/tcc-transaction-dubbo-sample/tcc-transaction-dubbo-order/src/main/resources/tccjdbc.properties
三個檔案修改後對應配置如下
# 根據具體的MySQL版本使用驅動名稱 jdbc.driverClassName=com.mysql.cj.jdbc.Driver # 換成你連線資料庫的地址 tcc.jdbc.url=jdbc:mysql://127.0.0.1:3306/TCC?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false # 換成你需要配置資料庫的使用者名稱 jdbc.username=root # 換成你需要配置資料庫的密碼 jdbc.password=rootroot c3p0.initialPoolSize=10 c3p0.minPoolSize=10 c3p0.maxPoolSize=30 c3p0.acquireIncrement=3 c3p0.maxIdleTime=1800 c3p0.checkoutTimeout=30000
同時修改tcc-transaction-sample-capital、tcc-transaction-sample-redpacket和tcc-transaction-sample-order三個專案中jdbc.proerties檔案的資料庫連線,修改後配置如下
jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/TCC_CAP?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false tcc.jdbc.url=jdbc:mysql://127.0.0.1:3306/TCC?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false jdbc.username=root jdbc.password=rootroot c3p0.initialPoolSize=10 c3p0.minPoolSize=10 c3p0.maxPoolSize=30 c3p0.acquireIncrement=3 c3p0.maxIdleTime=1800 c3p0.checkoutTimeout=30000
第四步:啟動專案
結合專案的README.md檔案以及網上的文章瞭解到如果要跑通示例專案,需要分別啟動三個專案。
tcc-transaction提供了兩個版本:
- 基於dubbo通訊的示例版本
- 基於http通訊的示例版本
這兩個版本對於的三個專案分別是
- tcc-transaction-dubbo-capital(賬戶資產服務)、 tcc-transaction-dubbo-redpacket(紅包服務)、 tcc-transaction-dubbo-order(交易訂單服務)
- tcc-transaction-http-capital(賬戶資產服務)、 tcc-transaction-http-redpacket(紅包服務)、 tcc-transaction-http-order(交易訂單服務)
其實這兩個版本我都跑過,最終成功跑通的只有基於dubbo通訊的示例版本(http版本在最後confirm的時候最是失敗,導致最終訂單狀態為unkown)。
以基於dubbo通訊的示例為例
tcc-transaction-dubbo-capital的啟動配置如下
tcc-transaction-dubbo-redpacket的啟動配置如下
tcc-transaction-dubbo-order的啟動配置如下
坑1:連線不上zk
啟動tcc-transaction-dubbo-capital專案,報錯資訊如下
[sample-dubbo-capital]2019-08-31 17:48:05,312 INFO [org.apache.zookeeper.ZooKeeper] Initiating client connection, connectString=127.0.0.1:2181 sessionTimeout=30000 watcher=org.I0Itec.zkclient.ZkClient@32c7bb63 [sample-dubbo-capital]2019-08-31 17:48:05,334 INFO [org.apache.zookeeper.ClientCnxn] Opening socket connection to server 127.0.0.1/127.0.0.1:2181. Will not attempt to authenticate using SASL (unknown error) [sample-dubbo-capital]2019-08-31 17:48:05,344 WARN [org.apache.zookeeper.ClientCnxn] Session 0x0 for server null, unexpected error, closing socket connection and attempting reconnect java.net.ConnectException: Connection refused at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method) at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717) at org.apache.zookeeper.ClientCnxnSocketNIO.doTransport(ClientCnxnSocketNIO.java:361) at org.apache.zookeeper.ClientCnxn$SendThread.run(ClientCnxn.java:1081) [sample-dubbo-capital]2019-08-31 17:48:06,456 INFO [org.apache.zookeeper.ClientCnxn] Opening socket connection to server 127.0.0.1/127.0.0.1:2181. Will not attempt to authenticate using SASL (unknown error) [sample-dubbo-capital]2019-08-31 17:48:06,459 WARN [org.apache.zookeeper.ClientCnxn] Session 0x0 for server null, unexpected error, closing socket connection and attempting reconnect java.net.ConnectException: Connection refused at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method) at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717) at org.apache.zookeeper.ClientCnxnSocketNIO.doTransport(ClientCnxnSocketNIO.java:361) at org.apache.zookeeper.ClientCnxn$SendThread.run(ClientCnxn.java:1081) [sample-dubbo-capital]2019-08-31 17:48:07,566 INFO [org.apache.zookeeper.ClientCnxn] Opening socket connection to server 127.0.0.1/127.0.0.1:2181. Will not attempt to authenticate using SASL (unknown error) [sample-dubbo-capital]2019-08-31 17:48:07,567 WARN [org.apache.zookeeper.ClientCnxn] Session 0x0 for server null, unexpected error, closing socket connection and attempting reconnect java.net.ConnectException: Connection refused
從報錯資訊,一眼就看出是連不上zk即zookeeper。
這個很好理解,因為本地沒有安裝zk,於是安裝並通過"./zkServer.sh start"啟動zk
坑2:redis連不上
啟動tcc-transaction-dubbo-order報錯部分資訊如下:
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: org.mengyun.tcctransaction.sample.dubbo.order.service.PlaceOrderServiceImpl org.mengyun.tcctransaction.sample.dubbo.order.web.controller.OrderController.placeOrderService; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'placeOrderServiceImpl': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: org.mengyun.tcctransaction.sample.dubbo.order.service.PaymentServiceImpl org.mengyun.tcctransaction.sample.dubbo.order.service.PlaceOrderServiceImpl.paymentService; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'paymentServiceImpl': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: org.mengyun.tcctransaction.sample.dubbo.capital.api.CapitalTradeOrderService org.mengyun.tcctransaction.sample.dubbo.order.service.PaymentServiceImpl.capitalTradeOrderService; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'captialTradeOrderService': Post-processing of FactoryBean's singleton object failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'compensableTransactionAspect' defined in class path resource [tcc-transaction.xml]: Cannot resolve reference to bean 'transactionConfigurator' while setting bean property 'transactionConfigurator'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionConfigurator': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private org.mengyun.tcctransaction.TransactionRepository org.mengyun.tcctransaction.spring.support.SpringTransactionConfigurator.transactionRepository; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionRepository' defined in file [/Users/jackie/workspace/tcc-transaction/tcc-transaction-tutorial-sample/tcc-transaction-dubbo-sample/tcc-transaction-dubbo-order/target/tcc-transaction-dubbo-order-1.2.6/WEB-INF/classes/config/spring/local/appcontext-service-tcc.xml]: Error setting property values; nested exception is org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are: PropertyAccessException 1: org.springframework.beans.MethodInvocationException: Property 'jedisPool' threw exception; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:526) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:295) ... 60 more
這個和上面的原因類似,本地沒有安裝redis,導致無法拿到redis連線。
於是安裝redis,並使用"redis-server"啟動redis。
坑3:Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection
三個專案都啟動後,可以看到一個商品連結列表頁,但是在點選連結後無法跳轉,並且報錯如下
Type Exception Report Message Request processing failed; nested exception is org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: Description The server encountered an unexpected condition that prevented it from fulfilling the request. Exception org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is java.sql.SQLException: An attempt by a client to checkout a Connection has timed out. ### The error may exist in URL [jar:file:/Users/jackie/workspace/tcc-transaction/tcc-transaction-tutorial-sample/tcc-transaction-http-sample/tcc-transaction-http-order/target/tcc-transaction-http-order-1.2.6/WEB-INF/lib/tcc-transaction-sample-order-1.2.6.jar!/config/sqlmap/main/sample-product.xml] ### The error may involve org.mengyun.tcctransaction.sample.order.infrastructure.dao.ProductDao.findByShopId ### The error occurred while executing a query ### Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is java.sql.SQLException: An attempt by a client to checkout a Connection has timed out. org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:965) org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:844) javax.servlet.http.HttpServlet.service(HttpServlet.java:634) org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:829) javax.servlet.http.HttpServlet.service(HttpServlet.java:741) org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88) org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:106) Root Cause org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is java.sql.SQLException: An attempt by a client to checkout a Connection has timed out. ### The error may exist in URL [jar:file:/Users/jackie/workspace/tcc-transaction/tcc-transaction-tutorial-sample/tcc-transaction-http-sample/tcc-transaction-http-order/target/tcc-transaction-http-order-1.2.6/WEB-INF/lib/tcc-transaction-sample-order-1.2.6.jar!/config/sqlmap/main/sample-product.xml]
根據錯誤資訊,排查是MySQL版本和資料庫驅動版本不匹配。
本地的MySQL版本是"8.0.11 MySQL Community Server - GPL",但是tcc-transaction中對應的驅動版本是
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.33</version> </dependency>
改為
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.11</version> </dependency>
同時針對高版本,需要在連線的jdbc-url後面加上useSSL=false
坑4:Loading class `com.mysql.jdbc.Driver'. This is deprecated
啟動tcc-transaction-dubbo-redpacket時,在日誌中看到一個警告"Loading class `com.mysql.jdbc.Driver'. This is deprecated"。
通過搜尋,發現是因為資料庫驅動com.mysql.jdbc.Driver'已經被棄用了,需要使用com.mysql.cj.jdbc.Driver,於是修改jdbc.proerties的配置(具體配置見上面),啟動正常。
踩完上面的坑後,啟動三個專案,完整走完流程,實現了一個基於分散式事務的商品購買行為,具體過程如下圖所示
4、 總結
執行示例專案的過程不算太順利,主要有一下幾個原因吧
- 本地環境配置和專案提供的不一致,導致走了很多彎路,比如MySQL的版本。
- 缺少詳細的跑示例專案的文件說明。
- 網上提供的資料比較粗略,也比較陳舊,文中能跑起來的步驟說明已經不適用現在的程式碼了。
所以,在踩完這麼多坑總結下,避免後面的人走同樣的彎路。
如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的“推薦”將是我最大的寫作動力!如果您想持續關注我的文章,請掃描二維碼,關注JackieZheng的微信公眾號,我會將我的文章推送給您,並和您一起分享我日常閱讀過的優質文章。