diff --git a/02-customer/pom.xml b/02-customer/pom.xml index 7e44fb8..c7adb41 100644 --- a/02-customer/pom.xml +++ b/02-customer/pom.xml @@ -12,6 +12,18 @@ 02-customer + + org.springframework.cloud + spring-cloud-starter-zipkin + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.cloud + spring-cloud-starter-bus-amqp + org.springframework.cloud spring-cloud-starter-stream-rabbit @@ -20,7 +32,10 @@ org.springframework.cloud spring-cloud-starter-netflix-hystrix-dashboard - + + org.springframework.cloud + spring-cloud-config-client + org.springframework.cloud spring-cloud-starter-netflix-hystrix diff --git a/02-customer/src/main/java/cn/zyjblogs/controller/CustomerController.java b/02-customer/src/main/java/cn/zyjblogs/controller/CustomerController.java index be5c4fc..06632f5 100644 --- a/02-customer/src/main/java/cn/zyjblogs/controller/CustomerController.java +++ b/02-customer/src/main/java/cn/zyjblogs/controller/CustomerController.java @@ -10,11 +10,19 @@ import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.RestTemplate; @RestController +@RefreshScope public class CustomerController { + + @Value("${env}") + private String env; + + + @Value("${version}") private String version; @@ -32,6 +40,14 @@ public class CustomerController { @Autowired private OtherServiceClient otherServiceClient; + + + @GetMapping("/env") + public String env() { + return env; + } + + @GetMapping("/version") public String version() throws InterruptedException { Thread.sleep(3000); diff --git a/02-customer/src/main/resources/application.yml b/02-customer/src/main/resources/application.yml deleted file mode 100644 index af70386..0000000 --- a/02-customer/src/main/resources/application.yml +++ /dev/null @@ -1,39 +0,0 @@ -server: - port: 8080 -#指定Eureka服务地址 -eureka: - client: - service-url: - defaultZone: http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka - #每隔多久去更新一下本地的注册表缓存信息 - registry-fetch-interval-seconds: 30 - instance: - #心跳间隔 - lease-renewal-interval-in-seconds: 30 - #多久没法送,就认为你宕机了 - lease-expiration-duration-in-seconds: 90 - -#指定具体服务的负载均衡策略 -SEARCH: #编写服务名称 - ribbon: -# NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList - NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule - -#feign和hystrix主件整合 -feign: - hystrix: - enabled: true - -version: v1 -#指定服务名称 -spring: - application: - name: CUSTOMER-${version} - rabbitmq: - port: 5672 - username: test - password: test - virtual-host: /test -hystrix: - dashboard: - proxy-stream-allow-list: localhost \ No newline at end of file diff --git a/02-customer/src/main/resources/bootstrap.yml b/02-customer/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..1fcdd67 --- /dev/null +++ b/02-customer/src/main/resources/bootstrap.yml @@ -0,0 +1,40 @@ +#指定Eureka服务地址 +eureka: + client: + service-url: + defaultZone: http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka + + + + +version: v1 +#指定服务名称 +spring: + application: + name: CUSTOMER-${version} + cloud: + config: + discovery: + enabled: true + service-id: CONFIG + profile: dev + sleuth: + sampler: + probability: 1 #百分之多少的sleuth信息需要输出到zipkin + zipkin: + base-url: http://127.0.0.1:9411/ #指定zipkin的地址 + sender: + type: rabbit + + +management: + endpoints: + web: + exposure: + include: "*" + +#CONFIG - CUSTOMER-v1-dev.yml + +logging: + level: + org.springframework.web.servlet.DispatcherServlet: DEBUG \ No newline at end of file diff --git a/03-search/pom.xml b/03-search/pom.xml index cde1b87..225ecfd 100644 --- a/03-search/pom.xml +++ b/03-search/pom.xml @@ -11,6 +11,10 @@ 03-search + + org.springframework.cloud + spring-cloud-starter-zipkin + org.projectlombok lombok diff --git a/03-search/src/main/resources/application.yml b/03-search/src/main/resources/application.yml index 5041b70..cd88be9 100644 --- a/03-search/src/main/resources/application.yml +++ b/03-search/src/main/resources/application.yml @@ -26,4 +26,13 @@ spring: myMessage: consumer: acknowledgeMode: MANUAL + sleuth: + sampler: + probability: 1 #百分之多少的sleuth信息需要输出到zipkin + zipkin: + base-url: http://127.0.0.1:9411/ #指定zipkin的地址 + +logging: + level: + org.springframework.web.servlet.DispatcherServlet: DEBUG \ No newline at end of file diff --git a/07-config/pom.xml b/07-config/pom.xml index 516bf49..f5d0695 100644 --- a/07-config/pom.xml +++ b/07-config/pom.xml @@ -11,10 +11,27 @@ 07-config + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.cloud + spring-cloud-starter-bus-amqp + org.springframework.boot spring-boot-starter-web + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + org.springframework.cloud + spring-cloud-config-server + \ No newline at end of file diff --git a/07-config/src/main/java/cn/zyjblogs/ConfigApplication.java b/07-config/src/main/java/cn/zyjblogs/ConfigApplication.java index ba3b305..b6e1e2c 100644 --- a/07-config/src/main/java/cn/zyjblogs/ConfigApplication.java +++ b/07-config/src/main/java/cn/zyjblogs/ConfigApplication.java @@ -2,8 +2,14 @@ package cn.zyjblogs; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.ServletComponentScan; +import org.springframework.cloud.config.server.EnableConfigServer; + +import javax.servlet.annotation.WebFilter; @SpringBootApplication +@EnableConfigServer +@ServletComponentScan("cn.zyjblogs.filter") public class ConfigApplication { public static void main(String[] args) { SpringApplication.run(ConfigApplication.class,args); diff --git a/07-config/src/main/java/cn/zyjblogs/filter/UrlFilter.java b/07-config/src/main/java/cn/zyjblogs/filter/UrlFilter.java new file mode 100644 index 0000000..7a5b8ca --- /dev/null +++ b/07-config/src/main/java/cn/zyjblogs/filter/UrlFilter.java @@ -0,0 +1,62 @@ +package cn.zyjblogs.filter; + + +import javax.servlet.*; +import javax.servlet.annotation.WebFilter; +import javax.servlet.http.HttpServletRequest; + +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; +import java.io.ByteArrayInputStream; +import java.io.IOException; + +@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; + } + } +} diff --git a/07-config/src/main/resources/application.yml b/07-config/src/main/resources/application.yml index 04dd7c2..70ef0ad 100644 --- a/07-config/src/main/resources/application.yml +++ b/07-config/src/main/resources/application.yml @@ -1,5 +1,5 @@ server: - port: 81 + port: 10000 eureka: client: @@ -9,8 +9,23 @@ eureka: #指定服务名称 spring: application: - name: OTHER-SERVICE #other-service - -# 指定代理的第三方服务 -sidecar: - port: 7001 + name: CONFIG #other-service + cloud: + config: + server: + git: + basedir: E:\config # 本地仓库地址 + username: xxxxx@xxxxx.com #远程仓库的用户名 + password: xxxxx #远程仓库的密码 + uri: https://gitee.com/zyjblog/config-resp.git + rabbitmq: + virtual-host: /test + host: localhost + username: test + password: test + port: 5672 +management: + endpoints: + web: + exposure: + include: "*" diff --git a/README.en.md b/README.en.md deleted file mode 100644 index a2aeb41..0000000 --- a/README.en.md +++ /dev/null @@ -1,36 +0,0 @@ -# springcloud - -#### Description -{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**} - -#### Software Architecture -Software architecture description - -#### Installation - -1. xxxx -2. xxxx -3. xxxx - -#### Instructions - -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README.md b/README.md index 2930838..fc07f8d 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,2031 @@ -# springcloud +# 微服务框架 -#### 介绍 -{**以下是 Gitee 平台说明,您可以替换此简介** -Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台 -无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)} +[toc] -#### 软件架构 -软件架构说明 +## 一、SpringCloud介绍 + +### 1.1 微服务架构 + +> https://martinfowler.com/articles/microservices.html +> +> 微服务架构提出者:马丁福勒 + +> 简而言之,微服务体系结构[样式 [1\]](https://martinfowler.com/articles/microservices.html#footnote-etymology)是一种将单个应用程序开发为一组小型服务的方法,每个应用程序在自己的进程中运行,并与轻量级机制(通常是 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就是帮助我们维护所有服务的信息,以便服务之间的相互调用 + +![1605019839175](images/1605019839175.png) + +### 2.2 Eureka的快速入门 + +#### 2.2.1 创建EurekaServer + +> 1、创建一个父工程,并且在父工程中指定SpringCloud版本,并且将packaing修改为pom + +```xml +pom + + + 1.8 + Hoxton.SR8 + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring.cloud-version} + pom + import + + + +``` + +> 2、创建eureka的server,创建springboot工程,并且导入依赖,再启动类中添`@EnableEurekaServer`注解和编写yml文件 + +> 2.1、导入依赖 + +```xml + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-server + + +``` + +> 2.2、启动类添加注解 + +```java +@EnableEurekaServer +@SpringBootApplication +public class EurekaApplication { + public static void main(String[] args) { + SpringApplication.run(EurekaApplication.class,args); + } +} +``` + +> 2.3 编写yml配置文件 + +```yml +server: + port: 8761 + +eureka: + instance: + hostname: localhost + client: + #当前的eureka是单机版的 + registerWithEureka: false + fetchRegistry: false + serviceUrl: + defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ +``` -#### 安装教程 -1. xxxx -2. xxxx -3. xxxx +#### 2.2.2 创建EurekaClient -#### 使用说明 +> 1、创建Maven工程,修改为SpringBoot -1. xxxx -2. xxxx -3. xxxx +> 2、导入依赖 -#### 参与贡献 +```xml + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + +``` -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +> 3、在启动类上添加注解`@EnableEurekaClient` + +```java +@EnableEurekaClient +@SpringBootApplication +public class CustomerApplication { + public static void main(String[] args) { + SpringApplication.run(CustomerApplication.class,args); + } +} +``` + +> 4、编写配置文件 + +```yml +#指定Eureka服务地址 +eureka: + client: + service-url: + defaultZone: http://localhost:8761/eureka + +#指定服务名称 +spring: + application: + name: CUSTOMER +``` + +![image-20201111095518705](images/image-20201111095518705.png) + +#### 2.2.3 测试Eureka + +> 1、创建了一个Search搜索模块,并且注册到Eureka + +> 2、使用EurekaClient的对象获取服务信息 + +```java +@Autowired + private EurekaClient eurekaClient; +``` + +> 3、创建RestTemplate + +```java +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} +``` + +> 4、使用RestTemplate调用 + +```java +@GetMapping("/customer") +public String customer() { + //1. 通过eurekaClient 获取到SEARCH服务的信息 + InstanceInfo info = eurekaClient.getNextServerFromEureka("SEARCH", false); + //2. 获取到访问的地址 + String url = info.getHomePageUrl(); + System.out.println(url); + //3. 通过restTemplate访问 + String result = restTemplate.getForObject(url + "/search", String.class); + //4. 返回 + return result; +} +``` + +### 2.3 Eureka的安全性 + +> 实现Eureka认证 + +> 1、导入依赖 + +```xml + + org.springframework.boot + spring-boot-starter-security + +``` + +> 2、编写配置类 + +```java +@EnableWebSecurity +class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + //忽略掉/eureka/**路径 + http.csrf().ignoringAntMatchers("/eureka/**"); + super.configure(http); + } +} +``` + +> 3、编写配置文件,配置用户密码 + +```yml +#指定用户名密码 +spring: + security: + user: + name: root + password: root +``` + +> 4、其他服务想注册到Eureka上需要添加用户名密码 + +```yml +#指定Eureka服务地址 +eureka: + client: + service-url: + defaultZone: http://用户名:密码@localhost:8761/eureka +``` + +### 2.4 Eureka的高可用性 + +> 如果程序正在运行,突然Eureka宕机了 +> +> 1、如果调用方访问过一次被调用方,Eureka的宕机就不会影响到功能 +> +> 2、如果调用方没有访问过被调用方,Eureka的宕机就会造成当前功能的不可用到功能 + +> 搭建Eureka高可用 + +![image-20201111110217411](images/image-20201111110217411.png) + +> 1、准备多态Eureka +> +> 采用了复制的方式,删除iml和target文件,并且修改pom.xml中的项目名称,再给负公差添加module + +> 2、让服务注册到多台Eureka上 + +```yml +server: + port: 8761 + +eureka: + client: + serviceUrl: + defaultZone: http://root:root@localhost:8762/eureka/ +``` + +```yml +server: + port: 8762 + +eureka: + client: + serviceUrl: + defaultZone: http://root:root@localhost:8761/eureka/ + +``` + +> 3、让多台Eureka之间相互通讯 + +```yml +eureka: + client: + #当前的eureka是单机版的 false单机版 true集群 + 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从注册表中移除) + +```yml +eureka: + instance: + lease-renewal-interval-in-seconds: 30 #心跳间隔 + lease-expiration-duration-in-seconds: 90 #多久没法送,就认为你宕机了 +``` + +> 4、EurekaClient会每个30s去EurekaServer中去更新本地的注册表 + +```yml +eureka: + client: + #每隔多久去更新一下本地的注册表缓存信息 + registry-fetch-interval-seconds: 30 +``` + +> 5、Eureka的自我保护机制,统计15分钟内,如果一个服务的心跳发送比例低于85%,EurekaServer就会开启自我保护机制 +> +> ​ 1、不会从EurekaServer中去移除长时间没有收到心跳的服务 +> +> ​ 2、EurekaServer还是可以正常提供服务的 +> +> ​ 3、网络稳定时,EurekaServer才会开始将自己的信息被其他节点同步过去 + +![image-20201111132523595](images/image-20201111132523595.png) + +```yml +eureka: + # Eureka保护机制配置 + server: + #true 开启 false关闭 + 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中自己做一个负载均衡的策略,选中某一个服务。 +> +> 服务端负载均衡:在注册中心中,直接根据你指定的负载均衡策略,帮你选中一个指定的服务器信息,并返回。 + +![image-20201111134022333](images/image-20201111134022333.png) + +### 3.2 Robbin的快速入门 + +> 1、启动两个Search模块 + +> 2、在customer导入robbin依赖 + +```xml + + org.springframework.cloud + spring-cloud-starter-netflix-ribbon + +``` + +> 3、配置整合RestTemplate和Robbin + +```java +@Configuration +public class RestTemplateConfig { + + @Bean + @LoadBalanced + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} +``` + +> 4、在customer中去访问Search + +```java +@GetMapping("/customer") +public String customer() { + String result = restTemplate.getForObject("http://SEARCH/search", String.class); + //4. 返回 + return result; +} +``` + +### 3.3 Robbin配置负载均衡策略 + +> 1、负载均衡策略 +> +> 1. RandomRule:随机策略 +> 2. RoundRobbinRule:轮询策略 +> 3. WeightedResponseTimeRule:默认会采用轮询的策略,后续会根据服务的响应时间,自动给你分配权重 +> 4. BestAvailableRule:根据被调用方并发数最小的去分配 + +> 2、采用注解的形式 + +```java +@Bean +public IRule robbinRule() { + return new RandomRule(); +} +``` + +> 3、配置文件去指定负载均衡的策略(推荐) + +```yml +#指定具体服务的负载均衡策略 +SEARCH: #编写服务名称 + ribbon: + NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule #具体负载均衡使用的类 +``` + +## 四、服务间的调用-Feign + +### 4.1 引言 + +> Feign可以帮助我们实现面向接口编程,就直接调用其他服务,简化开发。 + +### 4.2 Feign的快速入门 + +> 1、导入依赖 + +```xml + + org.springframework.cloud + spring-cloud-starter-openfeign + +``` + +> 2、添加一个注解`@EnableFeignClients` + +```java +@EnableEurekaClient +@SpringBootApplication +@EnableFeignClients +public class CustomerApplication { + public static void main(String[] args) { + SpringApplication.run(CustomerApplication.class,args); + } +} +``` + +> 3、创建一个接口,并且和search模块做映射 + +```java +//指定服务名称 +@FeignClient("SEARCH") +public interface SearchClient { + + //value -> 目标服务的请求路径,method -> 映射请求方式 + @RequestMapping(value = "/search",method = RequestMethod.GET) + public String search(); +} +``` + +> 4、测试使用 + +```java +@Autowired +private SearchClient searchClient; + +@GetMapping("/customer") +public String customer() { + String result = searchClient.search(); + return result; +} +``` + +### 4.3 Feign的传递参数方式 + +> 1、注意事项 +> +> 1. 如果你传递的参数,比较复杂时,默认会采用POST的请求方式。 +> 2. 传递单个参数时,推荐使用@PathVariable,如果传递的单个参数比较多,这里也可以采用@RequestParam,不要省略value属性 +> 3. 传递对象信息时,统一采用json的方式,添加@RequestBody +> 4. Client接口必须采用@RequestMapping + +> 2、在Search模块下准备三个接口 + +```java +@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 + +```java +@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接口 + +```java +@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) //会自动转化成POST请求 405 +Customer save(@RequestBody Customer customer); +``` + +> 5、测试 + +![image-20201111155409030](images/image-20201111155409030.png) + +### 4.4 Feign的Fallback + +> Fallback可以帮助我们在使用Feign去调用另一个服务时,如果出现了问题,走服务降级,返回一个错误的数据,避免功能因为一个服务出现问题,全部失效 + +> 1、创建一个POJO类,实现Client接口 + +```java +@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接口中的注解,添加一个属性 + +```java +@FeignClient(value = "SEARCH",fallback = SearchClientFallBack.class) +``` + +> 3、添加一个配置文件。 + +```yml +#feign和hystrix主件整合 +feign: + hystrix: + enabled: true +``` + +> 调用方无法知道具体的错误信息是什么,通过FallBackFactory的方式去实现这个功能 + +> 1、FallBackFactory基于fallback + +> 2、创建一个POJO类,实现FallBackFactory + +```java +@Component +public class SearchClientFallBackFactory implements FallbackFactory { + + @Autowired + private SearchClientFallBack searchClientFallBack; + @Override + public SearchClient create(Throwable throwable) { + throwable.printStackTrace(); + return searchClientFallBack; + } +} +``` + +> 3、修改Client接口中的属性 + +```java +@FeignClient(value = "SEARCH", + fallbackFactory = SearchClientFallBackFactory.class +) +``` + +## 五、服务的隔离及熔断器-Hystrix + +### 5.1 引言 + +![image-20201111162630381](images/image-20201111162630381.png) + +### 5.2 降级机制实现 + +> 1、导入依赖 + +```xml + + org.springframework.cloud + spring-cloud-starter-netflix-hystrix + +``` + +> 2、添加一个注解`@EnableCircuitBreaker` + +```java +@EnableEurekaClient +@SpringBootApplication +@EnableCircuitBreaker +public class CustomerApplication { + public static void main(String[] args) { + SpringApplication.run(CustomerApplication.class,args); + } +} +``` + +> 3、针对某一个接口去编写他的降级方法 + +```java +@GetMapping("/customer/{id}") +@HystrixCommand(fallbackMethod ="findByIdFallBack" ) +public Customer findById(@PathVariable Integer id) { + int i = 1/0; + return searchClient.findById(id); +} + +//findById的降级方法,方法描述要与接口一致 +public Customer findByIdFallBack(Integer id) { + return new Customer(-1,"",0); +} +``` + +> 4、在接口上添加注解 + +```java +@HystrixCommand(fallbackMethod ="findByIdFallBack" ) +``` + +> 5、测试 + +![image-20201111164239591](images/image-20201111164239591.png) + +### 5.3 线程隔离 + +> 如果使用Tomcat的线程池去接收用户的请求,使用当前线程去执行其他服务的功能,如果某一个服务出现了故障,导致tomcat的线程大量的堆积,导致tomcat无法处理其他业务功能。 +> +> 1、Hystrix线程池(默认),接收用户请求采用tomcat的线程池,执行业务代码,调用其他服务时,采用Hystrix的线程池。 +> +> 2、信号量,使用的还是Tomcat的线程池,帮助我们取关了Tomcat的线程池 + +> 1、Hystrix的线程池的配置(具体的配置属性需要去查看HystrixCommandProperties类) [wiki](https://github.com/Netflix/Hystrix/wiki/Configuration) +> +> 1. 线程隔离策略:name = `hystrix.command.default.execution.isolation.strategy`,value=`THREAD`,`SEMAPHORE ` +> 2. 指定超时时间(针对线程池):name= `hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds` ,value =`1000` +> 3. 是否开启超时时间配置:name=`hystrix.command.default.execution.timeout.enabled`,value=`true` +> 4. 超时之后是否中断线程:name=`hystrix.command.default.execution.isolation.thread.interruptOnTimeout`,value=`true` +> 5. 取消任务之后是否中断线程:name=`hystrix.command.default.execution.isolation.thread.interruptOnCancel`,value=false + +> 2、信号量配置信息 +> +> 1. 线程隔离策略:name = `hystrix.command.default.execution.isolation.strategy`,value=`THREAD`,`SEMAPHORE ` +> 2. 指定信号量的最大并发请求数: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状态。 + +![image-20201111181650929](images/image-20201111181650929.png) + +#### 5.4.2 配置断路器的监控界面 + +> 1、导入依赖 + +```xml + + org.springframework.cloud + spring-cloud-starter-netflix-hystrix-dashboard + +``` + +> 2、在启动类中添加注解`@EnableHystrixDashboard` + +```java +@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 + +```java +@WebServlet("/hystrix.stream") +public class HystrixServlet extends HystrixMetricsStreamServlet { +} +``` + +> 4、在启动类上添加扫描Servlet的注解`@ServletComponentScan("cn.zyjblogs.servlet")` + +> 5、配置文件 + +```yml +hystrix: + dashboard: + proxy-stream-allow-list: localhost +``` + +> 6、测试 + +直接访问: http://host:port/hystrix.stream + +![1605103421256](images/1605103421256.png) + +> 在当前位置输入映射好的servlet路径 + +![1605103436874](images/1605103436874.png) + +#### 5.4.3 配置断路器的属性 + +> 断路器的属性(10秒之内) +> +> 1. 断路器的开关:name=` hystrix.command.default.circuitBreaker.enabled `,value=`true` +> 2. 失败阈值的总请求数:name=` hystrix.command.default.circuitBreaker.requestVolumeThreshold `,value=`20` +> 3. 请求总数失败率达到%多少时打开断路器:name=` hystrix.command.default.circuitBreaker.errorThresholdPercentage `,value=`50` +> 4. 断路器open状态后,多少秒是拒绝请求的:name=` hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds `value=`5000` +> 5. 强制让服务拒绝请求:name=` hystrix.command.default.circuitBreaker.forceOpen `,value=`false` +> 6. 强制让服务接收请求:name=` hystrix.command.default.circuitBreaker.forceClosed `,value=`false` + +> 具体配置方式 + +```java +@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、在一次请求中,目标方法被调用过一次以后就都会被缓存 + +![1605104987931](images/1605104987931.png) + +#### 5.5.2 请求缓存的实现 + +> 1、创建一个Service,在Service中调用Search服务 + +```java +@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` +> +> 1. @CacheResult:帮助我们缓存当前方法的返回结果(必须配合@HystrixCommand使用) +> 2. @CacheRemove:帮助我们清除某一个缓存信息(基于commandKey) +> 3. @CacheKey:指定那个方法参数作为缓存标识 + +> 3、修改Search模块的结果返回值 + +```java + return new Customer(id, name, (int) (Math.random() * 100000)); +``` + +> 4、编写Filter,去构建HystrixRequestContext + +```java +@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 + +```java +@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、测试 + +![1605106759891](images/1605106759891.png) + +## 六、服务的网关-Zuul + +### 6.1 引言 + +> 1、客户端维护大量的ip和port信息,直接访问指定服务 +> +> 2、认证和授权操作,需要在每一个模块中添加认证和授权操作 +> +> 3、项目迭代,服务拆分,服务要合并,需要客户端镜像大量的变化 +> +> 4、统一的把安全性校验都放在Zuul中 + +![网关](images/image-20201112110708902.png) + +### 6.2 Zuul的快速入门 + +> 1、创建Maven项目,修改SpringBoot + +> 2、导入依赖 + +```xml + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.cloud + spring-cloud-starter-netflix-zuul + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + +``` + +> 3、添加注解`@EnableZuulProxy` `@EnableEurekaClient` + +```java +@SpringBootApplication +@EnableEurekaClient +@EnableZuulProxy +public class ZuulApplication { + public static void main(String[] args) { + SpringApplication.run(ZuulApplication.class,args); + } +} +``` + +> 4、编写配置文件 + +```yml +server: + port: 80 +#指定Eureka服务地址 +eureka: + client: + service-url: + defaultZone: http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka + +#指定服务名称 +spring: + application: + name: ZUUL +``` + +> 5、测试 + +![image-20201112113609523](images/image-20201112113609523.png) -#### 特技 -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +### 6.3 Zuul常用配置信息 + +#### 6.3.1 Zuul的监控界面 + +> 1、导入依赖 + +```xml + + org.springframework.boot + spring-boot-starter-actuator + +``` + +> 2、编写配置文件 + +```yml +#查看zuul的监考界面(开发时,配置为*,上线,不要配置) +management: + endpoints: + web: + exposure: + include: "*" +``` + +> 3、直接访问:`http://localhost/actuator/routes` + +![image-20201112132217601](images/image-20201112132217601.png) + +#### 6.3.2 忽略服务设置 + +```yml +# zuul的配置 +zuul: + #基于服务名忽略服务,无法查看,如果要忽略全部的服务,"*",默认配置的全部路径都会被忽略掉(自定义服务配置,通过这种方式是无法忽略的) + ignored-services: eureka + #监考界面依然可以查看,在访问的时候,404无法访问 + ignored-patterns: /**/search/** +``` + +#### 6.3.3 自定义服务配置 + +```yml +# zuul的配置 +zuul: + #基于服务名忽略服务,无法查看,如果要忽略全部的服务,"*",默认配置的全部路径都会被忽略掉(自定义服务配置,通过这种方式是无法忽略的) + ignored-services: "*" + #监考界面依然可以查看,在访问的时候,404无法访问 + ignored-patterns: /**/search/** + # 指定自定义服务(方式一,key(服务名):value(路径)) +# routes: +# search: /ss/** +# customer: /cc/** + # 指定自定义服务(方式二) + routes: + kehu: #自定义名称 + path: /cc/** # 映射路径 + serviceId: customer +``` + +#### 6.3.4 灰度发布 + +> 1、添加一个配置类 + +```java +@Configuration +public class ZuulConfig { + @Bean + public PatternServiceRouteMapper serviceRouteMapper() { + return new PatternServiceRouteMapper( + "(?^.+)-(?v.+$)", + "${version}/${name}"); + //服务名-v版本 + // /v版本/路径 + } +} +``` + +> 2、准备一个服务,提供2个版本 + +```yml +version: v1 + +#指定服务名称 +spring: + application: + name: CUSTOMER-${version} +``` + + + +> 3、修改Zuul的配置 + +```yml +zuul: + #基于服务名忽略服务,无法查看,如果需要用到-v的方式,一定要忽略掉 +# ignored-services: "*" +``` + +> 4、修改CustomerController + +```java +@Value("${version}") + private String version; + +@GetMapping("/version") + public String version() { + return version; + } +``` + + + +> 5、测试 + +![image-20201112135410803](images/image-20201112135410803.png) + +![image-20201112135429409](images/image-20201112135429409.png) + +### 6.4 Zuul的过滤器执行流程 + +> 客户端请求发送到Zuul服务商,首先通过PreFilter,如果正常放行,会把请求再次转发给RoutingFilter,请求转发到一个指定的服务,在指定的服务响应一个结果之后,再次走一个PostFilter的过滤器链,最终将响应信息返回给客户端。 + +![image-20201112140312697](images/image-20201112140312697.png) + +### 6.5 Zuul过滤器入门 + +> 1、创建POJO类,继承ZuulFilter + +```java +@Component +public class ZuulFilterTest extends ZuulFilter { + +} + +``` + +> 2、指定当前过滤器的类型 + +```java +@Override +public String filterType() { + return FilterConstants.PRE_TYPE; +} +``` + +> 3、指定过滤器的执行顺序 + +```java +@Override +public int filterOrder() { + return FilterConstants.PRE_DECORATION_FILTER_ORDER -1; +} +``` + +> 4、配置是否启用 + +```java +@Override +public boolean shouldFilter() { + //开启当前过滤器 + return true; +} +``` + +> 5、指定过滤器中的具体业务代码 + +```java +@Override +public Object run() throws ZuulException { + System.out.println("prefix过滤器已经执行~~~"); + return null; +} +``` + +> 6、测试 + +![image-20201112141523371](images/image-20201112141523371.png) + +### 6.6 PreFilter实现token校验 + +> 1、准备访问路径,请求参数专递token + +`http://localhost/v1/customer/version?token=123` + +> 2、创建AuthenticationFilter + +```java +@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方法中编写具体的业务逻辑代码 + +```java +@Override + public Object run() throws ZuulException { + System.out.println("AuthenticationFilter执行了"); + //1. 获取Request对象 + RequestContext requestContext = RequestContext.getCurrentContext(); + HttpServletRequest request = requestContext.getRequest(); + //2. 获取token参数 + String token = request.getParameter("token"); + //3.对比token + if (token == null || !"123".equalsIgnoreCase(token)) { + System.out.println("token校验失败"); + //4. token校验失败,直接响应数据 + requestContext.setSendZuulResponse(false); + requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value()); + } + return null; + } +``` + +> 4、测试 + +![image-20201112145449105](images/image-20201112145449105.png) + +### 6.7 Zuul的降级 + +> 1、创建POJO类,实现接口FallbackProvider + +```java +@Component +public class ZuulFallBack implements FallbackProvider { + +} + +``` + +> 2、重写两个方法 + +```java +@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 { + //指定具体的HttpStatus + 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、测试 + +![image-20201112151632111](images/image-20201112151632111.png) + +### 6.8 Zuul动态路由 + +> 1、创建一个过滤器 + +```java +@Component +public class DynamicRoutingFilter extends ZuulFilter{ + +} +``` + +> 2、在run方法中编辑业务逻辑 + +```java +@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执行了"); + //1、获取Request对象 + RequestContext context = RequestContext.getCurrentContext(); + HttpServletRequest request = context.getRequest(); + + //2、获取参数,redisKey + String redisKey = request.getParameter("redisKey"); + + //3、直接判断 + if (redisKey != null && redisKey.equalsIgnoreCase("customer")) { + System.out.println("DynamicRoutingFilter执行了路由到了/customer"); + //http://localhost:8080/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"); + //http://localhost:8081/search + context.put(FilterConstants.SERVICE_ID_KEY,"search"); + context.put(FilterConstants.REQUEST_URI_KEY,"/search/1"); + } + return null; +} +``` + +> 3、测试 + +![image-20201112155707148](images/image-20201112155707148.png) + +![image-20201112155652269](images/image-20201112155652269.png) + +## 七、多语言支持-Sidecar + +### 7.1 引言 + +> 在SpringCloud的项目中,需要接入一些非java程序,第三方接口,无法接入eureka,hystrix,feign等组件。启动一个代理的微服务,代理微服务去和非java的程序或第三方接口交流,通过代理的非服务去计入SpringCloud的相关组件。 + +![image-20201112160258956](images/image-20201112160258956.png) + +### 7.2 Sidecar实现 + +> 1、创建一个第三方的服务 + +`创建一个SpringBoot工程,并且添加一个Controller` + +> 2、创建Maven工程,修改为SpringBoot + +> 2、导入以来 + +```xml + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + org.springframework.cloud + spring-cloud-netflix-sidecar + +``` + +> 4、添加注解`@EnableSidecar` + +> 5、编写配置文件 + +```yml +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 #other-service + +# 指定代理的第三方服务 +sidecar: + port: 7001 +``` + +> 6、通过customer通过Feign的方式调用第三方服务 + +![image-20201112165325023](images/image-20201112165325023.png) + +## 八、服务间消息传递-Stream + +### 8.1 引言 + +> 用于构建消息驱动微服务的框架(在下面方便起见也叫它Stream框架),该框架在Spring Boot的基础上整合了Spring Integration来连接消息代理中间件(RabbitMQ,Kafka等)。它支持多个消息中间件的自定义配置,同时吸收了这些消息中间件的部分概念,例如持久化订阅、消费者分组,和分区等概念。使用Stream框架,我们不必关系如何连接各个消息代理中间件,也不必关系消息的发送与接收,只需要进行简单的配置就可以实现这些功能了,可以让我们更敏捷的进行开发主体业务逻辑了。 +> +> **Spring Cloud Stream框架的组成部分:** +> +> 1. Stream框架自己的应用模型; +> 2. 绑定器抽象层,可以与消息代理中间件进行绑定,通过绑定器的API,可实现插件式的绑定器。 +> 3. 持久化订阅的支持。 +> 4. 消费者组的支持。 +> 5. Topic分区的支持。 + +![image-20201112165641574](images/20190325232054468.png) + +### 8.2 Stream快速入门 + +> 1、启动RabbitMQ + +> 2、消费者-导入依赖 + +```xml + + org.springframework.cloud + spring-cloud-starter-stream-rabbit + +``` + +> 3、消费者-配置文件 + +```yml +spring: + rabbitmq: + port: 5672 + username: test + password: test + virtual-host: /test +``` + +> 4、消费者-监听的队列 + +```java +public interface StreamClient { + @Input("myMessage") + SubscribableChannel input(); +} +``` + +```java +@Component +@EnableBinding(StreamClient.class) +public class StreamReceiver { + @StreamListener("myMessage") + public void msg(Object msg) { + System.out.println("接收到消息" + msg); + } +} +``` + +> 5、启动类添加注解`@EnableBinding(StreamClient.class)` + +> 6、生产者-导入依赖 + +```xml + + org.springframework.cloud + spring-cloud-starter-stream-rabbit + +``` + +> 7、生产者-配置文件 + +```yml +spring: + rabbitmq: + port: 5672 + username: test + password: test + virtual-host: /test +``` + +> 8、生产者-发布消息 + +```java +public interface StreamClient { + @Output("myMessage") + MessageChannel output(); +} +``` + +```java +@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` + +![image-20201113093057704](images/image-20201113093057704.png) + +![image-20201112173342142](images/image-20201112173342142.png) + +### 8.3 Stream重复消费问题 + +> 只需要添加一个配置,指定消费者组 + +```yml +spring: + cloud: + stream: + binders: + myMessage: #队列名称 + group: customer #消费者组 +``` + +### 8.4 Stream的消费者手动ack + +> 1、编写配置 + +```yml +#指定服务名称 +spring: + cloud: + stream: + #实现手动ack + rabbit: + bindings: + myMessage: + consumer: + acknowledgeMode: MANUAL +``` + +> 2、修改消费端方法 + +```java +@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、修改配置文件,无法立即生效。 + +![image-20201112180552221](images/image-20201112180552221.png) + +### 9.2 搭建Config-Server + +> 1、创建Maven工程,修改SpringBoot + +> 2、导入依赖 + +```xml + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + org.springframework.cloud + spring-cloud-config-server + +``` + +> 3、添加注解`@EnableConfigServer` + +```java +@SpringBootApplication +@EnableConfigServer +public class ConfigApplication { + public static void main(String[] args) { + SpringApplication.run(ConfigApplication.class,args); + } +} +``` + +> 4、编写配置文件(git) + +```yml +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` + +![image-20201113094055276](images/image-20201113094055276.png) + +### 9.3、修改Customer连接Config + +> 1、导入依赖 + +```yml + + org.springframework.cloud + spring-cloud-config-client + +``` + +> 2、修改配置文件 + +```java +#指定Eureka服务地址 +eureka: + client: + service-url: + defaultZone: http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka + +version: v1 +#指定服务名称 +spring: + application: + name: CUSTOMER-${version} + cloud: + config: + discovery: + enabled: true + service-id: CONFIG + profile: dev +``` + +> 3、修改配置文件名称`application.yml`改为`bootstrap.yml` + +> 4、测试测试发布消息到RabbMQ + +![image-20201113102316294](images/image-20201113102316294.png) + +### 9.4 实现动态配置 + +#### 9.4.1 实现原理 + +![image-20201113103152115](images/image-20201113103152115.png) + +#### 9.4.2 服务连接RabbitMQ + +> 1、导入依赖 + +```xml + + org.springframework.cloud + spring-cloud-starter-bus-amqp + +``` + +> 2、编写配置文件 + +```yml +spring: + rabbitmq: + virtual-host: /test + host: localhost + username: test + password: test + port: 5672 +``` + +> 3、测试 + +![image-20201113104134210](images/image-20201113104134210.png) + +#### 9.4.3 实现手动刷新 + +> 1、导入依赖(两个服务config和customer均添加) + +```xml + + org.springframework.boot + spring-boot-starter-actuator + +``` + +> 2、编写配置文件(两个服务config和customer均添加) + +```yml +management: + endpoints: + web: + exposure: + include: "*" +``` + +> 3、为customer添加一个controller,添加注解`@RefreshScope` + +```java +@RestController +@RefreshScope +public class CustomerController { + + @Value("${env}") + private String env; + + @GetMapping("/env") + public String env() { + return env; + } +} +``` + +> 4、测试 +> +> 1. CONFIG在gitee修改之后,自动拉取最新的配置信息。 +> 2. 其他模块需要更新的话,手动发送一个POST请求:http://localhost:10000/actuator/bus-refresh ,不重启项目,即可获取最新的配置信息 + +#### 9.4.4 内网穿透 + +> 1、内网穿透官网:http://www.ngrok.cc/ + +> 2、注册登录 + +> 3、购买免费隧道,并配置 + +![image-20201113113814310](images/image-20201113113814310.png) + +> 4、下载客户端,并复制隧道id,点击运行客户端,复制到客户端中 + +![image-20201113114050201](images/image-20201113114050201.png) + +> 5、测试访问是否成功 + +![image-20201113114131931](images/image-20201113114131931.png) + +#### 9.4.5 实现自动刷新配置 + +> 1、配置Gitee中的WebHooks + +![image-20201113120733998](images/image-20201113120733998.png) + +> 2、给Config添加一个过滤器UrlFilter + +```java + +@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")` + +```java +@SpringBootApplication +@EnableConfigServer +@ServletComponentScan("cn.zyjblogs.filter") +public class ConfigApplication { + public static void main(String[] args) { + SpringApplication.run(ConfigApplication.class,args); + } +} +``` + +> 3、测试 + +![image-20201113121035781](images/image-20201113121035781.png) + +## 十、服务的追踪-Sleuth + +### 10.1 引言 + +> 在整个微服务架构中,微服务很多,一个请求可能需要调用很多很多的服务,最终才能完成一个功能,如果说,整个功能出现了问题,在这么多的服务中,如何区定位到问题的所在点,出现问题的原因是什么。 +> +> 1、Sleuth可以获取得到整个服务链路的信息 +> +> 2、Zipkin通过图形化界面去看到信息。 +> +> 3、Sleuth将日志信息存储到数据库中 + +![image-20201113131325996](images/image-20201113131325996.png) + +### 10.2 Sleuth使用 + +> 1、导入依赖 + +```xml + + org.springframework.cloud + spring-cloud-starter-sleuth + +``` + +> 2、编写配置文件 + +```yml +logging: + level: + org.springframework.web.servlet.DispatcherServlet: DEBUG +``` + +> 3、测试 +> +> SEARCH:服务名称 +> +> 012 总链路id +> +> b0e:当前服务的链路id +> +> false:不会将当前的日志信息,输出到其他系统中 + +![image-20201113133658258](images/image-20201113133658258.png) + +![image-20201113133713872](images/image-20201113133713872.png) + +![image-20201113133745985](images/image-20201113133745985.png) + +### 10.3 Zipkin的使用 + +> 1、搭建Zipkin的web工程 https://zipkin.io/pages/quickstart +> +> 1. docker安装Zipkin + +1、使用docker pull拉取 + +```bash +docker pull openzipkin/zipkin +``` + +2、使用docker-compose + +```yml +version: "3.1" +services: + zipkin: + image: daocloud.io/daocloud/zipkin:latest + restart: always + container_name: zipkin + ports: + - 9411:9411 +``` + +```bash +docker-compose up -d #启动 +docker-compose down #关闭 +``` + +> 2、导入依赖 + +```xml + + org.springframework.cloud + spring-cloud-starter-zipkin + +``` + +> 3、编写配置文件 + +```yml +spring: + sleuth: + sampler: + probability: 1 #百分之多少的sleuth信息需要输出到zipkin + zipkin: + base-url: http://127.0.0.1:9411/ #指定zipkin的地址 +``` + +> 4、测试 + +![image-20201113153443738](images/image-20201113153443738.png) + +### 10.4 整合RabbitMQ + +> 1、导入RabbitMQ依赖(zipkin中已经依赖了RabbitMQ了) + +> 2、修改配置文件 + +```yml +spring: + zipkin: + sender: + type: rabbit +``` + + + +> 3、修改Zipkin信息 + +```yml +version: "3.1" +services: + zipkin: + image: daocloud.io/daocloud/zipkin:latest + #image: docker.io/openzipkin/zipkin:latest + restart: always + container_name: zipkin + ports: + - 9411:9411 + environment: + - RABBIT_ADDRESSES=10.27.10.123:5672 #本地ipv4地址:端口 + - RABBIT_USER=test + - RABBIT_PASSWORD=test + - RABBIT_VIRTUAL_HOST=/test +``` + +> 3、测试 + +![image-20201113160658535](images/image-20201113160658535.png) + +![image-20201113160724897](images/image-20201113160724897.png) + +### 10.5 Zipkin存储数据到ES + +> 1、重新修改zipkin的文件yml文件 + +```yml +version: "3.1" +services: + zipkin: + image: daocloud.io/daocloud/zipkin:latest + #image: docker.io/openzipkin/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 + +```bash + #拉取镜像 + 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 + +```bash +#拉取kibana +docker pull kibana +#启动参数 +docker run --name kibana6.6.0 -e ELASTICSEARCH_URL=http://10.27.10.123:9200 -p 5601:5601 -d dfc685453eaa +``` + +创建索引 + +![image-20201113164636525](images/image-20201113164636525.png) + +> 4、重启zipkin后数据未丢失 + +```bash +docker-compose restart +``` + +> 5、测试 + +![image-20201113164930192](images/image-20201113164930192.png) + +## 十一、完整SpringCloud架构图 + +![image-20201113165759501](images/image-20201113165759501.png) \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..41c6a33 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,16 @@ +version: "3.1" +services: + zipkin: + image: daocloud.io/daocloud/zipkin:latest + #image: docker.io/openzipkin/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 diff --git a/images/1605019839175.png b/images/1605019839175.png new file mode 100644 index 0000000..e1411d9 Binary files /dev/null and b/images/1605019839175.png differ diff --git a/images/1605103387256.png b/images/1605103387256.png new file mode 100644 index 0000000..6e45ee5 Binary files /dev/null and b/images/1605103387256.png differ diff --git a/images/1605103421256.png b/images/1605103421256.png new file mode 100644 index 0000000..ab5338e Binary files /dev/null and b/images/1605103421256.png differ diff --git a/images/1605103436874.png b/images/1605103436874.png new file mode 100644 index 0000000..9289fa0 Binary files /dev/null and b/images/1605103436874.png differ diff --git a/images/1605104987931.png b/images/1605104987931.png new file mode 100644 index 0000000..2929462 Binary files /dev/null and b/images/1605104987931.png differ diff --git a/images/1605106759891.png b/images/1605106759891.png new file mode 100644 index 0000000..064849f Binary files /dev/null and b/images/1605106759891.png differ diff --git a/images/1605106760380.png b/images/1605106760380.png new file mode 100644 index 0000000..064849f Binary files /dev/null and b/images/1605106760380.png differ diff --git a/images/20190325232054468.png b/images/20190325232054468.png new file mode 100644 index 0000000..97301f4 Binary files /dev/null and b/images/20190325232054468.png differ diff --git a/images/image-20201111095513133.png b/images/image-20201111095513133.png new file mode 100644 index 0000000..b7a2bd4 Binary files /dev/null and b/images/image-20201111095513133.png differ diff --git a/images/image-20201111095518705.png b/images/image-20201111095518705.png new file mode 100644 index 0000000..b7a2bd4 Binary files /dev/null and b/images/image-20201111095518705.png differ diff --git a/images/image-20201111110217411.png b/images/image-20201111110217411.png new file mode 100644 index 0000000..5306ef6 Binary files /dev/null and b/images/image-20201111110217411.png differ diff --git a/images/image-20201111132523595.png b/images/image-20201111132523595.png new file mode 100644 index 0000000..9101fa2 Binary files /dev/null and b/images/image-20201111132523595.png differ diff --git a/images/image-20201111134022333.png b/images/image-20201111134022333.png new file mode 100644 index 0000000..b85b2f7 Binary files /dev/null and b/images/image-20201111134022333.png differ diff --git a/images/image-20201111155409030.png b/images/image-20201111155409030.png new file mode 100644 index 0000000..bf42f7f Binary files /dev/null and b/images/image-20201111155409030.png differ diff --git a/images/image-20201111162630381.png b/images/image-20201111162630381.png new file mode 100644 index 0000000..0af3393 Binary files /dev/null and b/images/image-20201111162630381.png differ diff --git a/images/image-20201111164239591.png b/images/image-20201111164239591.png new file mode 100644 index 0000000..ffcba13 Binary files /dev/null and b/images/image-20201111164239591.png differ diff --git a/images/image-20201111181650929.png b/images/image-20201111181650929.png new file mode 100644 index 0000000..b3cc0c7 Binary files /dev/null and b/images/image-20201111181650929.png differ diff --git a/images/image-20201112110708902.png b/images/image-20201112110708902.png new file mode 100644 index 0000000..cb885ca Binary files /dev/null and b/images/image-20201112110708902.png differ diff --git a/images/image-20201112113609523.png b/images/image-20201112113609523.png new file mode 100644 index 0000000..6bb1079 Binary files /dev/null and b/images/image-20201112113609523.png differ diff --git a/images/image-20201112132217601.png b/images/image-20201112132217601.png new file mode 100644 index 0000000..07f1bb6 Binary files /dev/null and b/images/image-20201112132217601.png differ diff --git a/images/image-20201112135410803.png b/images/image-20201112135410803.png new file mode 100644 index 0000000..ad3ad3c Binary files /dev/null and b/images/image-20201112135410803.png differ diff --git a/images/image-20201112135429409.png b/images/image-20201112135429409.png new file mode 100644 index 0000000..0337a84 Binary files /dev/null and b/images/image-20201112135429409.png differ diff --git a/images/image-20201112140037987.png b/images/image-20201112140037987.png new file mode 100644 index 0000000..f45e209 Binary files /dev/null and b/images/image-20201112140037987.png differ diff --git a/images/image-20201112140312697.png b/images/image-20201112140312697.png new file mode 100644 index 0000000..4961ba2 Binary files /dev/null and b/images/image-20201112140312697.png differ diff --git a/images/image-20201112141523371.png b/images/image-20201112141523371.png new file mode 100644 index 0000000..be46fa2 Binary files /dev/null and b/images/image-20201112141523371.png differ diff --git a/images/image-20201112145449105.png b/images/image-20201112145449105.png new file mode 100644 index 0000000..aa8cf94 Binary files /dev/null and b/images/image-20201112145449105.png differ diff --git a/images/image-20201112151632111.png b/images/image-20201112151632111.png new file mode 100644 index 0000000..77b1062 Binary files /dev/null and b/images/image-20201112151632111.png differ diff --git a/images/image-20201112155652269.png b/images/image-20201112155652269.png new file mode 100644 index 0000000..c3e3791 Binary files /dev/null and b/images/image-20201112155652269.png differ diff --git a/images/image-20201112155707148.png b/images/image-20201112155707148.png new file mode 100644 index 0000000..09c4d00 Binary files /dev/null and b/images/image-20201112155707148.png differ diff --git a/images/image-20201112160258956.png b/images/image-20201112160258956.png new file mode 100644 index 0000000..e4f9801 Binary files /dev/null and b/images/image-20201112160258956.png differ diff --git a/images/image-20201112165325023.png b/images/image-20201112165325023.png new file mode 100644 index 0000000..48c03dd Binary files /dev/null and b/images/image-20201112165325023.png differ diff --git a/images/image-20201112165641574.png b/images/image-20201112165641574.png new file mode 100644 index 0000000..eddb97a Binary files /dev/null and b/images/image-20201112165641574.png differ diff --git a/images/image-20201112173342142.png b/images/image-20201112173342142.png new file mode 100644 index 0000000..686019f Binary files /dev/null and b/images/image-20201112173342142.png differ diff --git a/images/image-20201112180552221.png b/images/image-20201112180552221.png new file mode 100644 index 0000000..c4ccc0e Binary files /dev/null and b/images/image-20201112180552221.png differ diff --git a/images/image-20201113093057704.png b/images/image-20201113093057704.png new file mode 100644 index 0000000..355bb35 Binary files /dev/null and b/images/image-20201113093057704.png differ diff --git a/images/image-20201113094055276.png b/images/image-20201113094055276.png new file mode 100644 index 0000000..3c012c8 Binary files /dev/null and b/images/image-20201113094055276.png differ diff --git a/images/image-20201113102316294.png b/images/image-20201113102316294.png new file mode 100644 index 0000000..c2ee8d0 Binary files /dev/null and b/images/image-20201113102316294.png differ diff --git a/images/image-20201113103152115.png b/images/image-20201113103152115.png new file mode 100644 index 0000000..c59adcc Binary files /dev/null and b/images/image-20201113103152115.png differ diff --git a/images/image-20201113104134210.png b/images/image-20201113104134210.png new file mode 100644 index 0000000..6baef9b Binary files /dev/null and b/images/image-20201113104134210.png differ diff --git a/images/image-20201113113814310.png b/images/image-20201113113814310.png new file mode 100644 index 0000000..d308de5 Binary files /dev/null and b/images/image-20201113113814310.png differ diff --git a/images/image-20201113114050201.png b/images/image-20201113114050201.png new file mode 100644 index 0000000..049b15b Binary files /dev/null and b/images/image-20201113114050201.png differ diff --git a/images/image-20201113114131931.png b/images/image-20201113114131931.png new file mode 100644 index 0000000..1d46212 Binary files /dev/null and b/images/image-20201113114131931.png differ diff --git a/images/image-20201113120733998.png b/images/image-20201113120733998.png new file mode 100644 index 0000000..46db1c8 Binary files /dev/null and b/images/image-20201113120733998.png differ diff --git a/images/image-20201113121001855.png b/images/image-20201113121001855.png new file mode 100644 index 0000000..72ebe65 Binary files /dev/null and b/images/image-20201113121001855.png differ diff --git a/images/image-20201113121035781.png b/images/image-20201113121035781.png new file mode 100644 index 0000000..30648be Binary files /dev/null and b/images/image-20201113121035781.png differ diff --git a/images/image-20201113131325996.png b/images/image-20201113131325996.png new file mode 100644 index 0000000..97773e0 Binary files /dev/null and b/images/image-20201113131325996.png differ diff --git a/images/image-20201113133658258.png b/images/image-20201113133658258.png new file mode 100644 index 0000000..2f9f5ef Binary files /dev/null and b/images/image-20201113133658258.png differ diff --git a/images/image-20201113133713872.png b/images/image-20201113133713872.png new file mode 100644 index 0000000..7a17d7b Binary files /dev/null and b/images/image-20201113133713872.png differ diff --git a/images/image-20201113133745985.png b/images/image-20201113133745985.png new file mode 100644 index 0000000..5d2bd78 Binary files /dev/null and b/images/image-20201113133745985.png differ diff --git a/images/image-20201113153443738.png b/images/image-20201113153443738.png new file mode 100644 index 0000000..b043c0a Binary files /dev/null and b/images/image-20201113153443738.png differ diff --git a/images/image-20201113160658535.png b/images/image-20201113160658535.png new file mode 100644 index 0000000..53b612a Binary files /dev/null and b/images/image-20201113160658535.png differ diff --git a/images/image-20201113160724897.png b/images/image-20201113160724897.png new file mode 100644 index 0000000..1b03a81 Binary files /dev/null and b/images/image-20201113160724897.png differ diff --git a/images/image-20201113164636525.png b/images/image-20201113164636525.png new file mode 100644 index 0000000..e6368cc Binary files /dev/null and b/images/image-20201113164636525.png differ diff --git a/images/image-20201113164930192.png b/images/image-20201113164930192.png new file mode 100644 index 0000000..c7e9ee7 Binary files /dev/null and b/images/image-20201113164930192.png differ diff --git a/images/image-20201113165759501.png b/images/image-20201113165759501.png new file mode 100644 index 0000000..4138e0a Binary files /dev/null and b/images/image-20201113165759501.png differ