# Study_SpringCloud **Repository Path**: vmvm/study_-spring-cloud ## Basic Information - **Project Name**: Study_SpringCloud - **Description**: 记录学习SpringCloud的一些笔记 - **Primary Language**: Java - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2025-12-05 - **Last Updated**: 2025-12-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SpringCloud 学习 ## 服务消费方直接调用服务提供方(通过 RestTemplate 直接进行 HTTP 调用) ![输入图片说明](https://images.gitee.com/uploads/images/2022/0305/151558_7b6aaac7_5647290.png "屏幕截图.png") ## Eureka 组件 ### Eureka 概述 Eureka 是基于 C-S 架构设计的,Eureka Client 其实就是对应的微服务,可以获取到 Eureka Server 上注册的用户(服务发现),Eureka Server 是服务的注册中心,可用于注册服务以及实现服务的负载均衡和故障转移。 Eureka 的整体运作图如下:
![输入图片说明](MD-Pictures/image.png) ### 启动 Eureka Server 1、新建一个 SpringBoot 项目,pom.xml 中添加下面的依赖(**加依赖**)。 ```xml org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-netflix-eureka-server ``` 2、配置 application.yml(**改配置**)。 ```yml server: port: 8761 eureka: instance: # 设置该服务注册中心的主机号 hostname: localhost client: # 当前的EurekaServer也是一个应用(默认也会将该应用注册到注册中心), 设置为false表示不需要将当前应用注册到注册中心 register-with-eureka: false # 指定服务注册中心的URL service-url.defaultZone: http://localhost:8761/eureka ``` 3、在启动类上加上`EnableEurekaServer`注解(**写注解**)。
![输入图片说明](MD-Pictures/image2.png) 4、访问`http://localhost:8761`,如下图:
![输入图片说明](MD-Pictures/image1.png) ### 微服务注册与调用 1、pom.xml 中添加下面的依赖(**加依赖**)。 ```xml org.springframework.cloud spring-cloud-starter-netflix-eureka-client ``` 2、配置 application.yml(**改配置**)。 ```yml server: port: 8080 spring: application: # 不写这个会在eureka监控页出现UNKNOW name: springcloud-service-portal eureka: client: # Eureka注册中心的连接地址 service-url.defaultZone: http://localhost:8761/eureka instance: # 每隔2秒向Eureka服务端发送一次心跳, 证明自己还存活着 lease-renewal-interval-in-seconds: 2 # 告诉Eureka服务端如果10秒内没有给你发心跳, 代表当前应用发生故障, 将我踢掉 lease-expiration-duration-in-seconds: 10 # 注册到Eureka服务端的服务实例的名字 instance-id: springcloud-service-portal ``` 3、在启动类上加上`EnableEurekaClient`注解(**写注解**)。
![输入图片说明](MD-Pictures/image3.png) 4、在注入 RestTemplate 的方法上加上注解 LoadBalanced。
![输入图片说明](MD-Pictures/1646475332(1).png) 5、修改调用的 URL。
![输入图片说明](MD-Pictures/image4.png) 6、访问`http://127.0.0.1:8080/cloud/goods`,结果如下图:
![输入图片说明](MD-Pictures/1646475427(1).png) ### Eureka 服务注册中心自我保护机制 在没有 Eureka 自我保护的情况下,如果 Eureka Server 在一定时间内没有接收到某个微服务实例的心跳,Eureka Server 将会注销该实例,但是当发生网络故障时,那么微服务与 Eureka Server 之间将无法正常通信,此时该微服务会被注册中心错误地剔除(微服务本身其实是正常的)。 此处联想到 Eureka Client 端下面的配置: ```yml eureka: instance: # 每隔2秒向Eureka服务端发送一次心跳, 证明自己还存活着 lease-renewal-interval-in-seconds: 2 # 告诉Eureka服务端如果10秒内没有给你发心跳, 代表当前应用发生故障, 将我踢掉 lease-expiration-duration-in-seconds: 10 ``` Eureka 通过**自我保护模式**来解决这个问题——当 Eureka Server 节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么就会把这个微服务节点进行保护,不删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该 Eureka Server 节点会再自动退出自我保护模式。 **Eureka Server 端可以使用配置项禁用自我保护模式**。`eureka.server.enable-self-preservation = false `
**生产上还是最好启用自我保护模式**,虽然该模式给我们带来一些困扰,比如:如果在保护期内某个服务提供者刚好非正常下线了,此时服务消费者就会拿到一个无效的服务实例,此时会调用失败,但对于这个问题需要**服务消费者端具有一些容错机制,如重试、断路器等**。 ### Eureka 高可用集群搭建 Eureka 服务注册中心它本身也是一个服务(可以看做是一个提供者,又可以看做是一个消费者),我们之前通过配置:`eureka.client.register-with-eureka=false`让注册中心不注册自己,但是我们可以向其他注册中心注册自己。 Eureka Server 的高可用实际上就是**将自己作为服务向其他服务注册中心进行注册**,这样就会形成一组互相注册的服务注册中心,进而实现服务清单的互相同步。**往注册中心 A 上注册的服务,可以被复制同步到注册中心 B 上**,所以从任何一台注册中心上都能查询到已经注册的服务,从而达到高可用的效果。Eureka 高可用集群架构图如下:
![输入图片说明](MD-Pictures/1646532423(1).png) 1、复制三份配置文件,做出如下修改: ```yml # application-eureka8762.yml server: port: 8762 eureka: instance: # 设置该服务注册中心的主机号 hostname: localhost client: # 当前的EurekaServer也是一个应用(默认也会将该应用注册到注册中心), 设置为false表示不需要将当前应用注册到注册中心 register-with-eureka: false # 指定服务注册中心的URL service-url.defaultZone: http://eureka8763:8763/eureka,http://eureka8764:8764/eureka # application-eureka8763.yml server: port: 8763 eureka: instance: # 设置该服务注册中心的主机号 hostname: localhost client: # 当前的EurekaServer也是一个应用(默认也会将该应用注册到注册中心), 设置为false表示不需要将当前应用注册到注册中心 register-with-eureka: false # 指定服务注册中心的URL service-url.defaultZone: http://eureka8762:8762/eureka,http://eureka8764:8764/eureka # application-eureka8764.yml server: port: 8764 eureka: instance: # 设置该服务注册中心的主机号 hostname: localhost client: # 当前的EurekaServer也是一个应用(默认也会将该应用注册到注册中心), 设置为false表示不需要将当前应用注册到注册中心 register-with-eureka: false # 表示不会从服务端主动检索其他服务信息(自己就是服务端) fetch-registry: false # 指定服务注册中心的URL service-url.defaultZone: http://eureka8762:8762/eureka,http://eureka8763:8763/eureka ``` 2、在`C:\Windows\System32\drivers\etc\hosts`文件中添加下图配置:
![输入图片说明](MD-Pictures/image6.png) 3、在 Program Arguments 中配置`--spring.profiles.active=eureka...`
![输入图片说明](MD-Pictures/image7.png) 4、访问其中一个 Eureka8762 后台页面,启动 goods、protal 服务并将其注册到 8762,可以看到 Eureka8763 上有该服务信息(从Eureka8762 上复制过来的)。 ![输入图片说明](MD-Pictures/1646532920(1).png) ![输入图片说明](MD-Pictures/image9.png) ### Eureka 宕机时分析 **当注册中心 Eureka 宕机时,服务消费者也可以调用服务提供者**,该机制和 Dubbo 一样,服务消费者那里会维护一份本地缓存的。 ## Ribbon 组件 ### 启用 Ribbon 只需要在 RestTemplate 上面加上 LoadBalanced 注解,即可实现**服务调用的负载均衡**。 ### 负载均衡实现的原理 客户端负载均衡时会调用`ILoadBalancer 实现类上的 chooseServer 方法`,然后会进入 BaseLoadBalancer 的 chooseServer 方法,真正负载均衡的逻辑在`IRule 实现类的 choose 方法`。 ![输入图片说明](MD-Pictures/image10.png) ![输入图片说明](MD-Pictures/image11.png) ![输入图片说明](MD-Pictures/image12.png)
IRule 实现类的类名策略
RandomRule随机
RoundRobinRule轮询
AvailabilityFilteringRule先过滤掉由于多次访问故障的服务,以及并发连接数超过阈值的服务,然后对剩下的服务按照轮询策略进行访问
ResponseTimeWeightedRule...
WeightedResponseTimeRule根据平均响应时间计算所有服务的权重,响应时间越快服务权重就越大被选中的概率即越高,如果服务刚启动时统计信息不足,则使用RoundRobinRule策略,待统计信息足够会切换到该WeightedResponseTimeRule策略
RetryRule先按照RoundRobinRule策略分发,如果分发到的服务不能访问,则在指定时间内进行重试,然后分发其他可用的服务
BestAvailableRule先过滤掉由于多次访问故障的服务,然后选择一个并发量最小的服务
ZoneAvoidanceRule(默认)综合判断服务节点所在区域的性能和服务节点的可用性,来决定选择哪个服务
### 自定义负载均衡算法 1、`继承 AbstractLoadBalancerRule 类并实现 choose 方法 `。 ```java @Slf4j public class MyRule extends AbstractLoadBalancerRule { private AtomicInteger integer = new AtomicInteger(0); @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { } @Override public Server choose(Object key) { log.info("执行了自定义的负载均衡算法..."); ILoadBalancer lb = getLoadBalancer(); if (lb == null) { return null; } List allList = lb.getAllServers(); int serverCount = allList.size(); if (serverCount == 0) { return null; } while(true) { int currentIndex = integer.get(); int nextIndex = (1 + currentIndex) % serverCount; if(integer.compareAndSet(currentIndex, nextIndex)) { return allList.get(nextIndex); } } } } ``` 2、将该规则注入到服务消费者的容器中。 ```java @Bean public IRule iRule() { return new MyRule(); } ``` 3、测试。 ![输入图片说明](MD-Pictures/1646652442(1).png) ![输入图片说明](MD-Pictures/1646652427(1).png) ## Feign 组件 ### Feign 概述 在之前进行微服务调用时都是直接需要写一大堆冗余代码(比如:一长串的 URL 和 RestTemplate 的相关调用),在实际开发中,由于服务提供者提供的接口很多,直接引用 RestTemplate 会有很多冗余代码,因此可以使用 Feign 组件。 Feign 在 Ribbon + RestTemplate 的基础上做了进一步封装,在 Feign封装之后,我们只需**创建一个接口并使用注解的方式来配置**,即可完成对服务提供方的接口绑定,简化了使用Ribbon + RestTemplate 的调用,自动封装服务调用客户端,减少了代码量。 ### Feign 使用 1、引依赖 在 commons 模块中引入下面的依赖(接口一般放在 commons 模块,和 Dubbo 开发类似): ```xml org.springframework.cloud spring-cloud-starter-openfeign ``` 2、写接口(配置服务名+URI)
![输入图片说明](MD-Pictures/image13.png) 3、加注解
消费者启动类上加上`@EnableFeignClients`。 ## Hystrix 组件(TODO:如何通过 yml 细粒度配置 hystrix 的超时时间、整合了 feign 的 hystrix 如何直接将异常抛给用户---不走降级方法) ### Hystrix 概述 Hystrix 被称为熔断器,可以用于处理分布式系统中的延迟和容错。比如:在分布式系统中,许多服务都需要通过远程调用其他服务来实现自己的功能,调用时不可避免会出现超时、出现异常等导致调用失败,Hystrix 组件可以保证在某个服务出现故障的情况下,不会导致整体服务失败,**避免级联故障**,以提高分布式系统的弹性。 ![输入图片说明](MD-Pictures/image14.png) ### 启动 Hystrix 1、引依赖 在服务消费者模块中引入下面的依赖: ```xml org.springframework.cloud spring-cloud-starter-netflix-hystrix ``` 2、加注解
![输入图片说明](MD-Pictures/image15.png) 服务消费者的启动类上加上`@EnableHystrix`。 3、Feign 整合 Hystrix(**此时降级都在 Feign 接口上完成,如果还是在 Controller 上做的话其实和之前普通的降级一样了**)
![输入图片说明](MD-Pictures/image21.png) ![输入图片说明](MD-Pictures/image22.png) **必须要将该实现类注入到容器中!** ```java //需要注入到容器 @Component public class GoodsRemoteClientFallback implements GoodsRemoteClient { @Override public ResultObject goods() { return new ResultObject(Constant.ERROR, "feign服务调用降级...", null); } } ``` ```yml feign: hystrix: # 开启feign的hystrix支持 enabled: true ``` ![输入图片说明](MD-Pictures/image20.png) ### 降级测试
① 服务提供者未启动,此时调用服务消费者的接口,执行了 fallback 备份方法。
![输入图片说明](MD-Pictures/image16.png) ![输入图片说明](MD-Pictures/image17.png) ② 在 fallback 备份方法中加入 Throwable 参数,即可捕获到服务调用时抛出的异常。
![输入图片说明](MD-Pictures/image18.png) ![输入图片说明](MD-Pictures/image19.png) ③ 普通的 hystrix 超时引起降级。 当前执行指令的超时时间为 5 秒,而方法内线程休眠了 8 秒,势必会导致降级。 ```java @HystrixCommand(fallbackMethod = "fallback", commandProperties = { //命令执行知否开启超时, 默认为true @HystrixProperty(name = "execution.timeout.enabled", value = "true"), //指定命令执行的超时时间, 默认为1秒 @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000") } ) @GetMapping("/cloud/goodsHystrixTimeout") public ResultObject goodsHystrixTimeout() throws InterruptedException { ResultObject result = restTemplate.getForEntity(GOODS_SERVICE_URL_2, ResultObject.class).getBody(); TimeUnit.SECONDS.sleep(8); return result; } ``` ![输入图片说明](MD-Pictures/image23.png) ④ 整合了 feign 的 hystrix 超时引起降级(**feign 调用服务存在默认时长上限制,当超过 1 秒还未连接上或者超过 1 秒还未返回结果都会报错**)。
![输入图片说明](MD-Pictures/image24.png) ![输入图片说明](MD-Pictures/image25.png) ![输入图片说明](MD-Pictures/image26.png) 可以通过下面的配置修改 Ribbon 的连接时长和处理时长以及 Hystrix 指令的执行时长。 ```yml ribbon: ReadTimeout: 5000 ConnectTimeout: 5000 hystrix: command: # default表示全局, 非全局的待研究 default: execution: timeout: enable: true isolation: thread: timeoutInMilliseconds: 4000 ``` ⑤ 捕获到整合了 feign 的 hystrix 抛出的异常。
![输入图片说明](MD-Pictures/image27.png) ![输入图片说明](MD-Pictures/image28.png) ```java @Slf4j //需要注入到容器, 泛型为Feign的接口类 @Component public class GoodsRemoteClientFallbackFactory implements FallbackFactory { @Override public GoodsRemoteClient create(Throwable throwable) { return new GoodsRemoteClient() { @Override public ResultObject goods() { log.info(throwable.getMessage()); return new ResultObject(Constant.ERROR, "feign服务调用降级...GoodsRemoteClientFallbackFactory", null); } }; } } ``` ![输入图片说明](MD-Pictures/image29.png) ![输入图片说明](MD-Pictures/image30.png) ⑥ 将服务提供方的异常忽略**即不执行备份方法**,直接抛给用户(默认情况下方法抛了异常会自动进行服务降级)。
在 @HystrixCommand 注解中指定 ignoreExceptions 属性为要忽略的异常类即可。 ```java @HystrixCommand(fallbackMethod = "fallback", ignoreExceptions = {RuntimeException.class}) @GetMapping("/cloud/goodsHystrixIgnoreExceptions") public ResultObject goodsHystrixIgnoreExceptions() throws InterruptedException { ResultObject result = restTemplate.getForEntity(GOODS_SERVICE_URL_2, ResultObject.class).getBody(); if (1 == 1) { throw new RuntimeException("has error..."); } return result; } ``` ![输入图片说明](MD-Pictures/image31.png) **经测试整合了 feign 的 hystrix 貌似无法通过上述方式忽略异常**。 ## Zuul 网关组件 ### Zuul 网关组件概述 微服务架构中有很多个独立服务都要对外提供服务,为了方便地管理这些微服务接口,同时要确保每个微服务接口的安全,每个微服务需要做权限管理,为了减少工作量,微服务架构中提出了 API 网关的概念,外部所有的请求都要经过该网关的调度和过滤,由 API 网关实现请求路由、负载均衡、权限验证等功能。 当前微服务的架构图如下:
![输入图片说明](MD-Pictures/image32.png) ### 初级路由功能 1、新建 Zuul 的 SpringBoot 项目,pom 文件中引入下面的依赖。 ```xml org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-devtools true org.springframework.cloud spring-cloud-starter-netflix-zuul org.springframework.cloud spring-cloud-starter-netflix-eureka-client ``` 2、启动类上加入下面注解(`@EnableEurekaClient` 和 `@EnableZuulProxy`)。PS:Zuul 网关也需要作为微服务注册到 Eureka 中。 ```java //激活Eureka的客户端 @EnableEurekaClient //开启Zuul网关的支持 @EnableZuulProxy @SpringBootApplication public class ZuulApplication { public static void main(String[] args) { SpringApplication.run(ZuulApplication.class, args); } } ``` 3、修改 application.yml 文件的配置。 ```yml server: port: 80 spring: application: # 不写这个会在eureka监控页出现UNKNOW name: springcloud-service-zuul eureka: client: # Eureka注册中心的连接地址 service-url.defaultZone: http://localhost:8761/eureka instance: # 每隔2秒向Eureka服务端发送一次心跳, 证明自己还存活着 lease-renewal-interval-in-seconds: 2 # 告诉Eureka服务端如果10秒内没有给你发心跳, 代表当前应用发生故障, 将我踢掉 lease-expiration-duration-in-seconds: 10 # 注册到Eureka服务端的服务实例的名字 instance-id: springcloud-service-zuul ``` 4、测试。 启动 Eureka 的管理界面,可以看到 goods、portal、zuul服务都成功注册了。
![输入图片说明](MD-Pictures/image33.png) `浏览器中只需要访问 80 端口(Zuul 应用的端口) + 服务名 + URI 即可完成服务的调用。`
![输入图片说明](MD-Pictures/1649408098(1).png) ![输入图片说明](MD-Pictures/1649408118(1).png) 可通过下面的配置禁止直接通过微服务名(之间用逗号分隔)调用接口。 ```yml zuul: ignored-services: springcloud-service-portal,springcloud-service-goods ``` 从下图中可以看到成功禁用了微服务名的直接调用。
![输入图片说明](MD-Pictures/image38.png) ![输入图片说明](MD-Pictures/image39.png) 可通过下面的配置将请求的 URL 前面加上固定的前缀。 ```yml # 经测试写成 /api/ 也可以生效 zuul: prefix: /api ``` ### 高级路由(自定义映射规则) 如果只是用初级路由功能,则访问时需要输入`服务名+URI`,此时对外暴露了服务名,增加了一定的隐患,而且实际开发当中肯定不会通过微服务名去调用,此时就需要配置路由规则(高级路由)。在 Zuul 应用的 application.yml 添加下面配置: ```yml zuul: routes: portal: service-id: springcloud-service-portal path: /portal/** goods: service-id: springcloud-service-goods path: /goods/** ``` 访问下面的 URL,可以看到接口调用的结果。
![输入图片说明](MD-Pictures/image34.png) ![输入图片说明](MD-Pictures/image35.png) 通配符规则:
通配符 含义 举例 说明
? 匹配任意单个字符 /springcloud-service-goods/? 匹配 /springcloud-service-goods/a, /springcloud-service-goods/b, /springcloud-service-goods/c 等
* 匹配任意数量的字符 /springcloud-service-goods/* 匹配 /springcloud-service-goods/aa, /springcloud-service-goods/bb, /springcloud-service-goods/cc 等,但无法匹配 /springcloud-service-goods/a/b
** 匹配任意数量的字符 /springcloud-service-goods/* 匹配 /springcloud-service-goods/aa, /springcloud-service-goods/bb, /springcloud-service-goods/cc 等,也可匹配 /springcloud-service-goods/a/b
### 过滤器 1、概述:
Zuul 中定义了四种标准过滤器类型(Pre、Route、Post、Error),分别对应请求的不同生命周期。Zuul 中过滤器的工作生命周期图如下:
![输入图片说明](MD-Pictures/1649417489(1).png)
过滤器类型 过滤器触发时机 过滤器作用
Pre 在请求被路由之前调用 利用这种过滤器可实现身份验证、在集群中选择请求的微服务、记录调试信息等
Route 将请求路由到微服务 该过滤器可用于构建发送给微服务的请求,并使用 Apache HttpClient 或 Netfilx Ribbon 请求微服务
Post 在路由到微服务以后执行 该过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等
Error 某阶段发生错误时执行 统一处理错误
2、自定义过滤器的使用: ```java @Slf4j @Component public class LoginFilter extends ZuulFilter { @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); log.info("进入了LoginFilter... request: {}", request.toString()); return null; } } ``` ![输入图片说明](MD-Pictures/image36.png) 3、可配置要禁用过滤器: 在 application.yml 文件中添加下面的配置: ```yml #第一个点后面表示过滤器的类名,第二个点后面表示过滤器的类型(pre等) zuul.LoginFilter.prev.disable=true ``` 从下图可以看到禁用 LoginFilter 成功了。
![输入图片说明](MD-Pictures/image37.png) ### 熔断降级 如果使用 Zuul 网关路由到的微服务宕机了,则会执行 Zuul 的熔断策略。 ```java @Component public class ZuulFallback implements FallbackProvider { @Override public String getRoute() { //所有经过网关路由的服务出异常都会执行该熔断 return "*"; } @Override public ClientHttpResponse fallbackResponse(String route, Throwable cause) { return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.BAD_REQUEST; } @Override public int getRawStatusCode() throws IOException { return HttpStatus.BAD_REQUEST.value(); } @Override public String getStatusText() throws IOException { return HttpStatus.BAD_REQUEST.getReasonPhrase(); } @Override public void close() { } @Override public InputStream getBody() throws IOException { String responseJson = "{\"data:\" null, \"msg:\" 服务正在维护,请稍后再试...}"; return new ByteArrayInputStream(responseJson.getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.set("Content-Type", "application/json; charset=UTF-8"); return headers; } }; } } ``` 将 goods 服务下线后,通过网关访问该服务,则会执行 Zuul 的熔断,如下如图所示:
![输入图片说明](MD-Pictures/image40.png) ## Config 分布式配置中心组件 ### 分布式配置中心概述 在微服务架构中,服务数量及其配置信息会不断增多,这对配置管理提出了如下挑战:
安全性:配置文件和源代码一起保存在代码库,容易造成配置泄露;
时效性:修改配置文件后,需要重启服务才能生效;
局限性:无法支持动态调整,例如:日志开关、功能开关。 Spring Cloud Config 是一个解决分布式系统的配置管理方案,其包含 Client 和 Server 两部分,Server 提供配置文件的存储(可用 Git 管理,**Git 天生就是为版本管理而生的,因此 Config 组件也自带了版本管理的功能**)、以接口的形式将配置文件的内容提供出去,Client 通过接口获取数据、并依据此数据初始化自己的应用。 ### Config 工作过程图 ![输入图片说明](MD-Pictures/image41.png) ### 基础配置 1、在 GitEE 或 GitHub 上创建中央仓库。
![输入图片说明](MD-Pictures/image42.png) 2、配置 Config 的 Server 端。 新建一个 SpringBoot 的应用,在 pom.xml 文件中加入下述依赖: ```xml org.springframework.cloud spring-cloud-config-server ``` 并在启动类上加上`@EnableConfigServer`。 新增 bootstrap.yml 文件,添加下面的配置: ```yml server: port: 8888 spring: application: name: springcloud-service-config cloud: config: server: git: # git仓库的URL uri: https://gitee.com/chenjie1561435010/spring-cloud-config.git # 查询配置文件的路径, 多个路径间用逗号分隔 search-paths: config-server/goods,config-server/portal # git的用户名 username: *** # git的密码 password: *** ``` 3、可通过下面的方式访问到对应远程仓库上的配置文件(**{application} 表示配置文件的名字,{profile} 表示环境,{label} 表示分支,默认放在 master 分支上**)。
① /{application}/{profile}[/{label}]
http://127.0.0.1:8888/application/goods-dev/master 或 http://127.0.0.1:8888/application/goods-dev
![输入图片说明](MD-Pictures/image43.png) ![输入图片说明](MD-Pictures/image44.png) ![输入图片说明](MD-Pictures/image45.png) ② /{application}-{profile}.properties 或 /{application}-{profile}.yml
http://127.0.0.1:8888/application-goods-dev.yml
![输入图片说明](MD-Pictures/image46.png) ③ /{label}/{application}-{profile}.properties 或 /{label}/{application}-{profile}.yml
http://127.0.0.1:8888/master/application-goods-dev.yml
![输入图片说明](MD-Pictures/image47.png) 4、配置 Config 的 Client 端。 此处新建了一个应用测试,其实可以复用原来的 goods 和 portal 微服务,在 pom.xml 文件中添加下述依赖: ```xml org.springframework.cloud spring-cloud-starter-config ``` 新增 bootstrap.yml 文件,分别在两服务中添加下面的配置: ```yml spring: cloud: config: # Config Server应用的IP+端口 uri: http://127.0.0.1:8888 # 远程仓库的分支 label: master # 配置文件的环境 profile: goods-dev spring: cloud: config: # Config Server应用的IP+端口 uri: http://127.0.0.1:8888 # 远程仓库的分支 label: master # 配置文件的环境 profile: portal-dev ``` 5、测试。 ![输入图片说明](MD-Pictures/image48.png) ![输入图片说明](MD-Pictures/image49.png) ### 高级配置 1、加密
Config Server 应用的配置文件上加上密钥配置,如下所示: ```yml encrypt: # 用来加密的密钥 key: springcloud-service-config ``` 将配置文件中要加密的用户名、密码等敏感数据依次通过 `http://127.0.0.1:8888/encrypt` 进行加密,也可以通过 `http://127.0.0.1:8888/decrypt` 将加密后的数据还原为原始数据。 ![输入图片说明](MD-Pictures/1649925147(1).png) 之后用响应的数据替换掉原始数据(PS:**前面加上{cipher},yml 文件中需要加上单引号才能正确解密**)。 ![输入图片说明](MD-Pictures/1649925484(1).png) 访问 `http://127.0.0.1:8888/application-goods-dev.yml`,可以看到配置文件被自动解密了。 ![输入图片说明](MD-Pictures/image50.png) 2、配置文件局部刷新
Config Client 端(goods 服务和 portal 服务)的 pom.xml 文件中添加 `actuator` 依赖。 ```xml org.springframework.boot spring-boot-starter-actuator ``` Config Client 端的 Controller 类上加上 `@RefreshScope` 注解。 ![输入图片说明](MD-Pictures/image51.png)
Config Client 端的配置文件中加上下面的配置: ```yml management: endpoints: web: exposure: include: '*' ``` 每次修改远程仓库的配置文件后,手动 POST 调用 `http://127.0.0.1:port/actuator/refresh`,之后 Config Client 就能从远程仓库获取到最新的配置数据了(**感觉就类似一个缓存问题,手动调用 refresh 接口后会手动删除本地缓存,从远程仓库读取最新的数据再放入本地缓存**)。
![输入图片说明](MD-Pictures/image54.png) ![输入图片说明](MD-Pictures/image53.png) 3、配置文件全局刷新
Config Server 端和 Client 端的 pom.xml 文件中都添加 `actuator` 和 `amqp` 依赖。 ```xml org.springframework.boot spring-boot-starter-actuator org.springframework.cloud spring-cloud-starter-bus-amqp ``` Config Server 端的配置文件中加上 RabbitMQ 的连接配置: ```yml spring: rabbitmq: host: 192.168.255.204 port: 5672 username: guest password: guest cloud: bus: # 开启bus总线 enabled: true ``` Config Client 端(goods 服务和 portal 服务)的配置文件中加上 RabbitMQ 的连接配置: ```yml spring: rabbitmq: host: 192.168.255.204 port: 5672 username: guest password: guest ``` 每次修改远程仓库的配置文件后,手动 POST 调用 `http://127.0.0.1:port/actuator/refresh`(**该端口是 Config Server 端的端口**),之后 Config Client 就能从远程仓库获取到最新的配置数据了。
![输入图片说明](MD-Pictures/image52.png) ### Config 高可用 要使得 Config 服务达到高可用,可以将 Config 服务注册到 Eureka 中,利用注册中心来实现 Config 高可用的目的。 1、新建三个 Config 的应用,端口号分别是 8887、8888、8889,将其注册到 Eureka 中。 ![输入图片说明](MD-Pictures/image55.png) 2、客户端只需要将之前在 bootstrap.yml 中配置的 URL 注释掉,从注册中心你中获取服务即可,如下图所示: ![输入图片说明](MD-Pictures/image56.png) 可以看到 Config 的高可用已经生效,而且客户端也可以成功访问到。 ![输入图片说明](MD-Pictures/image57.png) ![输入图片说明](MD-Pictures/image58.png) ### Config 的安全认证 该安全认证说穿了其实就是限制用户或微服务直接访问 Config 配置中心上的配置,Config 服务中引入 SpringSecurity 依赖并配置用户名和密码,客户端微服务要想读取配置中心上的配置,则需要在配置文件中增加下述配置: ```yml spring: cloud: config: username: root password: 123456 ``` ![输入图片说明](MD-Pictures/1650771601(1).png) ![输入图片说明](MD-Pictures/image59.png) ![输入图片说明](MD-Pictures/1650771767(1).png) ![输入图片说明](MD-Pictures/image60.png) ## Sleuth + Zipkin 分布式链路追踪 ### 概述 对于一个大型的几十个、几百个微服务构成的微服务架构系统,通常会遇到下面一些问题,比如:
如何串联整个调用链路,快速定位问题?
如何理清各个微服务之间的依赖关系?
如何进行各个微服务接口的性能分折?
如何跟踪整个业务流程的调用处理顺序?
Sleuth + Zipkin 其实可以帮我做上述事情,其中 **Sleuth 负责给数据打点,Zipkin 负责将跟踪的数据展示给用户**。 原理图如下所示:
![输入图片说明](MD-Pictures/image61.png) ### 简易搭建(默认将追踪的数据保存在 ZipkinServer 的内存中,重启会丢失) 1、搭建 ZipkinServer 应用。application.yml 配置如下: ```yml server: port: 9410 management: metrics: web: server: auto-time-requests: false ``` 2、引入下述依赖: ```xml io.zipkin.java zipkin-autoconfigure-ui 2.12.3 io.zipkin.java zipkin-server 2.12.3 ``` 3、启动类上添加 `@EnableZipkinServer` 注解。 4、微服务应用的 pom.xml 上加入下述依赖: ```xml org.springframework.cloud spring-cloud-starter-sleuth org.springframework.cloud spring-cloud-starter-zipkin ``` 5、微服务应用的 application.yml 上加入下面配置: ![输入图片说明](MD-Pictures/1659151345930.png) 6、访问 `http://127.0.0.1:9410/zipkin`,并请求 goods 微服务,从下图可以看到分布式链路追踪搭建成功了。 ![输入图片说明](MD-Pictures/image62.png) ![输入图片说明](MD-Pictures/image63.png) 但是一旦重启 ZipkinServer 应用后,从下图看到数据丢失了。 ![输入图片说明](MD-Pictures/image64.png) ### 使用 Elastic Search 对 ZipkinServer 进行持久化 1、Zipkin Server 的 pom.xml 中添加依赖。 ```xml io.zipkin.java zipkin-autoconfigure-storage-elasticsearch-http 2.8.4 ``` 2、Zipkin Server 的 application.yml 中添加下面的配置。 ```yml zipkin: storage: type: elasticsearch elasticsearch: cluster: elasticsearch # elasticsearch启动的端口号 hosts: http://localhost:9200 # 随便去个名字 index: zipkin ``` 3、此时访问打点后,即使 Zipkin Server 重新启动也不会丢失数据。 ![输入图片说明](MD-Pictures/image65.png) ## Spring Cloud Stream(TODO Add) ## 微服务添加安全认证 ### RestTemplate 添加安全认证 如果不添加认证信息,会报错,如下图: ![输入图片说明](MD-Pictures/1659158133814.png) 1、在 ConfigBean 中增加下面代码: ```java @Bean public HttpHeaders httpHeaders() { HttpHeaders httpHeaders = new HttpHeaders(); byte[] encodedAuth = Base64.getEncoder().encode(String.format("%s:%s", username, password).getBytes(Charset.forName("US-ASCII"))); String authHeader = "Basic " + new String(encodedAuth); httpHeaders.set("Authorization", authHeader); return httpHeaders; } ``` 2、并修改 RestTemplate 的调用: ![输入图片说明](MD-Pictures/1659159590347.png) 3、修改后再次访问,可以成功访问,如下图: ![输入图片说明](MD-Pictures/1659159527559.png) ### Feign 添加安全认证 如果不添加认证信息,会报错,如下图: ![输入图片说明](MD-Pictures/image67.png) 1、在 Feign 接口的工程中增加下面配置类: ```java @Configuration public class FeignConfiguration { @Bean public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { return new BasicAuthRequestInterceptor("admin", "123456"); } } ``` 2、并在注解上增加下面的属性: ![输入图片说明](MD-Pictures/image66.png) 3、可以看到可以成功通过 Feign 访问需要权限的微服务了。 ![输入图片说明](image67.png) ### 微服务统一安全认证的做法 新建一个模块专门用于认证(里面放个配置类,其他要保证安全的模块依赖这个模块即可)。 1、新建模块,并在 pom 文件中增加如下配置: ```xml org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-web ``` 2、新增如下的配置类: ```java @Configuration @EnableWebSecurity public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .passwordEncoder(new BCryptPasswordEncoder()) .withUser("admin") .password(new BCryptPasswordEncoder().encode("123456")) .roles("ADMIN"); } @Override protected void configure(HttpSecurity http) throws Exception { http.httpBasic().and().authorizeRequests().anyRequest().fullyAuthenticated(); http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } } ``` 3、其他模块依赖当前 auth 模块,访问时如下图所示就说明认证模块搭建成功。 ![输入图片说明](MD-Pictures/image68.png)