微服务框架
[TOC]
一、SpringCloud介绍
1.1 微服务架构
https://martinfowler.com/articles/microservices.html
微服务架构提出者:马丁福勒
简而言之,微服务体系结构[样式 1]是一种将单个应用程序开发为一组小型服务的方法,每个应用程序在自己的进程中运行,并与轻量级机制(通常是 HTTP 资源 API)通信。这些服务围绕业务功能构建,可通过全自动部署机制独立部署。这些服务的集中管理最少,可能以不同的编程语言编写,并使用不同的数据存储技术。
1.2 SpringCloud介绍
SpringCloud是微服务架构落地的一套技术栈
SpringCloud中的大多数技术都是基于Netflix公司的技术进行第二次开发。
1、SpringCloud的中文社区网站:http://springcloud.cn/
2、SpringCloud的中文网 :http://springcloud.cc/
八个技术点:
1、Eureka - 服务的注册与发现
2、Robbn - 服务之间的负载均衡
3、Feign - 服务之间的通讯
4、Hystrix - 服务的线程隔离及其熔断器
5、Zuul - 服务网关
6、Stream - 实现MQ的使用
7、Config - 动态配置
8、 Sleuth - 服务追踪
二、服务的注册与发现-Eureka
2.1 引言
Eureka就是帮助我们维护所有服务的信息,以便服务之间的相互调用
2.2 Eureka的快速入门
2.2.1 创建EurekaServer
1、创建一个父工程,并且在父工程中指定SpringCloud版本,并且将packaing修改为pom
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <packaging>pom</packaging>
<properties> <java.version>1.8</java.version> <spring.cloud-version>Hoxton.SR8</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>
|
2、创建eureka的server,创建springboot工程,并且导入依赖,再启动类中添@EnableEurekaServer
注解和编写yml文件
2.1、导入依赖
1 2 3 4 5 6 7 8 9 10
| <dependencies> <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-server</artifactId> </dependency> </dependencies>
|
2.2、启动类添加注解
1 2 3 4 5 6 7
| @EnableEurekaServer @SpringBootApplication public class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class,args); } }
|
2.3 编写yml配置文件
1 2 3 4 5 6 7 8 9 10 11 12
| server: port: 8761
eureka: instance: hostname: localhost client: registerWithEureka: false fetchRegistry: false serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
|
2.2.2 创建EurekaClient
1、创建Maven工程,修改为SpringBoot
2、导入依赖
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
|
3、在启动类上添加注解@EnableEurekaClient
1 2 3 4 5 6 7
| @EnableEurekaClient @SpringBootApplication public class CustomerApplication { public static void main(String[] args) { SpringApplication.run(CustomerApplication.class,args); } }
|
4、编写配置文件
1 2 3 4 5 6 7 8 9 10
| eureka: client: service-url: defaultZone: http://localhost:8761/eureka
spring: application: name: CUSTOMER
|
2.2.3 测试Eureka
1、创建了一个Search搜索模块,并且注册到Eureka
2、使用EurekaClient的对象获取服务信息
1 2
| @Autowired private EurekaClient eurekaClient;
|
3、创建RestTemplate
1 2 3 4 5 6 7 8
| @Configuration public class RestTemplateConfig {
@Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
|
4、使用RestTemplate调用
1 2 3 4 5 6 7 8 9 10 11 12
| @GetMapping("/customer") public String customer() { InstanceInfo info = eurekaClient.getNextServerFromEureka("SEARCH", false); String url = info.getHomePageUrl(); System.out.println(url); String result = restTemplate.getForObject(url + "/search", String.class); return result; }
|
2.3 Eureka的安全性
实现Eureka认证
1、导入依赖
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
|
2、编写配置类
1 2 3 4 5 6 7 8 9 10
| @EnableWebSecurity class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().ignoringAntMatchers("/eureka/**"); super.configure(http); } }
|
3、编写配置文件,配置用户密码
1 2 3 4 5 6
| spring: security: user: name: root password: root
|
4、其他服务想注册到Eureka上需要添加用户名密码
1 2 3 4 5
| eureka: client: service-url: defaultZone: http://用户名:密码@localhost:8761/eureka
|
2.4 Eureka的高可用性
如果程序正在运行,突然Eureka宕机了
1、如果调用方访问过一次被调用方,Eureka的宕机就不会影响到功能
2、如果调用方没有访问过被调用方,Eureka的宕机就会造成当前功能的不可用到功能
搭建Eureka高可用
1、准备多态Eureka
采用了复制的方式,删除iml和target文件,并且修改pom.xml中的项目名称,再给负公差添加module
2、让服务注册到多台Eureka上
1 2 3 4 5 6 7
| server: port: 8761
eureka: client: serviceUrl: defaultZone: http://root:root@localhost:8762/eureka/
|
1 2 3 4 5 6 7
| server: port: 8762
eureka: client: serviceUrl: defaultZone: http://root:root@localhost:8761/eureka/
|
3、让多台Eureka之间相互通讯
1 2 3 4 5 6 7
| eureka: client: registerWithEureka: true fetchRegistry: true serviceUrl: defaultZone: http://root:root@localhost:8761/eureka/
|
2.5 Eureka的细节
1、EurekaClient启动时,讲自己的信息注册到EurekaServer上,EurekaServer就会储存EurekaClient的注册信息。
2、当EurekaClient调用服务时,本地没有注册信息的缓存时,去EurekaServer中获取注册信息
3、EurekaClient会通过心跳的方式去和EurekaServer进行连接。(默认30s EurekaClient就会发送一次心跳请求,如果超过了90s还没有发送心跳信息的话,EurekaSevrer就认为你宕机了,将当前的EurekaClient从注册表中移除)
1 2 3 4
| eureka: instance: lease-renewal-interval-in-seconds: 30 lease-expiration-duration-in-seconds: 90
|
4、EurekaClient会每个30s去EurekaServer中去更新本地的注册表
1 2 3 4
| eureka: client: registry-fetch-interval-seconds: 30
|
5、Eureka的自我保护机制,统计15分钟内,如果一个服务的心跳发送比例低于85%,EurekaServer就会开启自我保护机制
1、不会从EurekaServer中去移除长时间没有收到心跳的服务
2、EurekaServer还是可以正常提供服务的
3、网络稳定时,EurekaServer才会开始将自己的信息被其他节点同步过去
1 2 3 4 5
| eureka: server: enable-self-preservation: true
|
6、CAP定理,C-一致性 A-可用性 P-分区容错性,这三个特新在分布是环境下,只能满足2个,而且分区容错性在分布式环境下,时必须要满足的。只能在AC之间进行权衡。
1、如果选择CP,保证了一致性,可能会造成你系统在一定时间内是不可以的,如果你同步数据的时间比较长,造成的损失就越大。
2、如果选择AP的效果,高可用的集群,Eureka集群是无中心,Eureka即便宕机几个也不会影响系统的使用,不需要重新去枚举一个master,也会导致一定时间内数据是不一致。
三、服务间的负载均衡-Robbin
3.1 引言
Robbin是帮助我们实现服务和服务负载均衡
客户端负载均衡:customer客户端模块,将2个Search模块信息全部拉取到本地的缓存,在customer中自己做一个负载均衡的策略,选中某一个服务。
服务端负载均衡:在注册中心中,直接根据你指定的负载均衡策略,帮你选中一个指定的服务器信息,并返回。
3.2 Robbin的快速入门
1、启动两个Search模块
2、在customer导入robbin依赖
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency>
|
3、配置整合RestTemplate和Robbin
1 2 3 4 5 6 7 8 9
| @Configuration public class RestTemplateConfig {
@Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }
|
4、在customer中去访问Search
1 2 3 4 5 6
| @GetMapping("/customer") public String customer() { String result = restTemplate.getForObject("http://SEARCH/search", String.class); return result; }
|
3.3 Robbin配置负载均衡策略
1、负载均衡策略
- RandomRule:随机策略
- RoundRobbinRule:轮询策略
- WeightedResponseTimeRule:默认会采用轮询的策略,后续会根据服务的响应时间,自动给你分配权重
- BestAvailableRule:根据被调用方并发数最小的去分配
2、采用注解的形式
1 2 3 4
| @Bean public IRule robbinRule() { return new RandomRule(); }
|
3、配置文件去指定负载均衡的策略(推荐)
1 2 3 4
| SEARCH: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
|
四、服务间的调用-Feign
4.1 引言
Feign可以帮助我们实现面向接口编程,就直接调用其他服务,简化开发。
4.2 Feign的快速入门
1、导入依赖
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
|
2、添加一个注解@EnableFeignClients
1 2 3 4 5 6 7 8
| @EnableEurekaClient @SpringBootApplication @EnableFeignClients public class CustomerApplication { public static void main(String[] args) { SpringApplication.run(CustomerApplication.class,args); } }
|
3、创建一个接口,并且和search模块做映射
1 2 3 4 5 6 7 8
| @FeignClient("SEARCH") public interface SearchClient {
@RequestMapping(value = "/search",method = RequestMethod.GET) public String search(); }
|
4、测试使用
1 2 3 4 5 6 7 8
| @Autowired private SearchClient searchClient;
@GetMapping("/customer") public String customer() { String result = searchClient.search(); return result; }
|
4.3 Feign的传递参数方式
1、注意事项
- 如果你传递的参数,比较复杂时,默认会采用POST的请求方式。
- 传递单个参数时,推荐使用@PathVariable,如果传递的单个参数比较多,这里也可以采用@RequestParam,不要省略value属性
- 传递对象信息时,统一采用json的方式,添加@RequestBody
- Client接口必须采用@RequestMapping
2、在Search模块下准备三个接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @GetMapping("/search/{id}") public Customer findById(@PathVariable Integer id) { return new Customer(1, "zhangsan", 23); }
@GetMapping("/getCustomer") public Customer getcustomer(@RequestParam Integer id, @RequestParam String name) { return new Customer(id, name, 23); }
@PostMapping("save") public Customer save(@RequestBody Customer customer) { return customer; }
|
3、封装Customer模块的Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @GetMapping("/customer/{id}") public Customer findById(@PathVariable Integer id) { return searchClient.findById(id); }
@GetMapping("/getCustomer") public Customer getcustomer(@RequestParam Integer id, @RequestParam String name) { return searchClient.getcustomer(id,name); }
@GetMapping("save") public Customer save(Customer customer) { return searchClient.save(customer); }
|
4、再封装Client接口
1 2 3 4 5 6 7 8
| @RequestMapping(value = "/search/{id}",method = RequestMethod.GET) Customer findById(@PathVariable(value = "id") Integer id);
@RequestMapping(value = "/getCustomer",method = RequestMethod.GET) Customer getcustomer(@RequestParam(value = "id") Integer id, @RequestParam(value = "name") String name);
@RequestMapping(value = "save",method = RequestMethod.GET) Customer save(@RequestBody Customer customer);
|
5、测试
4.4 Feign的Fallback
Fallback可以帮助我们在使用Feign去调用另一个服务时,如果出现了问题,走服务降级,返回一个错误的数据,避免功能因为一个服务出现问题,全部失效
1、创建一个POJO类,实现Client接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Component public class SearchClientFallBack implements SearchClient { @Override public String search() { return "出现问题了"; }
@Override public Customer findById(Integer id) { return null; }
@Override public Customer getcustomer(Integer id, String name) { return null; }
@Override public Customer save(Customer customer) { return null; } }
|
2、修改Client接口中的注解,添加一个属性
1
| @FeignClient(value = "SEARCH",fallback = SearchClientFallBack.class)
|
3、添加一个配置文件。
1 2 3 4
| feign: hystrix: enabled: true
|
调用方无法知道具体的错误信息是什么,通过FallBackFactory的方式去实现这个功能
1、FallBackFactory基于fallback
2、创建一个POJO类,实现FallBackFactory
1 2 3 4 5 6 7 8 9 10 11
| @Component public class SearchClientFallBackFactory implements FallbackFactory<SearchClient> {
@Autowired private SearchClientFallBack searchClientFallBack; @Override public SearchClient create(Throwable throwable) { throwable.printStackTrace(); return searchClientFallBack; } }
|
3、修改Client接口中的属性
1 2 3
| @FeignClient(value = "SEARCH", fallbackFactory = SearchClientFallBackFactory.class )
|
五、服务的隔离及熔断器-Hystrix
5.1 引言
5.2 降级机制实现
1、导入依赖
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
|
2、添加一个注解@EnableCircuitBreaker
1 2 3 4 5 6 7 8
| @EnableEurekaClient @SpringBootApplication @EnableCircuitBreaker public class CustomerApplication { public static void main(String[] args) { SpringApplication.run(CustomerApplication.class,args); } }
|
3、针对某一个接口去编写他的降级方法
1 2 3 4 5 6 7 8 9 10 11
| @GetMapping("/customer/{id}") @HystrixCommand(fallbackMethod ="findByIdFallBack" ) public Customer findById(@PathVariable Integer id) { int i = 1/0; return searchClient.findById(id); }
public Customer findByIdFallBack(Integer id) { return new Customer(-1,"",0); }
|
4、在接口上添加注解
1
| @HystrixCommand(fallbackMethod ="findByIdFallBack" )
|
5、测试
5.3 线程隔离
如果使用Tomcat的线程池去接收用户的请求,使用当前线程去执行其他服务的功能,如果某一个服务出现了故障,导致tomcat的线程大量的堆积,导致tomcat无法处理其他业务功能。
1、Hystrix线程池(默认),接收用户请求采用tomcat的线程池,执行业务代码,调用其他服务时,采用Hystrix的线程池。
2、信号量,使用的还是Tomcat的线程池,帮助我们取关了Tomcat的线程池
1、Hystrix的线程池的配置(具体的配置属性需要去查看HystrixCommandProperties类) wiki
- 线程隔离策略:name =
hystrix.command.default.execution.isolation.strategy
,value=THREAD
,SEMAPHORE
- 指定超时时间(针对线程池):name=
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
,value =1000
- 是否开启超时时间配置:name=
hystrix.command.default.execution.timeout.enabled
,value=true
- 超时之后是否中断线程:name=
hystrix.command.default.execution.isolation.thread.interruptOnTimeout
,value=true
- 取消任务之后是否中断线程:name=
hystrix.command.default.execution.isolation.thread.interruptOnCancel
,value=false
2、信号量配置信息
- 线程隔离策略:name =
hystrix.command.default.execution.isolation.strategy
,value=THREAD
,SEMAPHORE
- 指定信号量的最大并发请求数:name=
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests
,value=10
5.4 断路器
5.4.1 断路器介绍
如果调用指定服务时,如果说这个服务的失败率达到你输入的阈值麻将断路器从closed状态,转变为open状态,指定服务时无法被访问的,如果你访问就直接走fallback方法,在一定时间内,open状态会再次转变为half open状态,允许一个请求发送到我指定服务,如果成功,则转变为closed,如果失败,服务再次转变为open状态,会再次循环到hald open,直到专路器回到一个closed状态。
5.4.2 配置断路器的监控界面
1、导入依赖
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency>
|
2、在启动类中添加注解@EnableHystrixDashboard
1 2 3 4 5 6 7 8 9 10 11
| @EnableEurekaClient @SpringBootApplication @EnableFeignClients @EnableCircuitBreaker @EnableHystrixDashboard @ServletComponentScan("cn.zyjblogs.servlet") public class CustomerApplication { public static void main(String[] args) { SpringApplication.run(CustomerApplication.class,args); } }
|
3、配置一个Servlet路径,指定上Hystrix的Servlet
1 2 3
| @WebServlet("/hystrix.stream") public class HystrixServlet extends HystrixMetricsStreamServlet { }
|
4、在启动类上添加扫描Servlet的注解@ServletComponentScan("cn.zyjblogs.servlet")
5、配置文件
1 2 3
| hystrix: dashboard: proxy-stream-allow-list: localhost
|
6、测试
直接访问: http://host:port/hystrix.stream
在当前位置输入映射好的servlet路径
5.4.3 配置断路器的属性
断路器的属性(10秒之内)
- 断路器的开关:name=
hystrix.command.default.circuitBreaker.enabled
,value=true
- 失败阈值的总请求数:name=
hystrix.command.default.circuitBreaker.requestVolumeThreshold
,value=20
- 请求总数失败率达到%多少时打开断路器:name=
hystrix.command.default.circuitBreaker.errorThresholdPercentage
,value=50
- 断路器open状态后,多少秒是拒绝请求的:name=
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds
value=5000
- 强制让服务拒绝请求:name=
hystrix.command.default.circuitBreaker.forceOpen
,value=false
- 强制让服务接收请求:name=
hystrix.command.default.circuitBreaker.forceClosed
,value=false
具体配置方式
1 2 3 4 5 6 7
| @GetMapping("/customer/{id}") @HystrixCommand(fallbackMethod ="findByIdFallBack",commandProperties = { @HystrixProperty(name = "circuitBreaker.enabled",value="true"), @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value="10"), @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value="70"), @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value="5000") })
|
5.5 请求缓存
5.5.1 请求缓存的介绍
1、请求缓存的声明周期是一次请求
2、请求缓存是缓存当前线程中的一个方法,将方法参数作为key,方法的返回结果作为value
3、在一次请求中,目标方法被调用过一次以后就都会被缓存
5.5.2 请求缓存的实现
1、创建一个Service,在Service中调用Search服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Service public class CustomerService {
@Autowired private SearchClient searchClient;
@CacheResult @HystrixCommand(commandKey = "findById") public Customer findById(@CacheKey Integer id) throws InterruptedException { return searchClient.findById(id); }
@CacheRemove(commandKey = "findById") @HystrixCommand public void clearFindById(@CacheKey Integer id) { System.out.println("findById缓存被清空"); }
}
|
2、使用请求缓存的注解@CacheResult
CacheRemove
- @CacheResult:帮助我们缓存当前方法的返回结果(必须配合@HystrixCommand使用)
- @CacheRemove:帮助我们清除某一个缓存信息(基于commandKey)
- @CacheKey:指定那个方法参数作为缓存标识
3、修改Search模块的结果返回值
1
| return new Customer(id, name, (int) (Math.random() * 100000));
|
4、编写Filter,去构建HystrixRequestContext
1 2 3 4 5 6 7 8
| @WebFilter("/*") public class HystrixRequestContextFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HystrixRequestContext.initializeContext(); filterChain.doFilter(servletRequest,servletResponse); } }
|
5、修改Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @Autowired private CustomerService customerService;
@GetMapping("/customer/{id}") @HystrixCommand( fallbackMethod = "findByIdFallBack", commandProperties = { @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "70"), @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000"), }) public Customer findById(@PathVariable Integer id) throws InterruptedException { System.out.println(Thread.currentThread().getName()); if (id == 1) { int i = 1 / 0; } System.out.println(customerService.findById(id)); System.out.println(customerService.findById(id)); customerService.clearFindById(id); System.out.println(customerService.findById(id)); System.out.println(customerService.findById(id)); customerService.clearFindById(id);
return searchClient.findById(id); }
|
6、测试
六、服务的网关-Zuul
6.1 引言
1、客户端维护大量的ip和port信息,直接访问指定服务
2、认证和授权操作,需要在每一个模块中添加认证和授权操作
3、项目迭代,服务拆分,服务要合并,需要客户端镜像大量的变化
4、统一的把安全性校验都放在Zuul中
6.2 Zuul的快速入门
1、创建Maven项目,修改SpringBoot
2、导入依赖
1 2 3 4 5 6 7 8 9 10 11 12
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
|
3、添加注解@EnableZuulProxy
@EnableEurekaClient
1 2 3 4 5 6 7 8
| @SpringBootApplication @EnableEurekaClient @EnableZuulProxy public class ZuulApplication { public static void main(String[] args) { SpringApplication.run(ZuulApplication.class,args); } }
|
4、编写配置文件
1 2 3 4 5 6 7 8 9 10 11 12
| server: port: 80
eureka: client: service-url: defaultZone: http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka
spring: application: name: ZUUL
|
5、测试
6.3 Zuul常用配置信息
6.3.1 Zuul的监控界面
1、导入依赖
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
|
2、编写配置文件
1 2 3 4 5 6
| management: endpoints: web: exposure: include: "*"
|
3、直接访问:http://localhost/actuator/routes
6.3.2 忽略服务设置
1 2 3 4 5 6
| zuul: ignored-services: eureka ignored-patterns: /**/search/**
|
6.3.3 自定义服务配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| zuul: ignored-services: "*" ignored-patterns: /**/search/**
routes: kehu: path: /cc/** serviceId: customer
|
6.3.4 灰度发布
1、添加一个配置类
1 2 3 4 5 6 7 8 9 10 11
| @Configuration public class ZuulConfig { @Bean public PatternServiceRouteMapper serviceRouteMapper() { return new PatternServiceRouteMapper( "(?<name>^.+)-(?<version>v.+$)", "${version}/${name}"); } }
|
2、准备一个服务,提供2个版本
1 2 3 4 5 6
| version: v1
spring: application: name: CUSTOMER-${version}
|
3、修改Zuul的配置
4、修改CustomerController
1 2 3 4 5 6 7
| @Value("${version}") private String version;
@GetMapping("/version") public String version() { return version; }
|
5、测试
6.4 Zuul的过滤器执行流程
客户端请求发送到Zuul服务商,首先通过PreFilter,如果正常放行,会把请求再次转发给RoutingFilter,请求转发到一个指定的服务,在指定的服务响应一个结果之后,再次走一个PostFilter的过滤器链,最终将响应信息返回给客户端。
6.5 Zuul过滤器入门
1、创建POJO类,继承ZuulFilter
1 2 3 4
| @Component public class ZuulFilterTest extends ZuulFilter {
}
|
2、指定当前过滤器的类型
1 2 3 4
| @Override public String filterType() { return FilterConstants.PRE_TYPE; }
|
3、指定过滤器的执行顺序
1 2 3 4
| @Override public int filterOrder() { return FilterConstants.PRE_DECORATION_FILTER_ORDER -1; }
|
4、配置是否启用
1 2 3 4 5
| @Override public boolean shouldFilter() { return true; }
|
5、指定过滤器中的具体业务代码
1 2 3 4 5
| @Override public Object run() throws ZuulException { System.out.println("prefix过滤器已经执行~~~"); return null; }
|
6、测试
6.6 PreFilter实现token校验
1、准备访问路径,请求参数专递token
http://localhost/v1/customer/version?token=123
2、创建AuthenticationFilter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Component public class AuthenticationFilter extends ZuulFilter{ @Override public String filterType() { return FilterConstants.PRE_TYPE; }
@Override public int filterOrder() { return FilterConstants.PRE_DECORATION_FILTER_ORDER - 2; }
@Override public boolean shouldFilter() { return true; }
@Override public Object run() throws ZuulException { return null; } }
|
3、在run方法中编写具体的业务逻辑代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Override public Object run() throws ZuulException { System.out.println("AuthenticationFilter执行了"); RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); String token = request.getParameter("token"); if (token == null || !"123".equalsIgnoreCase(token)) { System.out.println("token校验失败"); requestContext.setSendZuulResponse(false); requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value()); } return null; }
|
4、测试
6.7 Zuul的降级
1、创建POJO类,实现接口FallbackProvider
1 2 3 4
| @Component public class ZuulFallBack implements FallbackProvider {
}
|
2、重写两个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| @Override public String getRoute() { return "*"; }
@Override public ClientHttpResponse fallbackResponse(String route, Throwable cause) { System.out.println("降级的服务"+route); cause.printStackTrace(); return new ClientHttpResponse() { @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; }
@Override public InputStream getBody() throws IOException { String msg = "当前服务" + route + "出现问题!!!"; return new ByteArrayInputStream(msg.getBytes()); }
@Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.INTERNAL_SERVER_ERROR; }
@Override public int getRawStatusCode() throws IOException { return HttpStatus.INTERNAL_SERVER_ERROR.value(); }
@Override public String getStatusText() throws IOException { return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(); }
@Override public void close() {
} }; }
|
3、测试
6.8 Zuul动态路由
1、创建一个过滤器
1 2 3 4
| @Component public class DynamicRoutingFilter extends ZuulFilter{
}
|
2、在run方法中编辑业务逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| @Override public String filterType() { return FilterConstants.PRE_TYPE; }
@Override public int filterOrder() { return FilterConstants.PRE_DECORATION_FILTER_ORDER +3; }
@Override public boolean shouldFilter() { return true; }
@Override public Object run() throws ZuulException { System.out.println("DynamicRoutingFilter执行了"); RequestContext context = RequestContext.getCurrentContext(); HttpServletRequest request = context.getRequest();
String redisKey = request.getParameter("redisKey");
if (redisKey != null && redisKey.equalsIgnoreCase("customer")) { System.out.println("DynamicRoutingFilter执行了路由到了/customer"); context.put(FilterConstants.SERVICE_ID_KEY,"customer-v1"); context.put(FilterConstants.REQUEST_URI_KEY,"/customer"); } else if (redisKey != null && redisKey.equalsIgnoreCase("search")) { System.out.println("DynamicRoutingFilter执行了路由到了/search/1"); context.put(FilterConstants.SERVICE_ID_KEY,"search"); context.put(FilterConstants.REQUEST_URI_KEY,"/search/1"); } return null; }
|
3、测试
七、多语言支持-Sidecar
7.1 引言
在SpringCloud的项目中,需要接入一些非java程序,第三方接口,无法接入eureka,hystrix,feign等组件。启动一个代理的微服务,代理微服务去和非java的程序或第三方接口交流,通过代理的非服务去计入SpringCloud的相关组件。
7.2 Sidecar实现
1、创建一个第三方的服务
创建一个SpringBoot工程,并且添加一个Controller
2、创建Maven工程,修改为SpringBoot
2、导入以来
1 2 3 4 5 6 7 8
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-netflix-sidecar</artifactId> </dependency>
|
4、添加注解@EnableSidecar
5、编写配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| server: port: 81
eureka: client: service-url: defaultZone: http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka
spring: application: name: OTHER-SERVICE
sidecar: port: 7001
|
6、通过customer通过Feign的方式调用第三方服务
八、服务间消息传递-Stream
8.1 引言
用于构建消息驱动微服务的框架(在下面方便起见也叫它Stream框架),该框架在Spring Boot的基础上整合了Spring Integration来连接消息代理中间件(RabbitMQ,Kafka等)。它支持多个消息中间件的自定义配置,同时吸收了这些消息中间件的部分概念,例如持久化订阅、消费者分组,和分区等概念。使用Stream框架,我们不必关系如何连接各个消息代理中间件,也不必关系消息的发送与接收,只需要进行简单的配置就可以实现这些功能了,可以让我们更敏捷的进行开发主体业务逻辑了。
Spring Cloud Stream框架的组成部分:
- Stream框架自己的应用模型;
- 绑定器抽象层,可以与消息代理中间件进行绑定,通过绑定器的API,可实现插件式的绑定器。
- 持久化订阅的支持。
- 消费者组的支持。
- Topic分区的支持。
8.2 Stream快速入门
1、启动RabbitMQ
2、消费者-导入依赖
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency>
|
3、消费者-配置文件
1 2 3 4 5 6
| spring: rabbitmq: port: 5672 username: test password: test virtual-host: /test
|
4、消费者-监听的队列
1 2 3 4
| public interface StreamClient { @Input("myMessage") SubscribableChannel input(); }
|
1 2 3 4 5 6 7 8
| @Component @EnableBinding(StreamClient.class) public class StreamReceiver { @StreamListener("myMessage") public void msg(Object msg) { System.out.println("接收到消息" + msg); } }
|
5、启动类添加注解@EnableBinding(StreamClient.class)
6、生产者-导入依赖
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency>
|
7、生产者-配置文件
1 2 3 4 5 6
| spring: rabbitmq: port: 5672 username: test password: test virtual-host: /test
|
8、生产者-发布消息
1 2 3 4
| public interface StreamClient { @Output("myMessage") MessageChannel output(); }
|
1 2 3 4 5 6 7 8 9 10
| @RestController public class MessageController { @Autowired private StreamClient streamClient; @GetMapping("/send") public String send() { streamClient.output().send(MessageBuilder.withPayload("Hello Stream").build()); return "消息发送成功!!"; } }
|
9、启动类添加注解@EnableBinding(StreamClient.class)
10、测试访问http://localhost:8080/send
8.3 Stream重复消费问题
只需要添加一个配置,指定消费者组
1 2 3 4 5 6
| spring: cloud: stream: binders: myMessage: group: customer
|
8.4 Stream的消费者手动ack
1、编写配置
1 2 3 4 5 6 7 8 9 10
| spring: cloud: stream: rabbit: bindings: myMessage: consumer: acknowledgeMode: MANUAL
|
2、修改消费端方法
1 2 3 4 5 6 7 8 9 10 11
| @Component @EnableBinding(StreamClient.class) public class StreamReceiver { @StreamListener("myMessage") public void msg(Object msg, @Header(name = AmqpHeaders.CHANNEL) Channel channel, @Header(name = AmqpHeaders.DELIVERY_TAG) Long deliveryTag) throws IOException { System.out.println("接收到消息" + msg); channel.basicAck(deliveryTag,false); } }
|
九、服务的动态配置-Config
9.1 引言
1、配置文件分散在不同项目中的,不方便去维护。
2、配置文件的安全问题。
3、修改配置文件,无法立即生效。
9.2 搭建Config-Server
1、创建Maven工程,修改SpringBoot
2、导入依赖
1 2 3 4 5 6 7 8
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency>
|
3、添加注解@EnableConfigServer
1 2 3 4 5 6 7
| @SpringBootApplication @EnableConfigServer public class ConfigApplication { public static void main(String[] args) { SpringApplication.run(ConfigApplication.class,args); } }
|
4、编写配置文件(git)
1 2 3 4 5 6 7 8 9
| spring: cloud: config: server: git: basedir: E:\config username: xxxxxx@xxxx.com password: xxxxxxxx uri: https://gitee.com/zyjblog/config-resp.git
|
5、测试(例子:http://localhost:82/master/customer-xxx.yml
(master可以省略))
访问方式如下:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
9.3、修改Customer连接Config
1、导入依赖
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency>
|
2、修改配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #指定Eureka服务地址 eureka: client: service-url: defaultZone: http:
version: v1 #指定服务名称 spring: application: name: CUSTOMER-${version} cloud: config: discovery: enabled: true service-id: CONFIG profile: dev
|
3、修改配置文件名称application.yml
改为bootstrap.yml
4、测试测试发布消息到RabbMQ
9.4 实现动态配置
9.4.1 实现原理
9.4.2 服务连接RabbitMQ
1、导入依赖
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
|
2、编写配置文件
1 2 3 4 5 6 7
| spring: rabbitmq: virtual-host: /test host: localhost username: test password: test port: 5672
|
3、测试
9.4.3 实现手动刷新
1、导入依赖(两个服务config和customer均添加)
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
|
2、编写配置文件(两个服务config和customer均添加)
1 2 3 4 5
| management: endpoints: web: exposure: include: "*"
|
3、为customer添加一个controller,添加注解@RefreshScope
1 2 3 4 5 6 7 8 9 10 11 12
| @RestController @RefreshScope public class CustomerController {
@Value("${env}") private String env;
@GetMapping("/env") public String env() { return env; } }
|
4、测试
- CONFIG在gitee修改之后,自动拉取最新的配置信息。
- 其他模块需要更新的话,手动发送一个POST请求:http://localhost:10000/actuator/bus-refresh ,不重启项目,即可获取最新的配置信息
9.4.4 内网穿透
1、内网穿透官网:http://www.ngrok.cc/
2、注册登录
3、购买免费隧道,并配置
4、下载客户端,并复制隧道id,点击运行客户端,复制到客户端中
5、测试访问是否成功
9.4.5 实现自动刷新配置
1、配置Gitee中的WebHooks
2、给Config添加一个过滤器UrlFilter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| @WebFilter("/*") public class UrlFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpServletRequest= (HttpServletRequest) servletRequest; String url=httpServletRequest.getRequestURI(); System.out.println(url); if(!url.endsWith("/actuator/bus-refresh")){ filterChain.doFilter(servletRequest,servletResponse); return; } String body=(httpServletRequest).toString(); System.out.println("original body: "+ body); RequestWrapper requestWrapper=new RequestWrapper(httpServletRequest); filterChain.doFilter(requestWrapper,servletResponse); } private class RequestWrapper extends HttpServletRequestWrapper { public RequestWrapper(HttpServletRequest request) { super(request); }
@Override public ServletInputStream getInputStream() throws IOException { byte[] bytes = new byte[0]; ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ServletInputStream servletInputStream = new ServletInputStream() { @Override public int read() throws IOException { return byteArrayInputStream.read(); }
@Override public boolean isFinished() { return byteArrayInputStream.read() == -1 ? true : false; }
@Override public boolean isReady() { return false; }
@Override public void setReadListener(ReadListener listener) {
} }; return servletInputStream; } } }
|
3、添加注解@ServletComponentScan("cn.zyjblogs.filter")
1 2 3 4 5 6 7 8
| @SpringBootApplication @EnableConfigServer @ServletComponentScan("cn.zyjblogs.filter") public class ConfigApplication { public static void main(String[] args) { SpringApplication.run(ConfigApplication.class,args); } }
|
3、测试
十、服务的追踪-Sleuth
10.1 引言
在整个微服务架构中,微服务很多,一个请求可能需要调用很多很多的服务,最终才能完成一个功能,如果说,整个功能出现了问题,在这么多的服务中,如何区定位到问题的所在点,出现问题的原因是什么。
1、Sleuth可以获取得到整个服务链路的信息
2、Zipkin通过图形化界面去看到信息。
3、Sleuth将日志信息存储到数据库中
10.2 Sleuth使用
1、导入依赖
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency>
|
2、编写配置文件
1 2 3
| logging: level: org.springframework.web.servlet.DispatcherServlet: DEBUG
|
3、测试
SEARCH:服务名称
012 总链路id
b0e:当前服务的链路id
false:不会将当前的日志信息,输出到其他系统中
10.3 Zipkin的使用
1、搭建Zipkin的web工程 https://zipkin.io/pages/quickstart
- docker安装Zipkin
1、使用docker pull拉取
1
| docker pull openzipkin/zipkin
|
2、使用docker-compose
1 2 3 4 5 6 7 8
| version: "3.1" services: zipkin: image: daocloud.io/daocloud/zipkin:latest restart: always container_name: zipkin ports: - 9411:9411
|
1 2
| docker-compose up -d docker-compose down
|
2、导入依赖
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>
|
3、编写配置文件
1 2 3 4 5 6
| spring: sleuth: sampler: probability: 1 zipkin: base-url: http://127.0.0.1:9411/
|
4、测试
10.4 整合RabbitMQ
1、导入RabbitMQ依赖(zipkin中已经依赖了RabbitMQ了)
2、修改配置文件
1 2 3 4
| spring: zipkin: sender: type: rabbit
|
3、修改Zipkin信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| version: "3.1" services: zipkin: image: daocloud.io/daocloud/zipkin:latest restart: always container_name: zipkin ports: - 9411:9411 environment: - RABBIT_ADDRESSES=10.27.10.123:5672 - RABBIT_USER=test - RABBIT_PASSWORD=test - RABBIT_VIRTUAL_HOST=/test
|
3、测试
10.5 Zipkin存储数据到ES
1、重新修改zipkin的文件yml文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| version: "3.1" services: zipkin: image: daocloud.io/daocloud/zipkin:latest restart: always container_name: zipkin ports: - 9411:9411 environment: - RABBIT_ADDRESSES=10.27.10.123:5672 - RABBIT_USER=test - RABBIT_PASSWORD=test - RABBIT_VIRTUAL_HOST=/test - STORAGE_TYPE=elasticsearch - ES_HOSTS=http://10.27.10.123:9200
|
2、安装Es
1 2 3 4 5 6 7 8 9 10 11
| docker pull elasticsearch docker run --name es1_6.6.0 \ -p 9200:9200 \ -p 9300:9300 \ -e ES_JAVA_OPTS="-Xms256m -Xmx256m" \ -v /d/elasticsearch/config/es1.yml:/usr/share/elasticsearch/config/elasticsearch.yml \ -v /d/elasticsearch/data/es1:/usr/share/elasticsearch/data \ -v /d/elasticsearch/logs/es1:/usr/share/elasticsearch/logs \ -d 13aa43015aa1
|
3、安装kibana
1 2 3 4
| docker pull kibana
docker run --name kibana6.6.0 -e ELASTICSEARCH_URL=http://10.27.10.123:9200 -p 5601:5601 -d dfc685453eaa
|
创建索引
4、重启zipkin后数据未丢失
5、测试
十一、完整SpringCloud架构图