📋 框架知识:SpringCloud
服务的注册与发现(Eureka)
SpringCloud 简介
以下简介来自SpringCloud官网(https://spring.io/projects/spring-cloud)
Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智能路由,微代理,控制总线)。分布式系统的协调导致了样板模式, 使用Spring Cloud开发人员可以快速地支持实现这些模式的服务和应用程序。他们将在任何分布式环境中运行良好,包括开发人员自己的笔记本电脑,裸机数据中心,以及Cloud Foundry等托管平台。
quote from https://www.springcloud.cc/spring-cloud-dalston.html
Spring Cloud provides tools for developers to quickly build some of the common patterns in distributed systems (e.g. configuration management, service discovery, circuit breakers, intelligent routing, micro-proxy, control bus, one-time tokens, global locks, leadership election, distributed sessions, cluster state). Coordination of distributed systems leads to boiler plate patterns, and using Spring Cloud developers can quickly stand up services and applications that implement those patterns. They will work well in any distributed environment, including the developer’s own laptop, bare metal data centres, and managed platforms such as Cloud Foundry.
quote from https://spring.io/projects/spring-cloud
Eureka Server
ps. 学习完 Eureka 之后,可以再了解一下 Consul。
-
创建 maven 主工程
你可以选择从https://start.spring.io/下载初始化项目。
也可以
new
>>project
>>Spring Initializr
进行初始化项目。 -
创建 Eureka Server 注册服务中心
创建module并选择Eureka Server依赖。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
在启动类中添加注解
@EnableEurekaServer
package com.xfc.eureka.server; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }
-
配置文件
application.yml
server: port: 8761 # 通过eureka.client.registerWithEureka:false和fetchRegistry:false来表明自己是一个eureka server. eureka: instance: hostname: localhost client: registerWithEureka: false fetchRegistry: false serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ spring: application: name: eurka-server
-
启动注册服务中心
启动主类并访问
http://localhost:8761/
Eureka Client
-
创建module并选择Eureka Discovery Client依赖。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
-
在启动类中添加注解
@EnableEurekaClient
package com.xfc.eureka.client; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient public class EurekaClientApplication { public static void main(String[] args) { SpringApplication.run(EurekaClientApplication.class, args); } }
-
配置文件
application.yml
server: port: 8762 # 指定当前服务名称 spring: application: name: eureka-client-1 # 指定eureka-server eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/
-
启动 Eureka Client
注意:启动 Eureka Client 时应当保证 Eureka Client也处于启动状态。
启动主类并访问
http://localhost:8761/
进行查看。关于 Eureka 错误提示信息,请参考:EMERGENCYI EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’ RE NOT. RENEWALS ARE LESSE
在
Instances currently registered with Eureka
列表中查看已注册的Eureka服务实例。
至此,一个 Eureka 服务即注册成功,同时注册多个 Eureka 服务,与 Eureka Client 方式相同,只须注意端口冲突即可。
服务消费者(rest+ribbon)
在微服务架构中,业务都会被拆分成一个独立的服务,服务与服务的通讯是基于http restful的。Spring cloud有两种服务调用方式,一种是ribbon+restTemplate,另一种是feign。
ribbon简介
Ribbon is a client side IPC library that is battle-tested in cloud. It provides the following features
Load balancing
Fault tolerance
Multiple protocol (HTTP, TCP, UDP) support in an asynchronous and reactive model
Caching and batching
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括后续我们将要介绍的Feign,它也是基于Ribbon实现的工具。
Ribbon 服务消费者示例
-
准备工作
根据 Eureka 章节相关知识,同时创建Eureka Server和两个Eureka Client。
注意:除端口不同外,两个Eureka Client的代码完全相同,以不同端口来模拟负载均衡。
-
创建服务消费者
新建一个module,并添加ribbon及eureka client依赖。
<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>
编辑配置文件
server: port: 8764 eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ spring: application: name: service-ribbon
配置启动类
在启动类中添加
@EnableEurekaClient
及@EnableDiscoveryClient
注解,并注入一个开启负载均衡的RESTFul模板。package com.xfc.service.ribbon; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableEurekaClient @EnableDiscoveryClient public class ServiceRibbonApplication { public static void main(String[] args) { SpringApplication.run(ServiceRibbonApplication.class, args); } @Bean @LoadBalanced// 开启负载均衡 RestTemplate restTemplate() { return new RestTemplate(); } }
在EurekaClient的服务中编写接口
这里直接在两个EurekaClient的启动类中编写RESTFul风格的接口即可,即修改启动类,如下:
package com.xfc.eureka.client; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @EnableEurekaClient @RestController public class EurekaClientApplication { public static void main(String[] args) { SpringApplication.run(EurekaClient2Application.class, args); } @Value("${server.port}") String port; @RequestMapping("/test") public String test(@RequestParam(value = "name", defaultValue = "ErDong") String name) { return "hi " + name + " ,this test api is from port: " + port; } }
-
在ribbon模块中添加测试
TestService.java
package com.xfc.service.ribbon.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; /** * @Auther: ErDong * @Email: xfc_exclave@163.com * @Date: 2019/12/27 0027 12:46 * @Description: */ @Service public class TestService { @Autowired RestTemplate restTemplate; public String test(String name) { return restTemplate.getForObject("http://EUREKA-CLIENT/test?name=" + name, String.class); } }
TestController.java
package com.xfc.service.ribbon.controller; import com.xfc.service.ribbon.service.TestService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * @Auther: ErDong * @Email: xfc_exclave@163.com * @Date: 2019/12/27 0027 12:56 * @Description: */ @RestController public class TestController { @Autowired TestService testService; @GetMapping(value = "/test") public String test(@RequestParam String name) { return testService.test(name); } }
-
启动服务及测试负载均衡
分别启动EurekaServer,两个EurekaClient及ServiceRibbon。
访问 http://localhost:8761/ 已注册的服务实例。
可以看到端口分别为
8762
和8763
两个 EUREKA-CLIENT 实例,及一个端口为8764
的 EUREKA-CLIENT 实例。负载均衡测试
多次访问 http://localhost:8764/test?name=testUser
可以看到相应结果如下:
hi testUser ,this test api is from port: 8762
hi testUser ,this test api is from port: 8763
hi testUser ,this test api is from port: 8762
hi testUser ,this test api is from port: 8763
hi testUser ,this test api is from port: 8762
hi testUser ,this test api is from port: 8763
……测试结论
从测试结果我们可以看到,端口分别为8762何8763的两个 EUREKA-CLIENT 实例被轮流调用,即实现了负载均衡。
服务消费者(Feign)
在微服务架构中,业务都会被拆分成一个独立的服务,服务与服务的通讯是基于http restful的。Spring cloud有两种服务调用方式,一种是ribbon+restTemplate,另一种是feign。
Feign简介
Feign is a Java to HTTP client binder inspired by Retrofit, JAXRS-2.0, and WebSocket. Feign’s first goal was reducing the complexity of binding Denominator uniformly to HTTP APIs regardless of ReSTfulness.
Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。使用Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,可使用Feign 注解和JAX-RS注解。Feign支持可插拔的编码器和解码器。Feign默认集成了Ribbon,并和Eureka结合,默认实现了负载均衡的效果。
简而言之:
- Feign 采用的是基于接口的注解
- Feign 整合了ribbon,具有负载均衡的能力
- 整合了Hystrix,具有熔断的能力
---- 摘自https://blog.csdn.net/forezp/article/details/81040965
-
准备工作
根据 Eureka 章节相关知识,同时创建Eureka Server和两个Eureka Client。
注意:除端口不同外,两个Eureka Client的代码完全相同,以不同端口来模拟负载均衡。
-
创建服务消费者
新建一个module,并添加feign及eureka client依赖。
<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>
配置文件
eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ server: port: 8765 spring: application: name: service-feign
配置启动类
在启动类中添加
@EnableEurekaClient
@EnableFeignClients
及@EnableDiscoveryClient
注解开启Feign的功能。package com.xfc.service.feign; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableEurekaClient @EnableDiscoveryClient @EnableFeignClients public class ServiceFeignApplication { public static void main(String[] args) { SpringApplication.run(ServiceFeignApplication.class, args); } }
定义一个 Feign 接口
这里直接在两个EurekaClient的启动类中编写RESTFul风格的接口即可,即修改启动类,如下:
package com.xfc.eureka.client; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @EnableEurekaClient @RestController public class EurekaClientApplication { public static void main(String[] args) { SpringApplication.run(EurekaClient2Application.class, args); } @Value("${server.port}") String port; @RequestMapping("/test") public String test(@RequestParam(value = "name", defaultValue = "ErDong") String name) { return "hi " + name + " ,this test api is from port: " + port; } }
-
在feign模块中添加测试
TestService.java
package com.xfc.service.feign.service; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; /** * @Auther: ErDong * @Email: xfc_exclave@163.com * @Date: 2019/12/27 0027 14:38 * @Description: */ @FeignClient(value = "EUREKA-CLIENT")// 指定服务名称 public interface TestService { // 指定服务方法及参数 @RequestMapping(value = "/test",method = RequestMethod.GET) String test(@RequestParam(value = "name") String name); }
TestController.java
package com.xfc.service.feign.controller; import com.xfc.service.feign.service.TestService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * @Auther: ErDong * @Email: xfc_exclave@163.com * @Date: 2019/12/27 0027 14:40 * @Description: */ @RestController public class TestController { @Autowired TestService testService; @GetMapping(value = "/test") public String test(@RequestParam String name) { return testService.test(name); } }
-
启动服务及测试负载均衡
分别启动EurekaServer,两个EurekaClient及ServiceFeign。
访问 http://localhost:8761/ 已注册的服务实例。
可以看到端口分别为
8762
和8763
两个 EUREKA-CLIENT 实例,及一个端口为8765
的 SERVICE-FEIGN 实例。负载均衡测试
多次访问 http://localhost:8765/test?name=testUser 可以看到相应结果如下:
hi testUser ,this test api is from port: 8762
hi testUser ,this test api is from port: 8763
hi testUser ,this test api is from port: 8762
hi testUser ,this test api is from port: 8763
hi testUser ,this test api is from port: 8762
hi testUser ,this test api is from port: 8763
……测试结论
从测试结果我们可以看到,端口分别为8762何8763的两个 EUREKA-CLIENT 实例被轮流调用,即实现了负载均衡。
熔断器(Hystrix)
在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和Feign来调用。为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证100%可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。为了解决这个问题,业界提出了熔断器(断路器)模型。
熔断器简介
Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable.
Hystrix 中文介绍可参考 https://www.jianshu.com/p/76dc45523807
Hystrix 熔断器示例
-
准备工作
根据 Eureka 章节相关知识,同时创建Eureka Server和两个Eureka Client。
注意:除端口不同外,两个Eureka Client的代码完全相同,以不同端口来模拟负载均衡。
-
在 Ribbon 中使用断路器
注意: 此部分内容基于springcloud002文档。
向
serice-ribbon
模块添加依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
配置启动类
在启动类中添加
@EnableHystrix
注解开启Hystrix功能。package com.xfc.service.ribbon; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.hystrix.EnableHystrix; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableEurekaClient @EnableDiscoveryClient @EnableHystrix public class ServiceRibbonApplication { public static void main(String[] args) { SpringApplication.run(ServiceRibbonApplication.class, args); } @Bean @LoadBalanced// 开启负载均衡 RestTemplate restTemplate() { return new RestTemplate(); } }
改写TestService
package com.xfc.service.ribbon.service; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; /** * @Auther: ErDong * @Email: xfc_exclave@163.com * @Date: 2019/12/27 0027 12:46 * @Description: */ @Service public class TestService { @Autowired RestTemplate restTemplate; // 对当前方法创建熔断器的功能,并指定fallbackMethod熔断方法 @HystrixCommand(fallbackMethod = "testError") public String test(String name) { return restTemplate.getForObject("http://EUREKA-CLIENT/test?name=" + name, String.class); } // fallbackMethod熔断方法 public String testError(String name) { return "hi,"+name+",sorry,error!"; } }
启动服务及测试熔断器功能
分别启动EurekaServer,两个EurekaClient及ServiceRibbon。
访问 http://localhost:8761/ 已注册的服务实例。
可以看到端口分别为
8762
和8763
两个 EUREKA-CLIENT 实例,及一个端口为8764
的 SERVICE-FEIGN 实例。熔断器功能测试
多次访问 http://localhost:8764/test?name=testUser
可以看到相应结果如下:
hi testUser ,this test api is from port: 8762
hi testUser ,this test api is from port: 8763
hi testUser ,this test api is from port: 8762
hi testUser ,this test api is from port: 8763
hi testUser ,this test api is from port: 8762
hi testUser ,this test api is from port: 8763
……此时断开端口为
8763
的 EUREKA-CLIENT 实例。再次进行多次访问,结果如下:
hi,testUser,sorry,error!
hi testUser ,this test api is from port: 8762
hi,testUser,sorry,error!
hi testUser ,this test api is from port: 8762
hi testUser ,this test api is from port: 8762
……此时再断开端口为
8762
的 EUREKA-CLIENT 实例。再次进行多次访问,结果如下:
hi,testUser,sorry,error!
hi,testUser,sorry,error!
hi,testUser,sorry,error!
hi,testUser,sorry,error!
……测试结论
从测试结果我们可以看到,端口分别为8762何8763的两个 EUREKA-CLIENT 实例在正常启用时,正常实现负载均衡,当其中某个服务实例出现故障时,客户端会进入fallbackMethod指定的熔断方法,直接返回一组字符串,而不是等待响应超时,这很好的控制了容器的线程阻塞。
-
在 Feign 中使用断路器
Feign 是自带断路器的,在D版本的 Spring Cloud 之后,默认关闭。
向
serice-feign
模块添加依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
编辑配置文件,向配置文件中添加熔断器配置:
# feign开启hystrix支持 feign: hystrix: enabled: true
根据 TestService 创建 TestService 接口的实现类TestServiceHystric.java作为熔断器。
package com.xfc.service.feign.service.hystric; import com.xfc.service.feign.service.TestService; import org.springframework.stereotype.Component; /** * @Auther: ErDong * @Email: xfc_exclave@163.com * @Date: 2019/12/27 0027 15:39 * @Description: */ @Component public class TestServiceHystric implements TestService { @Override public String test(String name) { return "hi,"+name+",sorry,error!"; } }
在 @FeignClient 注解中指定fallback
package com.xfc.service.feign.service; import com.xfc.service.feign.service.hystric.TestServiceHystric; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; /** * @Auther: ErDong * @Email: xfc_exclave@163.com * @Date: 2019/12/27 0027 14:38 * @Description: */ @FeignClient(value = "EUREKA-CLIENT", fallback = TestServiceHystric.class)// 指定服务名称,指定熔断器 public interface TestService { // 指定服务方法及参数 @RequestMapping(value = "/test", method = RequestMethod.GET) String test(@RequestParam(value = "name") String name); }
启动服务及测试熔断器功能
路由网关(zuul)
在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现、服务消费、负载均衡、断路器、智能路由、配置管理等,由这几个基础组件相互协作,共同组建了一个简单的微服务系统。
在Spring Cloud微服务系统中,一种常见的负载均衡方式是,客户端的请求首先经过负载均衡(zuul、Ngnix),再到达服务网关(zuul集群),然后再到具体的服。,服务统一注册到高可用的服务注册中心集群,服务的所有的配置文件由配置服务管理(下一篇文章讲述),配置服务的配置文件放在git仓库,方便开发人员随时改配置。
Zuul简介
Zuul is the front door for all requests from devices and web sites to the backend of the Netflix streaming application. As an edge service application, Zuul is built to enable dynamic routing, monitoring, resiliency and security. It also has the ability to route requests to multiple Amazon Auto Scaling Groups as appropriate.
Hystrix 中文介绍可参考 SpringCloud教程 | 第五篇: 路由网关(zuul)
Zuul 路由网关示例
-
准备工作
接续上一文档,在原有工程上添加新的模块。
-
创建zuul路由网关
新建一个 module,并添加 zuul 及 eureka client 依赖。
<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>
编辑配置文件
server: port: 8769 eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ spring: application: name: service-zuul # 以/api-a/开头的请求转发至service-ribbon服务 # 以/api-b/开头的请求转发至service-feign服务 zuul: routes: api-a: path: /api-a/** serviceId: service-ribbon api-b: path: /api-b/** serviceId: service-feign
配置启动类
在启动类中添加
@EnableEurekaClient
及@EnableZuulProxy
开启zuul的功能。package com.xfc.service.zuul; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; @SpringBootApplication @EnableZuulProxy @EnableEurekaClient @EnableDiscoveryClient public class ServiceZuulApplication { public static void main(String[] args) { SpringApplication.run(ServiceZuulApplication.class, args); } }
启动服务及测试路由网关
分别启动EurekaServer,两个EurekaClient及ServiceRibbon。
分别启动ServiceRibbon和ServiceFeign两个服务消费者。
启动ServiceZuul网关。
多次访问 http://localhost:8769/api-a/test?name=testUser 结果如下:
hi testUser ,this test api is from port: 8762
hi testUser ,this test api is from port: 8763
hi testUser ,this test api is from port: 8762
hi testUser ,this test api is from port: 8763
……多次访问 http://localhost:8769/api-b/test?name=testUser 结果如下:
hi testUser ,this test api is from port: 8762
hi testUser ,this test api is from port: 8763
hi testUser ,this test api is from port: 8762
hi testUser ,this test api is from port: 8763
……测试结论
Zuul 起到路由的作用,它将不同请求分向不同的服务消费者进行处理。
-
服务过滤
Zuul 不仅只是路由,并且还能过滤,做一些安全验证。
添加过滤器(新建TokenFilter.java,用于过滤token)
package com.xfc.service.zuul.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; /** * @Auther: ErDong * @Email: xfc_exclave@163.com * @Date: 2019/12/27 21:29 * @Description: */ @Component public class TokenFilter extends ZuulFilter { private static Logger log = LoggerFactory.getLogger(TokenFilter.class); /** * filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,如下: * 1. pre:前置过滤器 * 2. routing:路由之时 * 3. post: 路由之后 * 4. rror:发送错误调用 * * @return */ @Override public String filterType() { return "pre"; } /** * 过滤的顺序,数字越大,优先级越低 * * @return */ @Override public int filterOrder() { return 0; } /** * 是否要进行过滤 * * @return */ @Override public boolean shouldFilter() { return true; } /** * 过滤器的具体逻辑 * * @return */ @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); log.info(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString())); Object accessToken = request.getParameter("token"); if (accessToken == null) { log.warn("token is empty"); ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); try { ctx.getResponse().getWriter().write("token is empty"); } catch (Exception e) { } return null; } log.info("ok"); return null; } }
启动服务及测试路由过滤
访问 http://localhost:8769/api-a/test?name=testUser 得到结果:token is empty
访问 http://localhost:8769/api-b/test?name=testUser 得到结果:token is empty
访问 http://localhost:8769/api-a/test?name=testUser&token=abc 得到结果:hi testUser ,this test api is from port: 8762
访问 http://localhost:8769/api-b/test?name=testUser&token=abc 得到结果:hi testUser ,this test api is from port: 8763
测试结论:Zuul 实现了服务过滤。
配置中心(Spring Cloud Config)
使用配置服务来保存各个服务的配置文件,即Spring Cloud Config。
Spring Cloud Config 简介
在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server,二是config client。
---- 摘自https://blog.csdn.net/forezp/article/details/81041028
Spring Cloud Config
为分布式系统中的外部配置提供服务器和客户端支持。使用Config Server
,您可以在所有环境中管理应用程序的外部属性。客户端和服务器上的概念映射与Spring Environment
和PropertySource
抽象相同,因此它们与Spring应用程序非常契合,但可以与任何以任何语言运行的应用程序一起使用。随着应用程序通过从开发人员到测试和生产的部署流程,您可以管理这些环境之间的配置,并确定应用程序具有迁移时需要运行的一切。服务器存储后端的默认实现使用git,因此它轻松支持标签版本的配置环境,以及可以访问用于管理内容的各种工具。可以轻松添加替代实现,并使用Spring配置将其插入。
更多相关中文介绍,参考:Spring Cloud Config 中文文档
Spring Cloud Config 配置中心示例
构建 Config Server
-
创建模块并添加依赖
新建一个module,并添加config server依赖。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency>
-
编辑配置文件
server: port: 8888 # 如果Git仓库为公开仓库,可以不填写用户名和密码 spring: application: name: config-server cloud: config: label: master # 配置仓库的分支 server: git: uri: https://github.com/forezp/SpringcloudConfig/ # 配置git仓库地址 searchPaths: respo # 配置仓库路径 username: # 访问git仓库的用户名 password: # 访问git仓库的用户密码
-
配置启动类
在启动类中添加
@EnableConfigServer
注解开启配置服务器的功能。package com.xfc.config.server; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer; @SpringBootApplication @EnableConfigServer public class ConfigServerApplication { public static void main(String[] args) { SpringApplication.run(ConfigServerApplication.class, args); } }
-
启动服务及测试ConfigServer
启动服务后,访问 http://localhost:8888/config-client-dev.properties 得到如下结果:
democonfigclient.message: hello spring io
foo: foo version 21
结论:配置服务中心可以从远程程序获取配置信息。
http请求地址和资源文件映射如下:
/{application}/{profiles:.[^-].}
/{application}/{profiles}/{label:.*}
/{application}-{profiles}.properties
/{label}/{application}-{profiles}.properties
{application}-{profiles}.json
/{label}/{application}-{profiles}.json
/{application}-{profiles}.yml 或 /{application}-{profiles}.yml
/{label}/{application}-{profiles}.yml 或 /{label}/{application}-{profiles}.yml
构建Config Client
-
创建模块并添加依赖
新建一个module,并添加config client依赖。
<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>
-
编辑配置文件
server: port: 8881 spring: application: name: config-client cloud: config: label: master profile: dev # dev:开发环境,test:测试环境,pro:生产环境 uri: http://localhost:8888/ # 指明配置服务中心的网址
-
创建测试API
在启动类中添加API。
package com.xfc.config.client; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController public class ConfigClientApplication { public static void main(String[] args) { SpringApplication.run(ConfigClientApplication.class, args); } @Value("${foo}") String foo; @RequestMapping(value = "/getFoo") public String getFoo() { return foo; } }
启动并访问 http://localhost:8881/getFoo
结果:foo version 21
结论:config-client从config-server获取了foo的属性,而config-server是从git仓库读取的。
高可用的分布式配置中心
-
改造config-server
添加eureka client依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
修改配置文件
server: port: 8888 eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ # 如果Git仓库为公开仓库,可以不填写用户名和密码 spring: application: name: config-server cloud: config: label: master # 配置仓库的分支 server: git: uri: https://github.com/forezp/SpringcloudConfig/ # 配置git仓库地址 searchPaths: respo # 配置仓库路径 username: # 访问git仓库的用户名 password: # 访问git仓库的用户密码
主类添加
@EnableEurekaClient
注解 -
改造 config-client
添加eureka client依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
修改配置文件
server: port: 8881 eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ spring: application: name: config-client cloud: config: label: master profile: dev # dev:开发环境,test:测试环境,pro:生产环境 discovery: enabled: true serviceId: config-server # 通过服务名指定配置服务中心(需要config-server和config-client都在服务注册中心注册) # uri: http://localhost:8888/ # 通过网址指定配置服务中心
主类添加
@EnableEurekaClient
注解 -
启动并测试
依次启动
eureka-server
,config-server
和config-client
。结果:可以看到
config-server
和config-client
均注册到服务注册中心。访问 http://localhost:8881/getFoo
结果:foo version 21
结论:
config-server
及config-client
可以同时作为EurekaClient注册到服务注册中心,最终实现高可用。
消息总线(Spring Cloud Bus)
Spring Cloud Bus 将分布式的节点用轻量的消息代理连接起来。它可以用于广播配置文件的更改或者服务之间的通讯,也可以用于监控。本文要讲述的是用Spring Cloud Bus实现通知微服务架构的配置文件的更改。
-
准备工作
下载安装 Erlang 及 RabbitMQ。
-
改造config-client模块
添加 bus-ampq 依赖。
<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>
编辑配置文件
server: port: 8881 eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ management: endpoints: web: exposure: include: bus-refresh spring: application: name: config-client cloud: config: label: master profile: dev # dev:开发环境,test:测试环境,pro:生产环境 discovery: enabled: true serviceId: config-server # 通过服务名指定配置服务中心(需要config-server和config-client都在服务注册中心注册) # uri: http://localhost:8888/ # 通过网址指定配置服务中心 bus: enabled: true trace: enabled: true rabbitmq: host: localhost password: guest port: 5672 username: guest
在启动类中添加类注解
@RefreshScope
-
启动服务及测试
复制一份
config-client
,端口改为8882。依次启动
eureka-server
,config-server
及端口为8881和8882的两个config-client
。访问 http://localhost:8881/getFoo
访问 http://localhost:8882/getFoo
返回结果均如下:
foo version 3
此时前往配置中心仓库修改foo配置为
foo version 22
访问getFoo,返回结果仍为
foo version 3
使用http工具发送POST请求
http://localhost:8881/actuator/bus-refresh
查看
config-server
控制台,显示已从配置中心仓库中获取到最新的配置文件。再次访问getFoo,返回结果如下:
foo version 22
结论:
当git文件更改的时候,通过pc端用post 向端口为8882的config-client发送请求/bus/refresh/;此时8882端口会发送一个消息,由消息总线向其他服务传递,从而使整个微服务集群都达到更新配置文件。
服务链路追踪(Spring Cloud Sleuth)
Spring Cloud Sleuth 简介
Add sleuth to the classpath of a Spring Boot application (see below for Maven and Gradle examples), and you will see the correlation data being collected in logs, as long as you are logging requests.
quote from https://github.com/spring-cloud/spring-cloud-sleuth
Spring Cloud Sleuth 主要功能就是在分布式系统中提供追踪解决方案,并且兼容支持了 zipkin,你只需要在pom文件中引入相应的依赖即可。
构建server-zipkin
-
下载
zipkin
下载地址:https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/
-
运行
java -jar D:\jar\zipkin\zipkin-server-2.10.1-exec.jar
-
访问
zipkin 默认端口为
9411
。浏览器访问:http://localhost:9411
创建测试模块
-
创建模块并添加依赖
创建模块
service-zipkin-test1
和service-zipkin-test2
,并添加 zipkin-client 依赖:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>
-
编辑配置文件
server: port: 8989 spring: application: name: service-zipkin-test2 zipkin: base-url: http://localhost:9411 sleuth: sampler: probability: 1.0
-
修改启动类
模块
service-zipkin-test1
启动类:package com.xfc.service.zipkin.test1; import brave.sampler.Sampler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.logging.Level; import java.util.logging.Logger; @SpringBootApplication @RestController public class ServiceZipkinTest1Application { private static final Logger LOG = Logger.getLogger(ServiceZipkinTest1Application.class.getName()); public static void main(String[] args) { SpringApplication.run(ServiceZipkinTest1Application.class, args); } @Autowired private RestTemplate restTemplate; @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } @RequestMapping("/test1") public String callTest2() { LOG.log(Level.INFO, "interface of test1 application"); return restTemplate.getForObject("http://localhost:8989/test2", String.class); } @RequestMapping("/info") public String info() { LOG.log(Level.INFO, "application info of test1"); return "application info of test1"; } @Bean public Sampler defaultSampler() { return Sampler.ALWAYS_SAMPLE; } }
模块
service-zipkin-test2
启动类:package com.xfc.service.zipkin.test2; import brave.sampler.Sampler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.logging.Level; import java.util.logging.Logger; @SpringBootApplication @RestController public class ServiceZipkinTest2Application { public static void main(String[] args) { SpringApplication.run(ServiceZipkinTest2Application.class, args); } private static final Logger LOG = Logger.getLogger(ServiceZipkinTest2Application.class.getName()); @Autowired private RestTemplate restTemplate; @RequestMapping("/test2") public String callTest1Info() { LOG.log(Level.INFO, "interface of test2 application"); return restTemplate.getForObject("http://localhost:8988/info", String.class); } @RequestMapping("/info") public String home() { LOG.log(Level.INFO, "application info of test2"); return "application info of test2"; } @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } @Bean public Sampler defaultSampler() { return Sampler.ALWAYS_SAMPLE; } }
-
访问测试
参考上文启动 server-zipkin 服务。
启动模块
service-zipkin-test1
和service-zipkin-test2
。访问:http://localhost:8989/test2 返回结果:application info of test1
访问:http://localhost:8988/test1 返回结果:application info of test1
访问:http://localhost:9411/ 进行追踪,即可在
依赖
中查看到两个工程模块之间的依赖关系。
参考
著作権声明
本記事のリンク:https://www.chinmoku.cc/dev/java/framework/springcloud-tutorial/
本博客中的所有内容,包括但不限于文字、图片、音频、视频、图表和其他可视化材料,均受版权法保护。未经本博客所有者书面授权许可,禁止在任何媒体、网站、社交平台或其他渠道上复制、传播、修改、发布、展示或以任何其他方式使用此博客中的任何内容。