Eureka服务注册与发现
搭建父工程
父工程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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.cc</groupId>
<artifactId>springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>eureka-server</module>
<module>eureka-client</module>
</modules>
</project>
创建子工程Module,命名eureka-server作为服务注册中心
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.cc</groupId>
<artifactId>springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.cc</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置application.properties
server.port=9000
#提交IP信息
eureka.instance.prefer-ip-address=true
eureka.instance.hostname=localhost
#设置这两个防止注册自己
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
启动类上加上@EnableEurekaServer注解开启服务
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
启动项目访问http://localhost:9000
配置Eureka客户端
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.cc</groupId>
<artifactId>springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<artifactId>eureka-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-client</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties
#服务注册的地址
#eureka.instance.prefer-ip-address=true
eureka.client.service-url.defaultZone=http://127.0.0.1:9000/eureka
server.port=8001
spring.application.name=eureka-client
启动类
@SpringBootApplication
@EnableEurekaClient
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
}
访问http://localhost:9000
写一个controller类
@RestController
public class HiController {
@Value("${server.port}")
String port;
@GetMapping("hi")
public String hi(){
return "hi"+port;
}
}
访问http://localhost:8001/hi,显示hi80001
Eureka Server集群
创建application.properties
spring.profiles.active=
application-1.properties
server.port=9001
spring.application.name=eureka-server
eureka.instance.hostname=ek1
eureka.client.service-url.defaultZone=http://ek2:9002/eureka/,http://ek3:9003/eureka/
application-2.properties
server.port=9002
spring.application.name=eureka-server
eureka.instance.hostname=ek2
eureka.client.service-url.defaultZone=http://ek1:9001/eureka/,http://ek3:9003/eureka/
application-3.properties
server.port=9003
spring.application.name=eureka-server
eureka.instance.hostname=ek3
eureka.client.service-url.defaultZone=http://ek1:9001/eureka/,http://ek2:9002/eureka/
ek1,ek2,ek3对应127.0.0.1地址的别名
在C:\Windows\System32\drivers\etc\hosts修改
IDEA中设置可以启动多个服务
启动三个服务
启动前修改
application.properties
spring.profiles.active=1
spring.profiles.active=2
spring.profiles.active=3
访问http://ek1:9001
启动客户端
修改application.properties
#服务注册的地址
#eureka.instance.prefer-ip-address=true
eureka.client.service-url.defaultZone=http://127.0.0.1:**9001**/eureka
server.port=8001
spring.application.name=eureka-client
启动服务访问http://ek1:9001
访问http://ek2:9002同样可以发现服务
Ribbon实现负载均衡
在上面的基础上启动两个服务提供方(EUREK-CLIENT)端口为8001,8002
创建服务的消费方ribbon-client
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
添加application.properties配置
spring.application.name=ribbon-client
server.port=7000
eureka.client.service-url.defaultZone=http://localhost:9000/eureka/
添加配置类开启负载算法
@Configuration
public class RibbonConfig {
@Bean
@LoadBalanced
RestTemplate restTemplate(){
return new RestTemplate();
}
}
创建service消费eureka-client
@Service
public class RibbonService {
@Autowired
RestTemplate restTemplate;
public String hi(){
return restTemplate.getForObject("http://eureka-client/hi",String.class);
}
}
创建控制类
@RestController
public class RibbonController {
@Autowired
RibbonService ribbonService;
@GetMapping("hi")
public String hi(){
return ribbonService.hi();
}
}
启动类添加@EnableEurekaClient注解。启动项目,访问http://localhost/7000/hi
hi8001
hi8002
发现在这两个之间相互跳转,因而实现了负载均衡
Feign使用
在上面项目的基础上新创建一个Module,取名为feign-client
pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
application.properties
spring.application.name=feign-client
server.port=8800
eureka.client.service-url.defaultZone=http://localhost:9000/eureka/
启动类加上@EnableFeignClients注解
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class FeignClientApplication {
public static void main(String[] args) {
SpringApplication.run(FeignClientApplication.class, args);
}
}
配置一个FeignConfig类,注入一个Retryer的Bean,Feign在远程调用失败后会进行重试
@Configuration
public class FeignConfig {
@Bean
public Retryer feignRetryer(){
return new Retryer.Default(100,SECONDS.toMillis(1),5);
}
配置一个EurekaClientFeign接口进行远程调用服务
@FeignClient(value = "eureka-client",configuration = FeignConfig.class)
public interface EurekaClientFeign {
@GetMapping("hi")
String hi();
}
配置一个service消费服务
@Service
public class Hiservice {
@Autowired
EurekaClientFeign eurekaClientFeign;
public String hi(){
return eurekaClientFeign.hi();
}
}
配置一个controller暴露接口
@RestController
public class hicontroller {
@Autowired
Hiservice hiservice;
@GetMapping("hi")
public String hi(){
return hiservice.hi();
}
}
启动eureka-service,启动两个eureka-client,端口为8001,8002,启动feign-client
访问http://localhost:8800/hi
hi8001
hi8002
在这两个轮流出现,说明feign具有负载均衡能力。
熔断器Hystrix
分布式系统中,不免出现服务的故障,一个服务的故障,导致依赖于它的其他微服务可能出现线程阻塞,最终可能导致系统的瘫痪,为了提高系统的容错能力,Hystrix孕育而生。有两种使用Hystrix的场景,RestTemplate和Ribbon作为消费时使用Hystrix,Feign作为服务消费时使用Hystrix.
RestTemplate和Ribbon上使用Hystrix
在之前的基础上改造项目,在ribbon-client中添加如下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
启动类添加@EnableHystrix注解开启熔断功能
改造RibbonService类
@Service
public class RibbonService {
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "hiError")
public String hi(){
return restTemplate.getForObject("http://eureka-client/hi",String.class);
}
public String hiError(){
return "hi,sorry error!";
}
}
@HystrixCommand(fallbackMethod=“hiError”)注解是服务不可用时回退处理hiError方法。
依次启动eureka-server,eureka-client,ribbon-client,访问http://localhost:7000/hi,显示
hi8002
关闭eureka-client服务,再访问http://localhost:7000/hi,显示
hi,sorry error!
Feign中使用Hystrix
Feign中的起步依赖已经引入了Hystrix的依赖,所以不再导入依赖,再application.properties中开启Hystrix的功能
feign.hystrix.enabled=true
修改EurekaClientFeign接口的代码,在@FeignClient注解中加入回滚的类,该类必须实现被@FeignClient修饰的接口,最后需要注入IoC容器中。
@FeignClient(value = "eureka-client",configuration = FeignConfig.class,fallback = HiHystrix.class)
public interface EurekaClientFeign {
@GetMapping("hi")
String hi();
}
@Component
public class HiHystrix implements EurekaClientFeign{
@Override
public String hi(){
return "hi,sorry,error!";
}
}
启动eureka-server,eureka-client,feign-client,访问http://localhost:8800/hi,显示
hi8002
关闭eureka-client,再访问http://localhost:8800/hi,显示
hi,sorry,error!
使用Hystrix Dashboard监控熔断器的状态
在RestTemplate中使用Hystrix Dashboard
在ribbon-client工程中添加如下pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
在启动类中添加@EnableHystrixDashboard开启 Hystrix Dashboard的功能
在application.properties中添加如下代码暴露所有端口
management.endpoints.web.exposure.include=*
依次启动eureka-server,eureka-client,ribbon-client,
浏览器先访问http://localhost:7000/hi
再访问http://localhost:7000/actuator/hystrix.stream
再访问http://localhost:7000/hystrix
在上面填写http://localhost:7000/actuator/hystrix.stream, 2000, cc点击"monitor"进入
上图显示数据的各项指标
Feign中使用Hystrix Dashboard
在ribbon-client工程的pom中添加如下依赖,因为Feign自带的Hystrix的依赖不是起步依赖,所以还需加入spring-cloud-starter-netflix-hystrix
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
在启动类添加@EnalbeHystrixDashboard注解开启HystrixDashboard的功能。
application.properties中添加:
management.endpoints.web.exposure.include=*
在浏览器访问如之前一样。
使用Turbine聚合监控
新创建一个Module工程,取名monitor-client
pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
application.properties
spring.application.name=service-turbine
server.port=8888
turbine.combine-host-port=true
turbine.app-config=ribbon-client,feign-client
turbine.cluster-name-expression=new String("default")
turbine.aggregator.cluster-config=default
eureka.client.service-url.defaultZone=http://localhost:9000/eureka
启动类上添加@EnableTurbine注解
依次启动eureka-client,eureka-client,ribbon-client,feign-client,monitor-client,访问http://localhost:7000/hi,http://localhost:8800/hi,再访问http://localhost:7000/hystrix,http://localhost:8800/hystrix的任意一个,在监控流中输入:http://localhost:8888/turbine.stream,2000,cc,点击"monitor"进入
这页面就可以看见bibbon-client,feign-client的HystrixDashboard的数据。
Spring Cloud Zuul路由网关
Zuul作为微服务系统的网关组件,用于构建边界服务,致力于动态路由、过滤、监控、弹性伸缩和安全。
在之前的项目上创建一Module命名zuul-client
pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
启动类添加@EnableEurekaClient和@EnableZuulProxy注解
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class ZuulClientApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulClientApplication.class, args);
}
}
application.yml
server:
port: 5000
eureka:
client:
service-url:
defaultZone: http://localhost:9000/eureka/
spring:
application:
name: service-zuul
zuul:
routes:
hiapi:
path: /hiapi/**
service-id: eurka-client
ribbonapi:
path: /ribbonapi/**
service-id: ribbon-client
feignapi:
path: /feignapi/**
service-id: feign-client
依次启动eureka-server,eureka-client(2次),ribbon-client,feign-client,zuul-client,访问http://localhost:5000/hiapi/hi,浏览器交替显示
hi8001
hi8002
可见Zuul在路由转发做了负载均衡,多次http://localhost5000/ribbonapi/hi,http://localhost5000/feignapi/hi,访问类似
如果不需要Ribbon做负载均衡,修改application.yml(实际开发中不可取)
zuul:
routes:
hiapi: /hiapi/**
url: http://localhost:8001
重新启动zuul-service,访问http://localhost:5000/hiapi/hi,浏览器只显示
hi8001
而http://localhost5000/ribbonapi/hi,http://localhost5000/feignapi/hi能正常的负载均衡,因为在内部之前实现了负载均衡
如何像指定Url,且想负载均衡,那么需要自己维护一张注册表
application.yml修改
zuul:
routes:
hiapi:
path: /hiapi/**
serviceId: hiapi-vl
ribbon:
eureka:
enabled: false
hiapi-vl:
ribbon:
listOfServers: http://localhost:8001,http://localhost:8002 //listOfServers会出现cannot find
重新启动zuul-service,http://localhost:5000/hiapi/hi,确实能负载均衡,
但http://localhost5000/ribbonapi/hi,http://localhost5000/feignapi/hi不能正常访问了,出现了bug,总之,这部分没有必要这么做。
在Zuul上配置API接口的版本号
给服务API接口加前缀,例如:http://localhost:5000/v1/hiapi/hi,这时候序配置appplication.yml
zuul.prefix: /v1
在Zuul上配置熔断器
Zuul中集成了Hystrix依赖,只需要实现FallbackProvider接口即可
@Component
public class MyFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
return "*"; //*代表为所有的服务都添加熔断功能,成功。但“eureka-client"为单个服务添加熔断,我尝试失败了,为解决。
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 200;
}
@Override
public String getStatusText() throws IOException {
return "OK";
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("Oops!error,i'm the fallback".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return httpHeaders;
}
};
}
}
重新启动zuul-client工程,关闭掉所有eureka-client,访问http://localhost:5000/v1/hiapi/hi显示:
Oops!error,i’m the fallback
在Zuul中使用过滤器
继承ZuulFilter类,实现其中方法即可,如本案例,检查请求参数中是否传入了token参数。
@Component
public class MyFilter extends ZuulFilter {
private static Logger log= LoggerFactory.getLogger(MyFilter.class);
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request=currentContext.getRequest();
String token = request.getParameter("token");
if(token==null){
log.warn("token is empty");
currentContext.setSendZuulResponse(false);
currentContext.setResponseStatusCode(401);
try {
currentContext.getResponse().getWriter().write("token is empty");
}catch (Exception e){
return null;
}
}
log.info("ok");
return null;
}
}
重启zuul-client,访问http://localhost:5000/v1/hiapi/hi,显示
token is empty
访问http://localhost:5000/v1/hiapi/hi?token=sfsdf,显示
hi8001
服务网关(Spring Cloud Gateway)
服务网关是Spring Cloud官方推出的第二代网关框架,用于代替第一代网关Zuul,它建立在Spring Framework5之上,使用非阻塞模式,性能上优于Zuul,几乎实现了Netfix Zuul的全部功能。服务网关的核心组件也有路由和过滤器,不同在于多了一个断言(Predicate),用于判断请求交给哪个Gateway Web Hanler处理。如图
断言来自于Java8的接口
1.After路由断言工厂
在之前的项目上新建一个Module工程,取名gateway-client
pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
application.yml
server:
port: 9002
spring:
cloud:
gateway:
routes:
- id: getway9003
uri: http://localhost:7000
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
当请求在这个时间之后,请求会被转发到http://localhost:7000,启动项目工程eureka-service,eurka-client(2个),ribbon-client,gateway-client,访问http://localhost:9002/hi,会显示http://localhost:7000/hi返回的类容。与时间断言的还有Before,Between路由断言工厂。
2.Header断言工厂
application.yml
server:
port: 9002
spring:
cloud:
gateway:
routes:
- id: getway9003
uri: http://localhost:7000
predicates:
- Header=X-Request-Id, \d+
请求需要传入Header 的键为Request-Id,Header的数值为数字时才能匹配断言,重启项目,使用curl执行以下命令:
curl -H 'X-Request-Id:1' localhost:9002/hi
如果没有带X-Request-Id的Header的键,或者Header不为数字时,返回404
Postman请求如图
3.Cookie路由断言
server:
port: 9002
spring:
cloud:
gateway:
routes:
- id: getway9003
uri: http://localhost:7000
predicates:
- Cookie=name,cc
curl命令:
curl -H 'Cookie:name=cc' localhost:9002/hi
4.Host路由断言工厂
server:
port: 9002
spring:
cloud:
gateway:
routes:
- id: getway9003
uri: http://localhost:7000
predicates:
- Host=**.cc.com
curl可以这样:
curl -H 'Host:www.cc.com' localhost:9002/hi
5.Method路由断言工厂
该路由需要请求参数、如GET、POST、PUT、DELETED等
server:
port: 9002
spring:
cloud:
gateway:
routes:
- id: getway9003
uri: http://localhost:7000
predicates:
- Method=GET
curl localhost:9002/hi 成功
curl -XPOST localhost:9002/hi 失败404
6.Path路由断言工厂
server:
port: 9002
spring:
cloud:
gateway:
routes:
- id: getway9003
uri: https://blog.csdn.net
predicates:
- Path=/weixin_40911000/article/details/{segment}
curl localhost:9002/weixin_40911000/article/details/107523591
7.Query路由断言工厂
server:
port: 9002
spring:
cloud:
gateway:
routes:
- id: getway9003
uri: http://localhost:7000
predicates:
- Query=name,ab. //请求带有两个参数,也可以带有一个参数如:- Query=name
curl localhost:9002/hi?name=abc //请求带2个参数的 curl localhost:9002/hi?name=sdsg 这个会失败
curl localhost:9002/hi?name=sdsg //请求带1个参数的
Gateway中的过滤器
服务网关内置的过滤器工厂
过滤器工厂 | 作用 | 参数 |
---|---|---|
AddRequestHeader | 为原始请求添加Header | Header的名称及值 |
AddRequestParameter | 为原始请求添加请求参数 | 参数名称及值 |
AddResponseHeader | 为原始响应添加Header | Header的名称及值 |
DedupeResponseHeader | 剔除响应头中重复的值 | 需要去重的Header名称及去重策略 |
Hystrix | 为路由引入Hystrix的断路器保护 | HystrixCommand 的名称 |
FallbackHeaders | 为fallbackUri的请求头中添加具体的异常信息 | Header的名称 |
PrefixPath | 为原始请求路径添加前缀 | 前缀路径 |
PreserveHostHeader | 为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host | 无 |
RequestRateLimiter | 用于对请求限流,限流算法为令牌桶 | keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus |
RedirectTo | 将原始请求重定向到指定的URL | http状态码及重定向的url |
RemoveHopByHopHeadersFilter | 为原始请求删除IETF组织规定的一系列Header | 默认就会启用,可以通过配置指定仅删除哪些Header |
RemoveRequestHeader | 为原始请求删除某个Header | Header名称 |
RemoveResponseHeader | 为原始响应删除某个Header | Header名称 |
RewritePath | 重写原始的请求路径 | 原始路径正则表达式以及重写后路径的正则表达式 |
RewriteResponseHeader | 重写原始响应中的某个Header | Header名称,值的正则表达式,重写后的值 |
SaveSession | 在转发请求之前,强制执行WebSession::save 操作 | 无 |
secureHeaders | 为原始响应添加一系列起安全作用的响应头 | 无,支持修改这些安全响应头的值 |
SetPath | 修改原始的请求路径 | 修改后的路径 |
SetResponseHeader | 修改原始响应中某个Header的值 | Header名称,修改后的值 |
SetStatus | 修改原始响应的状态码 | HTTP 状态码,可以是数字,也可以是字符串 |
StripPrefix | 用于截断原始请求的路径 | 使用数字表示要截断的路径的数量 |
Retry | 针对不同的响应进行重试 | retries、statuses、methods、series |
RequestSize | 设置允许接收最大请求包的大小。如果请求包大小超过设置的值,则返回 413 Payload Too Large | 请求包大小,单位为字节,默认值为5M |
ModifyRequestBody | 在转发请求之前修改原始请求体内容 | 修改后的请求体内容 |
ModifyResponseBody | 修改原始响应体的内容 | 修改后的响应体内容 |
Default | 为所有路由添加过滤器 | 过滤器工厂名称及值 |
1.AddRequestHeader过滤器工厂
在之前的项目中修改application.yml
server:
port: 9002
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: http://httpbin.org:80
filters:
- AddRequestHeader=X-Request-Foo, Bar
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
启动工程,访问http://localhost:9002/get,显示从http://httpbin.org:80/get得到的请求:
{
"args": {},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Content-Length": "0",
"Forwarded": "proto=http;host=\"localhost:9002\";for=\"0:0:0:0:0:0:0:1:58513\"",
"Host": "httpbin.org",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36",
"X-Amzn-Trace-Id": "Root=1-5f1fe576-d773ec00bf8d2dd2dc0d5c0a",
"X-Forwarded-Host": "localhost:9002",
"X-Request-Foo": "Bar"
},
"origin": "0:0:0:0:0:0:0:1, 219.152.5.50",
"url": "http://localhost:9002/get"
}
从结果来看,确实在请求头中加入了X-Request-Foo请求头。
2.ReqwritePath过滤工厂
Nginx的强大功能一就是重写路径,服务网关也默认提供了这样的功能,而Zuul没有这个功能
修改application.yml
server:
port: 9002
spring:
cloud:
gateway:
routes:
- id: reqrite_path_route
uri: https://blog.csdn.net
filters:
- RewritePath=/foo/(?<segment>.*), /$\{segment}
predicates:
- Path=/foo/**
所有以/foo/**开始的路路径都会命中配置的路由,并执行过滤器的逻辑,本案例将/foo/(?.*)重写为{segment},如访问http://localhost:9002/foo/weixin_40911000,页面将转发到https://blog.csdn.net/weixin_40911000页面,如访问http://localhost:9002/foo/weixin_40911000/bac,页面报错404,因为不存在https://blog.csdn.net/weixin_40911000/abc页面。
3.自定义过滤器
服务网关内置了19种强大的过滤工厂,当然根据需要也可以自定义过滤器。需要实现GatewayFilter和Ordered这两个接口。现在写一个请求耗时的RequestTimeFilter.
public class RequestTimeFilter implements GatewayFilter, Ordered {
private static final Log log= LogFactory.getLog(GatewayFilter.class);
private static final String REQUEST_TIME_BEGIN="requestTimeBegin";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
exchange.getAttributes().put(REQUEST_TIME_BEGIN,System.currentTimeMillis());
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
Long starterTime=exchange.getAttribute(REQUEST_TIME_BEGIN);
if (starterTime !=null){
log.info(exchange.getRequest().getURI().getRawPath()+":"+(System.currentTimeMillis()-starterTime)+"ms");
}
})
);
}
@Override
public int getOrder() {
return 0;
}
}
将过滤器注册到路由中
@Configuration
public class MyRouteLocator {
@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder){
return builder.routes()
.route(r->r.path("/get/**")
.filters(f ->f.filter(new RequestTimeFilter())
.addResponseHeader("X-Response-Default-Foo","Default-Bar"))
.uri("http://httpbin.org:80")
.order(0)
.id("customer_filter_router")
)
.build();
}
}
启动项目访问http://localhost:9002/get控制台打印
2020-07-28 18:37:23.777 INFO 4100 --- [ctor-http-nio-2] o.s.cloud.gateway.filter.GatewayFilter : /get:599ms
4.全局过滤器
服务网关根据作用范围分为网关过滤器(GatewayFilter)和全局过滤器(GlobalFilter).
(1)GatewayFilter,通过spring.cloud.routes.filters配置在具体路由下,只作用在当前的路由上;或通过spring.cloud.default-filters配置在全局中,作用在所有路由上。
(2)GlobalFilter:不需要配置,作用在所欲路由上,系统初始化时加载。
上图每一个GlobalFilter都作用在每一个路由上,能够满足大多数的需求。如需定制需实现GlobalFilter,Ordered接口,如定制一个校验请求参数是否含有"token"
public class TokenFilter implements GlobalFilter, Ordered {
Logger logger= LoggerFactory.getLogger(TokenFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token= exchange.getRequest().getQueryParams().getFirst("token");
if(token == null || token.isEmpty()){
logger.info("token is empty..");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -100;
}
}
@Bean
public TokenFilter tokenFilter(){
return new TokenFilter();
}
启动项目访问:http://localhost:9002/get,控制台:
2020-07-28 19:02:24.523 INFO 13464 --- [ctor-http-nio-3] com.cc.gatewayclient.filter.TokenFilter : token is empty..
日志显示参数没有传入"token"
限流
高并发系统中往往需要做限流,一方面为了防止流量突发使服务过载,另一方面为了防止流量攻击。常见的限流维度有IP限流,Url限流,访问频次限流。常见的限流算法有:计数器算法、漏桶算法、令牌桶算法。在服务网关过滤器中可以自行实现这三种,,但服务网关官方只提供了RequestRateLimiterGatewayFilterFactory这个类,使用Redis和lua脚本实现令牌桶算法进行限流。
案例:在之前的gateway-client中演示:
pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
application.yml
server:
port: 9002
spring:
cloud:
gateway:
routes:
- id: limit_route
uri: http://httpbin.org:80
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
filters:
- name: RequestRateLimiter
args:
key-resolver: '#{@hostAddrKeyResolver}'
redis-rate-limiter.replenishRate: 1
ridis-rate-limiter.burstCapacity: 3
application:
name: gateway-limiter
redis:
host: localhost
port: 6379
database: 0
KeyResolver需要实现resolve方法
public class HostAddrKeyResolver implements KeyResolver {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
}
}
如根据Url限流
return Mono.just(exchange.getRequest().getURI().getPath());
return Mono.just(exchange.getRequest().getQueryParams().getFirst("user")); //根据用户的维度限流
注入容器
@Bean
public HostAddrKeyResolver hostAddrKeyResolver(){
return new HostAddrKeyResolver();
}
用Jemter进行测试,配置10thread去循环localhost:9002/get,从测试结果可以看到有的成功,有的失败。
服务化
简短说就是通过统一端口访问各个微服务。
pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
application.yml
server:
port: 9002
spring:
application:
name: gateway-service
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
eureka:
client:
service-url:
defaultZone:
http://localhost:9000/eureka/
启动eureka-server、eureka-client、gateway-client
访问http://localhost:9002/eureka-client/hi 显示:hi8002
上述例子向gateway-client发送请求,必须带上eureka-client这个前缀,才能转发到eureka-client上,转发之前会将eureka-client去掉。
也可以自定义服务名:
server:
port: 9002
spring:
application:
name: gateway-service
cloud:
gateway:
discovery:
locator:
enabled: false
lower-case-service-id: true
routes:
- id: eureka-client
uri: lb://EUREKA-CLIENT
predicates:
- Path=/demo/**
filters:
- StripPrefix=1
eureka:
client:
service-url:
defaultZone:
http://localhost:9000/eureka/
spring.cloud.gateway.discovery.locator.enabled设置为false,那么之前的http://localhost:9002/eureka-client/hi就不能正常访问了,
StripPrefix过滤器在转发之前将/demo去掉,访问http://localhost:9002/demo/hi 就能正常转发到eureka-client上:显示:hi8002
服务注册与发现Consul
1.下载Consul安装包,本案例下载windows版本,解压,只有一个consul.exe,在该路径上,在路径上输入cmd进入dos模式,使用consul agent -dev 启动。访问http://localhost:8500
Consul常见执行命令:
consul agent -dev 运行一个consul agent
consul join IP 加入集群
consul members 列出集群中的members
2.新创建一个微服务consul-provider
pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
application.yml
server:
port: 8001
spring:
application:
name: consul-provider
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: consul-provider
启动类:
@SpringBootApplication
@EnableDiscoveryClient
public class ConsulProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ConsulProviderApplication.class, args);
}
}
写一个服务:
@RestController
public class HiController {
@Value("${server.port}")
String port;
@GetMapping("/hi")
public String home(){
return "hi"+",my port:"+port;
}
}
访问:http://locahost:8500 可以在注册中心发现注册的服务consul-provider。访问http://locahost:8001/hi也能正常访问。
3.0创建消费服务:
新建一个服务:consul-consumer
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
server:
port: 8002
spring:
application:
name: consul-consumer
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: consul-consumer
loadbalancer: //这个不要忘记加,不然访问会出错
ribbon:
enabled: false
@FeignClient(value = "consul-provider")
public interface ClientFeign {
@GetMapping("/hi")
String home() ;
}
@Service
public class HiService {
@Autowired
ClientFeign clientFeign;
@GetMapping(value = "/hi")
public String hi(){
return clientFeign.home();
}
}
@RestController
public class HiController {
@Autowired
HiService hiService;
@GetMapping("/hi")
public String sayHi(){
return hiService.hi();
}
}
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ConsulConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsulConsumerApplication.class, args);
}
}
启动项目consul-provider,consul-consumer,访问http://localhost:8002/hi,如果服务提供者做了集群,默认也做了集群访问:默认为轮询。
4.Spring Cloud Consul Config做服务配置中心。
在上面的consul-provider上修改:在pom中添加如下依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
在application.yml中使用一下配置:
spring:
profiles:
active: dev
添加bootstrap.yml
spring:
application:
name: consul-provider
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: consul-provider
config:
enabled: true //开启config
format: yaml //可以为yaml或properties
prefix: config //配置的基本目录,比如config
profile-separator: ':' //配置分割符,默认为","
data-key: data //为应用配置的Key名字,值为整个应用配置的字符串
在consul的Key/Value中配置
新建一个controller
@RestController
public class FooBarController {
@Value("${foo.bar}")
String fooBar;
@GetMapping("/foo")
public String getFooBar(){
return fooBar;
}
}
启动项目可以发现端口为8888,访问http://localhost:8888/foo,显示 bar,说明已成功从Consul的配置中读取foo.bar的配置。
5.动态刷新配置
Spring Cloud Consul Config支持动态刷新,在需要动态刷新的类上添加@RefreScope
@RestController
@RefreshScope
public class FooBarController {
@Value("${foo.bar}")
String fooBar;
@GetMapping("/foo")
public String getFooBar(){
return fooBar;
}
}
重启项目,在consul配置中心重新配置foo.bar如:
foo:
bar:bar1
server:
port:8888
访问http://localhost:8888/foo,显示bar1。动态刷新成功。
SpringCloudConfig
1.ConfigServer读取本地文件
新建一个SpringCloud工程,父工程pom
<groupId>com.cc</groupId>
<artifactId>springcloud1</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
新建一个Module工程,取名config-server
pom文件代码
<parent>
<artifactId>springcloud1</artifactId>
<groupId>com.cc</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>config-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class,args);
}
}
server:
port: 8769
spring:
application:
name: config-server
profiles:
active: native
cloud:
config:
server:
native:
search-locations: classpath:/shared #读取配置的路径为classpath下的shared目录
在shared目录下新建一个config-client-dev.yml,注意:config-client和读取配置文件工程的spring.application.name的名字相同即可。
server:
port: 8762
foo: foo version 1
新建一个Module工程,取名config-client
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
新建配置文件bootstrap.yml,bootsrap.yml具有优于application.yml的执行顺序
spring:
application:
name: config-client
cloud:
config:
uri: http://localhost:8769
fail-fast: true
profiles:
active: dev
@SpringBootApplication
@RestController
public class ConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class,args);
}
@Value("${foo}")
String foo;
@RequestMapping(value = "/foo")
public String hi(){
return foo;
}
}
访问http://localhost:8762/foo,显示
foo version 1
可见读取配置文件成功。
2.Config Server从远程仓库Git中读取配置文件
修改config-server的application.yml
server:
port: 8769
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://github.com/cheng290/respo
search-paths: blob
username:
password:
label: master
uri为远程仓库的地址,search-paths为远程仓库的文件夹地址,username和password为Git仓库的登录名和密码,label为git仓库的分支名,本利从master读取。将config-client-dev.yml上传到远程仓库,上传地址为https://github.com/cheng290/respo.启动项目config-se/server,config-client,访问http://localhost:8762/foo,显示
foo version 1
可见配置远程仓库成功。
3.构建高可用的Config Server
当微服务很多时,可以考虑将Config Server做成一个为服务,并将其集群。
3.1构建Eureka Server
新建一个eureka-server工程,作为注册中心。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
server:
port: 8761
eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://localhost:${server.port}/eureka/
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class,args);
}
}
3.2改造config Server
添加如下pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency
@SpringBootApplication
@EnableConfigServer
@EnableEurekaClient
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class,args);
}
}
application.yml添加
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
3.3改造Config Client
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
@SpringBootApplication
@RestController
@EnableEurekaClient
public class ConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class,args);
}
@Value("${foo}")
String foo;
@RequestMapping(value = "/foo")
public String hi(){
return foo;
}
}
bootstrap.yml
spring:
application:
name: config-client
cloud:
config:
fail-fast: true
discovery:
enabled: true
service-id: config-server
profiles:
active: dev
server:
port: 8762
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
依次启动eureka-server,config-server,config-client,访问http://localhost:8762/foo,显示foo version 1配置成功。
搭建高可用的Config Server,将Config Server多启动几个,如端口为8769,8768,8767,就构成了集群。多次启动confgi-client,从控制台可以看到,服务会分别从这几个端口的微服务中读取配置文件。
Spring Cloud Bus刷新配置
在config-client工程中修改
pom添加如下依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
bootstrap.yml:注意从远程仓库读取配置时,为bootstrap.yml,如果写为application.yml报错
spring:
application:
name: config-client
cloud:
config:
fail-fast: true
discovery:
enabled: true
service-id: config-server
profiles:
active: dev
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
server:
port: 8762
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
management:
endpoints:
web:
exposure:
include: "*"
@SpringBootApplication
@RestController
@EnableEurekaClient
@RefreshScope //添加的注解
public class ConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class,args);
}
@Value("${foo}")
String foo;
@RequestMapping(value = "/foo")
public String hi(){
return foo;
}
}
启动eureka-server,config-server,config-client,访问http://localhsot:8762/foo,显示:foo version 1,去远程仓库修改配置,foo的值该为:foo version 222,使用postman发送一个post请求,http://localhost:8762/actuator/bus-refresh.访问http://localhsot:8762/foo显示:foo version 222.
将配置文件配置在MYSQL数据库中
当我们需要二次开发对配置进行展示或做管控功能时,将配置存储在关系型数据库中会更便捷。
在之前的config-server上做改造
pom
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
application.yml
server:
port: 8769
spring:
application:
name: config-server
profiles:
active: jdbc
cloud:
config:
label: master
server:
jdbc:
sql: select key1,value1 from config_properties where application=? and profile=? and label=?
datasource:
url: jdbc:mysql://localhost:3306/runoob?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&serverTimezone=GMT%2B8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
创建数据表
CREATE TABLE `config_properties` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`key1` varchar(255) DEFAULT NULL,
`value1` varchar(255) DEFAULT NULL,
`application` varchar(255) DEFAULT NULL,
`profile` varchar(255) DEFAULT NULL,
`label` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
插入数据
其中config-client中的的端口为8083,访问http://localhsot:8083/foo显示:version
注意这种方式,在config-client中必须配置端口号,而其他配置方式读取服务端的可以不用配置。
服务链路追踪Spring Cloud Sleuth
链路追踪去跟进一个请求到底有哪些服务的参与,参与的顺序有是怎么样的,从而达到每个请求的步骤清新可见,出现问题能快速定位的目的。目前常见的有Google的Dapper,Twitter的Zipkin,以及阿里的Eagleeye,本案例采用Zipkin。
1.启动Zipkin Server
curl -sSL https://zipkin.io/quickstart.sh | bash -s
java -jar zipkin.jar
第一条命令使用Gitbash下载Zipkin的jar包,第二条命令是启动jar包,默认端口为9411。
2.构建微服务提供者sleuth-provider
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
server:
port: 8763
spring:
application:
name: sleuth-provider
sleuth:
web:
client:
enabled: true
sampler:
probability: 1.0 #将采样比例设置为1.0,也就是全部需要,默认为。0.1
zipkin:
base-url: http://localhost:9411/
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
@RestController
public class HiController {
@Autowired
Tracer tracer;
@Value("${server.port}")
String port;
@GetMapping("/hi")
public String home( String name){
tracer.currentSpan().tag("name","cc");
return "hi"+name+",i am port:"+port;
}
}
@SpringBootApplication
@EnableEurekaClient
public class SleuthProviderApplication {
public static void main(String[] args) {
SpringApplication.run(SleuthProviderApplication.class,args);
}
}
3.构建微服务消费者sleuth-consumer
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
server:
port: 8765
spring:
application:
name: sleuth-consumer
sleuth:
web:
client:
enabled: true
sampler:
probability: 1.0 #将采样比例设置为1.0,也就是全部需要,默认为。0.1
zipkin:
base-url: http://localhost:9411/
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
@FeignClient(value = "sleuth-provider")
public interface EurekaClientFeign {
@GetMapping("/hi")
String home1(@RequestParam(value = "name") String name); //注意:有参数的时候@RequestParam这个注解一定不能少
}
@Service
public class HiService {
@Autowired
EurekaClientFeign eurekaClientFeign;
public String sayHi(String name){
return eurekaClientFeign.home1(name);
}
}
@RestController
public class HiController {
@Autowired
HiService hiService;
@GetMapping("/hi")
public String sayHi(@RequestParam(defaultValue = "cc",required = false)String name){
return hiService.sayHi(name);
}
}
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients //在使用Feign时,这个注解不要忘添加
public class SleuthConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(SleuthConsumerApplication.class,args);
}
}
启动eureka-server,sleuth-provider,sleuth-consumer,和zipkin.jar包,访问http://loclhost:8762/hi,再访问http://localhost:9411
4.在链路数据中添加自定义的数据
再本案例中添加操作人的代码:
@RestController
public class HiController {
@Autowired
Tracer tracer;
@Value("${server.port}")
String port;
@GetMapping("/hi")
public String home( String name){
tracer.currentSpan().tag("name","cc"); //添加操作的人
return "hi"+name+",i am port:"+port;
}
}
5.使用RabbitMQ传输数据
上例中zipkin-server接收数据是通过HTTP的方式,本案例采用RabbitMQ来传递。
下载安装rabbitMQ这里不再说,安装启动好之后,使用一下命令来启动Zipkin的服务:
rabbit_address=localhost java -jar zipkin.jar
在sleuth-privider,sleuth-consumer中添加如下依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
在这两个服务中,去掉spring.zikin.base-url的配置,添加如下代码
rabbitmq:
host: localhost
username: guest
password: guest
port: 5672
重新启动服务,同样可以达到这种使用zipkin的效果。
6.在ELasticSearch中存储链路中的数据
当Zipkin Server重启时,之前的链路数据全部消失,它是将数据存在在内存中的。使用数据能解决这种问题,支持MYSQL,ElasticSearch,Cassandra数据库,本案例采用ELasticSearch,在高并发的情况下也比较适用。
读者自行安装ElasticSearch和Kinbana(图形化web工具),ElasticSearch的默认端口为9200,Kibana的默认端口为5601
启动这两个服务,在使用一下命令启动zipkin server:
storage_type=elasticsearch es_hosts=http://localhost:9200 es_index=zipkin java -jar zipkin.jar
访问http://localhost:5601新建一个z*的查询。
重新启动zipkin访问http://localhost:9411,能看到数据任然存在。
微服务监控Spring Boot Admin
1.使用Spring Boot Admin监控Spring Boot应用
1.1新建一个admin-server服务
Caused by: java.lang.StackOverflowError: null
org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘adminHandlerMapping’ defined in class path resource [de/codecentric/boot/admin/server/config/AdminServerWebConfiguration$ServletRestApiConfirguation.class]: Invocation of init method failed; nested exception is java.lang.StackOverflowError
注意:以上这个错误一定要注意spirng-boot-admin-starter-server的版本与springboot对应,不然报内存溢出问题。本案例采用2.2.4
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
server:
port: 8769
spring:
application:
name: admin-server
@SpringBootApplication
@EnableAdminServer
public class AdminServerApplication {
public static void main(String[] args) {
SpringApplication.run(AdminServerApplication.class,args);
}
}
1.2创建一个Spring Boot Admin Client工程
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
server:
port: 8768
spring:
application:
name: admin-client
boot:
admin:
client:
url: http://localhost:8769
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
@SpringBootApplication
public class AdminClientApplication {
public static void main(String[] args) {
SpringApplication.run(AdminClientApplication.class,args);
}
}
依次启动admin-server,admin-client两个工程,访问http://localhost:8769,可以在监控端看见很多用用的信息。
2.使用Spring Boot Admin 监控Spring Cloud 为服务
2.1创建Admin Server
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
server:
port: 8769
spring:
application:
name: admin-server
security:
user:
name: admin
password: admin
eureka:
client:
registry-fetch-interval-seconds: 5
service-url:
defaultZone: ${EUREKA_SERVICE_URL:http://localhost:9000}/eureka/
instance:
lease-renewal-interval-in-seconds: 10
# health-check-url: /actuator/health
management:
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
include: "*"
@SpringBootApplication
@EnableAdminServer
@EnableEurekaClient
public class AdminServerApplication {
public static void main(String[] args) {
SpringApplication.run(AdminServerApplication.class,args);
}
}
2.2创建Amdin Client
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
server:
port: 8768
spring:
application:
name: admin-client
eureka:
instance:
lease-renewal-interval-in-seconds: 10
# health-check-url-path: /actuator/heath
client:
registry-fetch-interval-seconds: 5
service-url:
defaultZone: ${EUREKA_SERVICE_URL:http://localhost:9000}/eureka/
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
@SpringBootApplication
@EnableEurekaClient
public class AdminClientApplication {
public static void main(String[] args) {
SpringApplication.run(AdminClientApplication.class,args);
}
}
依次启动eureka-client,admin-server,admin-client,访问http://localhost:8769
2.3Spring Boot admin 集成Security组件
在admin-server的基础上改造,添加如下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
在之前的application.yml中添加如下配置
spring:
security:
user:
name: admin
password: admin
eureka:
instance:
metadata-map:
user.name: ${spring.security.user.name}
user.password: ${spring.security.user.password}
@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 {
SavedRequestAwareAuthenticationSuccessHandler successHandler=new SavedRequestAwareAuthenticationSuccessHandler();
successHandler.setTargetUrlParameter("redirectTo");
http.authorizeRequests()
.antMatchers(adminContextPath+"/assets/**").permitAll() //给静态资源css/img都加允许访问的权限,
.antMatchers(adminContextPath+"/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage(adminContextPath+"/login").successHandler(successHandler)
.and()
.logout().logoutUrl(adminContextPath+"/logout")
.and()
.httpBasic() //这些静态资源部不支持跨域禁用掉
.and()
.csrf()
.disable();
}
}
2.4Spring Boot Admin 集成Mail
当服务部监控或者下线了,可以通过邮箱发送信息。
在admin-server中添加如下
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
server:
port: 8769
spring:
application:
name: admin-server
security:
user:
name: admin
password: admin
mail:
host: smtp.qq.com
username: 123@qq.com
password:
boot:
admin:
notify:
mail:
to: 456@126.com
from: 123@qq.com
eureka:
client:
registry-fetch-interval-seconds: 5
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
lease-renewal-interval-in-seconds: 10
metadata-map:
user.name: admin
user.password: admin
management:
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
include: "*"
启动服务,再关闭掉admin-client下线,会发送邮件