SpringBoot 整合 Zookeeper 接入Starring微服務平台


背景

最近接的一個項目是基於公司產品Starring做的微服務支付平台,純后台項目,實現三方支付公司和銀行接口來完成用戶賬戶扣款,整合成通用支付接口發布給前端調用。

但是扯蛋了,這邊前端什么都不想做,只想我們提供一個鏈接,用戶可以選擇支付方式進行支付,這樣的話相當於咱們又得起一個WEB版的收銀台Project。

最近SpringBoot挺流行的,那就單獨給起一個H5項目跑幾個頁面,調用后台的支付接口就完事了,如下?

image

最終的系統架構成了這樣吧,隨便畫一畫,請客官別吐槽。

公司的產品的服務都是發布到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已經連接上了。

image

現在咱們要發請求到后台,該怎么在注冊中心找到自己需要的服務呢 ?

上面也已經提到整個微服務運行模式,由生產者(Starring支付平台)發布服務到 注冊中心(Zookeeper),我們收銀台項目是消費者要去訂閱服務的。也就是我們得去注冊中心搜服務。

所以我們首先得知道生產者發布的服務到注冊中心是一個什么路徑,就是生產者發布到 Zookeeper的目錄節點。

稍微要懂一點Zookeeper知識,你才知道怎么查節點。不懂的話,百度一下,或者看一下別人的: https://blog.csdn.net/java_66666/article/details/81015302,如果還看不會,那勸你 洗洗睡吧。

這里可以推薦一個圖形界面查Zookeeper的工具,如下圖:

image

通過工具查看到我們的服務目錄節點路徑:/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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM