背景
最近接的一個項目是基於公司產品Starring做的微服務支付平台,純后台項目,實現三方支付公司和銀行接口來完成用戶賬戶扣款,整合成通用支付接口發布給前端調用。
但是扯蛋了,這邊前端什么都不想做,只想我們提供一個鏈接,用戶可以選擇支付方式進行支付,這樣的話相當於咱們又得起一個WEB版的收銀台Project。
最近SpringBoot挺流行的,那就單獨給起一個H5項目跑幾個頁面,調用后台的支付接口就完事了,如下?
最終的系統架構成了這樣吧,隨便畫一畫,請客官別吐槽。
公司的產品的服務都是發布到Zookeeper注冊中心的,結果我們SpringBoot收銀台成了直連某個IP端口,要是交易量一起來把直連的12001壓垮了怎么辦?
這樣顯然會存在問題,就因為一個收銀台項目把整個微服務支付平台變成了單節點,所以我們收銀台SpringBoot項目也必須連到上面的ZK中去查找平台服務。
環境
SpringBoot 2.2.1.Release
解決思路
從單web項目轉成基於zookeeper調用的微服務項目:
1、Registry:服務注冊,公司產品Starring 采取Zookeeper 作為我們的注冊中心,我們現在要做的就是訂閱服務。
2、Provider:服務提供者(生產者),提供具體的服務實現,這個是支付后台提供的服務。
3、Consumer:消費者,從注冊中心中訂閱服務,這個就是我們這邊收銀台要實現的功能啦。
4、Monitor:監控中心,RPC調用次數和調用時間監控,這塊公司存在
從上圖中我們可以看出RPC 服務調用的過程主要為:
1、生產者發布服務到服務注冊中心
2、消費者在服務注冊中心中訂閱服務
3、消費者調用已注冊的服務
操作步驟
A、配置文件
B、創建自己的Zookeeper連接
C、查找自己需要的服務
D、服務調用
A、配置文件
1、Maven 配置文件 pom.xml,引入zookeeper和zkclient兩個包。
<dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.5.6</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.11</version> </dependency>排除slf4j是因為和其他jar包沖突,啟動時檢查報錯。
2、SpringBoot配置文件 application.yml 增加zookeeper配置
zookeeper: address: serverhost:2181,serverhost:2182,serverhost:2183 timeout: 20000
B、創建Zookeeper連接
SpringBoot項目啟動后,自動連接Zookeeper配置中心,並獲取到zookeeper實例,只需要連接一次,所以使用的單例。
關注SpringBoot平台啟動后執行事件【@PostConstruct 】
這里需要注意,嘗試過多種平台后執行事件來執行connect方法,只有這種方式在平台加載完所有Bean后執行。其他的方式下,無法獲取Zookeeper中的配置。放在主函數后面執行,也不行。
package com.adtec.pay.util; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.io.IOException; import java.util.concurrent.CountDownLatch; @Component public class ZKWatcher implements Watcher { @Value("${zookeeper.address}") public String ZK_ADDRESS; @Value("${zookeeper.timeout}") public int ZK_TIMEOUT; private static ZKWatcher instance = null; private CountDownLatch latch = new CountDownLatch(1); private ZooKeeper zooKeeper; public ZKWatcher() { } public static ZKWatcher getInstance() { if (instance == null) { instance = new ZKWatcher(); } return instance; } // 平台啟動后加載 @PostConstruct public void connect() throws IOException { zooKeeper = new ZooKeeper(ZK_ADDRESS, ZK_TIMEOUT, this); try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } setZooKeeper(zooKeeper); System.out.println("Zookeeper已連接成功:" + ZK_ADDRESS); } @Override public void process(WatchedEvent event) { if (event.getState() == Event.KeeperState.SyncConnected) { latch.countDown(); } } public ZooKeeper getZooKeeper() { return zooKeeper; } public void setZooKeeper(ZooKeeper zooKeeper) { this.zooKeeper = zooKeeper; } }
C、查找自己的服務
好了,啟動項目,到這里zookeeper已經連接上了。
現在咱們要發請求到后台,該怎么在注冊中心找到自己需要的服務呢 ?
上面也已經提到整個微服務運行模式,由生產者(Starring支付平台)發布服務到 注冊中心(Zookeeper),我們收銀台項目是消費者要去訂閱服務的。也就是我們得去注冊中心搜服務。
所以我們首先得知道生產者發布的服務到注冊中心是一個什么路徑,就是生產者發布到 Zookeeper的目錄節點。
稍微要懂一點Zookeeper知識,你才知道怎么查節點。不懂的話,百度一下,或者看一下別人的: https://blog.csdn.net/java_66666/article/details/81015302,如果還看不會,那勸你 洗洗睡吧。
這里可以推薦一個圖形界面查Zookeeper的工具,如下圖:
通過工具查看到我們的服務目錄節點路徑:/Inst/cty/800002/V1.0/IcpPayReq/V1.0
我們要調用的服務是:【IcpPayReq】,也就是我們定義的服務碼。
既然知道路徑,知道服務碼,事情就和把大象塞進冰箱需要幾步一樣。
1、獲取Zookeeper連接實例。
2、根據目錄節點獲取服務實例。
3、隨機選擇其中一個實例,獲取URL。
獲取請求的類如下:
package com.adtec.pay.util; import org.apache.zookeeper.ZooKeeper; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import java.util.LinkedList; import java.util.List; import java.util.Random; @Component public class ZKListener { // private static String SERVER_PATH = "/Inst/cty/800002/V1.0/IcpPayReq/V1.0"; private String SERVER_PATH = ""; private ZooKeeper zooKeeper; private List<String> paths = new LinkedList<>(); public void findTranUrl(String tranCode) { if (!StringUtils.isEmpty(tranCode)) { SERVER_PATH = "/Inst/cty/800002/V1.0/" + tranCode + "/V1.0"; } getChilds(); } private void getChilds() { List<String> ips = new LinkedList<>(); zooKeeper = ZKWatcher.getInstance().getZooKeeper(); try { //獲取子節點 List<String> childs = zooKeeper.getChildren(SERVER_PATH, false); for (String child : childs) { byte[] data = zooKeeper.getData(SERVER_PATH + "/" + child, false, null); String path = new String(data, "UTF-8"); ips.add(path); } this.paths = ips; } catch (Exception e) { e.printStackTrace(); } } public String getPath() { if (paths.isEmpty()) { return null; } //這里我們隨機獲取一個ip端口使用 int index = new Random().nextInt(paths.size()); return paths.get(index); } }這樣就能找到真實請求地址了,愉快的發送請求吧。
D、服務調用
這里我寫了一個通用類,因為調用的服務不會只有一個,服務目錄路徑相同服務碼不同就可以通用了。
package com.adtec.pay.entity; import com.adtec.pay.util.CommUtil; import com.adtec.pay.util.ZKListener; import com.alibaba.fastjson.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; public class Request { @Autowired private ZKListener zkListener; @Value("${spring.profiles.active}") private String env; protected String url; protected Class<? extends Response> responseClass; public Request(String tranCode, Class<? extends Response> responseClass) { if (env.equals("dev")){ if (tranCode.equals("HosOrderQuery")) { this.url = "http://serverhost:13008/HttpServer/MEDICAL_MNG_SVR/QryOrderDetail"; } else if (tranCode.equals("IcpPayReq")) { this.url = "http://serverhost:13008/HttpServer/MEDICAL_MNG_SVR/IcpPayReq"; } else if (tranCode.equals("QryOrderDetail")) { this.url = "http://serverhost:13008/HttpServer/MEDICAL_MNG_SVR/QryOrderDetail"; } } else { zkListener.findTranUrl(tranCode); String path = zkListener.getPath(); ZKStatusEntity zkStatus = JSONObject.parseObject(path, ZKStatusEntity.class); this.url = zkStatus.getCOM_HTTP().getURL() + "/" + tranCode; } this.responseClass = responseClass; } public void setUrl(String url) { this.url = url; } public void setResponseClass(Class<? extends Response> responseClass) { this.responseClass = responseClass; } public <T> Response send(Request request) { return CommUtil.httpRequestJSON(url, request, responseClass); } }
總結
這次通過做這個項目,摸索了很多SpringBoot的細節,遇到了很多看着很小又很影響進度的問題。
1、項目啟動后加載所有Bean文件后啟動,嘗試了很多種方式。
2、通用的請求類整合,泛型確實用的不太熟悉,需要再多理解。
3、新的HttpClient包包名 org.apache.httpcomponents 的請求方法調試,網上很多都是老的方法。
搞定,收工。
剛發出來,那什么源碼寺就copy過去了,也不標識,所以附上原文地址:https://www.cnblogs.com/laramia/p/11978271.html