学习目标
- 能使用SpringCloudConfig实现配置管理
- 能使用SpringCloudBus实现配置刷新
- 会使用Nacos作为注册中心
- 会使用Nacos作为配置中心
- 了解微服务部署
1 SpringCloud Config分布式配置中心
当项目上线部署时,往往会部署到数十台甚至数千台服务节点,如果我们需要修改某个配置文件中的内容,就需要逐个服务器去修改,工作量太大而且容易出错。
而统一配置中心就是解决这个问题的。
1.1 SpringCloud Config简介
Spring Cloud Config为分布式系统中的配置管理提供服务器和客户端支持。它包含两部分:
- Config Server:是一个可横向扩展、集中式的配置服务器,它用于集中管理应用程序各个环境下的配置,默认使用Git存储配置文件内容,也可以使用SVN存储,或者是本地文件存储。
- Config Client:是Config Server的客户端,用于操作存储在Config Server中的配置内容。微服务在启动时Config Client会请求Config Server以获取配置文件的内容,请求到后再启动容器并应用配置。
如图:
当用户提交并推送配置到Git仓库时,ConfigServer可以从Git获取到最新的配置,并缓存到本地。而ConfigClient则可以从ConfigServer中获取配置。
1.2 统一配置管理实现
1.2.1 准备Git仓库
首先,我们需要准备一个Git仓库,用来保存服务间共享的配置文件。
(1)登录https://gitee.com/
创建Git仓库,并添加一个配置文件,可以直接复制user-service工程中的配置文件
创建仓库
(2)在仓库中创建consumer-dev.yml
配置,并编写一些测试内容:
配置文件内容如下:
hystrix:
command:
default:
execution.isolation.thread.timeoutInMilliseconds: 2000
circuitBreaker:
errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50%
sleepWindowInMilliseconds: 10000 # 熔断后休眠时长,默认值5秒
requestVolumeThreshold: 10 # 触发熔断的最小请求次数,默认20
ribbon:
ConnectTimeout: 500 # 连接超时时长
ReadTimeout: 1000 # 数据通信超时时长
MaxAutoRetriesNextServer: 2 # 切换重试多少次服务
MaxAutoRetries: 1 # 当前服务器的重试次数
OkToRetryOnAllOperations: false # 是否对所有的请求方式都重试
feign:
hystrix:
enabled: true # 开启Feign的熔断功能
logging:
level:
com.itheima: debug
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka
spring:
application:
name: consumer-service
test:
redis:
host: localhost
port: 6379
1.2.2 搭建ConfigServer工程
接下来,我们来搭建一个ConfigServer,
1)创建工程
2)添加pom依赖
接下来,在项目的pom.xml中引入一些依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
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">
<parent>
<artifactId>sh_cloud_parent</artifactId>
<groupId>com.itheima.sh</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>config_server</artifactId>
<dependencies>
<!--eureka依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--config-server依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
这里引入了两个依赖:
- eureka-client依赖:因为ConfigServer也是一个微服务,需要注册到eureka
- config-serverr依赖:包含ConfigServer的全部功能
3)添加配置文件
然后在项目的 application.yml 中添加配置:
server:
port: 12580
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/jitheima/config-test.git # 需要拉取配置的Git仓库地址
# username:
# password:
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka/ # eureka地址
核心配置有两个:
spring.cloud.config.server.git.url
:管理的git仓库的地址,将来ConfigServer会去拉取配置eureka.client.service-url
:eureka地址
4)启动类
最后,我们添加一个启动类:
package com.itheima.sh;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
@EnableDiscoveryClient
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
在启动类上,通过@EnableConfigServer
注解来开启ConfigServer的功能。
5)基本测试
启动项目,查看eureka的页面发现服务注册成功:
然后,我们在浏览器输入地址:http://127.0.0.1:12580/consumer-dev.yml,结果如下:
配置文件访问必须符合下面的路径规范:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml # 没写label 默认就是master分支
/{label}/{application}-{profile}.yml
后缀名同样适用于.properties
。这是什么意思呢?
- application:应用名,也就是配置文件名,这里是consumer,最好与微服务名称一致
- profile:当前激活的开发环境,也就是SpringBoot中的
spring.profiles.active
属性值,例如:dev环境、prod环境、test环境等,可以自定义 - label:配置文件所在分支,缺省值是master
文件名后面的dev
和prod
就是profile。
6)分支测试
我们新建一个分支,名为feature:
然后修改feature分支中的consumer-dev.yml
文件,内容如下:
branch: feature
hystrix:
command:
default:
execution.isolation.thread.timeoutInMilliseconds: 2000
circuitBreaker:
errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50%
sleepWindowInMilliseconds: 10000 # 熔断后休眠时长,默认值5秒
requestVolumeThreshold: 10 # 触发熔断的最小请求次数,默认20
ribbon:
ConnectTimeout: 500 # 连接超时时长
ReadTimeout: 1000 # 数据通信超时时长
MaxAutoRetriesNextServer: 2 # 切换重试多少次服务
MaxAutoRetries: 1 # 当前服务器的重试次数
OkToRetryOnAllOperations: false # 是否对所有的请求方式都重试
feign:
hystrix:
enabled: true # 开启Feign的熔断功能
logging:
level:
com.itheima: debug
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka
spring:
application:
name: consumer-service
test:
redis:
host: localhost
port: 6379
提交以后,在浏览器访问路径:http://127.0.0.1:12580/feature/consumer-dev.yml
1.2.3 配置ConfigClient
我们已经能够从ConfigServer获取配置文件内容。接下来我们需要在微服务配置ConfigClient,去读取这些配置。
1)引入依赖
首先,在consumer-service服务中引入config依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
2)添加配置文件
接下来,我们要编写配置文件。与之前不同,项目启动要优先去读取配置,这些配置可能在后面创建Spring容器,初始化各种Bean的时候用到。
SpringBoot中提供了一个名为bootstrap.yml
的文件,加载顺序比application.yml优先。
因此我们需要在consumer-service
服务创建一个bootstrap.yml
的文件,内容如下:
spring:
cloud:
config:
name: consumer # 配置文件的名称
profile: dev # 环境
label: master # 分支
discovery:
enabled: true # 通过注册中心寻找ConfigServer
service-id: config-server # ConfigServer的服务id
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka/
3)定义属性读取类
接下来,为了测试,我们需要定义一个属性读取类,读取远程加载的consumer-dev.yml中的数据。
在 consumer-service 中定义一个属性类:
package com.itheima.consumer.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author
*/
@Data
@Component
@ConfigurationProperties(prefix = "test.redis")
public class RedisProperties {
private String host;
private int port;
}
4)测试
为了方便查看结果,我们在 consumer-service 中定义一个新的controller接口:
@RestController
@RequestMapping("hello")
public class HelloController {
@Autowired
private RedisProperties properties;
@GetMapping("prop")
public RedisProperties getProperties(){
return properties;
}
}
重启consumer-service后,在浏览器访问:http://localhost:8080/hello/prop,内容如下:
1.2.4 集成测试
现在,我们修改git仓库中,master分支的consumer-dev.yml文件,修改端口为6380:
test:
redis:
host: localhost
port: 6380
然后我们访问ConfigServer:http://localhost:12580/consumer-dev.yml:
我们访问:http://localhost:8080/hello/prop,内容如下:
发现consumer-service的内容没有跟着改变,说明默认情况下config-client并未发现配置变化,新的配置也没有生效。
1.2.4.1 开启手动刷新功能
如果要让提交在Git的配置立即生效,需要在微服务端做一些配置。
首先,consumer-service微服务中引入依赖:
<!-- springboot提供的监控依赖,可以查看当前springboot的一些信息
/health /info /up /refresh:刷新配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
然后,在consumer-service
的 bootstrap.ym
l 文件中添加配置:
management:
endpoints:
web:
exposure:
include: "*" # 暴露refresh接口
这个配置会对外暴露一个接口:/actuator/refresh,调用该接口即可刷新配置。
重启consumer-service
服务。
1.2.4.2 重新修改配置文件
现在,我们修改git仓库中,master分支的consumer-dev.yml文件,内容如下:
test:
redis:
host: localhost
port: 6381
然后我们访问ConfigServer:http://127.0.0.1:12580/consumer-dev.yml:
然后,访问http://localhost:8080/hello/prop,内容如下:
然后打开PostMan工具,访问:http://localhost:8080/actuator/refresh,注意是POST请求:
此时,查看consumer-service的日志:
可以看到,已经重新加载配置。再次访问:http://localhost:8080/hello/prop:
可以发现,项目无需重启,只需要访问接口即可实现配置更新!
2 SpringCloud Bus消息总线
现在,我们可以通过访问微服务的/actuator/refresh
接口来手动刷新配置。但是,如果我们的微服务有数百个节点,一个个去手动访问并不现实。有没有办法可以实现配置的自动更新呢?
这里可以使用消息总线来实现,例如Spring Cloud Bus技术。
2.1 SpringCloud Bus消息总线简介
在微服务架构体系中,我们通常会使用轻量级的消息代理(MQ)来构建一个共用的消息系统,让所有的微服务节点都连上来。由于该系统中的话题会被所有微服务节点订阅和消费,因此成为:消息总线。
我们可以利用消息总线方便的实现统一的配置管理和配置动态刷新。
目前,SpringCloud中的SpringCloud Bus技术就实现了消息总线的功能,其底层默认是采用RabbitMQ和Kafka来作为消息代理。
利用SpringCloudBus来实现配置动态刷新的流程如图:
2.2 安装MQ
略,之前已经学习过MQ的安装,此处略过。需要大家把MQ启动即可。
rabbitmq docker脚本
docker run -di --name mq -e RABBITMQ_DEFAULT_USER=guest -e RABBITMQ_DEFAULT_PASS=guest -p 15672:15672 -p 5672:5672 rabbitmq:management
2.3 手动刷新
2.3.1 配置ConfigServer
ConfigServer需要做两件事情:
- 连接MQ,并监听消息
- 暴露refresh接口,将来Git会主动调用和通知
2.3.1.1 引入依赖
我们在ConfigServer的pom.xml中引入依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2.3.1.2 配置文件
然后修改ConfigServer的application.yml文件,添加两部分内容:
- RabbitMQ地址
- 开启refresh端口
server:
port: 12580
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/jitheima/config-test.git # 需要拉取配置的Git仓库地址
# username:
# password:
rabbitmq:
host: 192.168.200.129
username: guest
password: guest
virtual-host: /
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka/ # eureka地址
management:
endpoints:
web:
exposure:
include: bus-refresh #通知消息总线 刷新事件
2.3.2 配置ConfigClient
接下来,需要在ConfigClient端做相同配置,这里我们修改consumer-service服务。
2.3.2.1 引入依赖
我们在consumer-service
的pom.xml中引入依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
<!-- 如果之前没有引过, 也要引入监控依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2.3.2.2 配置文件
然后修改consumer-service
的bootstrap.yml
文件,添加MQ地址后,完整配置如下:
spring:
cloud:
config:
name: consumer # 配置文件的名称
profile: dev # 环境
label: master # 分支
discovery:
enabled: true # 通过注册中心寻找ConfigServer
service-id: config-server # ConfigServer的服务id
rabbitmq:
host: 192.168.200.129
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka/
management:
endpoints:
web:
exposure:
include: "*" # 暴露refresh接口
2.3.3 测试
重启 config-server和consumer-service服务
修改Git仓库中的master分支下的consumer-dev.yml
,内容如下:
test:
redis:
host: localhost
port: 6382
然后,在postMan工具中访问ConfigServer的刷新接口:http://localhost:12580/actuator/bus-refresh
:
然后访问接口:http://localhost:8080/hello/prop
微服务的配置都刷新了!
2.6 自动刷新
现在,我们提交了代码后还需要手动调用刷新接口。能不能自动刷新呢?
gitee.com或者github.com这样的远程仓库,都提供了web hook
功能,允许我们配置一个地址,当有代码提交时,远程库会向配置的地址发送一个POST请求,即可实现自动刷新了!
2.6.1 内网穿透
不过,你配置的web hook必须是外网地址,这样才能被gitee.com这样的网站访问到。
推荐工具:https://www.u.tools/
http://config112.utools.club/userservice-dev.yml 是可以在公网上访问成功的
因此,只要访问:http://config112.utools.club/actuator/bus-refresh即可刷新配置
2.6.2 配置gitee中webHooks
现在,我们在gitee.com的仓库中配置一个web hook。
进入管理页面,点击左侧的 web hook 菜单,然后点击添加按钮:
在弹出的页面中填写web hook的表单:
在URL一栏填写刷新配置的endpoint地址,然后点击添加即可。
2.6.3 提交修改文件测试
然后在Git仓库提交一次修改,会发现控制台中已经发出web-hook,但是却得到一个400的请求错误信息:
查看错误信息,发现是JSON解析错误!
在config-server
工程中 ,创建filter过滤器 解决参数问题
package com.itheima.config.filter;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@Component
public class UrlFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest)request;
HttpServletResponse httpServletResponse = (HttpServletResponse)response;
String url = new String(httpServletRequest.getRequestURI());
//只过滤/actuator/bus-refresh请求
if (!url.endsWith("/bus-refresh")) {
chain.doFilter(request, response);
return;
}
//获取原始的body
String body = readAsChars(httpServletRequest);
System.out.println("original body: "+ body);
//使用HttpServletRequest包装原始请求达到修改post请求中body内容的目的
CustometRequestWrapper requestWrapper = new CustometRequestWrapper(httpServletRequest);
chain.doFilter(requestWrapper, response);
}
@Override
public void destroy() {
}
private class CustometRequestWrapper extends HttpServletRequestWrapper {
public CustometRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public ServletInputStream getInputStream() throws IOException {
byte[] bytes = new byte[0];
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
System.out.println(byteArrayInputStream.read());
return new ServletInputStream() {
@Override
public boolean isFinished() {
return true;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
}
}
public static String readAsChars(HttpServletRequest request)
{
BufferedReader br = null;
StringBuilder sb = new StringBuilder("");
try
{
br = request.getReader();
String str;
while ((str = br.readLine()) != null)
{
sb.append(str);
}
br.close();
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
if (null != br)
{
try
{
br.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
return sb.toString();
}
}
再次 更新 userservice-dev.yml 文件,对应的端口
test:
redis:
host: localhost
port: 6385
查看 MQ 界面:
查看浏览器地址:http://localhost:8081/hello/prop
可以看到已经可以正常的自动更新了。
3 Nacos
自从阿里巴巴将自己的微服务组件加入到SpringCloud中,成为现在的SpringCloudAlibaba,目前国内使用SpringCloudAlibaba的也越来越多。特别是其中的Nacos组件,同时具备了Eureka和SpringCloudConfig的功能,得到了很多国内企业的喜爱。
3.1 Nacos简介
Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。
Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。
官网地址:https://nacos.io/zh-cn/index.html
Nacos 的关键特性包括:
-
服务发现和服务健康监测
Nacos 支持基于 DNS 和基于 RPC 的服务发现。服务提供者使用 原生SDK、OpenAPI、或一个独立的Agent TODO注册 Service 后,服务消费者可以使用DNS TODO 或HTTP&API查找和发现服务。
Nacos 提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求。Nacos 支持传输层 (PING 或 TCP)和应用层 (如 HTTP、MySQL、用户自定义)的健康检查。 对于复杂的云环境和网络拓扑环境中(如 VPC、边缘网络等)服务的健康检查,Nacos 提供了 agent 上报模式和服务端主动检测2种健康检查模式。Nacos 还提供了统一的健康检查仪表盘,帮助您根据健康状态管理服务的可用性及流量。
-
动态配置服务
动态配置服务可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。
动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。
配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。
Nacos 提供了一个简洁易用的UI (控制台样例 Demo) 帮助您管理所有的服务和应用的配置。Nacos 还提供包括配置版本跟踪、金丝雀发布、一键回滚配置以及客户端配置更新状态跟踪在内的一系列开箱即用的配置管理特性,帮助您更安全地在生产环境中管理配置变更和降低配置变更带来的风险。
3.2 安装Nacos
开发阶段采用单机安装即可。
3.2.1 下载安装包
在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:
GitHub主页:https://github.com/alibaba/nacos
GitHub的Release下载页:https://github.com/alibaba/nacos/releases
网络不好的同学,可以直接使用课前资料提供的安装包:
3.2.2 解压
将压缩包上传到在 Linux 的 /opt/
目录下:
解压:tar -zxf nacos-server-1.3.2.tar.gz
目录说明:
- bin:启动脚本
- conf:配置文件
- data:本地数据
- logs:日志
Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。
如果无法关闭占用8848端口的进程,也可以进入nacos的conf目录,修改配置文件中的端口:
# 进入nacos目录下的conf文件夹
cd conf
# 可以修改 application.yml 文件
vim application.yml
3.2.3 启动
启动非常简单,进入bin目录
cd /opt/nacos/bin
然后执行命令即可:
sh startup.sh -m standalone
执行后的效果如图:
通过docker方式启动
# 拉取镜像
docker pull nacos/nacos-server:1.3.2
# 创建并运行容器
docker run -d \
-e MODE=standalone \
-e JVM_XMS=256m \
-e JVM_XMX=256m \
-e JVM_XMN=128m \
-p 8848:8848 \
--name nacos \
nacos/nacos-server:1.3.2
3.2.4 访问
在浏览器输入地址:http://192.168.200.129:8848/nacos即可:
默认的账号和密码都是nacos,进入后:
3.3 搭建工程
下面我们通过搭建一个Demo工程,学习下Nacos的基本使用。
3.3.1 导入
首先,与eureka学习的时候一样,我们也需要准备服务的提供者和消费者,这次我们不再自己写了,而是导入资料中准备好的一个Demo:
项目中包括:
- nacos-demo:父工程,管理依赖
- user-service:用户服务,提供根据id查询用户的功能,端口是8081
- consumer-service:消费者服务,通过RestTemplate远程查询user-service中的用户信息,端口是8080
如图:
3.3.2 代码流程介绍
消费者consumer-service的启动类中,定义了RestTemplate:
然后,在consumer的web
包中,提供了一个controller,并远程调用user-service,代码如下:
3.3.3 访问测试
启动user-service和consumer,然后在浏览器输入地址访问:http://localhost:8080/consumer/rest/itcast
效果:
3.4 Nacos注册中心
Nacos与Eureka一样,都可以作为注册中心使用,并且Nacos实现了SpringCloudCommon中的一些接口,并且提供了对应的自动配置,这就让Nacos的注册中心使用与Eureka几乎一模一样,没有什么学习成本。
3.4.1 服务注册
我们先将user-service注册到Nacos,基本步骤如下:
- 导入依赖
- 配置Nacos地址
3.4.1.1 导入依赖
为了统一管理SpringCloudAlibaba的组件版本,我们已经在父工程nacos-demo的pom文件中引入一个依赖,位置在<dependenciesManagement>
下:
这样,我们在引入alibaba的其它相关组件时就无需指定版本了。
我们在user-service的pom.xml文件中引入依赖:
<!--alibaba的 Nacos依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
3.4.1.2 配置Nacos地址
我们修改user-service
的application.yml
文件,其中配置nacos的地址:
spring:
# 。。。 其它配置略
cloud:
nacos:
discovery:
server-addr: 192.168.200.129:8848 # nacos服务地址
3.4.1.3 引导类添加注解
3.4.1.4 启动服务测试
重启user-service,然后去访问Nacos的页面:http://192.168.200.129:8848/nacos可以发现:
3.4.2 服务发现
服务发现可以用RestTemplate实现,也可以用OpenFeign来实现。
3.4.2.1 引入依赖
在consumer-service的pom文件中,引入依赖:
<!--alibaba的 Nacos依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
3.4.2.2 配置Nacos地址
在consumer-service的bootstrap.yaml文件中,配置nacos地址:
spring:
application:
name: consumer
profiles:
active: dev
cloud:
nacos:
discovery:
server-addr: 192.168.200.129:8848 # nacos服务地址
3.4.2.3 基于RestTemplate服务发现
RestTemplate服务发现和负载均衡与之前玩法完全一样,我们给consumer-service
的启动类:ConsumerApplication
中的restTemplate上添加@LoadBalanced
注解:
@Bean
@LoadBalanced // 负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
然后,修改consumer-service项目中的ConsumerController代码,将请求url中的IP和端口改为服务id:user-service:
重启consumer-service,然后在浏览器测试:http://localhost:8080/consumer/rest/itcast
3.4.2.4 基于Feign服务发现
Feign的负载均衡也与之前没有任何区别。
(1)在consumer-service中的cn.itcast.consumer.feign
包中,新建一个UserClient
接口:
package cn.itcast.consumer.feign;
import cn.itcast.consumer.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* @author
*/
@FeignClient("user-service")
public interface UserClient {
@GetMapping("/user/{id}")
User queryUserById(@PathVariable("id") String id);
}
(2)修改consumer-service的启动类ConsumerApplication类,添加@EnableFeignClients
注解:
(3)在consumer-service的ConsumerController
类中添加一段新的代码,使用UserClient来访问:
@Autowired
private UserClient userClient;
@GetMapping("/client/{id}")
public User queryUserById(@PathVariable("id") String id){
return userClient.queryUserById(id);
}
重启consumer-service,然后在浏览器测试:
http://localhost:8080/consumer/client/itcast
3.4.3 路由网关配置
3.4.3.1新建工程
添加gateway依赖:
3.4.3.2编写启动类
3.4.3.3编写配置
3.4.3.4编写路由规则
3.5 Nacos配置中心
Nacos除了作为注册中心,还可以用来代替SpringCloudConfig作为配置中心。而且SpringCloudConfig在使用是有下列弊端:
- 依赖于外部的Git仓库做配置管理
- 集群中的配置刷新依赖于消息总线SpringCloudBus
而Nacos的配置中心都有自己来实现,包括配置文件管理、文件历史记录都由Nacos自己完成。
微服务只需要监听Nacos即可,无需通过消息总线做配置同步。
目前市面上用的比较多的配置中心有:Spring Cloud Config、Apollo、Nacos等。
下面主要对比一下Spring Cloud Confifig、Apollo和Nacos。
从配置中心角度来看,性能方面Nacos的读写性能最高,Apollo次之,Spring Cloud Confifig依赖Git场景不适合开
放的大规模自动化运维API。功能方面Apollo最为完善,nacos具有Apollo大部分配置管理功能,而Spring Cloud
Confifig不带运维管理界面,需要自行开发。Nacos的一大优势是整合了注册中心、配置中心功能,部署和操作相比
Apollo都要直观简单,因此它简化了架构复杂度,并减轻运维及部署工作。
综合来看,Nacos的特点和优势还是比较明显的,下面我们一起进入Nacos的世界。
3.5.1 RestAPI
Nacos中的配置管理都是通过Rest风格的API来实现的,当然也可以通过UI界面来手动完成。其中RestAPI的官方说明如下:https://nacos.io/zh-cn/docs/open-api.html
配置管理的相关API包括:
例如,我们要发布一个新的配置,API文档是这样的:
-
请求类型:POST
-
请求路径:/nacos/v1/cs/configs
-
请求参数:
名称 类型 是否必须 描述 tenant string 否 租户信息,对应 Nacos 的命名空间ID字段 dataId string 是 配置 ID group string 是 配置分组 content string 是 配置内容 type String 否 配置类型 -
返回参数:boolean
请求示例:
curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=consumer-dev.properties&group=DEFAULT_GROUP&content=redis.host=localhost%0Aredis.port=6379"
这里是利用linux的curl命令发送了一个post请求,参数包括:
-
dataId:consumer-dev.properties,其一般格式是:
${spring.application.name}-${spring.profiles.active}.${file.extension} # {服务名称}-{springboot激活的profile}.{后缀名} # consumer:是服务名 # dev:是SpringBoot激活的profile # properties:后缀名
-
group:DEFAULT_GROUP
-
content:配置内容,包括
- redis.host=localhost
- redis.port=6379
可以使用PostMan测试
3.5.2 引入依赖
下面,我们在consumer-service中测试下Nacos的配置中心功能。Nacos的配置中心客户端,需要引入相关依赖。
我们在consumer-service的pom.xml中添加新依赖:
<!--alibaba的 Nacos配置中心客户端依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
3.5.3 设置配置中心地址
修改consumer-service的bootstrap.yaml文件,添加配置中心Nacos的地址:
spring:
application:
name: consumer
profiles:
active: dev
cloud:
nacos:
discovery:
server-addr: 192.168.200.129:8848 # nacos服务地址
config:
server-addr: 192.168.200.129:8848 # nacos配置中心地址
file-extension: yaml
3.5.4 测试配置刷新
我们在consumer-service中已经有一个RedisProperties
类,内容如下:
@Component
@Data
@ConfigurationProperties(prefix = "text.redis")
public class RedisProperties {
private String host;
private int port;
}
这个类会尝试读取yaml文件中的reids.host和redis.port两个属性,不过本地配置文件中是没有的,需要去nacos配置中心读取。
另外,我们的consumer-service中,已经有一个接口,可以用来测试该属性的读取效果:
我们重启consumer-service,访问页面:http://127.0.0.1:8080/consumer/hello
我们在nacos的控制台的配置管理界面中,点击编辑按钮:
在弹出的表单中,修改配置,将端口修改为6380:
然后,刷新浏览器,查看属性:
微服务中的属性已经更新为6380了!
3.6 Nacos集群部署
3.6.1 集群部署
3个或3个以上Nacos节点才能构成集群,
参考:https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html
(1)安装3个以上Nacos
我们可以复制之前已经解压好的nacos文件夹,分别命名为nacos1、nacos2、nacos3
(2)配置集群配置文件
在所有nacos目录的conf目录下,有文件 cluster.conf.example ,将其命名为 cluster.conf
,并将每行配置成 ip:port。(请配置3个或3个以上节点)
# ip:port
127.0.0.1:8848
127.0.0.1:8849
127.0.0.1:8850
由于是单机演示,需要更改nacos/的conf目录下application.properties
中server.port,防止端口冲突。
如果服务器有多个ip也要指定具体的ip地址,如:nacos.inetutils.ip-address=127.0.0.1
例如:
server.port=8850
nacos.inetutils.ip‐address=127.0.0.1
修改启动的JVM参数:
进入bin路径下,修改 startup.sh
启动文件
vim startup.sh
(3)集群模式启动
分别执行nacos目录的bin目录下的startup:
编写sh脚本执行:
vim start.sh
/opt/nacosCluster/nacos1/bin/startup.sh -p embedded
/opt/nacosCluster/nacos2/bin/startup.sh -p embedded
/opt/nacosCluster/nacos3/bin/startup.sh -p embedded
修改 start.sh
可执行权限:chmod u+x start.sh
在任意一个nacos的控制台中,可以看到如下内容:http://192.168.200.129:8849/nacos/
3.6.2 客户端配置
所有客户端,分别指定nacos集群中的若干节点:
spring:
cloud:
nacos:
config:
server‐addr: 192.168.200.129:8848,192.168.200.129:8849,192.168.200.129:8850
测试,使用快速上手的例子: 查看三个nacos上是否都有对应的服务
3.6.3 生产环境部署建议
下图是官方推荐的集群方案,通过域名 + VIP模式的方式来实现。客户端配置的nacos,当Nacos集群迁移时,客户端配置无需修改。
至于数据库,生产环境下建议至少主备模式。通过修改${nacoshome}/conf/application.properties文件,能够使 nacos拥有多个数据源。
spring.datasource.platform=mysql
db.num=2
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&autoReconnect=true
db.url.1=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&autoReconnect=true
db.user=root
db.password=root
参考:https://www.cnblogs.com/didispace/p/10412352.html
4 微服务部署
前面我们介绍基于SpringBoot开发的微服务项目,特点是快速构建web服务,但是我们需要在项目开发完之后可以立即快速构建统一的运行环境,并且还可以快速的部署项目上线。所以Docker在设计起初就希望开发者将微服务项目部署至容器中运行。
4.1 基于Dockerfile构建镜像部署
-
将EurekaServer微服务项目打成jar包
<build> <!--打包项目名称--> <finalName>boot_eureka</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
执行mvn打包
-
在
/opt/docker
目录下创建Dockerfile
文件# 新建文件 vim Dockerfile
添加一下内容:
# 仓库中也有关于jdk的镜像 FROM java:8-alpine # 也可以基于自己构建的镜像 # FROM jdk8 COPY ./boot_eureka.jar /tmp/app.jar EXPOSE 10086 ENTRYPOINT ["java","-jar","/tmp/app.jar"]
解读:
FROM java:8-alpine
:镜像基于java8来构建COPY ./boot_eureka.jar /tmp/app.jar
:拷贝jar包到镜像中的/tmp目录,并重命名为app.jarEXPOSE 10086
:对外暴露10086端口ENTRYPOINT ["java","-jar","/tmp/app.jar"]
:镜像入口,通过java -jar
来运行SpringBoot项目
-
构建镜像
docker build -t boot_eureka:1.0 .
通过
docker images
查看: -
运行容器
docker run -id --name=eureka --hostname=eureka -p 10086:10086 boot_eureka:1.0
-
查看日志
docker logs -f eureka
-
浏览器测试访问:http://192.168.200.130:10086
4.2 基于DockerMaven插件构建镜像部署
对于数量众多的微服务,手动部署无疑是非常麻烦的做法,并且容易出错。所以我们这里学习如何自动部署,这也是企业实际开发中经常使用的方法。
Maven插件自动部署步骤:
-
修改宿主机的docker配置,让其可以远程访问
vi /lib/systemd/system/docker.service
替换ExecStart 一行配置:
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock
-
刷新配置,重启服务
## 1 如果没有搭建则创建并启动容器 docker run -id --name=registry -p 5000:5000 registry ## 2 配置私有仓库地址 vi /etc/docker/daemon.json { "registry-mirrors": ["https://pasw6qxp.mirror.aliyuncs.com"], "insecure-registries": ["192.168.200.130:5000"] } ## 3 重启docker systemctl daemon-reload systemctl restart docker docker start registry eureka
-
在工程的pom.xml 文件中添加一下配置
<build> <!--修改app.jar--> <finalName>app</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <!-- docker的maven插件,官网:https://github.com/spotify/docker-maven-plugin --> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>1.2.2</version> <configuration> <!--镜像的名称 跳过上传到私有仓库--> <imageName>192.168.200.130:5000/${project.artifactId}:${project.version}</imageName> <!--上传到私有仓库--> <!--<imageName>106.14.241.224:5000/${project.artifactId}:${project.version}</imageName>--> <!--依赖一个基础镜像 带JDK 1.8--> <baseImage>java:8-alpine</baseImage> <!--java -jar app.jar --> <entryPoint>["java", "-jar", "/${project.build.finalName}.jar"]</entryPoint> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> </resource> </resources> <dockerHost>http://192.168.200.130:2375</dockerHost> </configuration> </plugin> </plugins> </build>
需要注意:
- 保证当前有私服并开启
- 要基于jdk使用
- 使用spring-boot-maven-plugin打包插件需要,当前父工程继承spring-boot-starter-parent工程
以上配置会自动生成Dockerfile [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pq8MCmQ3-1653639426871)(assets/image-20201108222027851.png)]
-
在windows的命令提示符下,进入config-server工程所在的目录,输入以下命令,进行打包和上传镜像
# 打包并且将镜像上传到私有注册中心中 mvn clean package docker:build -DpushImage # 跳过上传 mvn clean package docker:build -DskipDockerPush
(C:\Users\Alienware\AppData\Roaming\Typora\typora-user-images\1653043566787.png执行后,会有如下输出,代码正在上传
浏览器访问http://192.168.200.129:5000/v2/_catalog 查看镜像列表
-
进入宿主机 , 查看镜像列表
-
启动容器
1)先需要mq和eureka在同一个网络内
docker network connect my-net eureka # eureka docker network connect my-net mq # mq
2)创建并运行容器
docker run -id --name=config_server --network my-net -p 12580:12580 [镜像ID|镜像名称:tag]
-
查看日志config_server是否成功启动
-
访问 http://192.168.200.150:10086/ 查看是否注册
4.3 基于Idea一键构建容器部署
- Idea安装Docker插件
-
配置连接远程Docker
配置远程的地址为:tcp://192.168.200.129:2375
- 开始建立连接
注意:需要开启远程Docker访问权限
-
在项目的根目录下 添加Dockerfile文件,添加内容如下:
# 设置JAVA版本 FROM java:8-alpine # 指定存储卷, 任何向/tmp写入的信息都不会记录到容器存储层 VOLUME /tmp COPY /target/app.jar /app.jar # 设置JVM运行参数, 这里限定下内存大小,减少开销 ENV JAVA_OPTS="\ -server \ -Xms256m \ -Xmx512m \ -XX:MetaspaceSize=256m \ -XX:MaxMetaspaceSize=512m" # 入口点, 执行JAVA运行命令 ENTRYPOINT ["java", "-jar", "/app.jar"]
完整结构:
在工程的pom.xml 文件中添加一下配置
<build> <!--修改app.jar--> <finalName>app</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <!-- docker的maven插件,官网:https://github.com/spotify/docker-maven-plugin --> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>1.2.2</version> <configuration> <!--镜像的名称 跳过上传到私有仓库--> <imageName>192.168.200.130:5000/${project.artifactId}:${project.version}</imageName> <!--上传到私有仓库--> <!--<imageName>106.14.241.224:5000/${project.artifactId}:${project.version}</imageName>--> <!--依赖一个基础镜像 带JDK 1.8--> <baseImage>java:8-alpine</baseImage> <!--java -jar app.jar --> <entryPoint>["java", "-jar", "/${project.build.finalName}.jar"]</entryPoint> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> </resource> </resources> <dockerHost>http://192.168.200.130:2375</dockerHost> </configuration> </plugin> </plugins> </build>
-
编辑项目启动的配置
- 选择Dockerfile创建容器,配置如下:
- 点击Run运行,查看信息
Docker镜像列表和查看正在运行的容器
-
打开浏览器访问 http://192.168.200.150:10086/
浏览器测试:http://192.168.200.150:8081/hello/prop