zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    带箭头提示框
    文本溢出显示省略号
    Git高级操作
    sublime text 2 破解
    python如何画三维图像?
    pytorch梯度下降法讲解(非常详细)
    pytorch数学运算与统计属性入门(非常易懂)
    pytorch张量数据索引切片与维度变换操作大全(非常全)
    pytorch中tensor张量数据基础入门
    pytorch深度学习神经网络实现手写字体识别
  • 原文地址:https://www.cnblogs.com/laramia/p/11978271.html
Copyright © 2011-2022 走看看