在我面試招行外包的時候,與三位面試官進行了半個多小時的交鋒,從java基礎到框架,其中讓我記憶深刻的有一個問題。我說到我們系統采用了微服務架構,是根據不同崗位划分成幾個服務,服務之間的調用是用openFeign。A服務減庫存並調用B服務增加相應庫存,使用事務管理防止操作失敗。
面試官:等一下,你說一下事務有什么特性?
我:事務有四個特性,原子性、一致性、隔離性、額還有一個忘了(我太緊張了)
面試官:那你大概解釋一下每個特性
我:原子性就是事務要么全部成功要么全部失敗,一致性就是事務執行前和執行后是一致的,假設A和B共100塊錢,無論怎么相互轉賬,總共還是100塊錢,
隔離性就是每個事務之間是相互隔離不影響的,(補充:持久性就是事務一旦提交了,對數據庫的改變就是永久性的)
面試官: 事務的隔離級別有幾種
我:巴拉巴拉。。。
事務隔離級別 | 臟讀 | 不可重復讀 | 幻讀 |
---|---|---|---|
讀未提交(read-uncommitted) | 是 | 是 | 是 |
不可重復讀(read-committed) | 否 | 是 | 是 |
可重復讀(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
面試官: 你是怎么保證數據不出錯的
我:通過事務管理(我指的是spring本地事務,我還不知道分布式事務),如果有異常則數據回滾,B服務的方法執行完之后,校驗數據是否插入成功,如果沒有插入成功則手動拋出異常。
面試官:如果網絡延遲B服務沒有及時插入,或者其他原因導致你校驗的時候B數據庫並沒有插入數據,但是隨后又插入成功了,這種情況你是如何解決的
我:。。。(我就此被KO了)
過后我才了解到分布式事務這種東西,一個很經典的場景,在微服務架構中,訂單服務下訂單,就要調用庫存服務減庫存,如果庫存不足,就下單失敗。常見的分布式事務類型有 2PC、3PC、TCC、本地消息表、消息事務、最大努力通知,對於這幾種我在此先不講了,給大家推薦一篇我很喜歡的博主(敖丙)的文章,他寫的很好,我經常看他的文章,https://zhuanlan.zhihu.com/p/183753774,我今天就只談一下阿里開源的分布式事務框架Seata,Seata 是 Simple Extensible Autonomous Transaction Architecture 的簡寫,詳細介紹可參考官網https://seata.io/zh-cn/還有在碼雲上的倉庫https://gitee.com/itCjb/spring-cloud-alibaba-seata-demo里面有示例代碼和相關文檔。這些東西我覺得沒必要寫了,我寫的不一定有大佬和官方寫的好,所以我就分享一下我在安裝部署使用中碰到的一些坑:
1、安裝seata服務端並注冊到nacos(我是放在阿里雲ECS上面)
簡單粗暴的方法就是到seata的gitbub上去下載linux版本的壓縮包,我就是是這樣的,或者其他途徑也行,然后通過工具上傳到服務器,解壓,啟動就完事了,但是這樣是不行的,需要改配置才能成功連接數據庫和注冊到nacos
file.conf (在conf文件夾里)
1 ## transaction log store, only used in seata-server 2 store { 3 ## store mode: file、db、redis 4 mode = "db" 5 6 ## database store property 7 db { 8 ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc. 9 datasource = "druid" 10 ## mysql/oracle/postgresql/h2/oceanbase etc. 11 dbType = "mysql" 12 driverClassName = "com.mysql.jdbc.Driver" 13 url = "jdbc:mysql://127.0.0.1:3306/seata" 14 user = "root" 15 password = "123456" 16 minConn = 5 17 maxConn = 30 18 globalTable = "global_table" 19 branchTable = "branch_table" 20 lockTable = "lock_table" 21 queryLimit = 100 22 maxWait = 5000 23 } 24 }
這是我的配置,默認配置里還有redis和file模式的默認配置,我沒用到就刪掉了,這個文件只需要修改幾個地方,其余的用默認的就行,如果有興趣可以自行研究。
1 # 這個改成自己的數據庫類型,注意是seata要連接的數據庫,不是業務數據庫 2 dbType = "mysql" 3 # 數據庫驅動,根據自己的數據庫及版本選擇 4 driverClassName = "com.mysql.jdbc.Driver" 5 # 數據源配置(懂的都懂),需要自己先建這個庫,然后導入官方提供的sql腳本 6 url = "jdbc:mysql://127.0.0.1:3306/seata" 7 user = "root" 8 password = "123456"
相關sql腳本,可以到看官方文檔,我就不貼了,如果以后更新了貼了可能還會誤導,我盡量避免版本更新了我這篇文章就白寫了。然后還需要在每個參與全局事務的數據庫中加入undo_log表,這是用來存儲事務回滾所需的一些數據。
第二步就是修改config.txt文件並添加到nacos
1 # my_test_tx_group 可以根據自定義,多個服務可以配置多個 2 service.vgroupMapping.my_test_tx_group=default 3 # 因為我的數據庫也是在ECS上,所以IP是127.0.0.1,這里與上一步幾乎一樣 4 store.mode=db 5 store.db.datasource=druid 6 store.db.dbType=mysql 7 store.db.driverClassName=com.mysql.jdbc.Driver 8 store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true 9 store.db.user=root 10 store.db.password=123456 11 # 以下配置我沒動,采用默認配置 you can you do 12 store.db.minConn=5 13 store.db.maxConn=30 14 store.db.globalTable=global_table 15 store.db.branchTable=branch_table 16 store.db.queryLimit=100 17 store.db.lockTable=lock_table 18 store.db.maxWait=5000
改完之后可以用官方提供的腳本nacos-config.sh將配置添加到nacos配置中心,如果無聊也可以自己一個一個添加
第三步,修改registry.conf
1 registry { 2 # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa 3 # 類型選nacos 4 type = "nacos" 5 nacos { 6 # 應用名默認 7 application = "seata-server" 8 # IP 我改成我服務器IP 9 serverAddr = "127.0.0.1:8848" 10 # 分組就默認,注意:需要參與全局事務的服務都要跟seata-server在同一個命名空間同一個分組 11 group = "SEATA_GROUP" 12 # 我是全部放在自己建的test空間,若沒有配置新的命名空間則默認就行 13 namespace = "" 14 cluster = "default" 15 username = "nacos" 16 password = "nacos" 17 } 18 } 19 # 下面配置也一樣的,不然無法讀取在nacos配置中心的配置 20 config { 21 # file、nacos 、apollo、zk、consul、etcd3 22 # 類型選nacos 23 type = "nacos" 24 nacos { 25 serverAddr = "127.0.0.1:8848" 26 namespace = "" 27 group = "SEATA_GROUP" 28 username = "nacos" 29 password = "nacos" 30 } 31 }
到此seata服務端就安裝成功,然后就可以啟動seata-server,但是直接使用下面第一個命令啟動的話,會有2個問題,一個是關閉終端服務及關閉,二是本地服務會連接不上服務端,因為沒有指定IP的話,默認是內網IP,需要改成服務器IP,最好也定一下端口,然后加上其他命令后台啟動+后台運行
注意:阿里雲服務器的話需要在安全組新增策略開放8091端口,並在內部防火牆開通8091端口或者關閉內部防火牆
1 # 1、 2 ./seata-server.sh 3 # 2、將日志輸出到nohup.out文件中 4 sudo nohup ./seata-server.sh -p 8091 -h xxxxxxxxx >nohup.out 2>1 &
然后就可以登錄nacos查看seata-server是否啟動並注冊成功
2、配置seata客戶端
客戶端就簡單了,只需三步,添加依賴,添加配置,添加注解
添加依賴(至於版本可以根據官方推薦自行選擇)
1 <dependency> 2 <groupId>com.alibaba.cloud</groupId> 3 <artifactId>spring-cloud-starter-alibaba-seata</artifactId> 4 <version>2.2.2.RELEASE</version> 5 <exclusions> 6 <exclusion> 7 <groupId>io.seata</groupId> 8 <artifactId>seata-spring-boot-starter</artifactId> 9 </exclusion> 10 </exclusions> 11 </dependency> 12 <dependency> 13 <groupId>io.seata</groupId> 14 <artifactId>seata-spring-boot-starter</artifactId> 15 <version>1.3.0</version> 16 </dependency>
添加配置
1 seata: 2 enabled: true 3 # 我直接用服務名 4 application-id: user-service 5 # 這個就是 service.vgroupMapping.my_test_tx_group=default 中的my_test_tx_group 我也用服務名,這2個要一致 6 tx-service-group: user-service 7 enable-auto-data-source-proxy: true 8 config: 9 type: nacos 10 nacos: 11 namespace: "" 12 serverAddr: 127.0.0.1:8848 13 group: SEATA_GROUP 14 username: "nacos" 15 password: "nacos" 16 registry: 17 type: nacos 18 nacos: 19 application: seata-server 20 server-addr: 127.0.0.1:8848 21 group: SEATA_GROUP 22 namespace: “” 23 username: "nacos" 24 password: "nacos"
添加注解
1 @GlobalTransactional 2 @Override 3 public Result publish(Article article) { 4 5 articleMapper.insert(article); 6 userFeign.updatePublishNum(article.getAuthor()); 7 return null; 8 }
注意:如果采用本地事務@Transactional,insert方法執行成功,調用user-service有問題的話,article是沒法回滾的,這就需要使用分布式事務
seata目前官方提供四種模式,我使用的是最便捷的AT模式,TCC、Saga、XA模式,you can you do,還有所有的服務,包括seata-server一定要在nacos的同一個空間同一個分組
最后,碼字不容易,用你發財的小手點個贊吧,歡迎志同道合的同志,有什么問題或者意見,可以評論私信或者加我的企鵝群876083754(海綿寶寶的菠蘿屋)