聊聊Eureka的存储机制
既然是服务注册中心,必然要存储服务的信息,我们知道ZK是将服务信息保存在树形节点上。而下面是Eureka的数据存储结构:
Eureka的数据存储分了两层:数据存储层和缓存层。Eureka Client在拉取服务信息时,先从缓存层获取(相当于Redis),如果获取不到,先把数据存储层的数据加载到缓存中(相当于Mysql),再从缓存中获取。值得注意的是,数据存储层的数据结构是服务信息,而缓存中保存的是经过处理加工过的、可以直接传输到Eureka Client的数据结构。
Eureka实现了二级缓存来保存即将要对外传输的服务信息,数据结构完全相同。
一级缓存:ConcurrentHashMap<Key,Value> readOnlyCacheMap,本质上是HashMap,无过期时间,保存服务信息的对外输出数据结构。
二级缓存:Loading<Key,Value> readWriteCacheMap,本质上是guava的缓存,包含失效机制,保存服务信息的对外输出数据结构。
Eureka的服务续约机制
服务注册后,要定时(默认30S,可自己配置)向注册中心发送续约请求,告诉注册中心“我还活着”。
1、更新服务对象的最近续约时间,即Lease对象的lastUpdateTimestamp;
2、同步服务信息,将此事件同步至其他的Eureka Server节点。
Eureka服务注销机制
服务正常停止之前会向注册中心发送注销请求,告诉注册中心“我要下线了”。
注册中心服务接收到cancel请求后:
1、删除服务信息,将服务信息从registry中删除;
2、更新队列,将此事件添加到更新队列中,供Eureka Client增量同步服务信息使用。
3、清空二级缓存,即readWriteCacheMap,用于保证数据的一致性。
4、更新阈值,供剔除服务使用。
5、同步服务信息,将此事件同步至其他的Eureka Server节点。
Eureka自我保护
Eureka自我保护机制是为了防止误杀服务而提供的一个机制。Eureka的自我保护机制“谦虚”的认为如果大量服务都续约失败,则认为是自己出问题了(如自己断网了),也就不剔除了;反之,则是Eureka Client的问题,需要进行剔除。而自我保护阈值是区分Eureka Client还是Eureka Server出问题的临界值:如果超出阈值就表示大量服务可用,少量服务不可用,则判定是Eureka Client出了问题。如果未超出阈值就表示大量服务不可用,则判定是Eureka Server出了问题。
聊聊feign是啥
Feign是一种声明式、模板化的HTTP客户端(仅在Application Client中使用)。声明式调用是指,就像调用本地方法一样调用远程方法,无需感知操作远程http请求。 Spring Cloud的声明式调用, 可以做到使用 HTTP请求远程服务时能就像调用本地方法一样的体验,开发者完全感知不到这是远程方法,更感知不到这是个HTTP请求。Feign的应用,让Spring Cloud微服务调用像Dubbo一样,Application Client直接通过接口方法调用Application Service,而不需要通过常规的RestTemplate构造请求再解析返回数据。它解决了让开发者调用远程接口就跟调用本地方法一样,无需关注与远程的交互细节,更无需关注分布式环境开发。
聊聊Feign的原理呗
我们来想想平时我们使用feign的时候,会是一个怎么样的流程
- 添加了 Spring Cloud OpenFeign 的依赖
- 在 SpringBoot 启动类上添加了注解 @EnableFeignCleints
- 按照 Feign 的规则定义接口 DemoService, 添加@FeignClient 注解
- 在需要使用 Feign 接口 DemoService 的地方, 直接利用@Autowire 进行注入
- 使用接口完成对服务端的调用
那我们基于这些步骤来分析分析,本文并不会说非常深入去看每一行的源码
- SpringBoot 应用启动时, 由针对 @EnableFeignClient 这一注解的处理逻辑触发程序扫描 classPath中所有被@FeignClient 注解的类, 这里以 XiaoLiuLiuService 为例, 将这些类解析为 BeanDefinition 注册到 Spring 容器中
- Sping 容器在为某些用的 Feign 接口的 Bean 注入 XiaoLiuLiuService 时, Spring 会尝试从容器中查找 XiaoLiuLiuService 的实现类
- 由于我们从来没有编写过 XiaoLiuLiuService 的实现类, 上面步骤获取到的 XiaoLiuLiuService 的实现类必然是 feign 框架通过扩展 spring 的 Bean 处理逻辑, 为 XiaoLiuLiuService 创建一个动态接口代理对象, 这里我们将其称为 XiaoLiuLiuServiceProxy 注册到spring 容器中。
- Spring 最终在使用到 XiaoLiuLiuService 的 Bean 中注入了 XiaoLiuLiuServiceProxy 这一实例。
- 当业务请求真实发生时, 对于 XiaoLiuLiuService 的调用被统一转发到了由 Feign 框架实现的 InvocationHandler 中, InvocationHandler 负责将接口中的入参转换为 HTTP 的形式, 发到服务端, 最后再解析 HTTP 响应, 将结果转换为 Java 对象, 予以返回。
其实就是基于aop代理和面向切面编程,把那些重复的东西,帮我们封装了起来,然后再结合一起其他的组件如负载均衡Ribbon等。
Hystrix是什么
在分布式系统中,每个服务都可能会调用很多其他服务,被调用的那些服务就是依赖服务,有的时候某些依赖服务出现故障也是很正常的。 Hystrix 可以让我们在分布式系统中对服务间的调用进行控制,加入一些调用延迟或者依赖故障的容错机制。 Hystrix 通过将依赖服务进行资源隔离,进而阻止某个依赖服务出现故障时在整个系统所有的依赖服务调用中进行蔓延;同时Hystrix 还提供故障时的 fallback 降级机制。
总而言之,Hystrix 通过这些方法帮助我们提升分布式系统的可用性和稳定性。
hystrix实现原理
hystrix语义为“豪猪”,具有自我保护的能力。hystrix的出现即为解决雪崩效应,它通过四个方面的机制来解决这个问题
- 隔离(线程池隔离和信号量隔离):限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用。
- 优雅的降级机制:超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。
- 融断:当失败率达到阀值自动触发降级(如因网络故障/超时造成的失败率高),熔断器触发的快速失败会进行快速恢复。
- 缓存:提供了请求缓存、请求合并实现。
- 支持实时监控、报警、控制(修改配置)
聊聊hystrix的隔离机制
- 线程池隔离模式:使用一个线程池来存储当前的请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求堆积入线程池队列。这种方式需要为每个依赖的服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)
- 信号量隔离模式:使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃改类型的新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)
聊聊hystrix的融断机制 和降级
熔断器模式定义了熔断器开关相互转换的逻辑。
- 服务的健康状况 = 请求失败数 / 请求总数。熔断器开关由关闭到打开的状态转换是通过当前服务健康状况和设定阈值比较决定的。
- 当熔断器开关关闭时,请求被允许通过熔断器。 如果当前健康状况高于设定阈值,开关继续保持关闭。如果当前健康状况低于设定阈值,开关则切换为打开状态。当熔断器开关打开时,请求被禁止通过。当熔断器开关处于打开状态,经过一段时间后,熔断器会自动进入半开状态,这时熔断器只允许一个请求通过。当该请求调用成功时,熔断器恢复到关闭状态。若该请求失败,熔断器继续保持打开状态,接下来的请求被禁止通过。
- 熔断器的开关能保证服务调用者在调用异常服务时,快速返回结果,避免大量的同步等待,并且熔断器能在一段时间后继续侦测请求执行结果,提供恢复服务调用的可能。
降级需要对下层依赖的业务分级,把产生故障的丢了,换一个轻量级的方案,是一种退而求其次的方法,说白了就是我们代码中经常用到的fallback,比如说直接返回一个静态的常量之类的。
什么是网关
网关是整个微服务API请求的入口,负责拦截所有请求,分发到服务上去。可以实现日志拦截、权限控制、解决跨域问题、限流、熔断、负载均衡,隐藏服务端的ip,黑名单与白名单拦截、授权等,常用的网关有zuul(netflix的,但是已经停更了)和spring cloud gateway (springcloudalibaba)。这里主要讲springcloud gateway,springcloud gateway是一个全新的项目,其基于spring5.0 以及springboot2.0和项目Reactor等技术开发的网关,其主要的目的是为微服务架构提供一种简单有效的API路由管理方式.
综上:一般情况下,网关一般都会提供请求转发、安全认证(身份/权限认证)、流量控制、负载均衡、容灾、日志、监控这些功能。
聊聊Spring Cloud Gateway的大致流程
- 路由的配置转换为routeDefinition
- 获取请求对应的路由规则, 将RouteDefinition转换为Route
- 执行Predicate判断是否符合路由, 以及执行相关的过滤(全局过滤器以及路由过滤器)
- 负载均衡过滤器负责将请求中的serviceId转换为具体的服务实例Ip
其实网关其实还有很多说的,因为网关企业级的网关分类比较多,比如我们的对外网关 对内网关,对合作伙伴的网关等
网关的设计方案
- 基于Nginx+Lua+ OpenResty的方案,可以看到Kong,orange都是基于这个方案
- 基于Netty、非阻塞IO模型。 通过网上搜索可以看到国内的宜人贷等一些公司是基于这种方案,是一种成熟的方案。
- 基于Node.js的方案。 这种方案是应用了Node.js天生的非阻塞的特性。
- 基于java Servlet的方案。 zuul基于的就是这种方案,这种方案的效率不高,这也是zuul总是被诟病的原因。
最后问一个问题,mysql分库分表下的数据迁移问题
问题场景:就是比如说我们一开始设计架构的时候,我们并不知道这个项目能火,但是突然老板拿到了融资,然后数据量起来,原来的架构抗不住了,这个时候需要分库分表了,你怎么去迁移,怎么保证迁移之后的数据一致性
停机部署法
大致思路就是,挂一个公告,半夜停机升级,然后半夜把服务停了,跑数据迁移程序,进行数据迁移。 步骤如下:
- 出一个公告,比如“今晚00:00~6:00进行停机维护,暂停服务”
- 写一个迁移程序,读db-old数据库,通过中间件写入新库
- 然后测试一下数据的一致性
大家不要觉得这种方法low,我其实一直觉得这种方法可靠性很强。而且我相信大部分公司一定不是什么很牛逼的互联网公司,如果你们的产品凌晨1点的用户活跃数还有超过1000的,你们握个爪!毕竟不是所有人都在什么电商公司的,大部分产品半夜都没啥流量。所以此方案,并非没有可取之处。 但是此方案有一个缺点,累!不止身体累,心也累!你想想看,本来定六点结束,你五点把数据库迁移好,但是不知怎么滴,程序切新库就是有点问题。于是,眼瞅着天就要亮了,赶紧把数据库切回老库。第二个晚上继续这么干,简直是身心俱疲。 ps:这里教大家一些技巧啊,如果你真的没做过分库分表,又想吹一波,涨一下工资,建议答这个方案。因为这个方案比较low,low到没什么东西可以深挖的,所以答这个方案,比较靠谱。
双写部署法
- 首先我们用canal去监听我们需要分库分表的那个表,就是上线之后的那些事务操作,然后把它放到队列里面,存起来,先不消费。
- 启动一个程序把旧数据同步到分库分表的数据库,这里有一个问题怎么区分新旧数据,就是当这个项目启动的时候,算出最大的id,这个之前的就是老数据了,或者是按更新时间排序,再这个时间之前的就是老数据,之后的就是新数据了。
- 最后把迁移数据下线,再去消费队列,完成数据的迁移
- 测试验证数据是否正常
结束
下面我们来看看分布式的理论和Zk,对于分布式系统开发还是需要明白的。