服务治理
# Eureka注册中心
假如我们的服务提供者user-service部署了多个实例,如图:
存在几个问题:
- order-service在发起远程调用的时候,该如何得知user-service实例的ip地址和端口?
- 有多个user-service实例地址,order-service调用时该如何选择?
- order-service如何得知某个user-service实例是否依然健康,是不是已经宕机?
# Eureka的结构和作用
**作用:**处理服务名称与服务列表(ip端口)的映射关系
问题1:order-service如何得知user-service实例地址?
获取地址信息的流程如下:
- user-service服务实例启动后,将自己的信息注册到eureka-server(Eureka服务端)。这个叫服务注册
- eureka-server保存服务名称到服务实例地址列表的映射关系
- order-service根据服务名称,拉取实例地址列表。这个叫服务发现或服务拉取
问题2:order-service如何从多个user-service实例中选择具体的实例?
- order-service从实例列表中利用负载均衡算法选中一个实例地址
- 向该实例地址发起远程调用
问题3:order-service如何得知某个user-service实例是否依然健康,是不是已经宕机?
- user-service会每隔一段时间(默认30秒)向eureka-server发起请求,报告自己状态,称为心跳
- 当超过一定时间没有发送心跳时,eureka-server会认为微服务实例故障,将该实例从服务列表中剔除
- order-service拉取服务时,就能将故障实例排除了
注意:一个微服务,既可以是服务提供者,又可以是服务消费者,因此eureka将服务注册、服务发现等功能统一封装到了eureka-client端
# 使用步骤

# 搭建EurekaServer
引入SpringCloud为eureka提供的starter服务器端依赖:
<!-- 添加eureka服务端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
2
3
4
5
编写启动类
给eureka-server服务编写一个启动类,一定要添加一个**@EnableEurekaServer **** **注解,开启eureka的注册中心功能:
package cn.itcast.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
编写配置文件
编写一个application.yml文件,内容如下:
- 服务器端口号
- 服务器的服务名
- 注册中心的访问地址。最后一项defaultZone需要手动敲上去需要手动敲上去,并且URL地址前面有空格
# 服务器端口号
server:
port: 10086
spring:
application:
name: eureka-server
# 注册中心服务的访问地址
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
2
3
4
5
6
7
8
9
10
11
启动服务
启动微服务,然后在浏览器访问:http://127.0.0.1:10086 (opens new window)
# 服务注册
1)引入依赖
在user-service的pom文件中,引入下面的eureka-client客户端依赖:
<!-- 注册eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2
3
4
5
2)配置文件
在user-service中,修改application.yml文件
- 添加服务名称:userservice 微服务名称中没有短横
- eurekar的访问地址,与服务器端的访问地址相同。后面的defaultZone需要手动敲上去。
# 写在spring的配置部分中
spring:
application:
name: userservice
# 指定服务器端的访问地址
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
2
3
4
5
6
7
8
9
# 服务发现
1)引入依赖
之前说过,服务发现、服务注册统一都封装在eureka-client依赖,因此这一步与服务注册时一致。
在order-service的pom文件中,引入下面的eureka-client客户端依赖:
<!-- eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2
3
4
5
2)配置文件
服务发现也需要知道eureka地址,因此第二步与服务注册一致,都是配置eureka信息:
在order-service中,修改application.yml文件
- 添加服务名称orderservice
- eureka服务端地址同上
# 配置在spring的前缀中
spring:
application:
name: orderservice
# 配置服务器的访问地址
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
2
3
4
5
6
7
8
9
3)服务拉取和负载均衡
最后,我们要去eureka-server中拉取user-service服务的实例列表,并且实现负载均衡。
不过这些动作不用我们去做,只需要添加一些注解即可。
- 在order-service的OrderApplication中,给RestTemplate这个Bean添加一个**@LoadBalanced **** **注解:
LoadBalance中文意思是负载均衡
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
2
3
4
5
- 修改order-service服务中的cn.itcast.order.service包下的OrderService类中的queryOrderById方法。修改访问的url路径,用服务名代替ip、端口:
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
//2.获取用户id
Long userId = order.getUserId();
//3.使用服务名代替IP地址和端口号
User user = restTemplate.getForObject("http://userservice/user/" + userId, User.class);
order.setUser(user);
// 4.返回
return order;
}
2
3
4
5
6
7
8
9
10
11
spring会自动帮助我们从eureka-server端,根据user-service这个服务名称,获取实例列表,而后完成负载均衡。
启动orderservice服务,查看eureka管理页面
# Ribbon负载均衡
SpringCloud底层其实是利用了一个名为Ribbon的组件,来实现负载均衡功能的。
# LoadBalancerInterceptor

可以看到这里的intercept方法,拦截了用户的HttpRequest请求,然后做了几件事:
request.getURI():获取请求uri,本例中就是 http://userservice/user/8 (opens new window)originalUri.getHost():获取uri路径的主机名,其实就是服务id,userservicethis.loadBalancer.execute():处理服务id和用户请求。
这里的this.loadBalancer是LoadBalancerClient类型,我们继续跟入。
# LoadBalancerClient
继续跟入execute方法:
代码是这样的:
- getLoadBalancer(serviceId):根据服务名称获取负载均衡器,而负载均衡器会拿着服务名去eureka中获取服务列表并保存起来。

- getServer(loadBalancer):利用内置的负载均衡算法,从服务列表中选择一个。本例中,可以看到获取了8082端口的服务
放行后,再次访问并跟踪,发现获取的是8081:
果然实现了负载均衡。
# 负载均衡策略IRule
在刚才的代码中,可以看到获取服务使通过一个getServer方法来做负载均衡:
我们继续跟入:
继续跟踪源码chooseServer方法,发现BaseLoadBalancer类中这么一段代码:
我们看看这个rule是谁:
这里的rule默认值是一个RoundRobinRule,看类的介绍:
这不就是轮询的意思嘛。到这里,整个负载均衡的流程我们就清楚了。
Ribbon的负载均衡规则是一个叫做IRule的接口来定义的,每一个子接口都是一种规则:
负载均衡策略
| 内置负载均衡规则类 | 规则描述 |
|---|---|
| RoundRobinRule(默认) | 轮询规则:简单轮询服务列表来选择服务器。 |
| AvailabilityFilteringRule | 可用性过滤规则:对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的<clientName>.<clientConfigNameSpace>.ActiveConnectionsLimit属性进行配置。 |
| WeightedResponseTimeRule | 响应权重时间规则:为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。 |
| ZoneAvoidanceRule | 规避区域规则:以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。 |
| BestAvailableRule | 最可用规则:忽略那些短路的服务器,并选择并发数较低的服务器。 |
| RandomRule | 随机规则:随机选择一个可用的服务器。 |
| RetryRule | 重试实现:重试机制的选择逻辑 |
通过定义IRule实现可以修改负载均衡规则,有两种方式:
- **代码方式:**在order-service中的OrderApplication类中,定义一个新的IRule:
@Bean
public IRule randomRule(){
return new RandomRule();
}
2
3
4
- **配置文件方式:**在order-service的application.yml文件中,添加新的配置也可以修改规则:
userservice:
ribbon:
# 负载均衡规则
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
2
3
4
# 总结

基本流程如下:
- 拦截我们的RestTemplate请求
http://userservice/user/1 - restTemplate.getForObject()方法最终会调用到LoadBalancerInterceptor的intercept方法,会从请求url中获取服务名称,也就是userservice
- DynamicServerListLoadBalancer根据userservice,调用updateListOfServers方法到注册中心拉取服务列表
- eureka返回列表,localhost:8081、localhost:8082
- IRule利用内置负载均衡规则,从列表中选择一个,例如localhost:8081
- RibbonLoadBalancerClient修改请求地址,用localhost:8081替代userservice,得到http://localhost:8081/user/1,发起真实请求
# 饥饿加载
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过order-service中配置开启饥饿加载:
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients:
- userservice # 指定对user-service这个服务饥饿加载
2
3
4
5
ribbon是顶层元素,clients是一个List集合。如果要配置多个服务名字,则换一行。用-做为前缀,每行写一个
# Nacos注册中心
Nacos (opens new window)是阿里巴巴的产品,现在是SpringCloud (opens new window)中的一个组件。相比Eureka (opens new window)功能更加丰富,在国内受欢迎程度较高。
Windows安装教程:Nacos安装指南.pdf (opens new window)
# Docker 安装Nacos
参考:docker安装nacos_流沙的牵挂的博客-CSDN博客_docker nacos (opens new window)
1.官方镜像拉取
docker pull nacos/nacos-server
2.挂载目录,用于映射到容器,目录按自己的情况创建
mkdir -p /usr/local/docker/nacos/logs/ #新建logs目录
mkdir -p /usr/local/docker/nacos/init.d/
vim /usr/local/docker/nacos/init.d/custom.properties #修改配置文件
2
3
3.mysql新建nacos的数据库(nacos_config),并执行脚本 sql脚本地址如下
https://github.com/alibaba/nacos/blob/master/config/src/main/resources/META-INF/nacos-db.sql (opens new window)
CREATE DATABASE `nacos_config` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info */
/******************************************/
CREATE TABLE `config_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(255) DEFAULT NULL,
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(20) DEFAULT NULL COMMENT 'source ip',
`app_name` varchar(128) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`c_desc` varchar(256) DEFAULT NULL,
`c_use` varchar(64) DEFAULT NULL,
`effect` varchar(64) DEFAULT NULL,
`type` varchar(64) DEFAULT NULL,
`c_schema` text,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_aggr */
/******************************************/
CREATE TABLE `config_info_aggr` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(255) NOT NULL COMMENT 'group_id',
`datum_id` varchar(255) NOT NULL COMMENT 'datum_id',
`content` longtext NOT NULL COMMENT '内容',
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
`app_name` varchar(128) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_beta */
/******************************************/
CREATE TABLE `config_info_beta` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(20) DEFAULT NULL COMMENT 'source ip',
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_tag */
/******************************************/
CREATE TABLE `config_info_tag` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`tag_id` varchar(128) NOT NULL COMMENT 'tag_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(20) DEFAULT NULL COMMENT 'source ip',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_tags_relation */
/******************************************/
CREATE TABLE `config_tags_relation` (
`id` bigint(20) NOT NULL COMMENT 'id',
`tag_name` varchar(128) NOT NULL COMMENT 'tag_name',
`tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`nid` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`nid`),
UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = group_capacity */
/******************************************/
CREATE TABLE `group_capacity` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = his_config_info */
/******************************************/
CREATE TABLE `his_config_info` (
`id` bigint(64) unsigned NOT NULL,
`nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`data_id` varchar(255) NOT NULL,
`group_id` varchar(128) NOT NULL,
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL,
`md5` varchar(32) DEFAULT NULL,
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00',
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00',
`src_user` text,
`src_ip` varchar(20) DEFAULT NULL,
`op_type` char(10) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`nid`),
KEY `idx_gmt_create` (`gmt_create`),
KEY `idx_gmt_modified` (`gmt_modified`),
KEY `idx_did` (`data_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = tenant_capacity */
/******************************************/
CREATE TABLE `tenant_capacity` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';
CREATE TABLE `tenant_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`kp` varchar(128) NOT NULL COMMENT 'kp',
`tenant_id` varchar(128) default '' COMMENT 'tenant_id',
`tenant_name` varchar(128) default '' COMMENT 'tenant_name',
`tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',
`create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',
`gmt_create` bigint(20) NOT NULL COMMENT '创建时间',
`gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';
CREATE TABLE users (
username varchar(50) NOT NULL PRIMARY KEY,
password varchar(500) NOT NULL,
enabled boolean NOT NULL
);
CREATE TABLE roles (
username varchar(50) NOT NULL,
role varchar(50) NOT NULL,
constraint uk_username_role UNIQUE (username,role)
);
CREATE TABLE permissions (
role varchar(50) NOT NULL,
resource varchar(512) NOT NULL,
action varchar(8) NOT NULL,
constraint uk_role_permission UNIQUE (role,resource,action)
);
INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
4.修改配置文件custom.properties
# spring
server.servlet.contextPath=${SERVER_SERVLET_CONTEXTPATH:/nacos}
server.contextPath=/nacos
server.port=${NACOS_SERVER_PORT:8848}
spring.datasource.platform=${SPRING_DATASOURCE_PLATFORM:""}
nacos.cmdb.dumpTaskInterval=3600
nacos.cmdb.eventTaskInterval=10
nacos.cmdb.labelTaskInterval=300
nacos.cmdb.loadDataAtStart=false
db.num=${MYSQL_DATABASE_NUM:1}
db.url.0=jdbc:mysql://${MYSQL_SERVICE_HOST}:${MYSQL_SERVICE_PORT:3306}/${MYSQL_SERVICE_DB_NAME}?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.url.1=jdbc:mysql://${MYSQL_SERVICE_HOST}:${MYSQL_SERVICE_PORT:3306}/${MYSQL_SERVICE_DB_NAME}?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=${MYSQL_SERVICE_USER}
db.password=${MYSQL_SERVICE_PASSWORD}
### The auth system to use, currently only 'nacos' is supported:
nacos.core.auth.system.type=${NACOS_AUTH_SYSTEM_TYPE:nacos}
### The token expiration in seconds:
nacos.core.auth.default.token.expire.seconds=${NACOS_AUTH_TOKEN_EXPIRE_SECONDS:18000}
### The default token:
nacos.core.auth.default.token.secret.key=${NACOS_AUTH_TOKEN:SecretKey012345678901234567890123456789012345678901234567890123456789}
### Turn on/off caching of auth information. By turning on this switch, the update of auth information would have a 15 seconds delay.
nacos.core.auth.caching.enabled=${NACOS_AUTH_CACHE_ENABLE:false}
server.tomcat.accesslog.enabled=${TOMCAT_ACCESSLOG_ENABLED:false}
server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D
# default current work dir
server.tomcat.basedir=
## spring security config
### turn off security
nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/**,/v1/console/health/**,/actuator/**,/v1/console/server/**
# metrics for elastic search
management.metrics.export.elastic.enabled=false
management.metrics.export.influx.enabled=false
nacos.naming.distro.taskDispatchThreadCount=10
nacos.naming.distro.taskDispatchPeriod=200
nacos.naming.distro.batchSyncKeyCount=1000
nacos.naming.distro.initDataRatio=0.9
nacos.naming.distro.syncRetryDelay=5000
nacos.naming.data.warmup=true
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
5.创建容器,这里的指定参数根据/home/nacos/conf/application.properties 配置设置的,主要是mysql配置的修改
docker run -d \
-p 8848:8848 \
-p 8849:8849 \
-p 9848:9848 \
-p 9849:9849 \
-e MODE=standalone \
-e JVM_XMS=512m \
-e JVM_XMX=512m \
-e PREFER_HOST_MODE=hostname \
-e SPRING_DATASOURCE_PLATFORM=mysql \
-e MYSQL_SERVICE_HOST=192.168.66.128 \
-e MYSQL_SERVICE_PORT=3306 \
-e MYSQL_SERVICE_DB_NAME=nacos_config \
-e MYSQL_SERVICE_USER=root \
-e MYSQL_SERVICE_PASSWORD=root \
-e MYSQL_DATABASE_NUM=1 \
-v /usr/local/docker/nacos/init.d/custom.properties:/home/nacos/init.d/custom.properties \
-v /usr/local/docker/nacos/logs:/home/nacos/logs \
--restart always --name nacos nacos/nacos-server
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
注意: 单机standalone模式默认服务器堆大小512M、注意服务器内存不足,启动后内存溢出问题。
6.启动nacos
docker start nacos
7.访问http://localhost:8848/nacos/ (opens new window) 账号默认nacos、密码默认nacos
# 注册服务到Nacos
Nacos是SpringCloudAlibaba的组件,而SpringCloudAlibaba也遵循SpringCloud中定义的服务注册、服务发现规范。因此使用Nacos和使用Eureka对于微服务来说,并没有太大区别。
主要差异在于:
- 依赖不同
- 服务地址不同
# 1)引入依赖
在cloud-demo父工程的pom文件中的<dependencyManagement>中引入SpringCloudAlibaba的依赖:
<!-- nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
2
3
4
5
6
7
8
然后在子工程user-service和order-service中的pom文件中引入nacos-discovery依赖:
<!-- nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2
3
4
5
注意:子工程不要忘了注释掉eureka的依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2
3
4
# 2)配置nacos地址
在user-service和order-service的application.yml中添加nacos地址:
spring:
# 配置在spring的前缀中
application:
name: orderservice
cloud:
nacos:
server-addr: localhost:8848
2
3
4
5
6
7
注意:不要忘了注释掉eureka的地址
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
2
3
4
# 3)重启
重启微服务后,登录nacos管理页面,可以看到微服务信息:
# 服务集群配置
修改user-service中application.yml,添加如下内容:
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos 服务端地址
discovery:
cluster-name: HZ # 配置集群名称,也就是机房位置,例如:HZ,杭州
2
3
4
5
6
集群负载均衡配置
修改order-service中的application.yml,设置集群为HZ
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos 服务端地址
discovery:
cluster-name: HZ # 配置集群名称,也就是机房位置,例如:HZ,杭州
2
3
4
5
6
然后在order-service中设置负载均衡的IRule为NacosRule,这个规则优先会寻找与自己同集群的服务,而集群内部使用的是随机的负载均衡
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
2
3
# 环境隔离
Nacos既是注册中心,又是数据中心。为了便于管理,Nacos提供了namespace来实现环境隔离功能。用于进行租户级别的隔离,我们最常用的就是不同环境比如测试环境,线上环境进行隔离。
- nacos中可以有多个namespace
- namespace下可以有group等。业务相关性比较强的可以放在一组,比如:支付和订单
- 不同namespace之间相互隔离,即不同namespace的服务互相不可见

首先登录服务器创建新的命名空间
给微服务配置namespace
- 给微服务配置namespace只能通过修改配置来实现。
例如,修改order-service的application.yml文件:
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
namespace: 492a7d5d-237b-46a1-a99a-fa8e98e4b0f9 # 命名空间,填ID
2
3
4
5
6
重启后可以在dev命名空间中看见orderservice实例
# Nacos和Eureka的区别

**Nacos与eureka的共同点 **
- 都支持服务注册和服务拉取
- 都支持服务提供者心跳方式做健康检测
**Nacos与Eureka的区别 **
- Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
- 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
- Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
临时实例:如果实例宕机超过一定时间,会从服务列表剔除,默认的类型。 非临时实例:如果实例宕机,不会从服务列表剔除,也可以叫永久实例。
# Nacos配置中心
Nacos一方面可以将配置集中管理,另一方可以在配置变更时,及时通知微服务,实现配置的热更新。
进入Nacos服务器管理界面,在配置列表中添加配置
**约定:**不常变化的配置放在项目本地yml文件,常变化的配置放在Nacos配置中心
# 拉取配置
微服务要拉取nacos中管理的配置,并且与本地的application.yml配置合并,才能完成项目启动。但如果尚未读取application.yml,又如何得知nacos地址呢?因此Spring引入了一种新的配置文件:bootstrap.yaml文件,会在application.yml之前被读取,流程如下:
1)在user-service服务中,引入nacos-config的客户端依赖:
<!--nacos配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
2
3
4
5
2)添加bootstrap.yaml,然后在user-service中添加一个bootstrap.yaml文件,内容如下:
- 服务名为userservice,注:名字中间没有短横。
- 指定激活的配置dev
- 指定spring.cloud.nacos.server-addr的服务地址:localhost:8848
- 指定spring.cloud.nacos.config.file-extension配置文件的扩展名为yaml
spring:
application:
name: userservice # 服务名称,注:要与文件名一致
profiles:
active: dev #开发环境,这里是dev
cloud:
nacos:
server-addr: localhost:8848 # Nacos地址
config:
file-extension: yaml # 文件后缀名
2
3
4
5
6
7
8
9
10
这里会根据spring.cloud.nacos.server-addr获取nacos地址,再根据
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}作为文件id,来读取配置。本例中,就是去读取userservice-dev.yaml:
3)去掉application.yml中相关重复的配置
application:
name: userservice
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ
namespace: a8ace27d-a737-4122-9f3f-92f5ee795b62
2
3
4
5
6
7
8
9
4)读取nacos配置
在user-service中的UserController中添加业务逻辑,读取pattern.dateformat配置:
package cn.itcast.user.web;
import cn.itcast.user.pojo.User;
import cn.itcast.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Value("${pattern.dateformat}")
private String dateformat;
@GetMapping("now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
}
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
# 配置热更新
我们最终的目的,是修改nacos中的配置后,微服务中无需重启即可让配置生效,也就是配置热更新。底层基于一种长轮询的方式,微服务发送请求到Nacos后,Nacos不会立即响应,而是hole住这个请求,直到配置文件出现了修改,才响应修改通知。
方式一
在@Value注入的变量所在类上添加注解@RefreshScope:
源于Spring的@Scope注解,在接收到修改通知后,会重新创建一个Bean来更新配置
方式二
使用@ConfigurationProperties注解代替@Value注解。
在user-service服务的config包中添加PatternProperties类
- 添加@Component注解
- 添加@ConfigurationProperties(prefix = "pattern")注解
- 添加String dateformat属性
注:如果出现红色警告,在user-service的pom.xml中添加以下坐标即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
2
3
4
在UserController中使用这个类,并且使用@Autowired注入
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private PatternProperties patternProperties;
@GetMapping("now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
!!!!一定要在bootstrap.yaml文件中配置好nacos配置文件坐标。 首次拉取配置时,是通过application.yaml文件及bootstrap.yaml文件定位配置文件坐标 服务启动后,更新nacos配置时,仅扫描bootstrap.yaml文件定位配置文件坐标 若没有配置清晰配置文件坐标的话是无法实现动态更新配置的 比如application.name和profiles.active
# 配置共享
微服务启动时,会去nacos读取多个配置文件,例如:
[spring.application.name]-[spring.profiles.active].yaml,例如:userservice-dev.yaml[spring.application.name].yaml,例如:userservice.yaml
而[spring.application.name].yaml不包含环境,因此可以被多个环境共享。
运行两个UserApplication,使用不同的profile
修改UserApplication8082这个启动项,改变其profile值:
注:这个test的profiles是不存在的
这样,UserApplication8081使用的profile是dev,UserApplication8082使用的profile是test。
访问http://localhost:8081/user/prop,结果:
访问http://localhost:8082/user/prop,结果:
可以看出来,不管是dev,还是test环境,都读取到了envSharedValue这个属性的值。
# 配置共享的优先级
当nacos、服务本地同时出现相同属性时,优先级有高低之分:
服务器控制台显示:
Located property source: [BootstrapPropertySource {name='bootstrapProperties-userservice-dev.yaml,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-userservice.yaml,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-userservice,DEFAULT_GROUP'}]
# Nacos集群
集群搭建教程:nacos集群搭建.pdf (opens new window)
搭建结构
三个nacos节点的地址:
| 节点 | 换成实际服务器的IP地址 | port |
|---|---|---|
| nacos1 | 192.168.159.1 | 8845 |
| nacos2 | 192.168.159.1 | 8846 |
| nacos3 | 192.168.159.1 | 8847 |
Nginx反向代理配置
upstream nacos-cluster {
server 127.0.0.1:8845;
server 127.0.0.1:8846;
server 127.0.0.1:8847;
}
server {
listen 80;
server_name localhost;
location /nacos {
proxy_pass http://nacos-cluster;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
注意修改userservice服务的bootstrap.yaml文件配置如下:
spring:
cloud:
nacos:
server-addr: localhost:80 # Nginx地址 直接访问Nginx由Nginx反向代理
2
3
4
# Consul注册中心
**概述:**Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其他分布式服务注册与发现的方案,Consul 的方案更“一站式”,内置了服务注册与发现框架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案,不再需要依赖其他工具(比如 ZooKeeper 等)。使用起来也较 为简单。Consul 使用 Go 语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署,与 Docker 等轻量级容器可无缝配合。

Consul的优势
- 使用 Raft 算法来保证一致性, Consul 保持了 CAP 中的 CP,保持了强一致性和分区容错性。
- 支持多数据中心。
- 支持健康检查。
- 使用go语言开发,启动速度和运行速度快。
# 使用步骤
Consul官网:https://www.consul.io/ (opens new window) 下载对应版本
**使用开发模式启动consul:**在安装目录下进入cmd输入consul agent -dev