微服务框架是将某个应用程序开发划分为许多独立小型服务,实现敏捷开发和部署,这些服务一般围绕业务规则进行构建,可以用不同的语言开发,使用不同的数据存储,最终使得每个服务运行在自己的行程中。并且它们之间采用轻量级通信机制进行通信。
微服务的特点:
缺点:
CP(高可用)和AP:分区容错是必须保证的,当发生网络分区的时候,如果要继续服务,那么强一致性和可用性只能二选一。
BASE是 Basically Available(基本可用)、 Soft state(状态)和 Eventually consistent(最终一致性)
BASE理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于CAP
定理逐步演化而来的。BASE理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点
采用适当的方式来使系统达到最终一致性。
UUID:当前日期和时间(时间戳) + 时钟序列(计数器)+ 全局唯一的IEEE机器识别号
优点:代码简单,性能好(本地生成,没有网络消耗),保证唯一(相对而言,重复概率极低可以忽略)
缺点:
- 生成的ID都是无序的字符串,而且不是全数字
- UUID长度过长,不适用于存储,耗费数据库性能
- UUID无一定业务含义,可读性差
- 有信息安全问题,有可能泄露mac地址
数据库自增模式
单机模式:
优点:实现简单、成本低、ID是自增的数字、有一定的可读性
缺点:
- 强依赖DB,存在单点问题,如果数据库宕机,则业务不可用
- DB生成ID性能有限,单点数据压力大,不适合高并发场景
- 信息安全问题:比如暴露订单量,有可能查到别人的订单
数据库高可用:多主模式做负载,基于序列的起始值和步长设置,不同的初始值,相同的步长,步长大于节点数
优点:解决了ID生成的单点问题,同时平衡了负载
缺点:
系统扩容困难:系统定义好步长之后,增加机器之后调整步长困难
数据库压力大:每次获取一个ID都必频读写一次数据库
Leaf- segmen
采用每次获取一个ID区间段的方式来解决,区间段用完之后再去数据库获取新的号段,这样一来可以大大减轻数据库的压力
优点:
扩张灵活,性能强能够撑起大部分业务场景
ID号码是趋势递增的,满足数据库存储和查询性能要求
可用性高,即使ID生成服务器不可用,也能够使得业务在短时间内可用,为排查问题争取时间。
缺点:
可能存在多个节点同时请求ID区间的情况,依赖DB
基于redis、mongodb、zk等中间件生成
雪花算法:生成一个64bit的整性数字:第一位符号位固定为0,41位时间戳,10位workId,12位序列号位数可以有不同实现
优点:
每个毫秒值包含的ID值很多,不够可以变动位数来增加,性能佳(依赖work工d的实现)
时间戳值在高位,中间是固定的机器码,自增的序列在低位,整个ID是趋势递增的
能够根据业务场景数据库节点布置灵活挑战bit位划分,灵活度高
缺点:
强依赖于机器时钟,如果时钟回拨,会导致重复的ID生成,所以一般基于此的算法发现时钟回拨,都会抛异常处
理,阻止ID生成,这可能导致服务不可用
分布式锁:锁独立于每一个服务之外的锁
实现方式:setnx、radisson、redlock
setnx:指定的 key 不存在时,才能操作成功,为 key 设置指定的值
设置锁:给所有服务节点设置相同的key,返回为0、则锁获取失败(防止锁被别人所释放可以设置一个线程的唯一标识)
删除锁:判断线程唯一标志,再删除
问题:
实现的Redis分布式锁,其实不具有可重入性
存在任务超时问题,锁自动释放(key过期),导致并发问题
Redis没有实现可重入性及锁续期,可以通过 radisson解决(类似AQS的实现,看门狗监听机制)
redis多节点数据同步问题:
redlock:意思的机制都只操作单节点、即使Redis通过 sentinel保证高可用,如果这个 master节点由于某些原因发生了主从切换,那么就会出现锁丢失的情况( redis同步设置可能数据丢失)。 redlock从多个节点申请锁,当一半以上节点获取成功、锁才算获取成功,redission有相应的实现
两阶段协议:
第一阶段( prepare):每个参与者执行本地事务但不提交,进入 ready状态,并通知协调者已经准备就绪。
第二阶段( commit):当协调者确认每个参与者都 ready后,通知参与者进行 commit操作;如果有参与者fail则发送 rollback命令,各参与者做回滚。
存在的问题:
- 单点故障:一旦事务管理器出现故障,整个系统不可用(参与者都会阻塞住)
- 数据不一致:在阶段二,如果事务管理器只发送了部分 commit消息,此时网络发生异常,那么只有部分参与者接收到commit消息,也就是说只有部分参与者提交了事务,使得系统数据不—致
- 晌应时间较长:参与者和协调者资源都被锁住,提交或者回滚之后才能释放
- 不确定性:当协事务管理器发送 commit之后,并且此时只有一个参与者收到了 commit,那么当该参与者与事务管理器同时宕机之后,重新选举的事务管理器无法确定该条消息是否提交成功。
三阶段协议:
针对两阶段进行优化,引入了超时机制解决参与者阻塞的问题,超时后本地提交,解决了2PC单点故障问题,但性能问题和不一致问题仍然没有根本解决。(2PC只有协调者有超时机制)
超时机制:如果PreCommit消息执行成功,如果一定时间内还未收到DoCommit消息,则认为TM挂了,自动执行DoCommit
- 第一阶段: Cancommit阶段,协调者询问事务参与者,是否有能力完成此次事务,如果都返回yes,则进入第二阶段。有一个返回no或等待响应超时,则中断事务,并向所有参与者发送abort请求
- 第二阶段:Recommit阶段,此时协调者会向所有的参与者发送 Recommit请求,参与者收到后开始执行事务操作。参与者执行完事务操作后(此时属于未提交事务的状态),就会向协调者反馈
Ack
表示我已经准备好提交了,并等待协调者的下一步指令。- 第三阶段: Docommit阶段,在阶段二中如果所有的参与者节点都返回了
Ack
,那么协调者就会从“预提交状态转变为提交状态”。然后向所有的参与者节点发送 “docommit’'请求,参与者节点在收到提交请求后就会各自执行事务提交操作,并向协调者节点反馈Ack
消息,协调者收到所有参与者的Ack
消息后完成事务。相反,如果有一个参与者节点未完成 Recommit的反馈或者反馈超时,那么协调者都会向所有的参与者节点发送abort请求,从而中断事务。
TCC 是业务层面的分布式事务
TCC(补偿事务):Try(操作做业务检查及资源预留)、 Confirm(业务确认)、 Cancel(回滚操作)
针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作,TM首先发起所有的分支事务的Try操作,任何—个分支事务的Try操作执行失败,TM将会发起所有分支事务的 Cancel操作,若Try操作全部成功,TM将会发起所有分支事务的 Confirm操作,其中 Confirm/ Cancel操作若执行失败,TM会进行重试。
TCC模型对业务的侵入性较强,改造的难度较每个操作都需要有try、confirm、cancel三个接口实现,confirm和 cancel接口还必须实现幂等性。
图片来源:https://blog.csdn.net/leilei107/article/details/105738301
消息队列的事务消息
发送 prepare消息到消息中间件,发送成功后,执行本地事务:
如果事务执行成功,则 commit,消息中间件将消息下发至消费端( commit前,消息不会被消费)
如果事务执行失败,则回滚,消息中间件将这条 prepare消息删除消费端接收到消息进行消费,如果消费失败,则不断重试。
考虑到API接口的分类可以将API接口分为开发API接口和内网API接口,内网API接口用于局域网,为内部服务器提供服务。开放API接口用于对外部合作单位提供接口调用,需要遵循Oauth2.0权限认证协议。同时还需要考虑安全性、幂等性等问题。
如何实现接口的幂等性:
基于SpringCloud构建秒杀抢购活动,如何才能支持百万级级别以上访问 ?
设计原则:前台请求尽量少,后台数据尽量少,调用链路尽量短,尽量不要有单点
秒杀系统解决方案:
优点:低耦合,配置简单,面向服务,前后端分离,团队独立,技术独立,独立部署
缺点:部署困难,数据管理困难(数据库分离)、系统集成测试比较麻烦 、性能的监控比较麻烦
SpringBoot只是一个快速开发框架,可以独立使用开发项目,SpringCloud是一系列框架的集合,开发时依赖于SpringBoot框架。
Eureka服务注册中心,专注于微服务的服务注册与发现。
Map<服务名,Map<实例名,服务地址+端口>>
)。同时对服务维持心跳,剔除不可用的服务, Eureka集群各节点相互注册每个实例中都有一样的服务清单。Eureka集群(实现高可用AP),注册多个Eureka节点,然后把SpringCloud服务互相注册,客户端从Eureka获取信息时,按照Eureka的顺序来访问。这些节点都是平等的,当客户端向某一个节点注册时如果发现连接失败,会自动切换到其他节点。只要还有一台节点存在,服务就能正常工作(高可用),但是可能查询的信息不是最新的(不保证强一致性)。
Eureka的自我保护模式:
当服务间发起请求的时候,通过负载均衡算法(轮询法、随机法等)从一个服务的多台机器中选择一台, Ribbon也是通过发起http请求来进行的调用,只不过是通过调用服务名的地址来实现的。
负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间并避免任何单一资源的过载。
Ribbon底层实现原理:
Ribbon使用DiscoveryClient从注册中心读取目标服务信息,对同一接口请求进行计数,使用%取余算法获取目标服务集群索引,返回获取到的目标服务信息。
@LoadBalanced注解:开启客户端负载均衡。
DiscoveryClient的作用:可以从注册中心中根据服务别名获取注册的服务器信息。
Nginx是反向代理同时可以实现负载均衡,Nginx拦截客户端请求采用负载均衡策略根据upstream配置进行转发,相当于请求通过nginx服务器进行转发。Ribbon是客户端负载均衡,从注册中心读取目标服务器信息,然后客户端采用轮询策略对服务直接访问,全程在客户端操作。
当一个服务调用另一个服务由于网络原因或自身原因出现问题,调用者就会等待被调用者的响应当更多的服务请求到这些资源导致更多的请求等待,发生连锁效应(雪崩效应)
断路器有三种状态:
Hystrix:容错管理组件,实现断路器模式,发起请求都会通过 Hystrⅸ的线程池,不同的服务走不同的线程池,实现了不同服务调用的隔离,通过统计接口超时次数返回默认值,实现服务降级,服务熔断,服务隔离,监控等功能,防止雪崩。
服务降级底层是如何实现的?
Hystrix实现服务降级的功能是通过重写HystrixCommand中的getFallback()方法,当Hystrix的run方法或construct执行发生错误时转而执行getFallback()方法。
雪崩效应是在大型互联网项目中,当某个服务发生宕机时,调用这个服务的其他服务也会发生宕机,大型项目的微服务之间的调用是互通的,这样就会将服务的不可用逐步扩大到各个其他服务中,从而使整个项目的服务宕机崩溃。
发生雪崩效应的原因有以下几点:
- 单个服务的代码存在bug
- 请求访问量激增导致服务发生崩溃(如大型商城的枪红包,秒杀功能)
- 服务器的硬件故障也会导致部分服务不可用
在微服务中,使用Hystrix框架,实现服务隔离来避免出现服务的雪崩效应,从而达到保护服务的效果。
Feign:是一个声明web服务客户端,他基于 Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求,简化服务间的调用,在 Ribbon的基础上进行了进一步的封装。单独抽出了一个组件,就是 Spring Cloud Feign。在引入 Springoud Feign后,我们只需要创建一个接口并用注解的方式来配置它,即可完成对服务提供方的接口绑定。调用远程就像调用本地服务一样。
调用方式同:Ribbon需要我们自己构建Http请求,模拟Http请求然后通过RestTemplate发给其他服务,步骤相当繁琐。而Feign则是在Ribbon的基础上进行了一次改进,采用接口的形式,将我们需要调用的服务方法定义成抽象方法保存在本地就可以了,不需要自己构建Http请求了,直接调用接口就行了,不过要注意,调用方法要和本地抽象方法的签名完全一致。
RPC:在本地调用远程的函数,远程过程调用,可以跨语言实现HttpClient
RMI:远程方法调用,java中用于实现RPC的一种机制
网关相当于一个网络服务架构的入口,所有网络请求必须通过网关转发到具体的服务。网关用来统一管理微服务请求,权限控制、负载均衡、路由转发、监控、安全控制黑名单和白名单等。
什么是Spring Cloud Zuul(服务网关)
Zuul根据请求的路径不同,定位到指定的微服务,并代理请求到不同的微服务接口,他对外隐蔽了微服务的真正接口地址。 三个重要概念:动态路由表,路由定位,反向代理:
Zuul的应用场景:对外暴露,权限校验,服务聚合,日志审计等
网关与过滤器有什么区别:网关是对所有服务的请求进行分析过滤,过滤器是对单个服务而言。
ZuulFilter常用的方法:
Run():过滤器的具体业务逻辑
shouldFilter():判断过滤器是否有效
filterOrder():过滤器执行顺序
filterType():过滤器拦截位置
如何实现动态Zuul网关路由转发?
通过path配置拦截请求,通过ServiceId到配置中心获取转发的服务列表,Zuul内部使用Ribbon实现本地负载均衡和转发。
Zuul是java语言实现的,主要为java服务提供网关服务,尤其在微服务架构中可以更加灵活的对网关进行操作。Nginx是使用C语言实现,性能高于Zuul,但是实现自定义操作需要熟悉lua语言,对程序员要求较高,可以使用Nginx做Zuul集群。