熟练掌握SpringCloud(Eureka、Fegin、Hystrix、Gateway、Config)等分布式服务框架,了解阿里系组件(Nacos、Sentinel、Seata)。
eureka 注册中心
nacos 注册中心
nacos = eureka + config + bus
nacos的注册表
nacos怎么实现动态配置刷新的
- 配置动态刷新主要依靠以下:
- @Scope
- @RefreshScope
- RefreshScope
- GenericScope
- Scope
- ContextRefresher
- @Scope
- @RefreshScope可以实现动态刷新完全依赖于@Scope注解
- @Scope代表了bean的作用域,其中主要属性有value(属性介绍如下),proxyMode。proxyMode就是实现@RefreshScope的本质。在@RefreshScope中,proxyMode的默认是设置为ScopedProxyMode.TARGET_CLASS,使用@RefreshScope注解时会给当前创建的bean生成一个代理对象,会通过代理对象来访问,每次访问都会创建一个新的对象。
- singleton 表示该bean是单例的。(默认)
- prototype 表示该bean是多例的,即每次使用该bean时都会新建一个对象。
- request 在一次http请求中,一个bean对应一个实例。
- session 在一个httpSession中,一个bean对应一个实例
- 在scope接口中有一个Object get(String name,ObjectFactory objectFactory)方法,这个方法可以创建一个新的bean,也是就说@RefreshScope在调用刷新的时候会使用此方法来创建一个新的对象,这样就可以通过string的装配机制将属性重新注入了,也就实现了动态刷新。
- 如何处理老对象,怎么创建新对象?
- 主要依赖几个重要的类,其中 RefreshScope extends GenericScope,GenericScope impiements Scope.
- GenericScope实现了Scope中的Object get方法,在GenericScope里面包装了一个内部类BeanLifecycleWrapperCache来对使用@RefreshScope注解创建的对象进行缓存,使其在不刷新的时候获取的是同一个对象。(可以把BeanLifecycleWrapperCache想象为一个Map)
- 知道了对象是缓存,所以进行动态刷新时,只需要清除缓存,重新创建就好了。
- 实现流程
- 需要动态刷新的类使用注解@RefreshScope
- @RefreshScope标注了@Scope注解,并默认了ScopeProxyMode.TARGET_CLASS属性,此属性的功能就是会给当前bean创建一个代理对象,在每次调用时都用他来调用GenericScope类中的get方法来获取对象。
- 如果属性发生变更会调用ContextRefresher refresh() -> RefreshScope refreshAll()进行缓存清理方法调用,并发送刷新事件通知 -> GenericScope 的清理方法destory()实现清理缓存
- 在下一次调用对象时,会调用GenericScope的get方法 创建一个新的对象,并存入缓存中,此时新对象因为Spring的装配机制就是新的属性了
nacos实例注册的过程
- 服务注册主要是通过NamingService.registerInstance方法来实现的,主要实现:
- 将serviceName和GroupName进行字符串的拼接 得到GroupServiceName。
- 创建一个心跳任务。
- 调用registerService方法注册服务实例。方法内部实现:
- 创建一个Map
- 将namespaceId、serviceName、groupName、clusterName、IP端口等属性put到map
- 调用reqApi(UtilAndComs.nacosUrlInstance,params,HttpMethod.POST)方法,reqApi最终的重载方法主要实现
- 其中新增了两个参数,body(默认为空),servers(是我们配置的nacos服务端所在服务的ip和端口的集合)
- 注册之前先从servers中随机获取一个进行调用(如果失败则再次获取一个进行重试)。
- 假设此时随机了一个服务地址,则会进入callserver方法
- callserver方法实现比较简单,主要是将请求地址和请求路径名(UtilAndComs.nacosUrlInstance)进行拼接,然后发送http请求进行服务注册,然后接受客户端的响应。
- 至此,客户端的服务注册就完成了。
nacos心跳机制
- 进入注册实例方法NamingService.registerInstance(),方法中会给实例添加一个心跳任务,分为两步
- BeatInfo beatInfo = beatReactor.buildBeatInfo(groupServiceName, instance);
- 主要是构建出beatInfo所需参数
- addBeatInfo(groupedServicename, beantInfo);
- BeatInfo beatInfo = beatReactor.buildBeatInfo(groupServiceName, instance);
-
-
- 主要执行schedule方法,通过beatInfo构建一个beatTask,然后扔到调度线程池,等待一定时间后执行(默认5s),即BeatTask就是心跳机制执行的逻辑。 进入BeatTask方法。BeatTask实现了Runable接口,主要看run方法的逻辑
-
-
-
-
- 通过serverProxy发送一个http请求到服务端,服务端进行响应,这个过程完成了跟服务器的心跳,接下来解析服务端的响应数据。根据服务端返回的不同状态码进行判断,进行不同的操作。其中有一个状态码判断 (code == NamingResponseCode.RESOURCE_NOT_FOUND) 标示资源未找到,然后对其进行处理
- 其重要作用就是重新向服务端进行服务注册,为什么这样做?
- 正常情况下客户端发送心跳,服务实例应该存在于服务端,但是如果出现网络抖动等情况,客户端无法给服务端发送心跳,长时间服务端没有收到客户端的心跳,此时服务端判定这个服务实例出问题了,这样服务端就会主动从服务注册表中剔除该服务实例,该服务实例就不存在服务端了。当客户端网络恢复了,那么此时会恢复跟服务端的心跳机制,就会出现服务找不到的现象,这种情况下主要重新注册一下就行了。
- 重新构建一个beatTask,然后重新扔进调度线程池中,等待nextTime之后执行。但有一种情况就是当发送心跳是,服务端可能会相应给客户端一个下一次发送心跳的间隔时间,赋值给nextTime,通过这种形式,就实现了自动发送心跳的功能,当本次发送心跳后,继续构建下一次心跳任务,并扔进调度线程池之后执行,反复如此,就实现了定时发送心跳的机制。
-
-
聊一聊nacos是如何进行服务注册的_@zzyang的博客-CSDN博客_nacos怎么实现服务注册
nacos的长轮训
ribbon 负载均衡
feign / openfeign
feign默认的通信方式为http,可以通过配置修改为okHttp或httpClient 设置enable: true ,改成okhttp的原因是默认的urlHttpConnection没有连接池,这样频繁调用会增加服务器的开销
使用:
//准备远程调用的接口
@FeignClient(name = "client-name") //配置成注册中心的应用名
feign和openfeign的区别
底层都是内置了Ribbon,去调用注册中心的服务。
Feign是Netflix公司写的,是SpringCloud组件中的一个轻量级RESTful的HTTP服务客户端,是SpringCloud中的第一代负载均衡客户端。
OpenFeign是SpringCloud自己研发的,在Feign的基础上支持了Spring MVC的注解,如@RequesMapping等等。是SpringCloud中的第二代负载均衡客户端。
Feign本身不支持Spring MVC的注解,使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务
OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
gateway 网关
- 主要作用:
- 权限安全、监控,过滤(限流,容错hystrix) ,谓词校验
- 路由配置
routes: #配置网关中的一个完成路由,包括命名id,地址,predicates集合,过滤器集合
- id:frist #路由自定义id,唯一即可,命名符合java中的变量命名规则
uri:lb://app-service #当前路由定义对应的微服务转发地址, lb代表 loadbalance 负载均衡
predicates:
- Path=/api/** # 自定义断言
filters:#配置过滤器
- StripPrefix=1 #定义一个过滤器。过滤转发地址前缀,过滤1节
路由流程:
如请求地址 - http://localhost:9999/api/getUserInfo?name=admin&age=29
对应的断言规则是 /api/** ,符合断言规则
对应的uri是 lb://app-service ,转换成http://app-service 并且包含负载均衡
转发地址是 http://app-service/api/getUserInfo?name=admin&age=29
过滤器过滤转发地址前缀1节,即删除api 得到 http://app-service/getUserInfo?name=admin&age=29
zuul 和 spring cloud gateway 的对比
过滤器和网关的对比
网关与 nginx 区别
Gateway 的组成
网关路由 openfeign的http调用
config 配置中心
hystrix 熔断器
支持线程池隔离,信号量隔离,基于失败比率熔断,与sentinel比较不支持限流,没有完善的控制台,适配的框架没有sentinel多。
配置@HystrixCommand
线程池隔离
- 可以把不同的方法配置使用指定的线程池,多个线程池中的线程互不影响,可以避免个别接口的请求激增,导致线程被全部占用,没有线程处理其他接口的调用,导致程序瘫痪。
- 相关配置:
- groupKey = "order-service-listPool" 服务名称,相同名称使用同一个线程池
- commandKey = "selectProductList" 接口名称,默认为方法名
- commandProperties = { @HystrixProperty(name = "thread.timeoutInMilliseconds")} 设置超时时间
- threadPoolProperties = {
- coreSize 线程池大小
- maxQueueSize 等待队列最大值, 默认 -1
- keepAliveTimeMinutes 线程存活时间 默认1分钟
- queueSizeRejectionThreshold 超出等待时间拒绝策略 }
- fallbackMethed 降级方法
信号量隔离
- 通过信号量隔离可以限制同一时刻的最大并发量
- timeoutInMilliseconds 超时时间
- EXECUTION_ISOLATION_STRATEGY ,value = "SEMAPHORE" 配置信号量隔离
- SEMAPHORE_MAX_CONCURRER,value = "6" 信号量最大并发
关于超时时间
对于信号量超时模式,如果发生超时,Hystrix任务并不会结束,任务结束还是得依赖于run方法执行完毕。
对于线程池超时模式,如果发生超时,Hystrix任务可以结束,不依赖于run方法执行情况,但是run方法可能还会在执行,这依赖与run方法中的代码逻辑。
熔断
通过配置@HystrixCommand注解,设置commandproperties属性 @HystrixProperty
配置10秒内请求10次错误率大于50%调用fallback方法,还可以配置熔断后多少秒进行重试请求,默认为5秒
sentinel 限流控制,熔断降级
限流的方式有两种
- 通过配置FlowRule 指定关键字限制QPS,代码中配合使用SphU.entry(KEY) entry.exit(),如单独使用会导致调用链记录异常
- 通过注解配置异常处理及限流处理
- 注册Bean --- SentinelResourceAspect
- @SentinelResource(value = KEY, blockHandler = "限流或降级方法", fallback = "异常方法")
可以通过sentinel控制台控制接口的限流降级
Sentinel的限流怎么实现的 原理是滑动窗口算法
seata的在代码中的使用
- 引用seata包
- 在需要开始事务的方法上使用注解@GlobalTransactional
-----------------------spring boot------------------------
spring boot的原理
与spring相比,省去了繁琐的配置,可以再spring.io 或者 idea上一键生成spring boot项目,非常便利。
spring boot运行的三大原理:1.springboot的自动装配原理,2.springboot的初始化构造流程,3.springboot run方法运行流程。
SpringBoot原理概述_Leo木的博客-CSDN博客_springboot原理
自动装配原理
鸡肋版本:
- springboot自动装配主要依赖@springbootapplication 中的 @EnableAutoConfigration ,
- @EnableAutoConfigration接口中标注了@Import({AutoConfigrationImportSelcetor.class})
- @import注解 中可以直接导入一个类, 如 User.class
- 也可以导入实现了ImportSelector接口的实现类,根据重写selectImports方法得到String[]数组返回值,数组中为类的全限定路径
- AutoConfigrationImportSelcetor实现了deferredImportSelector接口
- deferredImportSelcetor接口继承了IMportSelector
- 在importSelector中有一个selectImports方法,该方法返回一个String数组,接口返回值为类的全路径名称,返回的这些类会被注册到ioc容器中
- 在AutoConfigImportSelector中重写了selectImports方法,方法内部最终会调用StringFactoriesLoader.loadFactoryNames 去读取META/spring.factories文件
- 文件内容为key=value格式,value可以为多个中间用逗号分隔
- 去spring.factories文件获取时的key是springboot中定义好的 EnableAutoConfigration.class
- 获取对应的类路径后进行ioc的注入,每个bean会根据自己配置的条件注解(conditionalOn.....)判断是否进行注入
旗舰版:
- 当启动SpringBootApplication时,会先创建SpringApplication对象,执行对象的构造方法,在构造方法中会进行某些参数的初始化工作,其中主要是判断应用程序类型,设置初始化器和监听器,然后扫描spring.factories中的bean添加到缓存中,方便后续执行调用。
- SpringApplication对象创建完成后,开始执行run()方法,方法中包含了context上下文的创建,banner打印,还有两个核心的方法,第一个是prepareContext,第二个是refreshContext。这两个方法完成springboot自动装配的核心功能。
- 在prepareContext方法中,主要完成了对上下文的初始化操作,包括属性值的设置,比如设置环境对象,在prepareContext中有一个重要的方法load,load主要完成一件事儿,就是把启动类当做一个beanDefinition注册到breanDefinitionRegistry中,方便后续在进行BeanFactoryPostProcessor调用执行的时候,找到对应的主类,来完成@SpringBootApplication,@EnableAutoConfiguration等注解的解析工作。
- 在refreshContext方法中会进行整个容器的刷新,会调用spring中的refresh方法,refresh方法是springioc容器启动的核心,在自动装备过程会中,会调用refresh中的invokeBeanFactoryPostProcessor方法,在此方法中主要对ConfigurationClassPostProcessor类的处理,这个类是BeanFactoryPostProcessor的子类也是BeanDefinitionRegistryPostProcessor的子类。在调用的时候先调用BeanDefinitionRegistryPostProcessor中的postProcessorBeanDefinitionRegistry方法,然后调用postprocessorBeanFactory方法,在执行postProcessorBeanDefinitionRegistry的时候会解析处理各种注解,其中最主要的就是@Import注解。
- 在解析@import注解的时候,会有一个getImports方法,从主类开始递归解析注解,把所有包含@import的注解都解析到,然后再processImport方法中对import的类进行分析,此处主要识别的是AutoConfigurationImportSelect归属于ImportSelect的子类,在后续过程中会调用deferredImportSelectorHandler中的process方法,来完成EnableAutoConfiguration的加载。
Springboot集成redis-cache
- @Cacheable:查询数据库时先判断缓存中是否有数据,如果有则直接查缓存,如果缓存没有则执行方法查询数据库,然后将结果写入缓存。
- @CachePut:更新缓存数据。
- @CacheEvict:删除缓存数据。