文章目录

前言1、微服务引入1.1、相关概念1.2、软件架构的演进1.2.1、单体架构1.2.2、垂直架构1.2.3、分布式架构1.2.4、SOA架构1.2.5、微服务架构

1.3.SpringCloud

2、服务拆分和远程调用2.1、服务拆分原则2.2、服务拆分示例2.3、数据库搭建2.4、项目搭建2.4.1、父工程-cloud-demo2.4.2、子工程-order-server2.4.3、子工程-user-server

2.5、远程调用示例2.5.1、接口实现2.5.2、远程调用实现2.5.2.1、RestTemplate2.5.2.2、修改查询方法2.5.2.3、结果演示

3、Eureka注册中心3.1、引入3.2、搭建eureka-server3.3、服务注册3.4、服务发现3.5、结果演示

4、Ribbon负载均衡4.1、初步解析4.2、负载均衡策略4.3、自定义策略4.4、饥饿加载

前言

在搞定了Spring Security之后,现在狗子已经正式向微服务发起进攻了(啊,又是一段掉头发的征程)。 这里的教学资源来源于黑马程序员的《SpringCloud微服务技术栈课程》,而笔记则是加上了部分自己的理解进行了修改,让其更适合了自己的学习,毕竟知识要进脑子。

1、微服务引入

随着互联网行业的发展,对服务的要求也越来越高,服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢?

1.1、相关概念

**互联网项目的特点:**用户多;流量大,并发高;海量数据;易受攻击;功能繁琐;高性能(提供快速的访问体验)。

衡量网站的性能指标:

响应时间:指执行一个请求从开始到最后收到响应数据所花费的总体时间。 并发数:指系统同时能处理的请求数量。

并发连接数:指的是客户端向服务器发起请求,并建立了TCP连接。每秒钟服务器连接的总TCP数量请求数:也称为 QPS(Query Per Second) 指每秒多少请求. 并发用户数:单位时间内有多少用户吞吐量:指单位时间内系统能处理的请求数量。

QPS:Query Per Second 每秒查询数。TPS:Transactions Per Second 每秒事务数。一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。 客户机在发送请求时开始计时,收到服务器响应后结束计时,以此来计算使用的时间和完成的事务个数。一个页面的一次访问,只会形成一个TPS;但一次页面请求,可能产生多次对服务器的请求,就会有多个QPS 高性能:提供快速的访问体验。 高可用:网站服务一直可以正常访问。 可伸缩:通过硬件增加/减少,提高/降低处理能力。 高可扩展:系统间耦合低,方便的通过新增/移除方式,增加/减少新的功能/模块。 安全性:提供网站安全访问和数据加密,安全存储等策略。 敏捷性:随需应变,快速响应。 集群:一个业务模块,部署在多台服务器上。 (很多“人”一起 ,干一样的事) 分布式:一个大的业务系统,拆分为小的业务模块,分别部署在不同的机器上。 (很多“人”一起,干不一样的事。这些不一样的事,合起来是一件大事) 集群分布式的优点:高性能+高可用+可伸缩+高可扩展

1.2、软件架构的演进

软件架构的发展经历了由单体架构、垂直架构、SOA架构到微服务架构的演进过程。

1.2.1、单体架构

优点:简单:开发部署都很方便,小型项目首选。缺点:项目启动慢;可靠性差;可伸缩性差;扩展性和可维护性差;性能低。

1.2.2、垂直架构

垂直架构是指将单体架构中的多个模块拆分为多个独立的项目。形成多个独立的单体架构。单体架构存在的问题:项目启动慢;可靠性差;可伸缩性差;扩展性和可维护性差;性能低。垂直架构存在的问题:重复功能太多。

1.2.3、分布式架构

分布式架构是指在垂直架构的基础上,将公共业务模块抽取出来,作为独立的服务,供其他调用者消费,以实现服务的共享和重用。RPC: Remote Procedure Call 远程过程调用。有非常多的协议和技术来都实现了RPC的过程。 比如:HTTP REST风格,Java RMI规范、WebService SOAP协议、Hession等等。垂直架构存在的问题:重复功能太多分布式架构存在的问题:服务提供方一旦产生变更,所有消费方都需要变更。

1.2.4、SOA架构

**SOA(Service-Oriented Architecture/面向服务的架构)**是一个组件模型,它将应用程序的不同功能单元(称为服务)进行拆分,并通过这些服务之间定义良好的接口和契约联系起来。ESB(Enterparise Servce Bus)企业服务总线,服务中介:主要是提供了一个服务于服务之间的交互。ESB包含的功能如:负载均衡,流量控制,加密处理,服务的监控,异常处理,监控告急等等。分布式架构存在的问题:服务提供方一旦产生变更,所有消费方都需要变更。

1.2.5、微服务架构

微服务架构是在SOA做的升华,微服务架构强调的一个重点是“业务需要彻底的组件化和服务化”,原有的单个业务系统会拆分为多个可以独立开发、设计、运行的小应用。这些小应用之间通过服务完成交互和集成。 微服务架构 = 80%的SOA服务架构思想 + 100%的组件化架构思想 + 80%的领域建模思想。 特点

服务实现组件化:开发者可以自由选择开发技术。也不需要协调其他团队。服务之间交互一般使用REST API。去中心化:每个微服务有自己私有的数据库持久化业务数据自动化部署:把应用拆分成为一个一个独立的单个服务,方便自动化部署、测试、运维 缺点

粒度太细导致服务太多,维护成本高。 分布式系统开发的技术成本高,对团队的挑战大。

1.3.SpringCloud

SpringCloud是目前国内使用最广泛的微服务框架。官网地址:https://spring.io/projects/spring-cloud。

SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。

其中常见的组件包括:

另外,SpringCloud底层是依赖于SpringBoot的,并且有版本的兼容关系,如下:

2、服务拆分和远程调用

2.1、服务拆分原则

任何分布式架构都离不开服务的拆分,微服务也是一样,且在拆分时有以下几个原则:

不同微服务,不要重复开发相同业务微服务数据独立,不要访问其它微服务的数据库微服务可以将自己的业务暴露为接口,供其它微服务调用

2.2、服务拆分示例

以一个简单的工程为例,其中cloud-demo为父工程,负责管理依赖,其下面有如下子工程,模仿拆分后的服务:

order-server:订单微服务,负责订单相关业务user-server:用户微服务,负责用户相关业务user-server(1):用于后面的负载均衡,模仿多服务

在拆分过程中我们需要做到:

订单服务和用户服务都必须有各自的数据库,相互独立订单服务和用户服务都对外暴露Restful的接口订单服务如果需要查询用户信息,只能调用用户服务的Restful接口,不能查询用户数据库

2.3、数据库搭建

由于user-server(1)是复制user-server的一个服务,因此我们结合拆分的需求,只需要建立两个库就行,但是这是两个库,一个库对应一张表,而不是如同往常的一个库装着两个表。

cloud-order数据库

-- ----------------------------

-- Table structure for tb_order

-- ----------------------------

DROP TABLE IF EXISTS `tb_order`;

CREATE TABLE `tb_order` (

`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单id',

`user_id` bigint(20) NOT NULL COMMENT '用户id',

`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商品名称',

`price` bigint(20) NOT NULL COMMENT '商品价格',

`num` int(10) NULL DEFAULT 0 COMMENT '商品数量',

PRIMARY KEY (`id`) USING BTREE,

UNIQUE INDEX `username`(`name`) USING BTREE

) ENGINE = InnoDB AUTO_INCREMENT = 109 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------

-- Records of tb_order

-- ----------------------------

INSERT INTO `tb_order` VALUES (101, 1, 'Apple 苹果 iPhone 12 ', 699900, 1);

INSERT INTO `tb_order` VALUES (102, 2, '雅迪 yadea 新国标电动车', 209900, 1);

INSERT INTO `tb_order` VALUES (103, 3, '骆驼(CAMEL)休闲运动鞋女', 43900, 1);

INSERT INTO `tb_order` VALUES (104, 4, '小米10 双模5G 骁龙865', 359900, 1);

INSERT INTO `tb_order` VALUES (105, 5, 'OPPO Reno3 Pro 双模5G 视频双防抖', 299900, 1);

INSERT INTO `tb_order` VALUES (106, 6, '美的(Midea) 新能效 冷静星II ', 544900, 1);

INSERT INTO `tb_order` VALUES (107, 2, '西昊/SIHOO 人体工学电脑椅子', 79900, 1);

INSERT INTO `tb_order` VALUES (108, 3, '梵班(FAMDBANN)休闲男鞋', 31900, 1);

cloud-user数据库

-- ----------------------------

-- Table structure for tb_user

-- ----------------------------

DROP TABLE IF EXISTS `tb_user`;

CREATE TABLE `tb_user` (

`id` bigint(20) NOT NULL AUTO_INCREMENT,

`username` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '收件人',

`address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地址',

PRIMARY KEY (`id`) USING BTREE,

UNIQUE INDEX `username`(`username`) USING BTREE

) ENGINE = InnoDB AUTO_INCREMENT = 109 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------

-- Records of tb_user

-- ----------------------------

INSERT INTO `tb_user` VALUES (1, '柳岩', '湖南省衡阳市');

INSERT INTO `tb_user` VALUES (2, '文二狗', '陕西省西安市');

INSERT INTO `tb_user` VALUES (3, '华沉鱼', '湖北省十堰市');

INSERT INTO `tb_user` VALUES (4, '张必沉', '天津市');

INSERT INTO `tb_user` VALUES (5, '郑爽爽', '辽宁省沈阳市大东区');

INSERT INTO `tb_user` VALUES (6, '范兵兵', '山东省青岛市');

2.4、项目搭建

由于这里我是直接导入黑马的基本项目模板,因此这里就做只是一个大概的介绍:

2.4.1、父工程-cloud-demo

父工程cloud-demo的作用为管理后续的子工程,是一个管理者的身份。又因为只是一个管理者的身份,因此我们只需要在建立一个Maven项目之后,将其的src文件夹删除就行(因为碍眼不因为啥)。

这里使用的框架为MyBatis,大家也可以使用MP,这样子可以操作少点。下面给出其pom.xml文件的具体内容。

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

4.0.0

cn.itcast.demo

cloud-demo

1.0

user-server

order-server

pom

org.springframework.boot

spring-boot-starter-parent

2.3.9.RELEASE

UTF-8

UTF-8

1.8

Hoxton.SR10

5.1.47

2.1.1

org.springframework.cloud

spring-cloud-dependencies

${spring-cloud.version}

pom

import

mysql

mysql-connector-java

${mysql.version}

org.mybatis.spring.boot

mybatis-spring-boot-starter

${mybatis.version}

org.projectlombok

lombok

2.4.2、子工程-order-server

子工程order-server用于实现订单的相关业务,创建步骤如下:

在父工程下右键新建module

选中maven项目点next

修改name为子工程名order-server,因为我已经创建过了,因此会爆红

pom.xml文件修改,因为我们需要借助Spring Boot的自动注入,因此还需要加入Spring Boot的依赖,当然也可以加在父工程里面

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

cloud-demo

cn.itcast.demo

1.0

4.0.0

order-server

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-maven-plugin

实体类,这对应着其数据库的表

@Data

public class Order {

private Long id;

private Long price;

private String name;

private Integer num;

private Long userId;

private User user;

}

配置文件

# 配置端口号

server:

port: 8080

spring:

# 配置数据源

datasource:

url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false

username: root

password: 123456

driver-class-name: com.mysql.jdbc.Driver

# 指定服务名

application:

name: orderservice

# 配置mybatis

mybatis:

type-aliases-package: cn.itcast.user.pojo

configuration:

map-underscore-to-camel-case: true

# 开启sql日志

logging:

level:

cn.itcast: debug

pattern:

dateformat: MM-dd HH:mm:ss:SSS

2.4.3、子工程-user-server

子工程user-server用于实现用户的相关业务,步骤和上一个工程相同,只是需要将name换成该工程名user-server。

pom文件

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

cloud-demo

cn.itcast.demo

1.0

4.0.0

user-server

org.springframework.boot

spring-boot-starter-web

app

org.springframework.boot

spring-boot-maven-plugin

实体类,对应数据库中的表

@Data

public class User {

private Long id;

private String username;

private String address;

}

配置文件

# 配置端口号

server:

port: 8081

spring:

# 配置数据源

datasource:

url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false

username: root

password: 123456

driver-class-name: com.mysql.jdbc.Driver

# 设置服务名

application:

name: userservice

# 配置mybatis

mybatis:

type-aliases-package: cn.itcast.user.pojo

configuration:

map-underscore-to-camel-case: true

# 开启sql日志

logging:

level:

cn.itcast: debug

pattern:

dateformat: MM-dd HH:mm:ss:SSS

到这时,整体的项目结构就如下所示啦:

2.5、远程调用示例

2.5.1、接口实现

首先得先单独在每一个服务中编写好一个测试使用的接口,这里使用的都是较简单的根据id查询数据的接口。

order-server中的接口,具体实现就不放出来了哈

@RestController

@RequestMapping("order")

public class OrderController {

@Autowired

private OrderService orderService;

@GetMapping("{orderId}")

public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {

return orderService.queryOrderById(orderId);

}

}

user-server中的接口,具体实现就不放出来了哈

@RestController

@RequestMapping("/user")

public class UserController {

@Autowired

private UserService userService;

@GetMapping("/{id}")

public User queryById(@PathVariable("id") Long id) {

return userService.queryById(id);

}

}

2.5.2、远程调用实现

由于在这里我们是需要在获取order订单数据的同时,根据order订单数据中的userId返回user用户数据

因此,我们需要在order-server中 向user-server发起一个http的请求,调用http://localhost:8081/user/{userId}这个接口。大概的步骤是这样的:

注册一个RestTemplate的实例到Spring容器修改order-server服务中的OrderService类中的查询方法,根据Order对象中的userId查询User将查询的User填充到Order对象,一起返回

2.5.2.1、RestTemplate

RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供了常见的REST请求方案的模版,例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute。

RestTemplate 继承自 InterceptingHttpAccessor 并且实现了 RestOperations 接口,其中 RestOperations 接口定义了基本的 RESTful 操作,这些操作在 RestTemplate 中都得到了实现。

方法名描述getForObject通过GET请求获取响应结果getForEntity通过GET请求获取ResponseEntity对象,其包含状态码、响应头和响应数据headForHeaders通过HEAD请求资源返回所有的响应头信息postForObject用POST请求创建资源,并返回响应数据中响应头的字段Location的数据postForObject用Post请求创建资源,获得响应结果put通过put方式请求来创建或者更新数据delete通过delete方式删除资源

因为我们是在order-server中调用user-server的数据,因此我们在order-server工程中新建一个BeanConfig配置类,并在其中注册RestTemplate实例:

public class BeanConfig {

@Bean

@LoadBalanced

public RestTemplate restTemplate() {

return new RestTemplate();

}

}

2.5.2.2、修改查询方法

修改order-server工程中的查询方法

注入RestTemplate请求user-server工程中查询用户数据的接口将查询到的用户数据封装到order订单数据中返回

@Service

public class OrderService {

@Autowired

private OrderMapper orderMapper;

@Autowired

private RestTemplate restTemplate;

public Order queryOrderById(Long orderId) {

// 1.查询订单

Order order = orderMapper.findById(orderId);

// 2. 远程查询user

String url = "http://localhost:8081/user/" + order.getUserId();

User user = restTemplate.getForObject(url, User.class);

// 3. 存入order

order.setUser(user);

// 4.返回

return order;

}

}

2.5.2.3、结果演示

在postman中输入http://localhost:8080/order/101进行测试,结果如期返回

3、Eureka注册中心

3.1、引入

上面服务拆分部分不难看出,在restTemplate调用的url中,url以及耦合死了,这明显也是不利于后期的维护,并且如果有多个相同的服务该如何选择调用?我们又怎么知道每个服务的健康情况?

因此,这里就需要Eureka注册中心,其主要有以下作用:

注册服务信息,服务提供者启动时向eureka注册自己的信息;拉取服务,根据服务名称向eureka拉取提供者信息;负载均衡,从服务列表中挑选一个;远程调用;心跳检测,服务提供者每30s会向Eureka发送一次心跳续约,以便Eureka知道服务提供者的健康状况,心跳不正常者会被剔除;

3.2、搭建eureka-server

首先注册中心服务端:eureka-server,这必须是一个独立的微服务。

在cloud-demo父工程下,创建一个子模块:

填写模块信息

填写服务信息

引入eureka依赖

org.springframework.cloud

spring-cloud-starter-netflix-eureka-server

在启动类上使用注解@EnableEurekaServer开启Eureka自动装配开关

@EnableEurekaServer

@SpringBootApplication

public class EurekaApplication {

public static void main(String[] args) {

SpringApplication.run(EurekaApplication.class, args);

}

}

配置文件编写

# 设置端口号

server:

port: 10086

# 设置服务名

spring:

application:

name: eureka-server

# 将自己加入指定的eureka中

eureka:

client:

service-url:

defaultZone: http://127.0.0.1:10086/eureka

3.3、服务注册

因为我们需要远程调用user-server中的接口,因此我们需要将其注册到eureka注册中心去,下面操作是在user-server工程中进行。

引入依赖

org.springframework.cloud

spring-cloud-starter-netflix-eureka-client

配置文件,在原来的配置中加入下面配置,指定eureka的路径

# 指定eureka路径

eureka:

client:

service-url:

defaultZone: http://127.0.0.1:10086/eureka

启用多个实例

为了演示一个服务有多个实例的场景,我们添加一个SpringBoot的启动配置,再启动一个user-service,即我们上面提及到的user-server(1)。

搜先复制原来的user-service启动配置

然后,在弹出的窗口中,修改复制的实例的端口号:

现在,SpringBoot窗口会出现两个user-service启动配置:

3.4、服务发现

下面,我们将order-service的逻辑修改:向eureka-server拉取user-service的信息,实现服务发现。

引入依赖

org.springframework.cloud

spring-cloud-starter-netflix-eureka-client

配置文件修改,因为这里也需要将该服务注册到eureka中,因此也需要指定eureka的地址

# 指定eureka地址

eureka:

client:

service-url:

defaultZone: http://127.0.0.1:10086/eureka

服务拉取。修改方法中的url地址,将写死的地址换成对应的服务名,即配置文件中的spring.application.name

@Service

public class OrderService {

@Autowired

private OrderMapper orderMapper;

@Autowired

private RestTemplate restTemplate;

public Order queryOrderById(Long orderId) {

// 1.查询订单

Order order = orderMapper.findById(orderId);

// 2. 远程查询user

String url = "http://userservice/user/" + order.getUserId();

User user = restTemplate.getForObject(url, User.class);

// 3. 存入order

order.setUser(user);

// 4.返回

return order;

}

}

负载均衡。在前面配置的RestTemplate中加入注解@LoadBalanced实现负载均衡。

public class BeanConfig {

@Bean

@LoadBalanced

public RestTemplate restTemplate() {

return new RestTemplate();

}

}

3.5、结果演示

重新在postman中进行请求http://localhost:8080/order/102进行测试,结果如期返回

4、Ribbon负载均衡

4.1、初步解析

在使用Eureka注册中心的时候使用到了@LoadBalanced注解实现负载均衡,那么这又是什么远离呢?这其实是在Spring Cloud中调用了Ribbon组件,从而实现负载均衡的。

至于服务名在代码中是如何转变成服务实例的IP和端口,感兴趣的小伙伴可以进行断点Debug进行源码追踪查看,这里直接给出结论啦。

SpringCloudRibbon的底层采用了一个拦截器,拦截了RestTemplate发出的请求,对地址做了修改。基本流程如下:

拦截我们的RestTemplate请求http://userservice/user/1RibbonLoadBalancerClient会从请求url中获取服务名称,也就是user-serviceDynamicServerListLoadBalancer根据user-service到eureka拉取服务列表eureka返回列表,localhost:8081、localhost:8082IRule利用内置负载均衡规则,从列表中选择一个,例如localhost:8081RibbonLoadBalancerClient修改请求地址**,用localhost:8081替代userservice,得到http://localhost:8081/user/1,发起真实请求

4.2、负载均衡策略

负载均衡的规则都定义在IRule接口中,而IRule有很多不同的实现类:

默认的ZoneAvoidanceRule策略在底层其实是一种轮询方案,在上图中也可以看出。不同规则的含义如下:

内置负载均衡规则类规则描述RoundRobinRule简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。AvailabilityFilteringRule对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的..ActiveConnectionsLimit属性进行配置。WeightedResponseTimeRule为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。ZoneAvoidanceRule(默认)以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。BestAvailableRule忽略那些短路的服务器,并选择并发数较低的服务器。RandomRule随机选择一个可用的服务器。RetryRule重试机制的选择逻辑

4.3、自定义策略

在知道了IRule下有如此之多的负载均衡策略,很显然我们是可以自定义策略的,一共有两种自定义的方式(当然,一般情况下我们都可以直接使用默认的负载均衡策略):

借助Spring注入。在order-service的配置类BeanConfig中将新的IRule加入到容器中

public class BeanConfig {

@Bean

@LoadBalanced

public RestTemplate restTemplate() {

return new RestTemplate();

}

/**

* @description 自定义负载均衡策略

*/

@Bean

public IRule randomRule(){

return new RandomRule();

}

}

使用配置文件。在order-service的application.yml文件中,添加新的配置也可以修改规则

# 指定给某个微服务配置负载均衡规则,这里是userservice服务

userservice:

ribbon:

# 负载均衡规则

NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

4.4、饥饿加载

在Ribbon中默认是采用的懒加载,即第一次访问时才会去创建LoadBalanceClient,这会导致第一次的请求时间会很长。而与其对应的饥饿加载则会在项目启动时创建,降低第一次访问的耗时。

可以通过下面配置开启饥饿加载,但是值得注意的是我们一般都指定需要饥饿加载的服务,因为加载的服务是放在内存中的,如果我们所有服务都进行饥饿加载,那么就会有可能造成内存浪费。

ribbon:

eager-load:

enabled: true

# 指定需要饥饿加载的服务,这里可以使用集合

clients: userservice

# 多个服务如下:

# - userservice

# - historyservice

# - x'x'x'x

相关阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: