參考地址:https://blog.csdn.net/u010882691/article/details/82256587
參考地址:https://blog.csdn.net/oyh1203/article/details/82189445
參考地址:https://blog.csdn.net/small_to_large/article/details/77836672 Spring Cloud Ribbon和Spring Cloud Feign
參考地址:https://blog.csdn.net/5iasp/article/details/79881691
事務等級:https://blog.csdn.net/gududedabai/article/details/82993700
目前,在Spring cloud 中服務之間通過restful方式調用有兩種方式
- restTemplate+Ribbon
- feign
從實踐上看,采用feign的方式更優雅(feign內部也使用了ribbon做負載均衡)。
zuul也有負載均衡的功能,它是針對外部請求做負載,那客戶端ribbon的負載均衡又是怎么一回事?
客戶端ribbon的負載均衡,解決的是服務發起方(在Eureka注冊的服務)對被調用的服務的負載,比如我們查詢商品服務要調用顯示庫存和商品明細服務,通過商品服務的接口將兩個服務組合,可以減少外部應用的請求,比如手機App發起一次請求即可,可以節省網絡帶寬,也更省電。
ribbon是對服務之間調用做負載,是服務之間的負載均衡,zuul是可以對外部請求做負載均衡。
參考地址:https://blog.csdn.net/jrn1012/article/details/77837658/
因為LCN實現分布式事務的回滾,需要在服務內部 微服務之間的 負載均衡的 請求操作,故而需要在配置文件中加上ribbon的相關配置,它不與使用feign沖突!!!
lcn使用spring boot2.0 報錯解決方案:https://www.jianshu.com/p/453741e0f28f
lcn集成到自己到自己的spring cloud項目中:https://blog.csdn.net/zhangxing52077/article/details/81587988
參考使用步驟1:
https://m.wang1314.com/doc/webapp/topic/20308073.html
修改LCN ,集成spring boot2.0
注意:LCN 4.1.0版本 目前不支持spring boot 2.x的版本,所以需要進行更改!!
【【因為我已經更改完成,打包了jar了,jar可以在百度網盤下載,然后直接走這一步的上傳第三方jar包到本地maven倉庫,然后在項目中直接引用即可】】
第一步:
先在lcn官網【http://www.txlcn.org/】 找到GitHub 地址【https://github.com/codingapi/tx-lcn】,拷下所有的源碼
第二步:
解壓下載的zip,放置在一個目錄下,用IDEA打開【注意打開父層項目】
導入完整的jar包,然后下面就要開始更改源碼中不支持spring boot 2.X的部分
第三步:
修改
transaction-springcloud 項目下com.codingapi.tx.springcloud.listener包中的ServerListener.java
源碼更改為:

package com.codingapi.tx.springcloud.listener; import com.codingapi.tx.listener.service.InitService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; /** * @Component注解會自動掃描配置文件中的server.port值 */ @Component public class ServerListener implements ApplicationListener<ApplicationEvent> { private Logger logger = LoggerFactory.getLogger(ServerListener.class); private int serverPort; @Value("${server.port}") private String port; @Autowired private InitService initService; @Override public void onApplicationEvent(ApplicationEvent event) { // logger.info("onApplicationEvent -> onApplicationEvent. "+event.getEmbeddedServletContainer()); // this.serverPort = event.getEmbeddedServletContainer().getPort(); //TODO Spring boot 2.0.0沒有EmbeddedServletContainerInitializedEvent 此處寫死;modify by young this.serverPort = Integer.parseInt(port); Thread thread = new Thread(new Runnable() { @Override public void run() { // 若連接不上txmanager start()方法將阻塞 initService.start(); } }); thread.setName("TxInit-thread"); thread.start(); } public int getPort() { return this.serverPort; } public void setServerPort(int serverPort) { this.serverPort = serverPort; } }
第四步:
修改tx-manager項目下com.codingapi.tm.listener包中的ApplicationStartListener.java

package com.codingapi.tm.listener; import com.codingapi.tm.Constants; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; import java.net.InetAddress; import java.net.UnknownHostException; @Component public class ApplicationStartListener implements ApplicationListener<ApplicationEvent> { @Override public void onApplicationEvent(ApplicationEvent event) { //TODO Spring boot 2.0.0沒有EmbeddedServletContainerInitializedEvent 此處寫死;modify by young // int serverPort = event.getEmbeddedServletContainer().getPort(); String ip = getIp(); Constants.address = ip+":48888";//寫死端口號,反正TxManager端口也是配置文件配好的(●′ω`●) } private String getIp(){ String host = null; try { host = InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { e.printStackTrace(); } return host; } }
第五步:
修改
tx-manager項目下com.codingapi.tm.manager.service.impl包中MicroServiceImpl.java類的getState()方法

package com.codingapi.tm.manager.service.impl; import com.codingapi.tm.Constants; import com.codingapi.tm.config.ConfigReader; import com.codingapi.tm.framework.utils.SocketManager; import com.codingapi.tm.manager.service.MicroService; import com.codingapi.tm.model.TxServer; import com.codingapi.tm.model.TxState; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import java.net.InetAddress; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * create by lorne on 2017/11/11 */ @Service public class MicroServiceImpl implements MicroService { @Autowired private RestTemplate restTemplate; @Autowired private ConfigReader configReader; @Autowired private DiscoveryClient discoveryClient; private boolean isIp(String ipAddress) { String ip = "([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}"; Pattern pattern = Pattern.compile(ip); Matcher matcher = pattern.matcher(ipAddress); return matcher.matches(); } @Override public TxState getState() { TxState state = new TxState(); String ipAddress = ""; //TODO Spring boot 2.0.0沒有discoveryClient.getLocalServiceInstance() 用InetAddress獲取host;modify by young //String ipAddress = discoveryClient.getLocalServiceInstance().getHost(); try { ipAddress = InetAddress.getLocalHost().getHostAddress(); } catch (Exception e) { e.printStackTrace(); } if (!isIp(ipAddress)) { ipAddress = "127.0.0.1"; } state.setIp(ipAddress); state.setPort(Constants.socketPort); state.setMaxConnection(SocketManager.getInstance().getMaxConnection()); state.setNowConnection(SocketManager.getInstance().getNowConnection()); state.setRedisSaveMaxTime(configReader.getRedisSaveMaxTime()); state.setTransactionNettyDelayTime(configReader.getTransactionNettyDelayTime()); state.setTransactionNettyHeartTime(configReader.getTransactionNettyHeartTime()); state.setNotifyUrl(configReader.getCompensateNotifyUrl()); state.setCompensate(configReader.isCompensateAuto()); state.setCompensateTryTime(configReader.getCompensateTryTime()); state.setCompensateMaxWaitTime(configReader.getCompensateMaxWaitTime()); state.setSlbList(getServices()); return state; } private List<String> getServices(){ List<String> urls = new ArrayList<>(); List<ServiceInstance> serviceInstances = discoveryClient.getInstances(tmKey); for (ServiceInstance instanceInfo : serviceInstances) { urls.add(instanceInfo.getUri().toASCIIString()); } return urls; } @Override public TxServer getServer() { List<String> urls= getServices(); List<TxState> states = new ArrayList<>(); for(String url:urls){ try { TxState state = restTemplate.getForObject(url + "/tx/manager/state", TxState.class); states.add(state); } catch (Exception e) { } } if(states.size()<=1) { TxState state = getState(); if (state.getMaxConnection() > state.getNowConnection()) { return TxServer.format(state); } else { return null; } }else{ //找默認數據 TxState state = getDefault(states,0); if (state == null) { //沒有滿足的默認數據 return null; } return TxServer.format(state); } } private TxState getDefault(List<TxState> states, int index) { TxState state = states.get(index); if (state.getMaxConnection() == state.getNowConnection()) { index++; if (states.size() - 1 >= index) { return getDefault(states, index); } else { return null; } } else { return state; } } }
第六步:
修改
tx-client下的com.codingapi.tx.aop.service.impl下的TransactionServerFactoryServiceImpl.java
修改這一截代碼:

//分布式事務已經開啟,業務進行中 **/ if (info.getTxTransactionLocal() != null || StringUtils.isNotEmpty(info.getTxGroupId())) { //檢查socket通訊是否正常 (第一次執行時啟動txRunningTransactionServer的業務處理控制,然后嵌套調用其他事務的業務方法時都並到txInServiceTransactionServer業務處理下) if (SocketManager.getInstance().isNetState()) { if (info.getTxTransactionLocal() != null) { return txDefaultTransactionServer; } else { // if(transactionControl.isNoTransactionOperation() // 表示整個應用沒有獲取過DB連接 // || info.getTransaction().readOnly()) { //無事務業務的操作 // return txRunningNoTransactionServer; // }else { // return txRunningTransactionServer; // } if(!transactionControl.isNoTransactionOperation()) { //TODO 有事務業務的操作 return txRunningTransactionServer; }else { return txRunningNoTransactionServer; } } } else { logger.warn("tx-manager not connected."); return txDefaultTransactionServer; } } //分布式事務處理邏輯*結束***********/
第七步:
現在都更改完成了,然后需要將所有的項目打包,注意我將本組項目中所有pom文件中所有的的4.2.0-SNAPSHOT 都更改成了4.2.0,注意 是所有
改成了
然后點擊右側maven插件對每一個ms挨個進行打包【打包可能報錯,解決方案:https://www.cnblogs.com/sxdcgaq8080/p/9841635.html https://www.cnblogs.com/sxdcgaq8080/p/9841701.html】
按照報錯后的兩個解決方案,進行打包完成后,可以看到
這些ms都已經打包完成了
第八步:
最后需要將所有修改完成的打包好的jar上傳到自己本地的maven倉庫中 【操作地址:https://www.cnblogs.com/sxdcgaq8080/p/7583767.html 最下方可以進行第三方jar包上傳到自己的maven倉庫中】
【jar可以在百度網盤下載,然后直接走這一步的上傳第三方jar包到本地maven倉庫,然后在項目中直接引用即可】【最新jar的使用參見https://www.cnblogs.com/sxdcgaq8080/p/7583767.html最下方】
【這里把上傳第三方jar到本地倉庫的命令給出來,也就是這兩個jar】
mvn deploy:deploy-file -DgroupId=com.codingapi -DartifactId=transaction-springcloud -Dversion=4.2.0 -Dpackaging=jar -Dfile=D:\document\IdeaProjects\myTestDocument\jar\transaction-springcloud-4.2.0.jar -Durl=http://localhost:8081/repository/myself_hosted/ -DrepositoryId=myself_hosted mvn deploy:deploy-file -DgroupId=com.codingapi -DartifactId=tx-plugins-db -Dversion=4.2.0 -Dpackaging=jar -Dfile=D:\document\IdeaProjects\myTestDocument\jar\tx-plugins-db-4.2.0.jar -Durl=http://localhost:8081/repository/myself_hosted/ -DrepositoryId=myself_hosted
上傳完了以后的位置如下:
注意 這里的版本都修改成了4.2.0
所以在引用的時候,在服務中引用的版本是4.2.0
這次啟動引用了這兩個jar的spring boot2.0的微服務,就可以成功了
將tx-Manager服務加入項目組,並啟用【此時是eureka服務已經啟動的情況下了,也就是說,微服務組引入修改以后的LCN jar依賴,已經可以成功啟動的情況下】
1.同上面的第一步一樣,進入官網,進入GitHub,拷貝所有源碼
先在lcn官網【http://www.txlcn.org/】 找到GitHub 地址【https://github.com/codingapi/tx-lcn】,拷下所有的源碼
2.解壓縮,取出tx-manager服務,拷貝至項目組根目錄下
3.將tx-Manager修改為本微服務組可以識別的子模塊module
導入更新
4.更改tx-manager的application.properties,修改eureka的配置和redis的相關配置

#######################################txmanager-start################################################# #服務端口 server.port=7000 #tx-manager不得修改 spring.application.name=tx-manager spring.mvc.static-path-pattern=/** spring.resources.static-locations=classpath:/static/ #######################################txmanager-end################################################# #zookeeper地址 #spring.cloud.zookeeper.connect-string=127.0.0.1:2181 #spring.cloud.zookeeper.discovery.preferIpAddress = true #eureka 地址 eureka.client.service-url.defaultZone=http://127.0.0.1:8000/eureka/ eureka.instance.prefer-ip-address=true #######################################redis-start################################################# #redis 配置文件,根據情況選擇集群或者單機模式 ##redis 集群環境配置 ##redis cluster #spring.redis.cluster.nodes=127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003 #spring.redis.cluster.commandTimeout=5000 ##redis 單點環境配置 #redis #redis主機地址 spring.redis.host=127.0.0.1 #redis主機端口 spring.redis.port=6379 #redis鏈接密碼 spring.redis.password= spring.redis.pool.maxActive=10 spring.redis.pool.maxWait=-1 spring.redis.pool.maxIdle=5 spring.redis.pool.minIdle=0 spring.redis.timeout=0 #####################################redis-end################################################### #######################################LCN-start################################################# #業務模塊與TxManager之間通訊的最大等待時間(單位:秒) #通訊時間是指:發起方與響應方之間完成一次的通訊時間。 #該字段代表的是Tx-Client模塊與TxManager模塊之間的最大通訊時間,超過該時間未響應本次請求失敗。 tm.transaction.netty.delaytime = 5 #業務模塊與TxManager之間通訊的心跳時間(單位:秒) tm.transaction.netty.hearttime = 15 #存儲到redis下的數據最大保存時間(單位:秒) #該字段僅代表的事務模塊數據的最大保存時間,補償數據會永久保存。 tm.redis.savemaxtime=30 #socket server Socket對外服務端口 #TxManager的LCN協議的端口 tm.socket.port=9999 #最大socket連接數 #TxManager最大允許的建立連接數量 tm.socket.maxconnection=100 #事務自動補償 (true:開啟,false:關閉) # 說明: # 開啟自動補償以后,必須要配置 tm.compensate.notifyUrl 地址,僅當tm.compensate.notifyUrl 在請求補償確認時返回success或者SUCCESS時,才會執行自動補償,否則不會自動補償。 # 關閉自動補償,當出現數據時也會 tm.compensate.notifyUrl 地址。 # 當tm.compensate.notifyUrl 無效時,不影響TxManager運行,僅會影響自動補償。 tm.compensate.auto=false #事務補償記錄回調地址(rest api 地址,post json格式) #請求補償是在開啟自動補償時才會請求的地址。請求分為兩種:1.補償決策,2.補償結果通知,可通過通過action參數區分compensate為補償請求、notify為補償通知。 #*注意當請求補償決策時,需要補償服務返回"SUCCESS"字符串以后才可以執行自動補償。 #請求補償結果通知則只需要接受通知即可。 #請求補償的樣例數據格式: #{"groupId":"TtQxTwJP","action":"compensate","json":"{\"address\":\"133.133.5.100:8081\",\"className\":\"com.example.demo.service.impl.DemoServiceImpl\",\"currentTime\":1511356150413,\"data\":\"C5IBLWNvbS5leGFtcGxlLmRlbW8uc2VydmljZS5pbXBsLkRlbW9TZXJ2aWNlSW1wbAwSBHNhdmUbehBqYXZhLmxhbmcuT2JqZWN0GAAQARwjeg9qYXZhLmxhbmcuQ2xhc3MYABABJCo/cHVibGljIGludCBjb20uZXhhbXBsZS5kZW1vLnNlcnZpY2UuaW1wbC5EZW1vU2VydmljZUltcGwuc2F2ZSgp\",\"groupId\":\"TtQxTwJP\",\"methodStr\":\"public int com.example.demo.service.impl.DemoServiceImpl.save()\",\"model\":\"demo1\",\"state\":0,\"time\":36,\"txGroup\":{\"groupId\":\"TtQxTwJP\",\"hasOver\":1,\"isCompensate\":0,\"list\":[{\"address\":\"133.133.5.100:8899\",\"isCompensate\":0,\"isGroup\":0,\"kid\":\"wnlEJoSl\",\"methodStr\":\"public int com.example.demo.service.impl.DemoServiceImpl.save()\",\"model\":\"demo2\",\"modelIpAddress\":\"133.133.5.100:8082\",\"channelAddress\":\"/133.133.5.100:64153\",\"notify\":1,\"uniqueKey\":\"bc13881a5d2ab2ace89ae5d34d608447\"}],\"nowTime\":0,\"startTime\":1511356150379,\"state\":1},\"uniqueKey\":\"be6eea31e382f1f0878d07cef319e4d7\"}"} #請求補償的返回數據樣例數據格式: #SUCCESS #請求補償結果通知的樣例數據格式: #{"resState":true,"groupId":"TtQxTwJP","action":"notify"} tm.compensate.notifyUrl=http://ip:port/path #補償失敗,再次嘗試間隔(秒),最大嘗試次數3次,當超過3次即為補償失敗,失敗的數據依舊還會存在TxManager下。 tm.compensate.tryTime=30 #各事務模塊自動補償的時間上限(毫秒) #指的是模塊執行自動超時的最大時間,該最大時間若過段會導致事務機制異常,該時間必須要模塊之間通訊的最大超過時間。 #例如,若模塊A與模塊B,請求超時的最大時間是5秒,則建議改時間至少大於5秒。 tm.compensate.maxWaitTime=5000 #######################################LCN-end################################################# logging.level.com.codingapi=debug
5.啟動啟動類,即可訪問tx-manager主頁面
========