# Java-study6-springcloud **Repository Path**: weiranyi/java-study6-springcloud ## Basic Information - **Project Name**: Java-study6-springcloud - **Description**: Java-study6-springcloud - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-10-14 - **Last Updated**: 2023-06-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 微服务的演进 单体->垂直->SOA->微服务 # 命令 ```bash -Dserver.port=8001 @Value("${server.port}") private String serverPort; log.info("*******result:" + result+"serverPort"+serverPort); ``` # 笔记 ## 1.Eureka ### 1.1Eureka元数据 - 标准元数据:主机名,ip,端口,这些信息注册到注册中心的注册表,用于服务调用 - 自定义元数据:metadata-map ```yml eureka: client: serviceUrl: # eureka server的路径 defaultZone: http://localhost:9000/eureka/,http://localhost:9001/eureka/ instance: #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip) prefer-ip-address: true #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@ # 自定义元数据,会和标准元数据一起注册到注册中心 metadata-map: name: weirnayi school: CDUT ``` ### 1.2 Eureka客户端 - 服务提供者注册过程 - 导入依赖,配置注册中心 - 服务启动时发起注册请求,携带服务的元数据 - 注册中心会把服务保存在注册表Map中(客户端默认每30s拉取注册表缓存到本地) - 服务提供者续约过程 - 客户端:默认每30s续约一次(心跳),在Eureka服务端更新状态 - 服务端:每隔90s,将没续约的微服务从注册表中剔除 ```yml instance: # 租约续约间隔时间,默认30秒 lease-renewal-interval-in-seconds: 30 # 租约到期,服务时效时间,默认值90秒,服务超过90秒没有发生心跳,EurekaServer会将服务从列表移除 lease-expiration-duration-in-seconds: 90 ``` - 获取服务注册表过程 - 每30s拉取一份列表,缓存到本地 ```yml eureka: client: # 每隔多久拉取一次服务列表 registry-fetch-interval-seconds: 30 ``` ### 1.2 Eureka服务端 - 服务下线 - 服务正常关闭时,会发送服务下线的Rest请求,经过EurekaServer - 注册中心接受请求后,将服务设为下线 - 失效剔除 - 定时每60s检查一次,如果发现90s内没有心跳就剔除失效服务 - 问题:网络延迟的时候,注册中心在时效内每检查到心跳怎么办?所以心跳检测来了 - 自我保护机制[默认开启](比Zookeeper一半不可用就整个集群不可用的机制进步一些) - 为了应对网络波动,引入了自我保护机制;如果在15min内,85%节点没有心跳,那么Eureka与注册中心出现了网络故障,Eureka进入自我保护状态 - 自我保护机制干可以做的事: - 不再移除因心跳问题需移除的微服务 [不可错杀一百] - 仅能够处理新请求,但不同步到集群其他节点 [网络资源有限,先顾自己这摊事] - 稳定时,再同步新注册的信息到其他注册中心节点等 ```yml eureka: server: enable-self-preservation: false # 关闭自我保护模式(默认打开true) # 自我保护机制内容: # EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE. ``` ## 2.Ribbon负载均衡策略 ### 2.1 几种策略 - **ZoneAvoidanceRule**区域权衡策略(默认策略,最佳):扩展了轮训策略,继承了两个过滤器,过滤链接多/超时的server 过滤不在一个区域的,之后在剩下的节点中轮询 ```bash ZoneAvoidancePredicate: 地区相关 AvailabilityPredicate: 有效性相关 ``` - RoundRobinRule轮询策略:默认超过10次获取到的server都不可用,会返回一个空的server ```bash public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { // 没有ILoadBalancer就是没做负载均衡 log.warn("no load balancer"); return null; } Server server = null; int count = 0; while (server == null && count++ < 10) { // 最多循环10次 List reachableServers = lb.getReachableServers(); // 可用的 List allServers = lb.getAllServers(); // 所有的 int upCount = reachableServers.size(); //统计可用服务数 int serverCount = allServers.size(); // 所有的服务数 // 可用服务数为0,或服务总数为0 if ((upCount == 0) || (serverCount == 0)) { log.warn("No up servers available from load balancer: " + lb); return null; } // 轮询要返回的server的下标 int nextServerIndex = incrementAndGetModulo(serverCount); server = allServers.get(nextServerIndex); // ...... return server; } // ...... } private int incrementAndGetModulo(int modulo) { for (;;) { int current = nextServerCyclicCounter.get(); int next = (current + 1) % modulo; if (nextServerCyclicCounter.compareAndSet(current, next)) return next; } } ``` - RandomRule随机策略:随机拿到的server为null或者不可用,会while循环选取 ```bash if (lb == null) { return null; } Server server = null; while (server == null) { if (serverCount == 0) { return null; } } // 得到随机数,serverCount是随机数的限制 int index = chooseRandomInt(serverCount); // 读取随机数对应的服务 server = upList.get(index); // 是空下一次循环 if (server == null) { continue;} // 是存活的 if (server.isAlive()) { return (server); } ``` - BestAvailableRule最小连接数策略:该算法里面有一个LoadBalancerStats的成员变量,会存储所有server的 运行状况和连接数。如果选取到的server为null,那么会调用RoundRobinRule重新选取。 ### 2.2 修改策略【建议走默认策略,先过滤再轮询】 - 调用方配置中追加以下代码 ```yml # weiranyi-service-product:不加这个就是全局生效,对所有服务 weiranyi-service-product: # 代表静态页面服务向weiranyi-service-product发起请求时采用以下策略模式 ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #轮询策略 ``` ## 2.3 Ribbon原理 - 请求->拦截器->均衡算法->目标微服务 - spring-cloud-commons -》 spring.factories -》org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration, ```bash # 配置类标识 @Configuration(proxyBeanMethods = false) # classpath下必须存在RestTemplate,才启用这个类【自动配置验证条件】 @ConditionalOnClass(RestTemplate.class) # 容器中必须引用LoadBalancerClient【自动配置验证条件】 @ConditionalOnBean(LoadBalancerClient.class) # 启用自动配置,从LoadBalancerRetryProperties类中加载配置信息 @EnableConfigurationProperties(LoadBalancerRetryProperties.class) public class LoadBalancerAutoConfiguration { @LoadBalanced @Autowired(required = false) private List restTemplates = Collections.emptyList(); #创建空的集合,执行自动注入操作,将restTemplate注进来 // 初始化中有一个restTemplate定制化工具,遍历定制 @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated( final ObjectProvider> restTemplateCustomizers) { return () -> restTemplateCustomizers.ifAvailable(customizers -> { for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) { for (RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); } } }); } # 负载均衡的拦截器配置 @Configuration(proxyBeanMethods = false) @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class LoadBalancerInterceptorConfig { @Bean public LoadBalancerInterceptor loadBalancerInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) { return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); } @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer( final LoadBalancerInterceptor loadBalancerInterceptor) { return restTemplate -> { List list = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); #增加负载均衡的拦截器 restTemplate.setInterceptors(list); # 将这一堆拦截器设置进restTemplate }; } } } # 拦截器 public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException { final URI originalUri = request.getURI(); String serviceName = originalUri.getHost(); # 服务名称 # 断言 Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri); # this.loadBalancer调用了负载均衡器,开始执行(传递调用目标的名称,...) return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution)); } } public interface LoadBalancerClient extends ServiceInstanceChooser { # 执行请求 T execute(String serviceId, LoadBalancerRequest request) throws IOException; } public class RibbonLoadBalancerClient implements LoadBalancerClient { public T execute(String serviceId, LoadBalancerRequest request) throws IOException { # (应用名称,请求对象,传过来一个null) return this.execute(serviceId, (LoadBalancerRequest)request, (Object)null); } public T execute(String serviceId, LoadBalancerRequest request, Object hint) throws IOException { # 通过名称获取负载均衡对象 ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId); # 已经拿到经过负载均衡算法后要处理的具体的一个服务 Server server = this.getServer(loadBalancer, hint); if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } else { RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server)); return this.execute(serviceId, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request); } } } # 选择默认的default public class RibbonLoadBalancerClient implements LoadBalancerClient { protected Server getServer(ILoadBalancer loadBalancer, Object hint) { if (loadBalancer == null) { return null; } // Use 'default' on a null hint, or just pass it on? return loadBalancer.chooseServer(hint != null ? hint : "default"); } } ``` ## 3.Hystrix ### 3.1 仓壁模式 > 流程:请求 -> 带@HystrixCommand的方法 -> 默认所有方法共维护一个线程池(容量10) -> 微服务 - 情况:Hystrix默认线程池不适合用于生产环境,多方法维护一个线程池(容量10)。 - 解决:仓壁模式,为每个方法创建单独的线程池(线程隔离策略) - 使用:只要定义了threadPoolKey(唯一标识一个线程)就开启了仓壁模式 ### 3.2 工作流程 - 请求->开启时间窗 - 时间窗:调用->是否到大最小请求数-> - 否:没问题 - 是:是否到达错误数量的阈值 - 没到:正常运转 - 到了:熔断(开启活动窗口[自我修复机制]) - 活动窗口: - 熔断 -> 远程服务是否正常 - 是:重置断路器,恢复正常访问 - 否:继续熔断 ## 3.3 生产环境配置笔记 # 4.Fegin远程调用组件 > 在学习此组件前用的template方式存在硬编码问题,定义了URL到代码里面 ## 4.1 概述 - 背景:Fegin也是奈非开发的轻量级基于RESTFULL的HTTP服务客户端,是以java接口注解 的方式调用Http请求,而不用像java中通过封装HTTP请求报文的方式直接调用,Fegin广泛 应用在SpringCloud解决方案中。使用同Dubbo类似 - 使用:服务的消费者中创建一个接口,在接口上添加注解,代码就完成啦 - SpringCloud对Fegin进行了增强,使Fegin支持SpringMVC注解(OpenFeign) - 本质:封装了Http调用流程(面向接口编程) - 一个顶三:Fegin = RseTemplate+Ribbon+Hystrix(前提是项目中已经有Ribbon+Hystrix) ## 4.2 负载均衡的支持 - 默认超时时间1s,如果配置了自己自定义的或ribbon超时就以自己或ribbon为主,Feign有自己的超时 设置,如果配置Ribbon超时,就会以Ribbon为准 ## 4.3 熔断器的支持 - 开启豪猪哥后,Feign中方法被接管,一旦出现问题进入回退逻辑 fallback - 兜底逻辑,实现一个类集成Fegin接口 > 注:如果设置了Ribbon,Ribbon的设置就是Fegin的设置;若同时设置了Fegin(Ribbon)/Hystrix > 熔断的时候,根据这两的最小值来进行,处理时长超过**最短那个超时时间**就熔断了,进入回退降级逻辑 # 5.网关组件 - 介绍: 基于一组非阻塞的IO模型,目前取代了阻塞的Zuul(GateWay性能是Zuul的1.6倍) - 访问链路:请求->负载均衡->网关集群->微服务 - 作用: 反向代理,提供过滤,功能例如:鉴权,流控(削峰填谷),熔断,路劲重写,日志监控等 - 组成: - 路由:最基础的工作单元(ID+目标URL+断言+过滤器),断言为true才匹配该路由 - 断言:类似于一个判断,参考java8的断言接口,如:匹配请求头、请求参数等 - 过滤器:标准的spring webFilter,可在请求【前后】做对应操作 - 客户端->网关控制器映射(找到与请求匹配的路由,将其发送到getway web handler)-> 网关控制器(通过制定过滤器将请求发送到服务实际的执行业务逻辑,然后返回)-> 过滤器(pre[鉴权,参数校验,浏览限制,日志,协议转换],post[修改相应头,日志等]) -> 微服务 - 断言规则: - 时间断言 - cookie断言 - 请求头断言 - 请求主机断言 - 请求方式断言 - 请求方式断言 - 请求路径断言 - 请求参数断言 - 远程地址断言 - 动态路由配置 ```yml # uri: http://127.0.0.1:80 改为动态获取路由【同时负载均衡】 uri: lb://weiranyi-service-page ``` - 过滤器:实现默认外的功能 - 时机 - 之前 - 请求被路由之前调用,处于网关这【身份验证,日志记录等】 - 之后 - 路由到微服务后【HTTP Header的处理,将相应从微服务给客户端】 - 类型 - 单个路由上:GatewayFilter - 全局路由上:GlobalFilter - 网关高可用 - -Dserver.port=9528 - 网关的集群搭建后,负载均衡需要它前面的人来做,如:Nginx ```bash # nginx #配置多个GateWay实例 upstream gateway { server 127.0.0.1:9527; server 127.0.0.1:9528; } location / { proxy_pass http://gateway; } ``` # 6.spring cloud config 分布式配置中心 - spring cloud config是官方推出的,运行期间动态调整配置信息,配置发生变动后微服务自动更新 - 作用 - **集中管理** - 动态切换配置环境 - 运行期间动态调整,无须重启,微服务自动更新 - 服务端和客户端职责 - 服务端:提供配置文件存储,以接口形式将配置文件提供出去,通过@EnableConfigServer注解在springboot应用中嵌入 - 使用git,svn 的仓库哦存储配置文件,推荐的git仓库:GitHub,Gitee等 - 客户端:通过接口获取数据并初始化自己的应用 - 实战运用: - 创建配置文件:weiranyi-config # 7.spring cloud bus 消息总线 - 类似MQ消息队列,开启广播模式 # 8.nacos - 服务: - 保护阈值: - 一般流程:将健康的服务实例返回 - 高并发大流量情形,如果一个100台,90台异常,10台正常,但此时10台扛不住; - 为了防止雪崩效应,此时保护阈值就有用了 - 假设阈值0.5(50%),一半的服务不健康了,阈值被触发,此时健康不健康的服务都会被返回去扛流量 - 权重:用于负载均衡 - 下线:不再处理请求