• Dubbo 接口测试原理及多种方法


    在这里插入图片描述

    1、什么是 Dubbo

    Dubbo 最开始是应用于淘宝网,由阿里巴巴开源的一款优秀的高性能服务框架,由 Java 开发,后来贡献给了 Apache 开源基金会组织。

    下面以官网的一个说明来了解一下架构的演变过程,从而了解 Dubbo 的诞生原因:
    在这里插入图片描述

    • 单一应用架构

    当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

    • 垂直应用架构

    当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,提升效率的方法之一是将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的 Web 框架(MVC)是关键。

    • 分布式服务架构

    当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。

    • 流动计算架构

    当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。

    2、Dubbo 架构简介

    在这里插入图片描述

    在这里插入图片描述

    Dubbo 比较有特点的地方就是这个注册中心,平常我们测试较多的 HTTP 接口,直接请求接口,调用后端服务即可;而 Dubbo 是要先走注册中心获取服务的位置,下面来举个现实生活中的例子来说明。

    现实举例

    好比大家平常约朋友一起出去吃饭,听说川菜馆“赠李白”不错,然后需要找这家饭店在哪(用小蓝或小黄App),知道了具体的地址才出发,至于是走路,打车还是骑车,就随意了。

    这里 App 就相当于注册中心(Registry),我们这群吃货就是消费者(Consumer),商家属于生产者(Provider)。商家把自己的信息注册在 App 上,消费者根据 App 查询到商家的信息,再根据信息找到商家进行消费。

    2.1、Zookeeper 简介

    之前经常有小伙伴问我 zk 干啥的?怎么用?下面就来简单了解一哈:

    ZK,全称就是zookeeper,是 Apache 软件基金会的一个软件项目,它为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。

    下面的图示也可以清晰的说明zk的部署和工作的一些方式(具体的技术细节需要的话可以针对zk专门搜索学习):
    在这里插入图片描述

    • Leader:集群工作的核心,事务请求的唯一调度和处理者,保证事务处理的顺序性。对于有写操作的请求,需统一转发给Leader处理。Leader需决定编号执行操作。

    • Follower:处理客户端非事务请求,转发事务请求转发给Leader,参与Leader选举。

    • Observer观察者:进行非事务请求的独立处理,对于事务请求,则转发给Leader服务器进行处理.不参与投票。

    3、什么是 Dubbo 接口?

    所谓的 Dubbo 接口,其实就是一个个 Dubbo 服务中的方法,而测试 Dubbo 接口就相当于我们测试人员充当消费者或者创造消费者去"消费"这个方法。

    具体的方式有很多,代码、工具、命令皆可,在接下来的内容中来一一演示。

    4、Dubbo 接口测试(创造消费者)

    以下我将以本地的一个简单的 Dubbo 服务 demo 为例,演示 Dubbo 测试的各种方法。

    interface只有两个,分别是OrderServiceUserService
    在这里插入图片描述

    OrderService :slight_smile:

     package com.qinzhen.testmall.service;
        
        import com.qinzhen.testmall.bean.UserAddress;
        import java.util.List;
        
        public interface OrderService {
        
            /**
             * 初始化订单
             * @param userID
             */
            public List initOrder(String userID);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    UserService :slight_smile:

      package com.qinzhen.testmall.service;
        
        import com.qinzhen.testmall.bean.UserAddress;
        import java.util.List;
        
        /**
         * 用户服务
         */
        public interface UserService {
        
        
            /**
             * 按照userId返回所有的收获地址
             * @param userId
             * @return
             */
            public List getUserAddressList(String userId);
        
        
            /**
             * 返回所有的收获地址
             * @param 
             * @return
             */
            public List getUserAddressList();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    JavaBean 对象 UserAddress 如下:

    package com.qinzhen.testmall.bean;
        
        import lombok.AllArgsConstructor;
        import lombok.Data;
        import java.io.Serializable;
        
        @AllArgsConstructor
        @Data
        public class UserAddress implements Serializable {
        
            private Integer id;
            private String userAddress; //用户地址
            private String userId; //用户ID
            private String consignee; //收货人
            private String phoneNum; //电话号码
            private String isDefault; //是否为默认地址 Y-是  N-否
        
            public UserAddress(){
        
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    创建一个provider 来实现UserServiceInterface
    在这里插入图片描述

    实现方法中,根据 id 返回对应的用户地址信息即可:
    ···
    package com.qinzhen.testmall.bootuserserviceprovider.service.impl;

    import com.alibaba.dubbo.config.annotation.Service;
    import com.qinzhen.testmall.bean.UserAddress;
    import com.qinzhen.testmall.service.UserService;
    import org.springframework.stereotype.Component;
    
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.List;
    
    @Component
    @Service  //暴露服务
    public class UserServiceImpl implements UserService {
    
        private UserAddress userAddress1 = new UserAddress(1, "杭州市西湖区XX公司", "1", "qz", "12345678", "Y");
        private UserAddress userAddress2 = new UserAddress(2, "杭州市西湖区花园", "2", "qz", "12345678", "N");
    
        @Override
        public List getUserAddressList(String userId) {
            if (userId.equals("1")){
                return Collections.singletonList(userAddress1);
            }
            else if (userId.equals("2")){
                return Collections.singletonList(userAddress2);
            }
            else {
                return Arrays.asList(userAddress1, userAddress2);
            }
        }
    
        @Override
        public List getUserAddressList(){
            return Arrays.asList(userAddress1, userAddress2);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    ···

    4.1 Java consumer 代码

    下面我们编写consumer代码,让服务消费者去注册中心订阅服务提供者的服务地址,以RPC方式,获取远程服务代理,从而执行远程方法,代码也很简单,如下:

    • 代码结构:
      在这里插入图片描述

    实现场景就是实现OrderService 中的initOrder() 方法,初始化订单,初始化中直接调用userServicegetUserAddressLis(java.lang.String) 方法,具体代码如下:

     package com.qinzhen.testmall.service.impl;
        
        import com.qinzhen.testmall.bean.UserAddress;
        import com.qinzhen.testmall.service.OrderService;
        import com.qinzhen.testmall.service.UserService;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Service;
        import java.util.List;
        
        /**
         * 1、讲服务提供者注册到注册中心(暴露服务)
         *          1)导入dubbo依赖:操作zookeeper的客户端(curator)
         * 2、让服务消费者去注册中心订阅服务提供者的服务地址
         */
        @Service
        public class OrderServiceImpl implements OrderService {
        
            @Autowired
            UserService userService;
        
            public List initOrder(String userId) {
                //1.查询用户的收货地址
                System.out.println("用户ID为:" + userId);
                List userAddressList = userService.getUserAddressList(userId);
                return userAddressList;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • consumer MainApplication
     package com.qinzhen.testmall;
        
        import com.qinzhen.testmall.bean.UserAddress;
        import com.qinzhen.testmall.service.OrderService;
        import com.qinzhen.testmall.service.UserService;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.context.support.ClassPathXmlApplicationContext;
        import org.springframework.stereotype.Service;
        
        import java.io.IOException;
        import java.util.List;
        
        
        /**
         * 1、将服务提供者注册到注册中心(暴露服务)
         *          1)导入dubbo依赖:操作zookeeper的客户端(curator)
         * 2、让服务消费者去注册中心订阅服务提供者的服务地址
         */
        @Service
        public class MainApplication {
            public static void main(String[] args) throws IOException {
                ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"consumer.xml"});
                context.start();
                OrderService orderService = context.getBean(OrderService.class); // 获取远程服务代理
                List userAddresses = orderService.initOrder("3");// 执行远程方法
                System.out.println(userAddresses);
                System.out.println("调用完成。。。");
                System.in.read();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • consumer.xml:
     
        
        
            
        
            
        
            
        
            
            
        
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 实例演示

    首先确保provider已启动:
    在这里插入图片描述

    运行consumer ,可以看到成功调用到dubbo方法,获取地址列表信息:
    在这里插入图片描述

    4.2 telnet+invoke

    我们使用 telnet 命令可以直接访问对应的服务,但是前提是你需要知道服务对应的ip+端口。

    如下配置文件,我们可以知道服务暴露在本地的20880端口

    dubbo.application.name=boot-user-service-provider
        dubbo.registry.address=127.0.0.1:2181
        dubbo.registry.protocol=zookeeper
        dubbo.protocol.name=dubbo
        dubbo.protocol.port=20880
    
    • 1
    • 2
    • 3
    • 4
    • 5

    使用 telnet 命令进行访问,如下出现 Dubbo 字样时说明连接成功:

    % telnet localhost 20880
        Trying ::1...
        Connected to localhost.
        Escape character is '^]'.
        
        dubbo>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Dubbo 内建的 telnet 命令的说明和用法如下

    • ls

      • ls: 显示服务列表

      • ls -l: 显示服务详细信息列表

      • ls XxxService: 显示服务的方法列表

      • ls -l XxxService: 显示服务的方法详细信息列表

     dubbo>ls
        com.qinzhen.testmall.service.UserService
        
        dubbo>ls -l
        com.qinzhen.testmall.service.UserService -> dubbo://192.168.2.xxx:20880/com.qinzhen.testmall.service.UserService?anyhost=true&application=boot-user-service-provider&bind.ip=192.168.2.xxx&bind.port=20880&dubbo=2.6.2&generic=false&interface=com.qinzhen.testmall.service.UserService&methods=getUserAddressList&pid=55472&qos.enable=false&side=provider×tamp=1615088321885
        
        dubbo>dubbo>ls com.qinzhen.testmall.service.UserService
        getUserAddressList
        getUserAddressList
        
        dubbo>dubbo>ls -l com.qinzhen.testmall.service.UserService
        java.util.List getUserAddressList(java.lang.String)
        java.util.List getUserAddressList()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • invoke

      • invoke XxxService.xxxMethod(1234, "abcd", {"prop" : "value"}): 调用服务的方法

      • invoke com.xxx.XxxService.XxxService.xxxMethod(1234, "abcd", {"prop" : "value"}): 调用全路径服务的方法

      • invoke xxxMethod(1234, "abcd", {"prop" : "value"}): 调用服务的方法(自动查找包含此方法的服务)

      • invoke xxxMethod({"name":"zhangsan","age":12,"class":"org.apache.dubbo.qos.legacy.service.Person"}) :当有参数重载,或者类型转换失败的时候,可以通过增加class属性指定需要转换类

      • 当参数为Mapkey的类型为Integer时,建议指定类型。例如invoke com.xxx.xxxApiService({"3":0.123, "class":"java.util.HashMap"})

    然后我们使用invoke 命令对dubbo方法getUserAddressList()进行调用,如下:

     dubbo>invoke getUserAddressList()
        [{"consignee":"qz","id":1,"isDefault":"Y","phoneNum":"12345678","userAddress":"杭州市西湖区xx公司","userId":"1"},{"consignee":"qz","id":2,"isDefault":"N","phoneNum":"12345678","userAddress":"杭州市西湖区xx花园","userId":"2"}]
        
        dubbo>invoke getUserAddressList("1")
        [{"consignee":"qz","id":1,"isDefault":"Y","phoneNum":"12345678","userAddress":"杭州市西湖区xx公司","userId":"1"}]
        elapsed: 14 ms.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    学习链接

    其他 Telnet 命令相关操作,需要可参考 Dubbo 官网:

    • https://dubbo.apache.org/zh/docs/v2.7/user/references/telnet
    4.3 JMeter

    对于 JMeter 测试 Dubbo 接口的方法,可参考往期文章:

    《基于 Jmeter 完成 Dubbo 接口的测试》

    4.4 Dubbo-admin

    对于 Dubbo-admin 的安装调试,可参考文章:

    《dubbo-admin+zookeeper 的环境搭建实操与 Could not extract archive 报错踩坑》

    • https://blog.csdn.net/weixin_43291944/article/details/103998883
    4.5 泛化调用

    测试 Dubbo 服务的时候,我们需要服务端的同学给我们提供 API,没有这个 API 我们是测不了的,而为了解决这个问题,Dubbo 官方又给我们提供了另外一个方法,就是泛化调用,来看看官方的解释:
    在这里插入图片描述

    • 泛化调用的使用

    Dubbo 给我们提供了一个接口GenericService,这个接口只有一个方法,就是$invoke,它接受三个参数,分别为方法名方法参数类型数组参数值数组

    下面我们直接上代码演示:

    import com.alibaba.dubbo.config.ApplicationConfig;
        import com.alibaba.dubbo.config.ReferenceConfig;
        import com.alibaba.dubbo.config.RegistryConfig;
        import com.alibaba.dubbo.rpc.service.GenericService;
        import org.junit.jupiter.api.Test;
        
        public class TestDemo {
        
            @Test
            void testDubboGenericService(){
                // 引用远程服务
                // 该实例很重量,里面封装了所有与注册中心及服务提供方连接,请缓存
                ReferenceConfig reference = new ReferenceConfig();
                // 弱类型接口名
                reference.setApplication(new ApplicationConfig("order-service-consumer"));
                reference.setInterface("com.qinzhen.testmall.service.UserService");
                reference.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
        
                // 声明为泛化接口
                reference.setGeneric(true);
        
                // 用org.apache.dubbo.rpc.service.GenericService可以替代所有接口引用
                GenericService genericService = reference.get();
        
                Object result = genericService.$invoke("getUserAddressList", new String[] {"java.lang.String"}, new Object[] {"2"});
                System.out.println(result);
        
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    运行后我们来看看结果,咦~也成功访问了:

    在这里插入图片描述

    • 泛化调用的原理

    我们通过 debug 跟入 Dubbo 的源码中,可以得到如下的调用链:

    服务消费端:
    在这里插入图片描述

    服务提供端:
    在这里插入图片描述

    从上面的调用链可以知道完成一次泛化调用,Dubbo 框架经历了很多过滤器,我们分别选取两端链路中的最后一步的 Filter 来简单了解一下泛化调用做了哪些事.

    简化后的调用关系就如下:
    在这里插入图片描述

    先来看consumer 端的GenericImplFilter ,大概看下核心的处理步骤:

    // 判断是否为泛化调用
        if (invocation.getMethodName().equals(Constants.$INVOKE)
                        && invocation.getArguments() != null
                        && invocation.getArguments().length == 3
                        && ProtocolUtils.isGeneric(generic)) {
           // 获取泛化调用参数
                    Object[] args = (Object[]) invocation.getArguments()[2];
                    // 判断是否为nativejava方式
                    if (ProtocolUtils.isJavaGenericSerialization(generic)) {
        
                        for (Object arg : args) {
                            if (!(byte[].class == arg.getClass())) {
                                error(byte[].class.getName(), arg.getClass().getName());
                            }
                        }
                    // 判断是否为bean方式
                    } else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
                        for (Object arg : args) {
                            if (!(arg instanceof JavaBeanDescriptor)) {
                                error(JavaBeanDescriptor.class.getName(), arg.getClass().getName());
                            }
                        }
                    }
           // 设置为泛化调用方式
                    ((RpcInvocation) invocation).setAttachment(
                            Constants.GENERIC_KEY, invoker.getUrl().getParameter(Constants.GENERIC_KEY));
                }
                // 发起远程调用
                return invoker.invoke(invocation);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    再来看provider端的GenericFilter,大概的核心处理步骤如下:

    package com.alibaba.dubbo.rpc.filter;
        
        import ...
        
        /**
         * GenericInvokerFilter.
         */
        @Activate(group = Constants.PROVIDER, order = -20000)
        public class GenericFilter implements Filter {
        
            @Override
            public Result invoke(Invoker invoker, Invocation inv) throws RpcException {
             // 判断是否为泛化请求
                if (inv.getMethodName().equals(Constants.$INVOKE)
                        && inv.getArguments() != null
                        && inv.getArguments().length == 3
                        && !ProtocolUtils.isGeneric(invoker.getUrl().getParameter(Constants.GENERIC_KEY))) {
                    // 获取参数名称、参数类型、参数值
                    String name = ((String) inv.getArguments()[0]).trim();
                    String[] types = (String[]) inv.getArguments()[1];
                    Object[] args = (Object[]) inv.getArguments()[2];
                    try {
                     // 使用反射获取调用方法
                        Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);
                        Class[] params = method.getParameterTypes();
                        if (args == null) {
                            args = new Object[params.length];
                        }
                        // 获取泛化引用方式使用的泛化类型
                        String generic = inv.getAttachment(Constants.GENERIC_KEY);
                        // 泛化类型为空的话就使用generic=true的方式
                        if (StringUtils.isEmpty(generic)
                                || ProtocolUtils.isDefaultGenericSerialization(generic)) {
                            args = PojoUtils.realize(args, params, method.getGenericParameterTypes());
                        // 判断是否为generic=nativejava方式
                        } else if (ProtocolUtils.isJavaGenericSerialization(generic)) {
                            for (int i = 0; i < args.length; i++) {
                                if (byte[].class == args[i].getClass()) {
                                    try {
                                        UnsafeByteArrayInputStream is = new UnsafeByteArrayInputStream((byte[]) args[i]);
                                        args[i] = ExtensionLoader.getExtensionLoader(Serialization.class)
                                                .getExtension(Constants.GENERIC_SERIALIZATION_NATIVE_JAVA)
                                                .deserialize(null, is).readObject();
                                    } catch (Exception e) {
                                        。。。
                                    }
                                } else {
                                    。。。
                                }
                            }
                          // 判断是否为generic=bean方式
                        } else if (ProtocolUtils.isBeanGenericSerialization(generic)) {
                            for (int i = 0; i < args.length; i++) {
                                if (args[i] instanceof JavaBeanDescriptor) {
                                    args[i] = JavaBeanSerializeUtil.deserialize((JavaBeanDescriptor) args[i]);
                                } else {
                                    。。。
                                }
                            }
                        }
                        // 传递请求,执行服务
                        Result result = invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));
                        。。。
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    上面的代码很多,着重来提取一小段看一下:

    Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);
        Class[] params = method.getParameterTypes();
    
    • 1
    • 2

    从上面的代码中我们便可以得知原来泛化调用中使用了Java的反射技术来获取对应的方法信息完成调用的

    4.6 用 Python 来测试 Dubbo

    我们知道 Dubbo 是个 Java 项目,测试 Dubbo 就是模拟消费者去调用 Dubbo 的 Java 方法,那显而易见,用 Python 是肯定没法去直接调用Java的,但是在日常的工作中,很多小伙伴可能是 Pyhton技术栈的,或者因为一些测试条件限制亦或历史原因,必须要将 Dubbo 测试用 Python 实现以满足各种接口测试的一个组合。

    • 1. python-hessian库

    Dubbo是支持hessian+http协议调用的,hessian是一种二进制序列化的方式。

    了解到可以通过这种方式实现,具体没有尝试过,还需要开发在项目中将序列化的方式改为hessian,并且需要知道URL,有兴趣的小伙伴可以去了解一下。

    • 2. telnetlib库

    telnetlib是Python3自带的一个库,可以调用telnet命令,其实也就相当于上面说到的使用telnet方式访问dubbo的方法

    • 3. 开发dubbo测试服务

    我们可以使用 Java 来开发一个 Dubbo 测试的 Web 服务,实现上就可以使用 Dubbo 的泛化调用,然后我们再用 HTTP 访问的形式去访问这个服务,将我们的测试参数信息传过去,剩下的就交给 Java 去处理就好了。

    这样经过封装设计后,可以实现 Python 端的使用者在访问 Dubbo 时就像在测试HTTP接口一样(例如 Python 的request库);另外服务的 IP、端口、注册中心等信息都不用出现在测试的工程中,只需要用环境标签做区分,在服务端进行请求转发即可,也保证了一定安全性。

    大体上的思路流程如下:
    在这里插入图片描述

    以上,期待与大家多交流学习。

  • 相关阅读:
    linux centos7虚拟机安装mysql5.7详细教程(tar包安装)
    workflow之 工作流笔记
    SDUT 2022 Autumn Team Contest 7th
    你的小程序为什么还是不被用户看好?
    Anniversary party(树形dp 基础题)
    Centos7 Shell编程之运算符、条件判断、流程控制、read读取控制台输入
    Taurus.MVC 微服务框架 入门开发教程:项目部署:3、微服务应用程序版本升级:全站升级和局部模块升级。
    HVDC-MMC互连(1000MW,±320KV)使用聚合MMC模型进行优化的SPS模拟
    Pytorch入门(6)—— 梯度计算控制
    如何将用户导入自己的私域流量?“分享购“案例分享
  • 原文地址:https://blog.csdn.net/Tester_muller/article/details/126735808