06_Spring Cloud Netflix
2021-08-25 09:01:47 1 举报
AI智能生成
微服务框架的一些知识梳理
作者其他创作
大纲/内容
Feign
声明式服务调用
Hystrix
资源隔离、熔断、降级
资源隔离
资源隔离技术
线程池资源隔离
信号量
熔断
降级
设计原则
阻止任何一个依赖服务耗尽所有的资源
避免请求排队和积压,采用限流和fail fast来控制故障
提供fallback降级机制来应对故障
通过近实时的统计/监控/报警功能,来提高故障发现速度
通过近实时的属性和配置热修改功能,来提高故障处理和恢复的速度
执行流程以及内部原理
第一步,构建一个HystrixCommand或者HystrixObservableCommand
第二步,调用command的执行方法,发起对依赖服务的调用
第三步,检查是否开启请求缓存,如果开启请求缓存,request cache,而且调用的结果在缓存中存在,那么直接从缓存中返回结果
第四步,检查断路器的开关状态,如果断路器是打开状态,hystrix就不会执行这个command,而是直接去执行fallback降级逻辑
第五步,检查线程池/队列/semaphore是否已经满,是的话,hystrix就不会执行这个command,而是直接去执行fallback降级逻辑
第六步,执行command,如果执行时间超过timeout时长的话,会抛出一个TimeoutException,直接执行fallback降级逻辑
第七步,Hystrix会将每一个依赖服务的调用成功,失败,拒绝,超时,等事件,都会发送给circuit breaker断路器
断路器就会对调用成功/失败/拒绝/超时等事件的次数进行统计
短路器会根据这些统计次数来决定,是否要进行短路,如果打开断路器,那么在一段时间内就会直接短路
之后一段时间,会有一个半开半闭的状态,如果在之后第一次检查发现调用成功,就关闭断路器
Zuul
网关
API网关是什么
API 网关是一个服务器,是系统对外的唯一入口,API 网关封装系统内部架构,为每个客户端提供定制的 API
所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有非业务功能,当然,API 网关并不是微服务场景中必须的组件
不管有没有 API 网关,后端微服务都可以通过 API 很好地支持客户端的访问,但是这个只限于单体应用
为什么需要网关
请求路由
统一处理,把所有后台服务都需要做的通用的一些业务,都放到网关去处理,比如统一安全认证、限流、降级、异常处理、请求处理、超时处理
spring cloud 环境下 zuul 核心原理
第一步,用户通过浏览器直接访问网关服务,而不是直接访问后端服务
第二步,zuul 网关服务整合 eureka client,拉取注册表信息
第三步,zuul 网关服务整合 ribbon,基于 LoadBalancer 进行负载均衡
第四步,zuul 网关服务整合 hystrix,基于 hystrix 包裹实际的请求,进行服务的限流、熔断和降级
第五步,hystrix 发送实际的请求到后端服务
ZuulServlet核心工作原理
ZuulServlet 是整个zuul 的核心类,类似 Spring MVC 中的 DispatchServlet,起到前端控制器的作用,所有的请求都由它接管
第一步,执行 init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse)方法
方法具体是执行的 ZuulRunner 的 init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) 方法
ZuulRunner 类将请求和响应初始化到 RequestContext 中,并将 FilterProcessor 的调用包装为preRoute()、route()、postRoute() 和 error()
第二步,运行 preRoute() 方法
获取 pre 类型的过滤器,遍历执行
preFilter
ServletDetectionFilter
默认情况下,会将 RequestContext 中设置 isDispatcherServletRequest = true
Servlet30WrapperFilter
默认情况下,会将原生的 HttpServletRequest 对象用 Servlet30RequestWrapper 进行包裹,主要是为了解决之前版本的Bug
FormBodyWrapperFilter
默认情况下,不会执行,media type 是 APPLICATION_FORM_URLENCODED 或者 MULTIPART_FORM_DATA 才会执行
DebugFilter
默认情况下,不会执行,只有请求中传入 debug 的参数 设置 true 的时候才会执行,打开 debug 标识,后面运行的时候打印日志;
PreDecorationFilter
核心过滤器,主要是解析请求以及路由匹配
第三步,运行 route() 方法
获取 route 类型的过滤器,遍历执行
routeFilter
RibbonRoutingFilter
SimpleHostRoutingFilter
SendForwardFilter
第四步,运行 postRoute() 方法
获取 post 类型的过滤器,遍历执行
postFilter
LocationRewriteFilter
默认情况下,不会执行,只有当需要重定向的时候,才会真正执行,将请求进行重定向
SendResponseFilter
默认情况下,会执行,将结果输出到浏览器
第五步,所有过滤器执行报错都会执行 errorFilter 的 error() 方法
面试精选
聊聊工作原理
为什么需要微服务
没有微服务,单块复杂系统存在哪些问题
代码重复问题,代码复用性
多人协作效率问题
扩容问题,只能对应用进行扩容,不能对功能点进行扩容,可伸缩性较差
可用性问题,任何一个模块的问题都会导致系统宕机
微服务架构引入新的技术问题
分布式事务
分布式锁
分布式会话
分布式接口幂等性
......
Eureka
服务注册与发现
eureka server 启动流程
第一步,设置数据中心和运行环境
第二步,加载 eureka-server.properties 配置文件,构造 EurekaServerConfig
第三步,加载 eureka-client.properties 配置文件,构造 EurekaInstanceConfig
第四步,将自己作为服务实例,基于 EurekaInstanceConfig 构造 InstanceInfo 服务实例信息
第五步,基于 EurekaInstanceConfig 和 InstanceInfo 构造 ApplicationInfoManager 应用信息管理器
第六步,加载 eureka-client.properties 配置文件,构造 EurekaClientConfig
第七步,将自己作为 eureka client,基于 ApplicationInfoManager 和 EurekaClientConfig 构造 DiscoveryClient
第八步,构造可以感知 eureka server 集群的注册表 PeerAwareInstanceRegistry
第九步,构造 eureka server 集群信息 PeerEurekaNodes
第十步,基于 EurekaServerConfig、PeerAwareInstanceRegistry、EurekaClientConfig、ServerCodecs、ApplicationInfoManager 构造 eureka server 上下文 EurekaServerContext
第十一步,从相邻节点拷贝注册表信息
第十二步,注册相关监控
eureka client 启动流程
第一步,加载 eureka-client.properties 文件,读取服务实例配置构造 EurekaClientConfig
第二步,基于 ApplicationInfoManager 和 EurekaClientConfig 构造 EurekaClient
第三步,保存 InstanceInfo(服务实例信息)、ApplicationInfoManager (服务实例信息管理器)、EurekaClientConfig (服务实例客户端配置)以及网络通信组件配置
第四步,如果配置需要抓取注册表和注册服务实例的话,会创建两个 ThresholdLevelsMetric,一些监控相关的组件,这里猜测一下可能是负责监控注册表抓取和注册行为
第五步,初始化三个线程池,一个支持调度的线程池;一个支持心跳的线程池;一个支持缓存刷新的线程池
第六步,初始化网络通信组件 EurekaTransport
第七步,尝试抓取注册表,如果抓取失败,从备用的注册表去抓取
第八步,执行初始化调度任务的逻辑
如果配置需要抓取注册表的话,调度线程池会调度出来一个 cacheRefresh 线程,每隔一定的间隔时间(默认30s)定时抓取注册表
如果配置需要向 eureka server 注册的话,调度线程池会调度出来一个 heartbeat 线程,每个一定的间隔时间(30s)定时发送心跳
如果配置需要向 eureka server 注册的话,创建服务实例副本传播器,作为一个定时任务进行调度
如果配置需要向 eureka server 注册的话,并且配置监听的话,会注册一个监听器,监听服务状态的变化
服务注册
服务正常启动,服务通过 eureka client组件将服务实例信息注册到 erueka server 内部的注册表
刷新读写缓存 readWriteCacheMap
更新注册表和 recentlyChangedQueue
注册表抓取
服务启动,服务通过 eureka client 组件到 eureka server 全量抓取服务注册表信息,缓存到服务的本地注册表,缓存起来
eureka server 服务注册表的多级缓存机制
内部维护只读缓存 readOnlyCacheMap 和读写缓存 readWriteCacheMap
先从只读缓存中获取,如果没有,就从读写缓存中获取,如果读写缓存中还是没有,就从注册表中获取,并且放入读写缓存和只读缓存
多级缓存的过期机制
主动过期
当有新的服务实例发送注册、下线、故障的时候,会去刷新读写缓存 readWriteCacheMap,让其主动过期
定时过期
readWriteCacheMap 构建的时候,指定了自动过期的时间,默认值就是180秒
往 readWriteCacheMap 中放入数据过后,自动会等180秒,将这个数据过期
被动过期
默认是每隔30秒,执行一个定时调度的线程任务
比对 readOnlyCacheMap 和 readWriteCacheMap 中的数据,数据不一致,就将 readWriteCacheMap 中的数据放到 readOnlyCacheMap
也就是说,如果 readWriteCacheMap 缓存的中的数据过期之后,会在30秒之后,同步到 readOnlyCacheMap
多级缓存机制的意义
服务通过 eureka client 组件每隔30秒定时增量抓取注册表
逻辑和全量抓取注册表的方法一样,都是利用多级缓存机制,区别在于拉取的数据不一样
从 ConcurrentLinkedQueue<RecentlyChangedItem> recentlyChangedQueue 中获取数据,里面放入最近三分钟内发生变化的服务实例信息
先从只读缓存中获取,如果没有,就从读写缓存中获取,如果还是没有,就从 recentlyChangedQueue 中获取,并且放入读写缓存和只读缓存
初始化注册表的时候,开启定时任务,每隔30秒检查一下 recentlyChangedQueue 中的数据是否已经超过三分钟,就剔除数据
读写缓存中的数据,每隔180s就会定时过期,每隔30s会将读写缓存中的数据同步到只读缓存
返回增量注册表信息的同时,还会携带本地注册表的哈希值
服务通过 eureka client 组件,增量拉取到最近三分钟变化的注册表信息,和本地缓存的注册表信息进行合并,计算出来一个哈希值,与增量拉取时返回的哈希值进行比较,如果不一致,就全量拉取注册表信息
服务续约
服务每隔30秒,通过eureka client组件发送一次心跳请求到eureka server
根据请求传过来的服务实例信息从本地注册表信息找到对应的服务实例信息,然后更新它的 lastUpdateTimestamp 时间戳字段
这个也成为后面服务实例自动故障下线的核心
服务下线
服务实例正常下线的时候,会调用 EurekaClient 的 shutdown() 方法
刷新读写缓存 readWriteCacheMap
更新注册表和 recentlyChangedQueue
根据请求传过来的服务实例信息从本地注册表信息找到对应的服务实例信息,然后更新它的 evictionTimestamp 时间戳字段
服务实例的自动故障感知以及服务实例自动摘除
原理
每隔 60s 定时检测,如果一段时间内没有接收到某个服务的心跳,就将这个服务实例给摘除,认为这个服务实例已经宕机
补偿时间:由于延迟调度的原因,比预期执行时间晚执行的时间
遍历所有的实例信息,比较当前时间是否大于(上次心跳时间 + 调度执行时间 + 补偿时间),如果大于,可以认为服务已经故障
服务实例摘取机制
分批摘取
不会一次性将所有故障的服务实例都摘除,而是每次最多将注册表中 15% 的服务实例给摘除掉,剩下的故障实例等下次调度任务执行
比如,现在服务实例有 20 个,故障实例有 6 个,那么只能一次性摘除 3 个故障实例
随机摘取
真正进行服务实例摘除的时候,会从故障实例中随机选择进行摘除,现在要摘除 3 个实例,会从 6 个故障实例中随机选择 3 个进行摘除
刷新读写缓存 readWriteCacheMap
更新注册表和 recentlyChangedQueue
根据请求传过来的服务实例信息从本地注册表信息找到对应的服务实例信息,然后更新它的 evictionTimestamp 时间戳字段
自我保护模式
Ribbon
客户端负载均衡
客户端行为,将负载均衡的策略绑定到客户端上,客户端会维护一份服务提供者列表,通过客户端负载均衡策略分发到不同的服务提供者
负载均衡规则(IRule)
负载均衡器基于一个 IRule 接口指定的负载均衡规则,来从服务器列表里获取每次要请求的服务器
内置负载均衡策略
RoundRobinRule:系统内置的默认负载均衡规则,直接轮询
RetryRule:RoundRobinRule 的基础上添加重试机制,即在指定的重试时间内,反复使用线性轮询策略来选择可用实例
WeightedResponseTimeRule:RoundRobinRule 的扩展,响应速度越快的实例选择权重越大,越容易被选择
AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例
ZoneAwareLoadBalancer:采用双重过滤,同时过滤不是同一区域的实例和故障实例,选择并发较小的实例
BestAvailableRule:选择并发较小的实例
RandomRule:随机选择一个实例
Ribbon 的使用非常简单,只需要在 RestTemplate 上加上 @LoadBalanced 注解,之后通过 RestTemplate 实例就可以负载均衡的访问服务
工作原理
第一,通过给 RestTemplate 标注 @LoadBalanced注解,后面会对这些 RestTemplate 进行定制化处理,简单来说就是添加一个拦截器
第二,后面所有的请求都会执行拦截器的执行方法
第三,简单理解就是基于负载均衡器,内置的负载均衡策略从注册表中选择一个服务,进行请求处理
源码阅读的意义
源码实际上是码农技术水平的分水岭
技术功底,阅读一个技术源码之后,技术功底会得到大幅度的提升
把控全场,阅读一个技术源码之后,可以在公司里把控全场
架构设计能力大幅提升
职场竞争力大幅提升
收藏
收藏
0 条评论
下一页