当前架构不断演进,分布式架构的重要性越开越高,本文就记录一下整个搭建过程,本次主要利用springcloud自带的分布式特性,由于nacos可以支持动态刷新以及拥有可视化界面,方便服务上下线管理,故采用nacos提代eureka以及config,利用springboot admin配合acturaror对各微服务进行监控,同时利用nacos的动态刷新配合gateway实现动态更新路由,为了保证数据安全,采用Springcloud security对admin模块进行加密,防止数据泄露,具体技术选型大家自行判断。
您可以在Nacos的release notes中找到每个版本支持的功能的介绍,当前使用的稳定版本为2.0.3。
Nacos 依赖Java环境来运行,需要安装JDK8+
您可以从 最新稳定版本 下载编译好的安装包如nacos-server-2.0.3.tar.gz。
下载后解压安装包
tar -xvf nacos-server-2.0.3.tar.gz
进入解压目录nacos
cd nacos
解压后目录结构如截图
按照官方文档配置启动默认是不需要登录的这样会导致你的配置中心对外直接暴露,如果需要在微服务注册时进行认证,则需要修改conf目录下的application.properties,改为如下配置:
nacos.core.auth.enabled=true
进入bin目录,启动命令(standalone代表着单机模式运行,非集群模式):
sh startup.sh -m standalone
关闭命令为
sh shutdown.sh
nacos默认密码为nacos/nacos。如果需要修改密码,则进入页面可以修改密码
页面地址:http://ip:8848/nacos
将ip改为你部署的实际IP
具体nacos官网说明地址为Nacos 快速开始
使用springcloud多模块搭建,父工程pom文件如下:
4.0.0
com.test
springcloud-demo
1.0.0
pom
springcloud-demo
org.springframework.boot
spring-boot-starter-parent
2.2.10.RELEASE
gateway
user
producer
consumer
admin
UTF-8
UTF-8
1.8
4.12
2.6
1.3.1
2.5
1.10
1.2.78
2.9.9
1.18.4
2.7.0
Hoxton.SR8
junit
junit
${junit.version}
test
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-logging
org.apache.logging.log4j
log4j-api
com.alibaba
fastjson
${fastjson.version}
org.projectlombok
lombok
${lombok.version}
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
org.springframework.cloud
spring-cloud-starter-openfeign
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
com.alibaba.cloud
spring-cloud-alibaba-dependencies
2.2.5.RELEASE
pom
import
io.springfox
springfox-swagger2
${swagger.version}
io.springfox
springfox-swagger-ui
${swagger.version}
joda-time
joda-time
${joda.time.version}
commons-lang
commons-lang
${commons.lang.version}
commons-fileupload
commons-fileupload
${commons.fileupload.version}
commons-io
commons-io
${commons.io.version}
commons-codec
commons-codec
${commons.codec.version}
org.apache.maven.plugins
maven-compiler-plugin
3.1
1.8
UTF-8
public
aliyun nexus
http://maven.aliyun.com/nexus/content/groups/public/
true
public
aliyun nexus
http://maven.aliyun.com/nexus/content/groups/public/
true
false
pom文件如下:
com.test
springcloud-demo
1.0.0
4.0.0
com.example
admin
1.0.0
admin
admin for Spring Boot
org.springframework.boot
spring-boot-starter-web
de.codecentric
spring-boot-admin-starter-server
2.2.1
org.springframework.boot
spring-boot-starter-security
de.codecentric
spring-boot-admin-server-ui
2.2.1
org.apache.maven.plugins
maven-compiler-plugin
3.8.1
1.8
UTF-8
org.springframework.boot
spring-boot-maven-plugin
com.example.demo.AdminApplication
repackage
repackage
在resources目录下删除application.yml,新建bootstrap.yml,文件内容如下:
# 应用名称
spring:
security:
user:
name: admin
password: admin
application:
name: admin
cloud:
nacos:
username: nacos
password: XXX
config:
server-addr: nacos.com:8848
namespace: 4712216e-5fb1-4855-b57e-d57fae45809b
file-extension: yaml
discovery:
server-addr: nacos.com:8848
namespace: 4712216e-5fb1-4855-b57e-d57fae45809b
metadata:
user.name: ${spring.security.user.name}
user.password: ${spring.security.user.password}
profiles:
active: dev
jmx:
enabled: true
server:
port: 9000
其中,spring.security.user.name为admin界面的用户名配置,spring.security.user.password为admin界面的密码配置,经测试,若通过nacos下发配置文件用户名和密码不生效,在启动日志中会输出默认密码。spring.cloud.nacos.username为nacos设置的用户名,spring.cloud.nacos.password为nacos设置的相应的密码。另外注意的是,spring.cloud.nacos.discovery.metadata.user.name 的配置是因为引入security后若不配置相应的密码,会导致无法在注册中心注册。另外如果在nacos上新建了命名空间的话则需要上述的spring.cloud.nacos.discovery.namespace以及spring.cloud.nacos.config.namespace配置,如果在默认命名空间则不需要该配置。
在admin server工程中新建SecuritySecureConfig类,具体内容如下
import de.codecentric.boot.admin.server.config.AdminServerProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
@Configuration
public class SecuritySecureConfig extends WebSecurityConfigurerAdapter {
private final String adminContextPath;
public SecuritySecureConfig(AdminServerProperties adminServerProperties) {
this.adminContextPath = adminServerProperties.getContextPath();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successHandler.setTargetUrlParameter("redirectTo");
successHandler.setDefaultTargetUrl(adminContextPath + "/");
http.authorizeRequests()
//授予对所有静态资产和登录页面的公共访问权限
.antMatchers(adminContextPath + "/assets/**").permitAll()
.antMatchers(adminContextPath + "/login").permitAll()
//必须对每个其他请求进行身份验证
.anyRequest().authenticated()
.and()
//配置登录和注销
.formLogin().loginPage(adminContextPath + "/login").successHandler(successHandler).and()
.logout().logoutUrl(adminContextPath + "/logout").and()
//启用HTTP-Basic支持。这是Spring Boot Admin Client注册所必需的
.httpBasic().and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringAntMatchers(
// 禁用CRSF保护Spring引导管理客户端用来注册的端点。
adminContextPath + "/instances",
// 禁用执行器端点的CRSF保护
adminContextPath + "/actuator/**"
);
}
}
在admin server工程中的启动类上添加@EnableAdminServer 以及@EnableDiscoveryClient注解。并在resources目录下新建logback-spring.xml,具体内容配置如下:
${APP_Name}
${CONSOLE_LOG_PATTERN}
utf8
${LOG_HOME}/admin.log
${LOG_HOME}/output-%d{yyyy-MM-dd}.log
30
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n
在nacos上创建配置admin-dev.yaml(具体命名规则见官网说明),配置如下:
#### 暴露端点
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
loggers:
enabled: true
logfile:
external-file: /apps/logs/admin/admin.log
health:
show-details: always
spring:
jmx:
enabled: true
其中,management.endpoint.logfile.external-file参数为logback-spring.xml中配置的日志文件,这样在admin的管理界面可以动态查看日志记录。
至此admin server搭建完成,大家可以启动看一下是否注册成功,并登录admin server的管理界面进行查看监控数据,地址一般为http://ip:port/login ,其中ip为部署admin server的地址,port为yml文件中配置的地址,输入用户名和密码即可查看。
pom文件如下:
4.0.0
com.test
springcloud-demo
1.0.0
gateway
gateway网关
1.0.0
org.springframework.cloud
spring-cloud-starter-gateway
de.codecentric
spring-boot-admin-starter-client
2.2.1
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
com.alibaba.cloud
spring-cloud-alibaba-sentinel-gateway
org.jolokia
jolokia-core
io.springfox
springfox-swagger2
io.github.openfeign
feign-okhttp
org.springframework.cloud
spring-cloud-starter-openfeign
${project.artifactId}-${project.version}
org.springframework.boot
spring-boot-maven-plugin
在resources目录下删除application.yml,新建bootstrap.yml,文件内容如下:
#端口号
server:
port: 9100
# nacos 注册
spring:
jmx:
enabled: true
application:
name: gateway-zuul #服务名
cloud:
nacos:
username: nacos
password: Haha135790
config:
server-addr: nacos.com:8848
namespace: 4712216e-5fb1-4855-b57e-d57fae45809b
file-extension: yaml
group: DEFAULT_GROUP
discovery:
server-addr: nacos.com:8848
namespace: 4712216e-5fb1-4855-b57e-d57fae45809b
metadata:
management:
context-path: /actuator
profiles:
active: dev
具体密码配置与admin server类似,主要是配置nacos的密码,只要相应的通知nacos即可注册成功,同目录下也需要配置logback-spring.xml。
大家可以参考openfeign的使用配置一些服务间调用,这个比较基础,这里就不详细展开了,后续可以进行测试。
在工程启动类上配置@EnableDiscoveryClient以及@EnableFeignClients(basePackages = "com.test.demo.gateway.feign"),并在nacos上配置配置文件gateway-zuul-dev.yaml,具体内容如下:
test:
name: shen1
cors-config:
origin: "*"
session-filter:
ignored-path: /login
#### 暴露端点
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
loggers:
enabled: true
logfile:
external-file: /apps/logs/zuul/zuul.log
health:
show-details: always
spring:
cloud:
gateway:
routes:
- id: demo-producer
uri: lb://demo-producer
predicates:
- Path=/producer/**
- id: user
uri: lb://user-api
predicates:
- Path=/user/**
discovery:
locator:
enabled: true
jmx:
enabled: true
日志文件路径也需要与logback-spring.xml中设置的一致,lb配置后面跟的是服务名,用于转发,spring.cloud.discovery.locator.enable需要设置为true,用于通过服务名进行查找相关微服务。
若想实现动态刷新路由,还需要增加一个配置文件NacosDynamicRouteConfig,具体内容如下:
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
/**
* nacos动态路由配置
*/
@Component
@Slf4j
@RefreshScope
public class NacosDynamicRouteConfig implements ApplicationEventPublisherAware {
@Value("${spring.application.name}"+"-"+"${spring.profiles.active}")
private String dataId;
@Value("${spring.cloud.nacos.config.group}")
private String group;
@Value("${spring.cloud.nacos.config.server-addr}")
private String serverAddr;
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher applicationEventPublisher;
private static final List ROUTE_LIST = new ArrayList<>();
@PostConstruct
public void dynamicRouteByNacosListener() {
try {
log.info("dataId:{}",dataId);
ConfigService configService = NacosFactory.createConfigService(nacosConfigProperties.assembleConfigServiceProperties());
String configInfo = configService.getConfig(dataId, group, 5000);
log.info("configInfo:{}",configInfo);
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
clearRoute();
try {
List gatewayRouteDefinitions = JSONObject.parseArray(configInfo, RouteDefinition.class);
for (RouteDefinition routeDefinition : gatewayRouteDefinitions) {
addRoute(routeDefinition);
}
publish();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Executor getExecutor() {
return null;
}
});
} catch (NacosException e) {
e.printStackTrace();
}
}
private void clearRoute() {
for(String id : ROUTE_LIST) {
this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
}
ROUTE_LIST.clear();
}
private void addRoute(RouteDefinition definition) {
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
ROUTE_LIST.add(definition.getId());
} catch (Exception e) {
log.error("添加路由异常!",e);
}
}
private void publish() {
this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this.routeDefinitionWriter));
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
}
需要注意的是,因为我们nacos设置了密码,所以获取ConfigService时需要参照示例,否则会出现认证问题,这样网关模块也搭建完成,只需要动态更新nacos的配置就可以实时更新路由,当然网关的一些个人配置大家可以自行配置,这里只是主要说明如何搭建整体框架。
这里的用户微服务模块在我搭建过程中只是为了给网关提供feign调用,需要注意的是,当微服务配置了server.servlet.context-path参数后,采集也需要配置,否则admin展示采集数据会有问题,也就是文件中相应的配置了spring.cloud.nacos.discovery.metadata.management.context-path参数。user模块的bootstrap.yml配置文件如下:
# nacos 注册
spring:
application:
name: user-api #服务名
cloud:
nacos:
username: nacos
password: Haha135790
config:
server-addr: nacos.com:8848
namespace: 4712216e-5fb1-4855-b57e-d57fae45809b
file-extension: yaml
discovery:
server-addr: nacos.com:8848
namespace: 4712216e-5fb1-4855-b57e-d57fae45809b
metadata:
management:
context-path: ${server.servlet.context-path}/actuator
profiles:
active: dev
server:
port: 8090
servlet:
context-path: /user
其他具体设置就不写了,也没有什么特别的。
整体框架就是这样,注意的是,nacos要想实现动态刷新,需要在引入配置文件的地方加上@RefreshScope注解,这样就整体搭建了一套微服务,可实现服务的注册、配置文件的下发与动态更新,同时通过admin实现微服务的监控,整个过程都通过Spring Security设置了用户名以及密码防止安全问题,其实密码可以通过加密算法加密来实现,这样基本上可以保证一些安全性,如果大家有更好的想法,也欢迎随时沟通。
程序之路漫漫,吾将上下而求索