写在开局 : 本文接近 4 万字长文, 717行, 全部阅读花费时间比较久!
用途 : 仅用作个人复习使用!
包括 SpringCloud 和阿里巴巴 常用的系列组件的部分知识点和常见面试题, 可以针对性复习阅读!
Spring Cloud 是一套分布式微服务的技术解决方案,它提供了快速构建分布式系统的常用的一些组件
比如说配置管理、服务的注册与发现、服务调用的负载均衡、资源隔离、熔断降级等等
不过 Spring Cloud 只是 Spring 官方提供的一套微服务标准定义,而真正的实现目前有两套体系用的比较多。
- 一个是 Spring Cloud Netflix
- 一个是 Spring Cloud Alibaba
Spring Cloud Netflix是基于Netflix这个公司的开源组件集成的一套微服务解决方案,其中的组件有
- Ribbon——负载均衡
- Hystrix——服务熔断
3.Zuul——网关- Eureka——服务注册与发现
- Feign——服务调用
Spring Cloud Alibaba 是基于阿里巴巴开源组件集成的一套微服务解决方案,其中包括- Dubbo——消息通讯
- Nacos——服务注册与发现
3.Seata——事务隔离- Sentinel——熔断降级
有了 Spring Cloud 这样的技术生态,使得我们在落地微服务架构时。不用去考虑第三方技术集成带来额外成本,只要通过配置组件来完成架构下的技术问题,从而可以让我们更加侧重性能方面
异同 | Nacos | Eureka |
---|---|---|
相同点 | 都支持服务注册和服务拉取,都支持服务提供者心跳方式做健康检测 | |
区别1 | 支持服务端主动检测提供者状态:临时实例用心跳模式,非临时实例用主动检测模式 | |
区别2 | 临时实例心跳不正常会被剔除,非临时实例则不会被剔除 | |
区别3 | 服务列表变更的消息推送模式,服务列表更新更及时(push/pull) | 只支持定时拉取,时效性相对差 |
区别4 | 默认采用AP方式,当集群中存在非临时实例,采用CP | AP, 不支持切换 |
互联网早期,单体架构就能完成业务支撑,随着用户量增加,服务器并发量,请求也越多,单体会有以下问题:
基于 DNS 实现负载均衡
基于硬件实现负载均衡
基于软件实现负载均衡
基于 DNS 实现负载均衡的方式,实现方式比较简单,只需要在 DNS服务器上针对某个域名做多个 IP 映射即可;
工作原理是: 当用户通过域名访问某个网站时,会先通过 DNS 服务器进行域名解析得到一个 IP 地址,DNS 服务器可以随机分配一个 IP 地址进行访问,这样就可以实现目标服务集群的请求分发。
除此之外,DNS 还可以根据不同的地域分配就近机房的 IP,比如长沙的小伙伴,可能会得到在湖南范围内最近的一个机房的 IP,在这个模式下可以实现「就近原则」实现请求处理,缩短了通信距离从而提升网站访问效率。
DNS 实现负载均衡的优点是: 配置简单,实现成本低,无需额外的开发和维护。
缺点是: 由于 DNS 多级缓存的特性,当我们修改 DNS 配置之后,会因为缓存导致 IP 变更不及时,从而影响负载均衡的效果。
基于硬件实现负载均衡
硬件负载设备,我们可以简单把它理解成一个网络设备,类似于网络交换机,它
3. 它的性能很好,每秒能够处理百万级别的请求,
4. 支持多种负载均衡算法,我们可以非常灵活的配置不同的负载策略
5. 它还具备防火墙等安全功能。
6. 硬件负载是商业产品,有专门的售后来支持,所以企业不需要花精力去做维护。
F5 是比较常见的硬件负载设备,由于硬件负载设备价格比较贵,一般应用在大型银行、政府、电信等领域。
基于软件实现负载均衡
所谓软件负载,就是通过一些开源软件或者商业软件来完成负载均衡的功能。常见的软件负载技术有:Nginx、LVS、HAProxy 等。
目前互联网企业绝大部分采用的都是软件负载,主要原因是:
负载均衡是作用在网络通信上,来实现请求的分发。
而在网络架构中,基于 OSI 模型,又分为 7 层网络模型
也就是意味着我们可以在网络的某些分层上做请求分发处理,因此根据这样一个特性,对于负载均衡的作用范围又可以分为:
二层负载:基于 Mac 地址来实现请求分发,一般采用虚拟 Mac 的方式实现,服务器收到请求后,通过动态分配后端服务的实际 Mac 地址进行响应从而实现负载均衡
三层负载:基于 IP 层负载,一般通过虚拟 IP 的方式实现,外部请求访问虚拟 IP,服务器收到请求后根据后端实际 IP 地址进行转发。
四层负载:通过请求报文中的目标地址和端口进行负载,Nginx、F5、LVS 等都可以实现四层负载
七层负载:七层负载是基于应用层负载,也就是服务器端可以根据 http 协议中请求的报文信息来决定把请求分发到哪个目标服务器上,比如 Cookie、消息体、RequestHeader 等
负载均衡算法,就是决定当前客户端请求匹配到目标服务器集群中的具体哪个节点
问题定位 性能分析 服务关系 服务告警
springboot-admin prometheus+grafana(普罗米修斯) zipkin+skywalking链路追踪工具
微服务用skywalking监控
skywalking主要可以监控接口 服务 物理实例的状态,特别是压测时候可发现众多服务中哪些服务和接口比较慢,针对性分析优化;
设置告警规则,特别是项目上线后,如果报错,分别设置可以给相关负责人发短信和发邮件, 第一时间知道项目 bug 情况,进行修复;
Nginx 限流
控制速率 (突发流量)
limit_req_zone key zone rate
key 定义限流对象 binary_remote_addr 就是一种 key ,基于客户端 ip 限流
Zone 定义共享存储区来存储访问信息, 10m 可以存储 16 wip 地址访问信息
Rate 最大访问速率 rate=1r/s 每秒最多请求十个
burst=20 相当于桶大小
Nodelay 快速处理
控制并发连接数
网关限流, yml文件路由设置添加局部过滤器 RequestRate
XA模式优点: ①事务强一致性,ACID原则; ②常用数据库都支持,实现简单,且没有代码侵入;
XA模式缺点: ①一阶段要锁定DB资源,等待二阶段结束才释放,性能较差; ②依赖RDBMS实现事务
类别 | AT模式 | XA模式 |
---|---|---|
提交和资源 | 一阶段直接提交,不锁定资源 | 一阶段不提交事务,锁定资源 |
回滚原理 | 利用数据快照实现 | 依赖数据库机制实现 |
一致性 | 最终一致 | 强一致 |
:::info
全局锁 : TC 记录当前正在操作某行数据的事务,该事务持有全局锁,具备执行权
默认30次,间隔10ms;
:::
AT模式缺点:
TCC模式: try(资源检查和预留) confirm(业务执行和提交) cancel(预留资源的释放)
TCC模式优点:
TCC模式缺点:
个人回答 ; Seata 是用来解决分布式事务问题的框架。是阿里开源的中间件。 Seata 里面有几种事务模型,有一种 AT 模式、还有 TCC模式。然后 AT 是一种二阶段提交的事务,它是采用的最终一致性来实现数据的一致性;
1 是什么
2 解决什么问题
3 特点优势
4 核心原理或底层,
- 在微服务架构下,由于数据库和应用服务的拆分,导致原本一个事务单元中的多个DML 操作,变成了跨进程或者跨数据库的多个事务单元的多个 DML 操作,而传统的数据库事务无法解决这类的问题,所以就引出了分布式事务的概念;
- 分布式事务本质上要解决的就是跨网络节点的多个事务的数据一致性问题,业内常见的解决方法有两种
- a. 强一致性,就是所有的事务参与者要么全部成功,要么全部失败,全局事务协调者需要知道每个事务参与者的执行状态,再根据状态来决定数据的提交或者回滚!
- b. 最终一致性,也叫弱一致性,也就是多个网络节点的数据允许出现不一致的情况,但是在最终的某个时间点会达成数据一致。
- 综上 ; 基于 CAP 定理我们可以知道,强一致性方案对于应用的性能和可用性会有影响,所以对于数据一致性要求不高的场景,就会采用最终一致性算法。
- 在分布式事务的实现上,对于强一致性,我们可以通过基于 XA 协议下的二阶段提交来实现,对于弱一致性,可以基于 TCC 事务模型、可靠性消息模型等方案来实现。
- 市面上有很多针对这些理论模型实现的分布式事务框架,我们可以在应用中集成这些框架来实现分布式事务
- 而 Seata 就是其中一种,它是阿里开源的分布式事务解决方案,提供了高性能且简单易用的分布式事务服务。
Seata 中封装了四种分布式事务模式,分别是:
- AT 模式,是一种基于本地事务+二阶段协议来实现的最终数据一致性方案,也是Seata 默认的解决方案
- TCC 模式,TCC 事务是 Try、Confirm、Cancel 三个词语的缩写,简单理解就是把一个完整的业务逻辑拆分成三个阶段,然后通过事务管理器在业务逻辑层面根据每个分支事务的执行情况分别调用该业务的 Confirm 或者 Cancel 方法。
- Saga 模式,Saga 模式是 SEATA 提供的长事务解决方案,在 Saga 模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者。
- XA 模式,XA 可以认为是一种强一致性的事务解决方法,它利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种 事务模式。
从这四种模型中不难看出,在不同的业务场景中,我们可以使用 Seata 的不同事务模型来解决不同业务场景中的分布式事务问题,因此我们可以认为 Seata 是一个一站式的分布式事务解决方案。
:::info
幂等 : 多次调用方法或者接口不会改变业务状态,可以保证重复调用结果和单次调用结果一致
场景 : 用户重复点击(网络波动) | MQ消息重复 | 应用使用失败或超时重试机制
:::
数据库唯一索引 新增
token + redis 新增和修改(增量修改)
分布式锁 新增和修改
public void saveOrder(Item item) throws InterruptedException{
// 获取锁 (重入锁)
RLock lock = redissonClient.getLock("itkakalock);
// 尝试获取锁, 参数分别是 最大等待时间(期间重试),锁自动释放时间.时间单位
boolean isLock = lock.tryLock(10,TimeUnit.SECONDS);
try{
// 判断是否获取成功
if(!isLock){
log.info("下单操作获取锁失败,order:{}",item);
throw new RuntimeException("更新失败!");
}
// 下单操作
} finally {
// 释放锁
lock.unlock();
}
}
:::info
快速失败 (抢不到锁的线程)
控制锁的粒度
:::
分类 | S C (网非) | SpringCloudAlibba |
---|---|---|
注册中心 | Eureka | Nacos (配置中心) |
负载均衡 | Ribbon | Ribbon |
远程调用 | Feign | Feign |
服务熔断 | Hystrix | sentinel服务保护 |
网关 | Zuul | Gateway |
第一 : 个人理解主要是服务注册 , 服务发现 以及 服务状态监控;
第二 : XX项目采用的 eureka 作为注册中心,这个也是 SpringCloud 的核心组件之一;
服务注册 : 服务的提供方把自己信息注册到eureka,由注册中心保存这些信息,比如ip,端口号等;
服务发现 : 消费者向 eureka 拉取服务列表信息,如果服务提供者有集群,则消费者利用负载均衡算法
选择一个发起调用;
服务监控 : 服务提供者每30s向eureka发起心跳,报告健康状态,如果注册中心服务90s没收到心跳,
就从eureka中剔除;
XX项目采用的 Nacos 作为注册中心,选择 Nacos 很重要的一个原因就是支持配置中心,不过Nacos作为注册中心
也是比eureka方便好用一些:
共同点 : 都能注册服务和拉取服务, 都通过心跳模式对服务提供者进行健康检测;
不同点 :
① Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
② 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
③ Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
④ Nacos集群默认AP,当集群存在非临时实例,用CP, Eureka用AP模式
在服务调用过程中的负载均衡一般使用SpringCloud的Ribbon 组件实现 ,
Feign的底层已经自动集成了Ribbon , 使用起来非常简单
当发起远程调用时,ribbon先从注册中心拉取服务地址列表,然后按照一定
的路由策略选择一个发起远程调用,一般的调用策略是轮询
RoundRobinRule : 简单轮询服务列表来选择服务器
WeightedResponseTimeRule:按照权重来选择服务器,响应时间越长,权重越小
RandomRule:随机选择一个可用的服务器
ZoneAvoidanceRule:区域敏感策略,以区域可用的服务器为基础进行服务器的选择。使用对服务器进行分
类,这个Zone内的多个服务做轮询(默认)
自定义策略:
第一种 : 类实现 IRule 接口,可以指定负载均衡策略,这个是全局的,对所有远程调用起作用
第二种 : 客户端配置文件,可配置某一个服务调用的负载均衡策略,值对配置的服务生效远程调用
服务雪崩是指一个服务失败,导致整条链路的服务都失败的情形,一般我们
在项目解决的话就是两种方案,第一个是服务降级,第二个是服务熔断,如
果流量太大的话,可以考虑限流
服务降级:服务自我保护的一种方式,或者保护下游服务的一种方式,用于
确保服务不会受请求突增影响变得不可用,确保服务不会崩溃,一般在实际
开发中与feign接口整合,编写降级逻辑
服务熔断:默认关闭,需要手动打开,如果检测到 10 秒内请求的失败率超
过 50%,就触发熔断机制。之后每隔 5 秒重新尝试请求微服务,如果微服务
不能响应,继续走熔断机制。如果微服务可达,则关闭熔断机制,恢复正常
请求
采用的 Skywalking 进行监控
1,skywalking主要可以监控接口、服务、物理实例的一些状态。特别是在
压测的时候可以看到众多服务中哪些服务和接口比较慢,我们可以针对性的
分析和优化。
2,我们还在skywalking设置了告警规则,特别是在项目上线以后,如果报
错,我们分别设置了可以给相关负责人发短信和发邮件,第一时间知道项目
的bug情况,第一时间修复
XX项目用到微服务架构, 因为啥场景,会有突发流量,最大QPS可以达到2000,但是服务撑不住,我们项目都通过
压测最多可以支撑1200QPS, 因为日常QPS也就不到100,为了解决这些突发流量,所以采用限流
【版本1】
我们当时采用的nginx限流操作,nginx使用的漏桶算法来实现过滤,让请求
以固定的速率处理请求,可以应对突发流量,我们控制的速率是按照ip进行
限流,限制的流量是每秒20
【版本2】
我们当时采用的是spring cloud gateway中支持局部过滤器
RequestRateLimiter来做限流,使用的是令牌桶算法,可根据ip或路径进行限流,可设置每秒填充平均速率
和令牌桶总容量
限流常见算法
漏桶算法 和 令牌桶算法
漏桶算法是把请求存入到桶中,以固定速率从桶中流出,可以让我们的服务
做到绝对的平均,起到很好的限流效果
令牌桶算法在桶中存储的是令牌,按照一定的速率生成令牌,每个请求都要
先申请令牌,申请到令牌以后才能正常请求,也可以起到很好的限流作用
它们的区别是,漏桶和令牌桶都可以处理突发流量,其中漏桶可以做到绝对
的平滑,令牌桶有可能会产生突发大量请求的情况,一般nginx限流采用的 漏桶,gateway可支持令牌桶
CAP主要是在分布式项目下的一个理论。包含了三项,一致性、可用性、分区容错性
一致性(Consistency)是指更新操作成功并返回客户端完成后,所有节点在同一时间
的数据完全一致(强一致性),不能存在中间状态。
可用性(Availability) 是指系统提供的服务必须一直处于可用的状态,对于用户的每
一个操作请求总是能够在有限的时间内返回结果。
分区容错性(Partition tolerance) 是指分布式系统在遇到任何网络分区故障时,仍
然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发
生了故障。
面试官:为什么分布式系统中无法同时保证一致性和可用性?
回答:
嗯,是这样的~~
首先一个前提,对于分布式系统而言,分区容错性是一个最基本的要求,因
此基本上我们在设计分布式系统的时候只能从一致性(C)和可用性(A)之
间进行取舍。
如果保证了一致性(C):对于节点N1和N2,当往N1里写数据时,N2上的
操作必须被暂停,只有当N1同步数据到N2时才能对N2进行读写请求,在N2
被暂停操作期间客户端提交的请求会收到失败或超时。显然,这与可用性是
相悖的。
如果保证了可用性(A):那就不能暂停N2的读写操作,但同时N1在写数据
的话,这就违背了一致性的要求。
面试官:什么是BASE理论?
回答:
嗯,这个也是CAP分布式系统设计理论
BASE是CAP理论中AP方案的延伸,核心思想是即使无法做到强一致性
(StrongConsistency,CAP的一致性就是强一致性),但应用可以采用适合
的方式达到最终一致性(Eventual Consitency)。它的思想包含三方面:
1、Basically Available(基本可用):基本可用是指分布式系统在出现不可
预知的故障的时候,允许损失部分可用性,但不等于系统不可用。
2、Soft state(软状态):即是指允许系统中的数据存在中间状态,并认为
该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数
据副本之间进行数据同步的过程存在延时。
3、Eventually consistent(最终一致性):强调系统中所有的数据副本,在
经过一段时间的同步后,最终能够达到一个一致的状态。其本质是需要系统
保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
我们当时是xx项目,主要使用到的seata的at模式解决的分布式事务
seata的AT模型分为两个阶段:
1、阶段一RM的工作:① 注册分支事务 ② 记录undo-log(数据快照)③执行业务sql并提交 ④报告事务状态
2、阶段二提交时RM的工作:删除undo-log即可
3、阶段二回滚时RM的工作:根据undo-log恢复数据到更新前
at模式牺牲了一致性,保证了可用性,不过,它保证的是最终一致性
嗯,我们当时有一个xx项目的下单操作,采用的token+redis实现的,流程是这样的
第一次请求,也就是用户打开了商品详情页面,我们会发起一个请求,在后台生成一个唯一token存入redis,key就是用户的id,value就是这个token,同时把这个token返回前端
第二次请求,当用户点击了下单操作会后,会携带之前的token,后台先到redis进行验证,如果存在token,可以执行业务,同时删除token;如果不存在,则直接返回,不处理业务,就保证了同一个token只处理一次业务,就保证了幂等性
xxl-job提供了很多的路由策略,我们平时用的较多就是:轮询、故障转移、分片广播等
有这么几个操作
第一:路由策略选择故障转移,优先使用健康的实例来执行任务
第二,如果还有失败的,我们在创建任务时,可以设置重试次数
第三,如果还有失败的,就可以查看日志或者配置邮件告警来通知相关负责人解决
我们会让部署多个实例,共同去执行这些批量的任务,其中任务的路由策略是分片广播
在任务执行的代码中可以获取分片总数和当前分片,按照取模的方式分摊到各个实例执行就可以了
网关功能 : ①身份认证和权限验证 ②服务路由和负载均衡 ③请求限流
路由配置包括 : 路由id 路由目标url 路由断言predicates 路由过滤器
路由断言工厂Route Predicate Factory
Dubbo 是一个 RPC 框架,它为我们的应用提供了远程通信能力的封装,同时,Dubbo在 RPC 通信的基础上,逐步在向一个生态在演进,它涵盖了服务注册、动态路由、容错、服务降级、负载均衡等能力,基本上在微服务架构下面临的问题,Dubbo 都可以解决。
而对于 Dubbo 服务请求失败的场景,默认提供了重试的容错机制,也就是说,如果基于 Dubbo 进行服务间通信出现异常,服务消费者会对服务提供者集群中其他的节点发起重试,确保这次请求成功,默认的额外重试次数是 2 次。
除此之外,Dubbo 还提供了更多的容错策略,我们可以根据不同的业务场景来进行选择。
- 快速失败策略,服务消费者只发起一次请求,如果请求失败,就直接把错误抛出去。这种比较适合在非幂等性场景中使用
- 失败安全策略,如果出现服务通信异常,直接把这个异常吞掉不做任何处理
- 失败自动恢复策略,后台记录失败请求,然后通过定时任务来对这个失败的请求进行重发。
- 并行调用多个服务策略,就是把这个消息广播给服务提供者集群,只要有任何一个节点返回,就表示请求执行成功。
- 广播调用策略,逐个调用服务提供者集群,只要集群中任何一个节点出现异常,就表示本次请求失败
综上 : 注意的是,默认基于重试策略的容错机制中,需要注意幂等性的处理,否则在事务型的操作中,容易出现多次数据变更的问题
Dubbo 是以高性能 RPC 框架,它提供了分布式架构下的服务之间通信方案,使得开发者可以不需要关心网络通信的细节。通过该框架可以使得远程服务调用方式和本地服务调用方式一样简单。
Dubbo 是一款高性能、轻量级的开源 RPC 框架。由 10 层模式构成,整个分层依赖由上至下。
通过这张图我们也可以将 Dubbo 理解为三层模式:
第一层的 Business 业务逻辑层由我们自己来提供接口和实现还有一些配置信息。
第二层的 RPC 调用的核心层负责封装和实现整个 RPC 的调用过程、负载均衡、集群容错、代理等核心功能。
第三层的Remoting 则是对网络传输协议和数据转换的封装。
根据 Dubbo 官方文档的介绍,Dubbo 提供了六大核心能力
第一种是加权随机
第二种是最小活跃数
第三种是一致性 hash,通过 hash 算法,把 provider 的 invoke 和随机节点生成 hash,并将这个 hash 投射到 [0, 2^32 - 1] 的圆环上,查询的时候根据 key 进行 md5 然后进行 hash,得到第一个节点的值大于等于当前 hash 的 invoker。
第四种是加权轮询
第五种是最短响应时间权重随机:计算目标服务的请求的响应时间,根据响应时间最短的服务,配置更高的权重进行随机访问。
Dubbo 是 SOA 时代的产物,它的关注点主要在于服务的调用,流量分发、流量监控和熔断。而 Spring Cloud 诞生于微服务架构时代,考虑的是微服务治理的方方面面,另外由于依托了 Spirng、Spirng Boot 的优势之上,两个框架在开始目标就不一致,Dubbo 定位服务治理、Spirng Cloud 是一个生态。
两者最大的区别是 Dubbo 底层是使用 Netty 这样的 NIO 框架,是基于 TCP 协议传输的,配合以 Hession 序列化完成 RPC 通信。而 SpringCloud 是基于 Http 协议+Rest 接口调用远程过程的通信,相对来说,Http 请求会有更大的报文,占的带宽也会更多。但是 REST 相比 RPC 更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖
1.服务启动的时候,provider 和 consumer 根据配置信息,连接到注册中心 register,分别向注册中心注册和订阅服务
2.register 根据服务订阅关系,返回 provider 信息到 consumer,同时 consumer 会把 provider 信息缓存到本地。如果信息有变更,consumer 会收到来自 register 的推送
3.consumer 生成代理对象,同时根据负载均衡策略,选择一台 provider,同时定时向monitor 记录接口的调用次数和时间信息
4.拿到代理对象之后,consumer 通过代理对象发起接口调用
5.provider 收到请求后对数据进行反序列化,然后通过代理调用具体的接口实现
远程调用是指跨进程的功能调用,跨进程可以理解成一个计算机节点的多个进程,或者多个计算机节点的多个进程。远程指进程和进程之间彼此跨越进程;
RPC 的概念与技术其实是比较早的,40 年前,也就是 1981 年由 Nelson 提出。1984年,Birrell 和把它用于分布式系统间的通讯。Java 在 1.1 版本提供了 Java 版本的 RPC框架(RMI)。
全称为 Remote Procedure Call,翻译过来就是远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议,凡是符合该协议的框架,我们都可以称它为 RPC 框架。
关于 RPC 协议,通俗来讲就是,A 计算机提供一个服务,B 计算机可以像调用本地服务那样调用 A 计算机的服务。要实现 RPC,需要通过网络传输数据,并对调用的过程进行封装。现在比较流行的 RPC 框架,都会采用 TCP 作为底层传输协议。RPC 强调的是过程调用,调用的过程对用户而言是是透明的,用户不需要关心调用的细
节,可以像调用本地服务一样调用远程服务。
RPC架构四大件,
客户端–>服务调用方
服务端–>服务提供者
客户端存根Client Stub–>存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方。
服务端存根(Server Stub),接收客户端发送过来的消息,将消息解包,并调用本地的方法。
目前比较流行的开源 RPC 框架有 Google 的 gRPC,Facebook 的 Thrift,阿里巴巴的Dubbo。
RPC 的运用场景和优势
在开发电商系统时,随着业务越来越复杂,走分布式架构这个方向是绝大部分互联网企业必然的选择。
分布式架构落地的里程碑,应该是阿里的去 IOE 运动的成功,它让互联网企业看到了,如何利用更少的软硬件成本来支撑海量的用户。
分布式架构的核心,就是利用多个普通计算机节点,组成一个庞大而复杂的计算网络,提供高性能以及高并发的能力支撑。
在分布式架构中,原本的单体应用被拆分成多个独立部署的服务分部在计算及网络上,这些服务必然需要通过网络进行数据交互。
而 RPC 框架就是解决在分布式架构中的各个业务服务彼此的网络通信问题。
一般来说,RPC 服务主要是针对大型的企业,也就说当我们业务复杂度以及用户量都比较高时,需要解耦服务,扩展性强、部署灵活
一般市面上开源的 PRC 框架,除了提供基础的远程通信功能以外,还会在性能消耗、传输效率、服务治理等方面做很多的设计,比如阿里开源的 RPC 框架 Dubbo。
一般情况下,Redis 用来实现应用和数据库之间读操作的缓存层,主要目的是减少数据库 IO,还可以提升数据的 IO 性能。
它的整体架构是 当应用程序需要去读取某个数据的时候,首先会先尝试去 Redis 里面加载,如果命中就直接返回。如果没有命中,就从数据库查询,查询到数据后再把这个数据缓存到 Redis里面。
在这样一个架构中,会出现一个问题,就是一份数据,同时保存在数据库和Redis 里面,当数据发生变化的时候,需要同时更新 Redis 和 Mysql,由于更新是有先后顺序的,并且它不像 Mysql 中的多表事务操作,可以满足 ACID 特性。所以就会出现数据一致性问题
在这种情况下,能够选择的方法只有几种。
方案介绍:
最终一致,允许延时一致的业务,采用异步通知
①用MQ中间件,更新数据后,通知缓存删除
②用canal中间件,不用改业务代码,伪装为 mysql 的一个从节点,canal通过读取 binlog数据更新缓存强一致,用 Redisson 提供的读写锁
①共享锁:读取readLock,加锁之后,其它线程可以共享读操作;
②排他锁:独占锁writeLock写锁,加锁之后,阻塞其它线程读写操作
分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于分布式系统的不同节点之上。
比如大型的电商系统中的下单场景,会涉及到扣库存、优惠促销计算、订单 ID 生成。
通常情况下,库存、促销、主键生成策略都位于不同的服务器和数据库表中。
下单接口的成功与否,不仅取决于本地节点的数据库操作,而且还依赖第三方系统的结果,这时候分布式事务就保证这些操作要么全部成功,要么全部失败。
因此,本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
基于 CAP 定理可以知道,对于上述情况产生的分布式事务问题,我们要么采用强一致性方案、要么采用弱一致性方案。
在弱一致性方案中,常见的解决方案:
Elasticsearch ,简称 ES 。它是建立在全文搜索引擎库 Apache Lucene 基础之上的一个开源的搜索引擎,也可以作为 NoSQL 数据库,存储任意格式的文档和数据。也可以做大数据的分析,是一个跨界开源产品。
它最主要的应用场景是 ELK 的日志分析系统。
另外它还有以下特点:
近些年,随着 Docker 与微服务的普及,K8s 也乘着这两股东风,迅速蹿红起来。
作为最火的容器编排工具之一,它的很多思想设计都契合了微服务和云原生应用的设计法则。
也正因如此,越来越多的公司开始使用起 k8s。
那么,称霸容器界的 k8s,究竟有什么魔力呢?
K8s 全称 Kubernetes,8 是中间 8 个字母的简称。
作为一种容器自动部署、扩容以及管理的技术,我们可以简单理解其是一种容器编排技术。前身是 Borg 系统,在谷歌内部已经有了十多年的使用经验
什么是容器编排技术?
如图所示,在一台centos物理机上安装了Docker镜像,然后在镜像上跑几个container容器。
那最明显的一个问题是,当物理机 centos 资源不够使用的时候,就没办法再创建新的container 了,
另外一个问题就是单点故障,也就是当安装了 Docker Engine 的这台 centos 机器挂了之后,所有的 container 都会挂掉,这样很多中间件和服务都变成不可用了,
所以最直接的解决方案,就是对 centos 机器做集群化的部署,如图所示。
可以看到这是由 3 台 centos 机器组成的 Docker 集群或者 K8s 集群,其中 1 台是master,另外 2 台是 worker。
每台机器上都安装了 Docker Engine,当需要对容器进行操作时,会通过 kubectl 来接收用户的操作命令。
API Server 会做客户端的命令进行认证授权,接下来会通过 Scheduler 组件进行调度,就可以知道要创建的资源到底会落到哪个机器上。
然后会通过不同类型的 Controller Manager 进行管理要创建的资源,比如要将资源创建在其中一个 worker 节点上,就会把请求转发给 worker 节点的 kube-proxy 组件。
看到这里,要跟大家强调一点,在 K8s 中最小的操作单元是 Pod,它可以理解为是container 的逻辑单位,不会直接操作 docker 中的 container,而是会以 Pod 的形式来操作,因为 Pod 里面包含了各个类型的 container
那在 worker 中是如何操作 Pod 的呢?
就是通过 kubelet 组件完成的。当然可以发现,整个 K8s 集群中还可以持久化一些数据到 ETCD 组件里,
可以通过 Dashboard 查看整个集群资源的状态,可以通过 DNS 组件进行相应的域名解析等, 这些组件和功能都是可选的、可扩展的。
到这里我们不妨再想一个问题,K8s 集群中的物理机可以是不同的硬件和操作系统吗?
显然是没问题的,这样一来我们就可以通过 Docker 和 K8s 来屏蔽基础设施的差异性,在上层进行容器创建以及管理就变得方便多了,所以 K8s 也是云原生中很重要的基础保障。
如图所示,最下面的基础设施可以是各种公有云、私有云、混合云,在此基础上构建K8s 的集群支撑,然后就可以非常方便调度管理不同的容器了。
容器作为一种先进的虚拟化技术,是云原生时代软件开发和运维不可或缺的部分。而 K8s 作为当前最知名的容器编排工具,称得上是云原生时代的“操作系统”,熟悉和使用它是研发、运维、产品等的必备技能。
我个人理解,Docker 是一个超轻量级的虚拟机,也是实现容器化技术的一种应用工具。
Docker 是使用时下很火的 Golang 语言进行开发的,基于 Linux 内核的 cgroup,namespace,以及 AUFS 类的 UnionFS 等技术,对进程进行封装隔离,属于操作系统层面的虚拟化技术。 由于隔离的进程独立于宿主和其它的隔离进程,因此也被称为容器。
Docker的LOGO,一头鲸鱼驮着一堆集装箱,集装箱表现的含义是:把所有不规整的货物,按照统一标准进行包装,在 Docker 中,它代表各种不同的应用程序使用 Docker 的标准来实现构建。而运输集装箱的鲸鱼,代表操作系统!
资源占用小
由于容器不需要进行硬件虚拟,也不需要运行完整操作系统等额外的资源开销,使得Docker 对系统资源的利用率更高,无论是应用执行速度还是文件存储速度,都要比传统虚拟机技术更高效,内存消 耗更少 。
启动速度快
传统的虚拟机技术启动应用服务往往需要较长时间,而 Docker 容器应用,由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级,甚至毫秒级的启动时间,大大的节约了开发,测试,部署的时间
迁移更轻松
由于 Docker 确保了执行环境的一致性,使得应用的迁移更加容易,Docker 可以在很多平台上运行,无论是物理机,虚拟机,公有云,私有云,它们的运行结果是一致的,因此用户可以很轻易的将一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行这类的问题。
维护和拓展更轻松
docker 使用的分层存储和镜像技术,让应用重复部分的复用更容易,也让应用的维护更新更简单,基于基础镜像进一步扩展镜像也变得十分简单。另外,docker 团队和各个开源项目团队一起维护了一大批高质量的官网镜像,既可以直接在生产环境使用,又可以作为基础进一步定制,大大降低了应用服务的镜像制作成本
运行环境一致
开发过程中一个常见的问题是环境一致性问题,由于开发环境,测试环境,生产环境不一致,导致有些 bug 并未在开发过程中被发现,而 Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性。
持续交付和部署
使用 Docker 可以通过定制应用镜像来实现持续集成,持续交付,部署。开发人员可以通过 Dockerfile 来进行镜像构建,并结合持续集成系统进行集成测试,而运维人员则可以在生产环境中快速部署该镜像,甚至结合持续部署系统进行自动部署
Docker 包含三个基本组件:
镜像(Image)
容器(Contaier)
仓库(Repository)
镜像(Image)——一个特殊的文件系统
简单地理解,Docker 镜像就是一个 Linux 的文件系统(Root FileSystem),这个文件系统里面包含可以运行在 Linux 内核的程序以及相应的数据。
一个镜像可以包含一个完整的操作系统环境,里面仅安装了 Apache 或用户需要的其它应用程序。镜像可以用来创建 Docker 容器。Docker 提供了一个很简单的机制来创建镜像或者更新现有的镜像,用户甚至可以直接从其他人那里下载一个已经做好的镜像来直接使用。
容器(Container)——镜像运行的实体
Docker 利用容器来运行应用。容器是从镜像创建的运行实例。它可以被启动、开始、停止、删除。每个容器都是相互隔离的、保证安全的平台。
可以把容器看做是一个简易版的 Linux 环境(包括 root 用户权限、进程空间、用户空间和网络空间等)和运行在其中的应用程序。
仓库(Repository)——集中存放镜像文件的地方
仓库是集中存放镜像文件的场所。很多人会把仓库和仓库注册服务器(Registry)混为一谈。实际上,仓库注册服务器上往往存放着多个仓库,每个仓库中又包含了多个镜像,每个镜像有不同的标签(tag)。
仓库分为公开仓库(Public)和私有仓库(Private)两种形式。最大的公开仓库是 Docker Hub,存放了数量庞大的镜像供用户下载。
RPC 全称(Remote Procedure Call),它是一种针对跨进程或者跨网络节点的应用之间的远程过程调用协议。
它的核心目标是,让开发人员在进行远程方法调用的时候,就像调用本地方法一样,不需要额外为了完成这个交互做过的编码。
为了达到这样一个目标,就有了这样一个 RPC 协议模型[图]。
Client stub 作为客户端存根,代理客户端请求并把请求参数进行组装,再把组装的参数通过 Pack 也就是序列化。
最后通过远程通信协议把序列化的数据传输到目标服务器上。
服务端收到数据以后,通过 Unpack 也就是反序列化,再通过 Server stub 服务端存根来调用目标方法获取执行结果。
所以,通过这样一个模型,就实现了 RPC 的目标,让开发者在没有任何感知的情况下实现了远程方法的调用。
而 Http 协议是为 Web 浏览器与 Web 服务器之间的通信而设计的远程通信协议,它定义了通信协议的报文规范(如图),我们可以使用 http 协议来实现跨网络节点的数据传输。
基于这样的特点,在 RPC 协议底层的数据传输,即可以直接使用 TCP 协议,也可以使用 http 协议。
因此,Rpc 协议和 Http 协议完全不是同一个纬度的东西,这两者并没有什么可比性。
SPI 全称为 Service Provider Interface,是 Java 中一种服务发现机制。
在 SPI 机制中,服务提供者为某个接口实现具体的类,而在运行时,通过 SPI 机制,查找到对应的实现类,并将其加载进行使用。
比较典型的一个例子就是 java.jdbc.Driver 这个数据库驱动接口,在 Java 中只提供了接口的定义,具体的实现类由各个数据库厂商提供的驱动包来完成,程序在运行的时候会根据当前导入的驱动包来完成对应数据库的连接
在 Dubbo 中,根据 Java SPI 的思想,衍生了不同的 SPI 实现。
指定名称的扩展点实现,也就是根据指定名称获取并加载对应的扩展点实现类
@Adaptive 注解定义自适应扩展点,它会根据上下文参数动态适配一个具体的实现类
@Activate 注解定义的激活扩展点,它会根据上下文的相关参数配置,来决定哪些扩展点会被动态激活,主要体现在 Filter 中。
SPI 思想可以为程序提供比较强大的的可扩展性能力,在企业级开发中,可以尝试去借鉴和使用。
服务网格这个概念出来很久了,从 2017 年被提出来,到 2018 年正式爆发,很多云厂商和互联网企业都在纷纷向服务网格靠拢。像蚂蚁集团、美团、百度、网易等一线互联网公司,都有服务网格的落地应用。
在我看来呢,服务网格是微服务架构的更进一步升级,它的核心目的是实现网络通信与业务逻辑的分离,使得开发人员更加专注在业务的实现上
服务网格,也就是 Service Mesh,它是专门用来处理服务通讯的基础设施层。它的主要功能是处理服务之间的通信,并且负责实现请求的可靠性传递。
Service Mesh,我们通常把他称为第三代微服务架构,既然是第三代,那么意味着他是在原来的微服务架构下做的升级。
为了更好的说明 Service Mesh,那我就不得不说一下微服务架构部分的东西。
首先,当我们把一个电商系统以微服务化架构进行拆分后,会的到这样的一个架构(如图),其中包括 Webserver、payment、inventory 等等。
(如图)这些微服务应用,会被部署到 Docker 容器、或者 Kubernetes 集群。由于每个服务的业务逻辑是独立的,比如 payment 会实现支付的业务逻辑、order 实现订单的处理、Webserver 实现客户端请求的响应等
(如图)所以,服务之间必须要相互通信,才能实现功能的完整性。比如用户把一个商品加入购物车,请求会进入到 Webserver,然后转发到 shopping cart 进行处理,并存到数据库。
而在这个过程中,每个服务之间必须要知道对方的通信地址,并且当有新的节点加入进来的时候,还需要对这些通信地址进行动态维护。所以,在第一代微服务架构中,每个微服务除了要实现业务逻辑以外,还需要解决上下游寻址、通讯、以及容错等问题。(如图)于是,在第二代微服务架构下,引入了服务注册中心来实现服务之间的寻址,并且服务之间的容错机制、负载均衡也逐步形成了独立的服务框架,比如主流的 SpringCloud、或者 Spring Cloud Alibaba。
在第二代微服务架构中,负责业务开发的小伙伴不仅仅需要关注业务逻辑,还需要花大量精力去处理微服务中的一些基础性配置工作,虽然 Spring Cloud 已经尽可能去完成了这些事情,但对于开发人员来说,学习 Spring Cloud,以及针对 Spring Cloud 的配置和维护,仍然存在较大的挑战。另外呢,也增加了整个微服务的复杂性。
实际上,在我看来,“微服务中所有的这些服务注册、容错、重试、安全等工作,都是为了保证服务之间通信的可靠性”。
于是,就有了第三代微服务架构,Service Mesh
(如图)原本模块化到微服务框架里的微服务基础能力,被进一步的从一个 SDK 中演进成了一个独立的代理进程-SideCar
SideCar 的主要职责就是负责各个微服务之间的通信,承载了原本第二代微服务架构中的服务发现、调用容错、服务治理等功能。使得微服务基础能力和业务逻辑迭代彻底解耦
之所以我们称 Service Mesh 为服务网格,是因为在大规模微服务架构中,每个服务的通信都是由 SideCar 来代理的,各个服务之间的通信拓扑图,看起来就像一个网格形状(如图)。
Istio 是目前主流的 Service Mesh 开源框架
首先,Dubbo 默认采用 Zookeeper 实现服务的注册与服务发现,简单来说啊,就是多个 Dubbo 服务之间的通信地址,是使用 Zookeeper 来维护的。
(如图)在 Zookeeper 上,会采用树形结构的方式来维护 Dubbo 服务提供端的协议地址,Dubbo 服务消费端会从 Zookeeper Server 上去查找目标服务的地址列表,从而完成服务的注册和消费的功能。
Zookeeper 会通过心跳检测机制,来判断 Dubbo 服务提供端的运行状态,来决定是否应该把这个服务从地址列表剔除。
当 Dubbo 服务提供方出现故障导致 Zookeeper 剔除了这个服务的地址,
那么 Dubbo 服务消费端需要感知到地址的变化,从而避免后续的请求发送到故障节点,导致请求失败。
也就是说 Dubbo 要提供服务下线的动态感知能力。
(如图)这个能力是通过 Zookeeper 里面提供的 Watch 机制来实现的,
简单来说呢,Dubbo 服务消费端会使用 Zookeeper 里面的 Watch 来针对 Zookeeper
Server 端的/providers 节点注册监听,一旦这个节点下的子节点发生变化,Zookeeper Server 就会发送一个事件通知 Dubbo Client 端.
Dubbo Client 端收到事件以后,就会把本地缓存的这个服务地址删除,这样后续就不会把请求发送到失败的节点上,完成服务下线感知
Eureka 是一个服务注册中心,在 Eureka 的设计里面,为了保证 Eureka 的高可用性,提供了集群的部署方式。
Eureka 的集群部署采用的是两两相互注册的方式来实现,也就是说每个 Eureka Server节点都需要发现
集群中的其他节点并建立连接,然后通过心跳的方式来维持这个连接的状态。(如图),Eureka Server 集群节点之间的数据同步方式非常简单粗暴,使用的是对等复制的方式来实现数据同步。
也就是说,在 Eureka Server 集群中,不存在所谓主从节点,任何一个节点都可以接收或者写入数据。一旦集群中的任意一个节点接收到了数据的变更,就直接同步到其他节点上。
这种无中心化节点的数据同步,需要考虑到一个数据同步死循环的问题,也就是需要区分 Eureka Server 收到的数据是属于客户端传递来的数据还是集群中其他节点发过来的同步数据。
Eureka 使用了一个时间戳的标记来实现类似于数据的版本号来解决这个问题。
另外,从 Eureka 的数据同步方案来看,Eureka 集群采用的是 AP 模型,也就是只提供高可用保障,而不提供数据强一致性保障。
之所以采用 AP,我认为注册中心它只是维护服务之间的通信地址,数据是否一致对于服务之间的通信影响并不大。
而注册中心对 Eureka 的高可用性要求会比较高,不能出现因为 Eureka 的故障导致服务之间无法通信的问题
:::info
** Eureka 的整个框架设计上的思想 : **多级缓存设计、集群之间的数据同步方案、多区域隔离以及就近访问的设计等等
:::
写在最后 : 本文仅仅用于个人复习资料用!