微服务
01.微服务介绍
01.单体和微服务介绍
1.1 单体服务架构
1.2 微服务架构介绍
通俗来说,微服务架构就是把一个大系统按业务功能分解成多个职责单一的小系统,并利用简单的方法使多个小系统相互协作,组合成一个大系统
- 微服务(Microservices)是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。
- 系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。
- 每个微服务仅关注于完成一件任务并很好地完成该任务。
- 在所有情况下,每个任务代表着一个小的业务能力。
1.3 简单微服务访问缺陷
- 比如要查询订单商品信息
- 第一:通过
用户
服务的ip和端口号访问到用户信息 - 第二:拿着用户返回的信息,去查询
订单
服务ip和端口号查询到订单 - 第三: 拿着订单的信息,去查询
产品
服务 ip:端口号 查询到最终商品的信息
- 第一:通过
- 缺点:
一旦某一个服务升级后,ip和端口号发生变化,所有调用服务都需要修改调用的ip和端口和重启服务
1.4 域名解决方案
- 微服务访问之域名解析
- 这个时候每一个服务都需要一个nginx代理,到后面的真实服务集群中
- 这样会使得整个服务变得很复杂
02.微服务架构
2.1 微服务组件
- 服务注册
Eureka、consul、nacos
- 服务提供方将自己调用地址注册到服务注册中心,让服务调用方能够方便地找到自己。
- 服务发现
Config、consul、nacos
- 服务调用方从服务注册中心找到自己需要调用的服务的地址。
- 负载均衡
Ribbon
- 服务提供方一般以多实例的形式提供服务,负载均衡功能能够让服务调用方连接到合适的服务节点。
- 并且,节点选择的工作对服务调用方来说是透明的。
- 服务网关
ZUUL、gateway
- 服务网关是服务调用的唯一入口,可以在这个组件是实现用户鉴权、动态路由、灰度发布、A/B 测试、负载限流等功能。
- 配置中心
Config、nacos、consul
- 将本地化的配置信息(properties, xml, yaml 等)注册到配置中心,实现程序包在开发、测试、生产环境的无差别性,方便程序包的迁移。
- API 管理
- 以方便的形式编写及更新 API 文档,并以方便的形式供调用者查看和测试。
- 集成框架
Spring-cloud、Double、Go-micro
- 微服务组件都以职责单一的程序包对外提供服务,
- 集成框架以配置的形式将所有微服务组件(特别是管理端组件)集成到统一的界面框架下
- 让用户能够在统一的界面中使用系统。
- 分布式事务
- 对于重要的业务,需要通过分布式事务技术(TCC、高可用消息服务、最大努力通知)保证数据的一致性。
- 调用链
Skywalking、zipkin、Pinpoint、CAT
- 记录完成一个业务逻辑时调用到的微服务,并将这种串行或并行的调用关系展示出来。
- 在系统出错时,可以方便地找到出错点。
- 支撑平台
- 系统微服务化后,系统变得更加碎片化,系统的部署、运维、监控等都比单体架构更加复杂,那么,就需要将大部分的工作自动化。
- 现在,可以通过 Docker 等工具来中和这些微服务架构带来的弊端。
- 例如持续集成、蓝绿发布、健康检查、性能健康等等。
可以这么说,如果没有合适的支撑平台或工具,就不要使用微服务架构
。
2.2 微服务架构优点
- 降低系统复杂度:每个服务都比较简单,只关注于一个业务功能。
- 松耦合:微服务架构方式是松耦合的,每个微服务可由不同团队独立开发,互不影响。
- 跨语言
- 只要符合服务 API 契约,开发人员可以自由选择开发技术。
- 这就意味着开发人员可以采用新技术编写或重构服务,由于服务相对较小,所以这并不会对整体应用造成太大影响。
- 独立部署
- 微服务架构可以使每个微服务独立部署,开发人员无需协调对服务升级或更改的部署。
- 这些更改可以在测试通过后立即部署,所以微服务架构也使得 CI/CD 成为可能。
- Docker 容器:和 Docker 容器结合的更好。
- DDD 领域驱动设计:和 DDD 的概念契合,结合开发会更好。
2.3 微服务架构的缺点
- 微服务的分布式特点带来的复杂性
- 开发人员需要基于 RPC 或者消息实现微服务之间的调用和通信
- 而这就使得服务之间的发现、服务调用链的跟踪和质量问题变得的相当棘手
- 分区的数据库体系和分布式事务
- 更新多个业务实体的业务交易相当普遍,不同服务可能拥有不同的数据库。
- CAP 原理的约束,使得我们不得不放弃传统的强一致性,而转而追求最终一致性,这个对开发人员来说是一个挑战。
- 测试挑战
- 传统的单体WEB应用只需测试单一的 REST API 即可,而对微服务进行测试,需要启动它依赖的所有其他服务。
- 这种复杂性不可低估。
- 跨多个服务的更改
- 比如在传统单体应用中,若有 A、B、C 三个服务需要更改,A 依赖 B,B 依赖 C。
- 我们只需更改相应的模块,然后一次性部署即可。
- 但是在微服务架构中,我们需要仔细规划和协调每个服务的变更部署。
- 我们需要先更新 C,然后更新 B,最后更新 A。
- 部署复杂
- 微服务由不同的大量服务构成,每种服务可能拥有自己的配置、应用实例数量以及基础服务地址。
- 这里就需要不同的配置、部署、扩展和监控组件。
- 此外,我们还需要服务发现机制,以便服务可以发现与其通信的其他服务的地址。
- 因此,成功部署微服务应用需要开发人员有更好地部署策略和高度自动化的水平。
- 总的来说(问题和挑战)
- API Gateway、服务间调用、服务发现、服务容错、服务部署、数据调用。
03.微服务实现流程
3.1 微服务实现流程
1、服务都注册到服务注册中心
- 要构建微服务体系,首先我们需要独立部署一款实现服务注册/发现功能的组件服务,目前可供选择的主流方案一般有Eureka、Consul、Nacos等
- 搞定服务注册/发现后,我们编写一个Java微服务,此时为了将该服务注册到服务注册中心
- 一般会引入Spring Cloud提供的支持对应注册中心接入的SDK,并在应用入口类中通过@EnableDiscoveryClient注解的方式标注
- 之后SDK中的逻辑就会在应用启动时执行服务注册动作,并提供给注册中心相应地探测接口,以此实现微服务与服务注册中心之间的连接。
- 以此类推,我们可以通过这种方式将一组微服务都注册到服务注册中心!
2、服务之间要互相调用
- 一般我们会通过编写FeignClient接口来实现微服务之间的调用
- 而其底层的逻辑则是通过Feign所集成的Ribbon组件去注册中心中获取目标服务的服务地址列表
- 之后Ribbon根据服务地址列表进行负载均衡调用。
- 至于服务与注册中心之间如何保证连接有效性,则依赖于服务注册中心与其SDK之间的协作机制。
3、负载均衡、熔断、限流、网关
- 而高级一点,服务之间的调用除了实现负载均衡,还要实现熔断限流
- 那么此时可以通过部署服务网关组件(例如Zuul/Spring Cloud GateWay)来实现微服务入口的熔断限流
- 内部服务之间的限流熔断则通过集成Hystrix或Sentinel组件,以客户端本地配置或远程配置中心的方式来实现。
3.2 微服务遇到的问题
**1、框架/SDK太多,后续升级维护困难**
- 在这套体系中,与服务治理相关的逻辑都是以SDK代码依赖的方式嵌入在微服务之中
- 如果某天我们想升级下服务注册中心的SDK版本,或者熔断限流组件Hystrix或Sentinel的版本,那么需要升级改造的微服务可能会是成百上千
- 且由于这些组件都与业务应用绑定在一起,在升级的过程中会不会影响业务稳定,这都是需要谨慎对待的事情,所以对SDK的升级难度可想而知的!
**2、多语言微服务SDK维护成本高**
- 试想下如果构建的微服务体系,也要支持像Go、Python或者其他语言编写的微服务的话
- 那么上述这些微服务治理相关的SDK是不是得单独再维护几套呢?
- 所以在这种体系结构中,对多语言微服务的支持就成了一个问题!
**3、服务治理策略难以统一控制**
- 基于该套体系构建的微服务体系,在对像熔断、限流、负载均衡等服务治理相关的策略管理上,都是比较分散的
- 可能有人会写到自己的本地配置文件,有人会硬编码到代码逻辑中,也可能有人会将其配置到远程配置中心
- 总之对于服务治理策略逻辑都是由对应的开发人员自己控制,这样就很难形成统一的控制体系!
**4、服务治理逻辑嵌入业务应用,占有业务服务资源**
- 在这套微服务体系中,服务治理相关的逻辑都是在微服务应用进程中寄生运行的
- 这多少会占有宝贵的业务服务器资源,影响应用性能的发挥!
**5、额外的服务治理组件的维护成本**
- 无论是服务注册中心、还是服务网关,这些除了微服务应用本身之外服务治理组件,都需要我们以中间件基础服务的方式进行维护
- 需要额外的人力、额外的服务器成本
04.各语言微服务框架
4.1 Java
Spring Boot
- Spring Boot的设计目的是简化新Spring应用初始搭建以及开发过程,2017年有64.4%的受访者决定使用Spring Boot,可以说是最受欢迎的微服务开发框架。
- 利用Spring Boot开发的便捷度简化分布式系统基础设施的开发,比如像配置中心、注册、负载均衡等方面都可以做到一键启动和一键部署。
Spring Cloud
- Spring Cloud是一个系列框架的合计,基于HTTP(s)的RETS服务构建服务体系,Spring Cloud能够帮助架构师构建一整套完整的微服务架构技术生态链。
Dubbo
- Dubbo是由阿里巴巴开源的分布式服务化治理框架,通过RPC请求方式访问。
- Dubbo是在阿里巴巴的电商平台中逐渐探索演进所形成的,经历过复杂业务的高并发挑战,比Spring Cloud的开源时间还要早。
- 目前阿里、京东、当当、携程、去哪等一些企业都在使用Dubbo。
Dropwizard
- Dropwizard将Java生态系统中各个问题域里最好的组建集成于一身,能够快速打造一个Rest风格的后台,还可以整合Dropwizard核心以外的项目。
- 国内现在使用Dropwizard还很少,资源也不多,但是与Spring Boot相比,Dropwizard在轻量化上更有优势,同时如果用过Spring,那么基本也会使用Spring Boot。
框架对比
-
从公司整体规划: 不会选择很久没人维护的 dubbo,重启之后也未必是原班人马
-
从程序员招聘难度 :招 springcloud 的程序员会更好招,因为更新更炫
-
从系统结构简易程序:
- springcloud 的系统结构更简单、“注册 + springmvc=springcloud”
-
从性能:
- dubbo 的网络消耗小于 springcloud,但是在国内 95% 的公司内,网络消耗不是什么太大问题
- 如果真的成了问题,通过压缩、二进制、高速缓存、分段降级等方法,很容易解
-
从开发难易度:
- dubbo 的神坑是 jar 包依赖,开发阶段难度极大
4.2 Go
Go-Kit
是分布式开发的工具合集,适合用于大型业务场景下构建微服务;Goa
是用Go语言构建的微服务框架;Dubbogo
是和阿里巴巴开源的Dubbo能够兼容的Golang微服务框架。
4.3 Python
- Python相关的微服务框架非常少,用的比较多的是
Nameko
。 - Nameko让实现微服务变得更简单,同时也提供了很丰富的功能,比如支持负载均衡、服务发现还支持依赖自动注入等
- 使用起来很方便,但是有限速、超时和权限机制不完善等缺点。
02.Eureka注册中心
01.注册中心原理
1.1 为什么需要注册中心
- 在RPC服务和微服务诞生的时候,就已经有了注册中心的需求了。
- 在最初的架构体系中,集群的概念还不那么流行,且机器数量也比较少
此时直接使用DNS+Nginx
就可以满足几乎所有RESTful服务的发现,相关的注册信息直接配置在Nginx
。- 但是随着微服务的流行与流量的激增,机器规模逐渐变大,并且机器会有频繁的上下线行为
- 这种时候需要
运维手动地去维护这个配置信息
是一个很麻烦的操作。 - 所以开发者们开始希望有这么一个东西,它能维护一个服务列表,哪个机器上线了,哪个机器宕机了
- 这些信息都会
自动更新到服务列表上
,客户端拿到这个列表,直接进行服务调用即,这个就是注册中心
1.2 注册中心原理
- 注册中心主要涉及到三大角色
- 服务提供者
- 服务消费者
- 注册中心
- 它们之间的关系大致如下
- 各个微服务在启动时,将自己的网络地址等信息注册到注册中心,注册中心存储这些数据。
- 服务消费者从注册中心查询服务提供者的地址,并通过该地址调用服务提供者的接口。
- 各个微服务与注册中心使用一定机制(例如心跳)通信。如果注册中心与某微服务长时间无法通信,就会注销该实例。
- 微服务网络地址发送变化(例如实例增加或IP变动等)时,会重新注册到注册中心。
- 这样,服务消费者就无需人工修改提供者的网络地址了。
- 注册中心的架构图如下
1.3 注册中心功能
1)服务注册表
- 服务注册表是注册中心的核心,它用来记录各个微服务的信息,例如微服务的名称、IP、端口等。
- 服务注册表提供查询API和管理API,查询API用于查询可用的微服务实例,管理API用于服务的注册与注销
2)服务注册与发现
- 服务注册是指微服务在启动时,将自己的信息注册到注册中心的过程。
- 服务发现是指查询可用的微服务列表及网络地址的机制。
3)服务检查
- 注册中心使用一定的机制定时检测已注册的服务,如发现某实例长时间无法访问,就会从服务注册表移除该实例。
02.Eureka注册中心
2.1 Eureka图解
- 第一:服务注册
- user和product两个微服务,在启动时将自己的 名称、IP、端口等注册到 Eureka服务端
- 第二:服务发现
- user微服务要访问产品微服务,会请求Eureka服务端,Eureka返回 user微服务注册的 ip:端口 列表
- user微服务根据负载均衡策略选择其中一个 ip:端口 来访问 product产品微服务
- 第三:服务检查
- 在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒
- 如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)
2.2 Eureka介绍
- Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。
- SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。
- Eureka重要概念
- Register(服务注册):把自己的IP和端口注册给Eureka。
- Renew(服务续约):发送心跳包,每30秒发送一次。告诉Eureka自己还活着。
- Cancel(服务下线):当provider关闭时会向Eureka发送消息,把自己从服务列表中删除。防止consumer调用到不存在的服务。
- Get Registry(获取服务注册列表):获取其他服务列表。
- Replicate(集群中数据同步):eureka集群中的数据复制与同步。
- Make Remote Call(远程调用):完成服务的远程调用。
2.3 Eureka组件功能
- Eureka包含两个组件:Eureka Server和Eureka Client。
- Eureka Server
- Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册
- 这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
- Eureka Client
- Eureka Client是一个java客户端,用于简化与Eureka Server的交互
- 客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。
- 在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳
- Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。
03.常用注册中心比较
3.0 CAP理论
C(Consistency)
这里指的是强一致性
保证在一定时间内,集群中的各个节点会达到较强的一致性
,同时,为了达到这一点,一般会牺牲一点响应时间。- 而放弃C也不意味着放弃一致性,而是放弃强一致性,允许系统内有一定的数据不一致情况的存在
A (Avalibility):可用性
- 意味着系统一直处于可用状态,个别节点的故障不会影响整个服务的运作
P(Partition Tolerance):分区容忍性
- 当系统出现网络分区等情况时,依然能对外提供服务。
- 想到达到这一点,一般来说会把数据复制到多个分区里,来提高分区容忍性。这个一般是不会被抛弃的
3.1 Zookeeper
- 特点:
- 1)牺牲可用性,保证一致性
- 2)主从模式,无配置管理
- Apache Zookeeper所选择的是CP,也就是放弃了高可用性。
- 为了达到C,Zookeeper采用的是自己的ZAB协议。
- 总得来说,Zookeeper集群在进行消息同步的时候,必须有一半以上结点完成了同步才会返回;
- 而当Master结点挂了或者集群中有过半的结点不能工作了,此时就会触发故障恢复,重新进行Master选举。
在这个过程中,整个Zookeeper集群无法对外提供服务,从而失去了A(可用性)
牺牲服务可用性、保证数据一致性
- 对于大多数的分布式环境来说,特别是数据储存的环境,数据一致性是非常重要的标准。
- 但对于服务发现来说,其实并没有那么严格。
- 就算一个注册中心没有及时获取到实时的服务实例状态,直接把服务列表(可能存在错误)发给客户端,也不会导致灾难性的错误。
- 因为对于客户端来说会有重试机制,少数的实例无法访问不会导致大问题。
3.2 Eureka
- 特点:
- 1)牺牲一致性,保证可用性
- 2)无主从模式,无配置管理
- Spring Cloud Eureka所选择的是AP,放弃了强一致性。
- 从使用角度来看,Eureka的特点就是使用Java语言来开发的,并且也是Spring Cloud的子项目
- 所以可以直接通过引入jar包的方式来集成Eureka,这点非常方便
- 而在架构上,Eureka集群采用的是去中心化结构,也就是说Eureka集群中的各个结点都是平等的,
没有主从的概念
。 - 通过互相注册的方式来进行消息同步和保证高可用。
- 并且一个Eureka Server结点挂掉了,还有其他同等的结点来提供服务,并不会引发服务的中断。
保证可用性,牺牲强一致性
- 但同样的也会带来一定的不一致性。但是之前也说了,在服务注册这种场景,一致性的要求其实并没有很高
- 另外,Eureka还有一个自我保护机制,用来应对网络问题导致的服务不可用,从而能更进一步地保证可用性。
3.3 Consul
- 特点:
- 1)牺牲可用性,保证一致性
- 2)主从模式,有配置管理
- Consul是HashiCorp公司推出的一个开源工具。
- 它和Eureka一个区别就似乎,
Consul是用Go语言编写的,所以无法像Eureka那样直接引入jar包就能集成
,它还需要去服务器中进行额外的安装。 - Consul的功能相比于Eureka来说也更加强大,因为除了注册中心的功能之外,
Consul还能起到配置中心的作用
。 - 而Eureka只能当注册中心,想搞配置中心的话,还得搭配Spring Cloud Config+Spring Cloud Bus。
牺牲服务可用性、保证数据一致性
- Consul它保证的是CP,使用raft协议,要求必须有过半的结点都写入成功才算是注册成功了
- 并且它也有Master和Follower的概念,在Master挂掉后,也需要自己内部进行新一轮Master选举,在此期间,Consul服务不可用
3.4 Nacos
- 特点:
- 1)强一致性和强可用性,可以自己选择
- 2)无主从模式,有配置中心
- Nacos是阿里巴巴旗下的开源项目,在2018年开源,是Spring Cloud Alibaba的子项目。
- Nacos一大特性是即支持CP,也支持AP,可以根据需要灵活选择。
- Nacos除了注册中心之外,也能充当配置中心的作用。
- 且配置中心可以按照namespace,group等维度来进行数据隔离,来达到不同环境之间配置隔离的功能。
- 另外值得一提的是,Nacos作为配置中心的持久化机制可以依赖于Mysql来完成(默认依赖于内置数据库)。
- 只需要将Nacos目录下的sql脚本放到mysql中执行(会生成11个表),然后在nacos配置文件里面配一下mysql的账号密码即可。
- 这样使用mysql作为数据源的方式相比于nacos内置数据库来说更容易管理
03.Ribbon负载均衡
01.负载均衡介绍
1.1 服务端负载均衡
客户端发送请求被服务端负载均衡拦截,根据负载均衡算法分发请求到具体服务器上处理请求
1.2 Ribbon客户端负载均衡
- 第一:通过微服务域名从注册中获取要访问的服务列表
- 第二:使用负载均算法,从服务列表中获取到合适的 ip服务
02.Ribbon介绍
- Ribbon是Spring Cloud核心组件之一,它提供的最重要的功能就是负载均衡,和硬件负载均衡F5不同,它的负载均衡是基于客户端的
- Zuul网关和Feign可以通过Ribbon轻松的实现服务的负载均衡,同时避免了与业务无关的冗余代码。
2.1 @LoadBalanced原理
- 在Ribbon示例中可以看到,Ribbon通过一个@LoadBalanced注解就实现了RestTemplate请求的负载均衡
- RestTemplate在发送请求的时候会被ClientHttpRequestInterceptor拦截,LoadBalancerInterceptor是ClientHttpRequestInterceptor的实现类
- 它的作用就是用于RestTemplate的负载均衡,LoadBalancerInterceptor将负载均衡的核心逻辑交给了loadBalancer
2.2 获取服务实例列表
- Ribbon使用ServerList接口抽象服务实例列表,Ribbon获取服务实例有如下两种方法,可以使用参数{service-name}.ribbon.NIWSServerListClassName进行选择。
- Ribbon通过ConfigurationBasedServerList类实现配置服务列表,多个服务实例用逗号隔开
spring.application.name=shop-order
shop-product.ribbon.listOfServers=http://localhost:8001,http://localhost:8002
- 利用注册中心获取
- 利用配置文件获取服务实例列表扩展性很差,因为在服务实例上线或者下线的情况下,需要手动修改配置文件,扩展性很低
- 一个健壮的微服务系统会采用注册中心的方式维护服务的上下线。
- Ribbon可以使用DiscoveryEnabledNIWSServerList维护和Eureka之间的服务上下线
2.3 动态更新服务实例列表
- 服务实例上下线在微服务系统中是一个非常常见的场景,Ribbon也实现了该功能,Ribbon定时更新的接口抽象为ServerListUpdater。
- 当Ribbon从注册中心获取了服务实例列表之后,Ribbon需要动态更新服务实例列表,抽象接口为ServerListUpdater
- 更新的方式有两种,一种是通过定时任务定时拉取服务实例列表,另一种是通过Eureka服务事件通知的方式。
- Ribbon可以通过配置项{service-name}.ribbon.ServerListUpdaterClassName进行选择更新方
方法1:定时拉取
- Ribbon会使用一个定时任务线程池定时拉取更新数据。
方法2:事件通知
- 和PollingServerListUpdater不同的是,如果注册中心是Eureka,可以采用事件通知的方式
- 即当Eureka注册中心发生注册信息变更的时候,那么就将消息发送到事件监听者
- Ribbon使用EurekaNotificationServerListUpdater实现类进行更新,首先会创建一个Eureka监听器
- 当接口接受到通知事件之后,会将更新逻辑提交到线程池中执行
2.4 对服务进行心跳检测
- 服务列表中的服务实例未必一直都处于可用的状态,Ribbon会对服务实例进行检测
- PingerStrategy接口抽象检测的策略,Ribbon默认采用了串行的方式进行检测,如果有必要,我们可以通过该接口实现并行的检测方式。
- Pinger会定时通过PingerStrategy获取更新的服务实例,并调用监听者。
2.5 负载均衡调度器
- 从ServerListFilter获取到一个微服务实例集合后,ILoadBalancer需要使用某个策略从集合中选择一个服务实例, 而策略的抽象接口为IRule
- 选择服务实例之后,ILoadBalancer在调用过程中,会记录请求的执行结果,比如请求的失败成功情况,调用耗时等,IRule接口也可以根据这些信息决定是否使用某个Server。
| 名称 | 解释 |
| — | — |
| RoundRobinRule | 轮训策略 |
| RandomRule | 随机策略 |
| BestAvailableRule | 过滤出故障服务器后,选择一个并发量最小的 |
| WeightedResponseTimeRule | 针对响应时间加权轮询 |
| AvailabilityFilteringRule | 可用过滤策略,先过滤出故障的或并发请求大于阈值的一部分服务实例,然后再以线性轮询的方式从过滤后的实例清单中选出一个; |
| ZoneAvoidanceRule | 从最佳区域实例集合中选择一个最优性能的服务实例 |
| RetryRule | 选择一个Server,如果失败,重新选择一个Server重试 |
04.Hystrix熔断
01.背景介绍
1.1 服务雪崩
- 分布式系统环境下,服务间类似依赖非常常见,一个业务调用通常依赖多个基础服务
- 但是如果其中一个服务崩坏掉会出现什么样的情况呢?如下图
So,简单地讲,一个服务失败,导致整条链路的服务都失败的情形,我们称之为服务雪崩。
- 当Service A的流量波动很大,流量经常会突然性增加!那么在这种情况下,就算Service A能扛得住请求,Service B和Service C未必能扛得住这突发的请求。
- 此时,如果Service C因为抗不住请求,变得不可用。
- 那么Service B的请求也会阻塞,慢慢耗尽Service B的线程资源,Service B就会变得不可用。
- 紧接着,Service A也会不可用。
1.2 引起雪崩的原因
- 原因大致有四
- 1、硬件故障;
- 2、程序Bug;
- 3、缓存击穿(用户大量访问缓存中没有的键值,导致大量请求查询数据库,使数据库压力过大);
- 4、用户大量请求;
- 服务雪崩的三个阶段
- 第一阶段: 服务不可用;
- 第二阶段:调用端重试加大流量(用户重试/代码逻辑重试);
- 第三阶段:服务调用者不可用(同步等待造成的资源耗尽);
1.3 解决雪崩的方案
- 1) 应用扩容(扩大服务器承受力)
- 加机器
- 升级硬件
- 2)流量控制
(超出限定流量,返回类似重试页面让用户稍后再试)- 限流
- 关闭重试
- 3) 缓存
- 将用户可能访问的数据大量的放入缓存中,减少访问数据库的请求。
- 4)服务降级
- 服务接口拒绝服务
- 页面拒绝服务
- 延迟持久化
- 随机拒绝服务
- 5) 服务熔断
- 如果对服务降级和服务熔断的概念模糊 点击此处了解详情
02.服务熔断与服务降级
2.1 什么是服务熔断
- 当下游的服务因为某种原因突然变得不可用或响应过慢,上游服务为了保证自己整体服务的可用性
- 不再继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用
2.2 服务熔断的原理
- 当远程服务被调用时,断路器将监视这个调用,如调用时间太长,断路器将会介入并中断调用。
- 此外,断路器将监视所有对远程资源的调用,如对某一个远程资源的调用失败次数足够多
- 那么断路器会出现并采取快速失败,阻止将来调用此远程资源的请求.
- 断路器模式的状态图
- 最开始处于closed状态,一旦检测到错误到达一定阈值,便转为open状态;
- 这时候会有个 reset timeout,到了这个时间了,会转移到half open状态,尝试放行一部分请求到后端
- 一旦检测成功便回归到closed状态,即恢复服务
断路器实现:
阿里公司出的Sentinel
netflix的Hystrix
Hystrix中熔断的常用配置:
circuitBreaker.requestVolumeThreshold
默认值20.意思是至少有20个请求才进行errorThresholdPercentage错误百分比计算。比如一段时间(10s)内有19个请求全部失败了。错误百分比是100%,但熔断器不会打开,因为requestVolumeThreshold的值是20. 这个参数非常重要,
circuitBreaker.sleepWindowInMilliseconds
过多长时间,熔断器再次检测是否开启,默认为5000,即5s钟
circuitBreaker.errorThresholdPercentage
设定错误百分比,默认值50%,例如一段时间(10s)内有100个请求,其中有55个超时或者异常返回了,那么这段时间内的错误百分比是55%,大于了默认值50%,这种情况下触发熔断器-打开。
按照以上配置的熔断器如下:
每当20个请求中,有50%失败时,熔断器就会打开,此时再调用此服务,将会直接返回失败,不再调远程服务。直到5s钟之后,重新检测该触发条件,判断是否把熔断器关闭,或者继续打开
2.3 服务降级
服务熔断可视为降级方式的一种!
- 一、当下游的服务因为某种原因响应过慢,下游服务主动停掉一些不太重要的业务,释放出服务器资源,增加响应速度!
- 二、当下游的服务因为某种原因不可用,上游主动调用本地的一些降级逻辑,避免卡顿,迅速返回给用户!
**在实际的项目中,采用以下的方式来完成降级工作**
- 梳理出核心业务流程和非核心业务流程。
- 然后在非核心业务流程上加上开关,一旦发现系统扛不住,关掉开关,结束这些次要流程。
- 一个微服务下肯定有很多功能,那自己区分出主要功能和次要功能。
- 然后次要功能加上开关,需要降级的时候,把次要功能关了吧!
- 降低一致性了,即将核心业务流程的同步改异步,将强一致性改最终一致性。
03.Hystrix
3.1 Hystrix简介
- Hystrix是由Netflix创建一个类库。
- 在微服务的分布式环境中,系统存在许多服务依赖。在高并发访问下,这些依赖的稳定性与否对系统的影响非常大,
- 但是依赖有很多不可控问题:如网络连接缓慢,资源繁忙,暂时不可用,服务脱机等。
- Hystrix可以通过添加延迟容错和容错逻辑来帮助我们控制这些分布式服务之间的交互。
- Hystrix通过隔离服务之间的接入点,
阻止它们之间的级联故障,并提供备用选项,从而提高系统的整体弹性
。 断路器就是能够在发生问题的时候将请求断开,类似于保险丝
,当电压过高的时候自动熔断。这也就是断路器的熔断机制。
设计目标
1. 对来自依赖的延迟和故障进行防护和控制——这些依赖通常都是通过网络访问的
2. 阻止故障的连锁反应
3. 快速失败并迅速恢复
4. 回退并优雅降级
5. 提供近实时的监控与告警
设计原则
1. 防止任何单独的依赖耗尽资源(线程)
2. 过载立即切断并快速失败,防止排队
3. 尽可能提供回退以保护用户免受故障
4. 使用隔离技术(例如隔板,泳道和断路器模式)来限制任何一个依赖的影响
5. 通过近实时的指标,监控和告警,确保故障被及时发现
6. 通过动态修改配置属性,确保故障及时恢复
7. 防止整个依赖客户端执行失败,而不仅仅是网络通信
3.2 服务提供方降级
- 场景假设1( 服务提供方报错) :
- 在服务提供端中因为访问不到数据库中的数据(比如数据不存在,或是数据库压力过大,查询请求队列中)
- 在这种情况下,服务提供方这边如何实现服务降级,以防止服务雪崩.
- 在 ProductController中加入断路逻辑
@RequestMapping("/get/{id}")
@HystrixCommand(fallbackMethod="errorCallBack") //模仿没有这个数据时,服务降级
public Object get(@PathVariable("id") long id){
Product p=this.productService.findById(id);
if( p==null){
throw new RuntimeException("查无此产品");
}
return p;
}
//指定一个降级的方法
public Object errorCallBack( @PathVariable("id") long id ){
return id+"不存在,error";
}
- 启动服务后测试
3.3 服务消费方降级
- 场景假设2:
- 因为网络抖动,或服务端维护导致的服务暂时不可用,此时是客户端联接不到服务器
- 因为feign有重试机制,这样会导致系统长时间不响应,那么在这种情况上如何通过 feign+hystrix 在服务的消费方实现服务熔断(回退机制)呢?
1)建立一个包 fallback
- 用于存回退处理类 IProductClientServiceFallbackFactory,这个类有出现请求异常时的处理
package com.yc.springcloud2.fallback;
import com.yc.springcloud2.bean.Product;
import com.yc.springcloud2.service.IProductClientService;
import feign.hystrix.FallbackFactory;
import java.util.List;
@Component //必须被spring 托管
public class IProductClientServiceFallbackFactory implements FallbackFactory<IProductClientService> {
@Override
public IProductClientService create(Throwable throwable) {
//这里提供请求方法出问题时回退处理机制
return new IProductClientService(){
@Override
public Product getProduct(long id) {
Product p=new Product();
p.setProductId(999999999L);
p.setProductDesc("error");
return p;
}
@Override
public List<Product> listProduct() {
return null;
}
@Override
public boolean addPorduct(Product product) {
return false;
}
};
}
}
2)在业务接口上加入 fallbackFactory属性指定异常处理类
@FeignClient(name="MICROSERVICE-PROVIDER-PRODUCT",
configuration = FeignClientConfig.class,
fallbackFactory = IProductClientServiceFallbackFactory.class) // 配置要按自定义的类FeignClientConfig
public interface IProductClientService {
3)启动 microservice-consumer-feign客户端进行测试, 在测试时,尝试关闭生产端,看它是否回退
05.Turbine监控
01.Turbine
1.1 Turbine介绍
- Turbine是聚合服务器发送事件流数据的一个工具,Hystrix的监控中,只能监控单个节点,实际生产中都为集群
- 因此可以通过Turbine来监控集群下Hystrix的metrics情况
- Turbine的github地址:https://github.com/Netflix/Turbine
1.2 使用场景
- 在复杂的分布式系统中,相同服务的结点经常需要部署上百甚至上千个
- **很多时候,运维人员希望能够把相同服务的节点状态以一个整体集群的形式展现出来,这样可以更好的把握整个系统的状态。 **
- 为此,
**Netflix**
又提供了一个开源项目**Turbine**
**来提供把多个 ****hystrix.stream**
的内容聚合为一个数据源供**Dashboard**
展示。 **Turbine**
使用了Netflix的另一个开源项目**Archaius**
来做配置文件的管理,其提供了非常强大的
06.ZUUL网关
https://www.cnblogs.com/duanxz/p/7527765.html
01.网关介绍
1.1 微服务架构-不足
- 现有架构介绍
- 我们使用Spring Cloud Netflix中的Eureka实现了服务注册中心以及服务注册与发现;
- 而服务间通过Ribbon或Feign实现服务的消费以及均衡负载;
- 通过Spring Cloud Config实现了应用多环境的外部化配置以及版本管理。
- 为了使得服务集群更为健壮,使用Hystrix的融断机制来避免在微服务架构中个别服务出现异常时引起的故障蔓延。
- 在该架构中,我们的服务集群包含
- 内部服务Service A和Service B,他们都会注册与订阅服务至Eureka Server
- 而Open Service是一个对外的服务,通过均衡负载公开至服务调用方。
- 这里我们把焦点聚集在对外服务这块,这样的实现是否合理,或者是否有更好的实现方式呢?
1.2 上面架构问题
先来说说这样架构需要做的一些事儿以及存在的不足
- 第一,破坏了服务无状态特点
- 为了保证对外服务的安全性,我们需要实现
对服务访问的权限控制
,而开放服务的权限控制机制将会贯穿并污染整个开放服务的业务逻辑 - 这会带来的最直接问题是,破坏了服务集群中REST API无状态的特点。
- 从具体开发和测试的角度来说,在工作中除了要考虑实际的业务逻辑之外,还需要额外可续对接口访问的控制处理。
- 为了保证对外服务的安全性,我们需要实现
- 第二,无法直接复用既有接口
- 当我们需要对一个即有的集群内访问接口,实现外部服务访问时
- 我们不得不通过在原有接口上增加校验逻辑,或增加一个代理调用来实现权限控制,无法直接复用原有的接口。
让客户端直接与各个微服务通讯,会有以下的问题:
- 客户端会多次请求不同的微服务,增加了客户端的复杂性。
- 存在跨域请求,在一定场景下处理相对复杂。
- 认证复杂,每个服务都需要独立认证。
- 难以重构,随着项目的迭代,可能需要重新划分微服务。
- 例如,可能将多个服务合并成一个或者将一个服务拆分成多个。
- 如果客户端直接与微服务通讯,那么重构将会很难实施。
- 某些微服务可能使用了防火墙/浏览器不友好的协议,直接访问会有一定困难。
1.3 网关主要功能
**路由转发**
- 之前说了「API网关」是内部微服务的对外唯一入口,所以外面全部的请求都会先发到这个「API网关」上
- 然后由「API网关」来根据不同的请求去路由到不同的微服务节点上。
- 例如
可以 根据路径 来转发、也可以 根据参数 来转发
。 - 并且由于内部微服务实例也会随着业务调整不停的变更,增加或者删除节点
- 「API网关」可以与「服务注册」模块进行协同工作,保证将外部请求转发到最合适的微服务实例上面去。
**负载均衡**
- 既然「API网关」是内部微服务的单一入口,所以「API网关」在收到外部请求之后,还可以根据内部微服务每个实例的负荷情况进行动态的负载均衡调节。
- 一旦内部的某个微服务实例负载很高,甚至是不能及时响应,则「API网关」就通过负载均衡策略减少或停止向这个实例转发请求。
- 当所有的内部微服务实例都处理不过来的时候,「API网关」还可以采用限流或熔断的形式阻止外部请求,以保障整个系统的可用性。
**安全认证**
- 「API网关」就像是微服务的大门守卫,每一个请求进来之后,都必须先在「API网关」上进行身份验证
- 身份验证通过后才转发给后面的服务,转发的时候一般也会带上身份信息。
- 同时「API网关」也需要对每一个请求进行安全性检查,例如参数的安全性、传输的安全性等等。
**日志记录**
- 既然所有的请求都需要走「API网关」,那么我们就可以在「API网关」上统一集中的记录下这些行为日志。
- 这些日志既可以作为我们后续事件查询使用,也可以作为系统的性能监控使用。
**数据转换**
- 因为「API网关」对外是面向多种不同的客户端,不同的客户端所传输的数据类型可能是不一样的。
- 因此「API网关」还需要具备数据转换的功能,将不同客户端传输进来的数据转换成同一种类型再转发给内部微服务上
- 这样,兼容了这些请求的多样性,保证了微服务的灵活性。
1.4 开源网关服务
**Zuul**
- Zuul 是由 Netflix 所开源的组件,基于JAVA技术栈开发的。
- Zuul网关的使用热度非常高,并且也集成到了 Spring Cloud 全家桶中了,使用起来非常方便。
- 看到Zuul的一个简化结构,过滤器filter是整个Zuul的核心
- 分为前置过滤器(pre filter)
- 路由过滤器(routing filter)
- 后置过滤器(post filter)
- 错误过滤器(error filter)
- 一个请求过来,会先执行所有的 pre filter,然后再通过 routing filter 将请求转发给后端服务,后端服务进行结果响应之后,再执行 post filter,最后再响应给客户端。
- 在不同的filter里面可以执行不同的逻辑,比如安全检查、日志记录等等。
Tyk
- Tyk是一个基于GO编写的,轻量级、快速可伸缩的开源的API网关。
Kong
- Kong可以做到高性能、插件自定义、集群以及易于使用的Restful API管理。
02.zuul
2.1 Zuul与Nginx配合
- Nginx的作用是反向代理、负载均衡,Zuul的作用是保障微服务的安全访问,拦截微服务请求,校验合法性及负载均衡。
2.2 Zuul 1.0
- Zuul网关的核心是一系列的过滤器,这些过滤器可以对请求或者响应结果做一系列过滤
- Zuul 提供了一个框架可以支持动态加载,编译,运行这些过滤器
- 虽然Zuul 支持任何可以在jvm上跑的语言,但是目前zuul的过滤器只能使用Groovy脚本来编写
- 编写好的过滤器脚本一般放在zuul服务器的固定目录,zuul服务器会开启一个线程定时去轮询被修改或者新增的过滤器
- 然后动态进行编译,加载到内存,然后等后续有请求进来,新增或者修改后的过滤器就会生效了。
在zuul中过滤器分为四种:
- PRE Filters(前置过滤器)
- 当请求会路由转发到具体后端服务器前执行的过滤器,比如鉴权过滤器,日志过滤器,还有路由选择过滤器
- ROUTING Filters (路由过滤器)
- 该过滤器作用是把请求具体转发到后端服务器上,一般是通过Apache HttpClient 或者 Netflix Ribbon把请求发送到具体的后端服务器上
- POST Filters(后置过滤器)
- 当把请求路由到具体后端服务器后执行的过滤器;
- 场景有添加标准http 响应头,收集一些统计数据(比如请求耗时等),写入请求结果到请求方等。
- ERROR Filters(错误过滤器)
- 当上面任何一个类型过滤器执行出错时候执行该过滤器
2.3 Zuul 2.0新特性
- Netty作为高性能异步网络通讯框架,在dubbo,rocketmq,sofa等知名开源框架中都有使用
- netty server作为网关监听服务器监听客户端发来的请求,然后把请求转发到前置过滤器(inbound filters)进行处理
- 处理完毕后在把请求使用netty client代理到具体的后端服务器进行处理
- 处理完毕后在把结果交给后者过滤器(outbound filters)进行处理
- 然后把处理结果通过nettyServer写回客户端
- 特性说明
- 在zuul1.0时候客户端发起的请求后需要同步等待zuul网关返回,zuul网关这边对每个请求会分派一个线程来进行处理,这会导致并发请求数量有限。
- 而zuul2.0使用netty作为异步通讯,可以大大加大并发请求量。
07.Config配置中心
01.配置中心介绍
1.1 配置中心
- Spring Cloud 配置中心为分布式系统中的服务器端和客户端提供外部化配置支持。
- 通过Config-Server,你可以在一个地方集中对所有环境中的应用程序的外部化配置进行管理。
- 例如,当一个应用程序从开发环境切换到测试环境,然后再从测试环境切换到生产环境
- 你可以使用Config-Server统一管理这些环境之间的配置,并确保应用程序在迁移时能够拥有它运行所需要的一切配置。
- 简而言之:
Config-Server 就是用来实现配置统一管理和不同环境间配置的统一切换的。
- Config-Server 服务器的后端存储默认使用Git,因此它很容易支持配置环境的标签版本,同时可供多数的内容管理工具去访问。
- 你也可以很容易地添加其他的替代实现,并将它们插入到Spring配置中。
- 相关产品:
- 来自淘宝的Diamond:https://github.com/takeseem/diamond
- 来自百度的Disconf:https://disconf.readthedocs.io/zh_CN/latest/
- 来自Springcloud的Config-Server:https://cloud.spring.io/spring-cloud-stream/
1.2 配置中心三个角色
1)配置中心服务端:
- 为配置客户端提供对应的配置信息,配置信息的来源是配置仓库。
- 应用启动时,会从配置仓库拉取配置信息缓存到本地仓库中。
连接配置仓库、拉取远程配置&本地缓存、对外提供API接口服务。
2)配置中心客户端:
- 就是在启动时从服务端把配置信息拉取到本地,然后设置到 Enviroment 中。
- Spring Cloud Config 在项目启动时加载配置内容这一机制,导致了它存在一个缺陷,修改配置文件内容后,不会自动刷新。
- 默认访问 http://localhost:3302/actuator/refresh 接口会刷新配置
- github 提供了一种 webhook 的方式,当有代码变更的时候,会调用我们设置的地址,实现自动刷新
3)配置仓库:
- 为配置中心服务端提供配置信息存储,Spring Cloud Config 默认是使用git作为仓库的。
08.Skywalking调用链
01.调用工具链
- https://blog.youkuaiyun.com/weixin_39866487/article/details/111581322
- https://blog.youkuaiyun.com/weixin_38004638/article/details/115975798
1.1 调用用工具链对比
1.CAT
- 是一个更综合性的平台,提供的监控功能最全面,国内几个大厂生产也都在使用。
- 但研发进度及版本更新相对较慢。
2.Zipkin
- 由Twitter开源,调用链分析工具,基于spring-cloud-sleuth得到广泛使用,非常轻量,使用部署简单。
3.Skywalking
- 专注于链路和性能监控,国产开源,埋点无侵入,UI功能较强。
- 能够加入Apache孵化器,设计思想及代码得到一定认可,后期应该也会有更多的发展空间及研发人员投入。
- 目前使用厂商最多,版本更新较快。
4.Pinpoint
- 专注于链路和性能监控,韩国研发团队开源,埋点无侵入,UI功能较强
- 但毕竟是小团队,不知道会不会一直维护着,目前版本仍在更新中
1.2 前言
- 在微服务架构中,一次请求往往涉及到多个模块,多个中间件,多台机器的相互协作才能完成。
- 这一系列调用请求中,有些是串行的,有些是并行的,那么如何确定这个请求背后调用了哪些应用,哪些模块,哪些节点及调用的先后顺序?
- 如果有用户反馈某个页面很慢,我们知道这个页面的请求调用链是 A -----> C -----> B -----> D,此时如何定位可能是哪个模块引起的问题。
- 每个服务 Service A,B,C,D 都有好几台机器,怎么知道某个请求调用了服务的具体哪台机器呢?
- 排查问题难度大,周期长
- 特定场景难复现
- 系统性能瓶颈分析较难
分布式调用链就是为了解决以上几个问题而生
1.3 调用链的作用
- 自动采取数据
- 分析数据产生完整调用链:有了请求的完整调用链,问题有很大概率可复现
- 数据可视化:每个组件的性能可视化,能帮助我们很好地定位系统的瓶颈,及时找出问题所在
02.调用链标准
2.1 分布式调用链标准
OpenTracing 规范
- 为了解决不同的分布式追踪系统 API 不兼容的问题,诞生了 OpenTracing 规范
- OpenTracing 是一个轻量级的标准化层,它位于应用程序/类库和追踪或日志分析程序之间
OpenTracing 的数据模型,主要有以下三个
**1)Trace**
:一个完整请求链路- 一次下单的完整请求就是一个 Trace, 显然对于这个请求来说,必须要有一个全局标识来标识这一个请求
- 一个完整请求链路的追踪ID(traceid)用于查出本次请求调用的所有服务,每一次服务调用的跨度ID(spanid)用来记录调用顺序
**2)Span**
:一次调用过程(需要有开始时间和结束时间)- 每一次调用就称为一个 Span,每一次调用都要带上全局的 TraceId, 这样才可把全局 TraceId 与每个调用关联起来
- 上游服务parenetid用来记录调用的层级关系
**3)SpanContext**
:Trace 的全局上下文信息, 如里面有traceId- 这个 TraceId 就是通过 SpanContext 传输的,既然要传输显然都要遵循协议来调用。
- 调用时间timestamp,把请求发出、接收、处理的时间都记录下来,计算业务处理耗时和网络耗时,然后用可视化界面展示出来每个调用链路,性能,故障
我们把传输协议比作车,把 SpanContext 比作货,把 Span 比作路应该会更好理解一些
2.2 调用链图解
理解了这三个概念,接下来我看看分布式追踪系统如何采集统一图中的微服务调用链
- 有了这些信息,Collector 收集的每次调用的信息如下
以上实现看起来确实简单,但有以下几个问题需要我们仔细思考一下
- 怎么自动采集 span 数据:自动采集,对业务代码无侵入
- 如何跨进程传递 context
- traceId 如何保证全局唯一
- 请求量这么多采集会不会影响性能
03.SkyWalking结构设计
3.1 skywalking的工作机制
skywalking的工作机制,需要三块协同
- 第一块是skywalking server:负责接收、存储并展示,所以server模块包含一个展示web子模块;
- 第二块是agent:负责代理微服务并收集需要的信息,转发给server;
- 第三块便是微服务本身:需要在启动时指定agent,以便生成代理类;
3.2 SkyWalking 核心模块介绍
1)Skywalking Agent
- 链路数据采集tracing(调用链数据)和metric(指标)信息并上报,上报通过HTTP或者gRPC方式发送数据到Skywalking Collector
2)Skywalking Collector
- 链路数据收集器,对agent传过来的tracing和metric数据进行整合分析通过Analysis Core模块处理并落入相关的数据存储中
- 同时会通过Query Core模块进行二次统计和监控告警
3)Storage
- Skywalking的存储,支持以ElasticSearch、Mysql、TiDB、H2等主流存储作为存储介质进行数据存储,H2仅作为临时演示单机用。
4)SkyWalking UI
- Web可视化平台,用来展示落地的数据,目前官方采纳了RocketBot作为SkyWalking的主UI
04.SkyWalking原理
4.1 自动采集 span 数据
- SkyWalking 采用了插件化 + javaagent 的形式来实现了 span 数据的自动采集
- 这样可以做到对代码的 无侵入性,插件化意味着可插拔,扩展性好
4.2 跨进程传递 context
- 我们知道数据一般分为 header 和 body, 就像 http 有 header 和 body
- RocketMQ 也有 MessageHeader,Message Body, body 一般放着业务数据
- 所以不宜在 body 中传递 context,应该在 header 中传递 context
4.3 traceId保证全局唯一
- 要保证全局唯一 ,我们可以采用分布式或者本地生成的 ID
- 使用分布式话需要有一个发号器,每次请求都要先请求一下发号器,会有一次网络调用的开销
- 所以 SkyWalking 最终采用了本地生成 ID 的方式,它采用了大名鼎鼎的 snowflow 算法,性能很高
- 时间回拨
- 每个 id,都会记录一下生成 id 的时间(lastTimestamp),如果发现当前时间比上一次生成 id 的时间(lastTimestamp)还小
- 那说明发生了时间回拨,此时会生成一个随机数来作为 traceId。
4.4 全部采集性能如何
- 如果对每个请求调用都采集,那毫无疑问数据量会非常大,其实没有必要
- 我们可以设置采样频率,只采样部分数据,
SkyWalking 默认设置了 3 秒采样 3 次
,其余请求不采样 如果上游有携带 Context
过来(说明上游采样了),则下游强制采集数据。这样可以保证链路完整。
05.Skywalking Agent原理
使用Skywalking的时候,并没有修改程序中任何一行 Java 代码,这里便使用到了 Java Agent 技术,我们接下来展开对Java Agent 技术的学习。
5.1 Java Agent
- Java Agent 是从 JDK1.5 开始引入的,算是一个比较老的技术了。
- 我们常用的命令之一就是 java 命令,而 Java Agent 本身就是 java 命令的一个参数(即 -javaagent)。
- -javaagent 参数之后需要指定一个 jar 包,这个 jar 包需要同时满足下面两个条件
- 第一:在 META-INF 目录下的 MANIFEST.MF 文件中
必须指定 premain-class 配置项
。 - 第二:premain-class 配置项指定的类
- 第一:在 META-INF 目录下的 MANIFEST.MF 文件中
必须提供了 premain() 方法
。
- 在 Java 虚拟机启动时,执行 main() 函数之前,虚拟机会先找到 -javaagent 命令指定 jar 包
- 然后执行premain-class 中的 premain() 方法。
- 用一句概括其功能的话就是:main() 函数之前的一个拦截器。
使用 Java Agent 的步骤大致如下
public static void premain(String agentArgs, Instrumentation inst){
//...
}
1
2
3
- 3)将 MANIFEST.MF 文件和 premain-class 指定的类一起打包成一个 jar 包
- 4)使用 -javaagent 指定该 jar 包的路径即可执行其中的 premain() 方法
5.2 探针
- 在 SkyWalking中,探针表示集成到目标系统中的代理或SDK库,它负责收集遥测数据,包括链路追踪和性能指标。
- 根据目标系统的技术栈,探针可能有差异巨大的方式来达到以上功能。
- 但从根本上来说都是一样的,即
收集并格式化数据,并发送到后端
。
5.3 服务自动打点代理
- 对于最终用户来说他们不需要修改代码(至少在绝大多数情况下),只是被代理给修改了,这种做法通常叫做"在运行时操作代码"。
- 底层原理就是自动打点代理利用了虚拟机提供的用于修改代码的接口来动态加入打点的代码,如通过 javaagent premain 来修改 Java 类。
- 此外, 我们说大部分自动打点代理是基于虚拟机的,但实际上你也可以在编译期构建这样的工具。
09.skywalking安装
01.skywalking安装
1.1 下载对应版本
[root@skywalking skywalking]# mkdir /usr/local/skywalking # 创建安装路径
'''1、elasticsearch-7.12.0.tar.gz,下载地址'''
[root@~]# wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.12.0-linux-x86_64.tar.gz
'''2、apache-skywalking-apm-es7-8.5.0.tar.gz,下载地址'''
[root@~]# wget https://archive.apache.org/dist/skywalking/8.5.0/apache-skywalking-apm-es7-8.5.0.tar.gz
'''3、安装JDK '''
[root@skywalking ~]# yum -y install java-1.8.0-openjdk-devel
02.安装ES
2.1 配置所有者
1)创建安装路径
[root@skywalking skywalking]# mkdir /usr/local/skywalking # 创建安装路径
[root@skywalking skywalking]# tar -zxvf elasticsearch-7.12.0-linux-x86_64.tar.gz
[root@skywalking skywalking]# cd elasticsearch-7.12.0
2)修改两个配置
[root@skywalking skywalking]# vi /etc/security/limits.conf # 修改后自动生效 (#末尾新增下面四行)
es soft nofile 65536
es hard nofile 65536
es soft nproc 4096
es hard nproc 4096
[root@skywalking skywalking]# vi /etc/sysctl.conf # 末尾新增下面一行
vm.max_map_count=262144
[root@skywalking skywalking]# sysctl -p # 修改后执行sysctl -p 生效
3)创建独立用户与组(es默认不允许root用户登录)
[root@skywalking skywalking]# groupadd es # 创建用户组
[root@skywalking skywalking]# useradd -g es es # 创建用户es,并添加至用户组es
[root@skywalking skywalking]# passwd es # 设置密码
New password:123456
Retype new password:123456
4)更改软件包属主属组
[root@skywalking skywalking]# chown -R es:es /usr/local/skywalking/elasticsearch-7.12.0
2.2 修改配置文件
1)单节点配置ip
[root@skywalking config]# vim /usr/local/skywalking/elasticsearch-7.12.0/config/elasticsearch.yml
# 修改如下7个配置
cluster.name: CollectorDBCluster
path.data: /data/elasticsearch/data
path.logs: /data/elasticsearch/logs
network.host: 192.168.56.65
http.port: 9200
node.name: node-1
cluster.initial_master_nodes: ["node-1"]
# 注意:一定要将elasticsearch.yml中的 #cluster.initial_master_nodes: ["node-1","node-2"]注释去掉,并将“node-2”去掉
# 创建数据存储目录和日志存储目录
[root@skywalking elasticsearch-7.12.0]# mkdir -p /data/elasticsearch/data
[root@skywalking elasticsearch-7.12.0]# mkdir -p /data/elasticsearch/logs
#更改属主和属组
[root@skywalking elasticsearch-7.12.0]# chown -R es:es /data/elasticsearch
- 配置参数说明
各配置项含义:
cluster.name 集群名称,各节点配成相同的集群名称。
node.name 节点名称,各节点配置不同。
node.master 指示某个节点是否符合成为主节点的条件。
node.data 指示节点是否为数据节点。数据节点包含并管理索引的一部分。
path.data 数据存储目录。
path.logs 日志存储目录。
bootstrap.memory_lock 内存锁定,是否禁用交换。
bootstrap.system_call_filter 系统调用过滤器。
network.host 绑定节点IP。
http.port 端口。
discovery.zen.ping.unicast.hosts 提供其他 Elasticsearch 服务节点的单点广播发现功能。
discovery.zen.minimum_master_nodes 集群中可工作的具有Master节点资格的最小数量,官方的推荐值是(N/2)+1,其中N是具有master资格的节点的数量。
discovery.zen.ping_timeout 节点在发现过程中的等待时间。
discovery.zen.fd.ping_retries 节点发现重试次数。
http.cors.enabled 是否允许跨源 REST 请求,用于允许head插件访问ES。
http.cors.allow-origin 允许的源地址。
#2.3 启动并访问
- 切换至elasticsearch用户
[root@skywalking elasticsearch-7.12.0]# su es
[es@k8s-node1 root]$ cd /usr/local/skywalking/elasticsearch-7.12.0/bin/
[es@k8s-node1 bin]$ ./elasticsearch
[es@skywalking bin]$ cat ../logs/elasticsearch.log # 查看启动日志
- 访问页面会显示版本号和一些配置信息
curl http://192.168.56.65:9200
{
"name" : "node-1",
"cluster_name" : "CollectorDBCluster",
"cluster_uuid" : "sW-D83J9SPueVsQJvML22Q",
"version" : {
"number" : "7.12.0",
"build_flavor" : "default",
"build_type" : "tar",
"build_hash" : "78722783c38caa25a70982b5b042074cde5d3b3a",
"build_date" : "2021-03-18T06:17:15.410153305Z",
"build_snapshot" : false,
"lucene_version" : "8.8.0",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
03.安装Skywalking服务
3.1 替换存储为ES
[es@skywalking skywalking]$ pwd
/usr/local/skywalking
[es@skywalking skywalking]$ tar -zxvf apache-skywalking-apm-es7-8.5.0.tar.gz
- 配置(说明 SkyWalking服务默认使用H2存储,不具有持久存储的特性,所以需要将存储组件修改为elasticsearch。)
[root@skywalking config]# vim /usr/local/skywalking/apache-skywalking-apm-bin-es7/config/application.yml
修改配置如下:
#注释h2的selector,添加elasticsearch7,注意elasticsearch7中localhost改为ip地址
storage:
#selector: ${SW_STORAGE:h2}
selector: ${SW_STORAGE:elasticsearch7}
elasticsearch7:
nameSpace: ${SW_NAMESPACE:"skywalking-index"}
clusterNodes: ${SW_STORAGE_ES_CLUSTER_NODES:10.153.61.71:9200}
protocol: ${SW_STORAGE_ES_HTTP_PROTOCOL:"http"}
trustStorePath: ${SW_STORAGE_ES_SSL_JKS_PATH:""}
trustStorePass: ${SW_STORAGE_ES_SSL_JKS_PASS:""}
参数 | 说明 |
---|---|
selector | 存储选择器。本文设置为elasticsearch7。 |
nameSpace | 命名空间。Elasticsearch实例中,所有索引的命名会使用此参数值作为前缀。 |
clusterNodes | 指定Elasticsearch实例的访问地址。由于实例与SkyWalking不在同一专有网络VPC(Virtual Private Cloud)下,因此要使用公网访问地址,获取方式请参见查看实例的基本信息 |
。 | |
user | Elasticsearch实例的访问用户名,默认为elastic。 |
password | 对应用户的密码。elastic用户的密码在创建实例时指定,如果忘记可重置。重置密码的注意事项和操作步骤,请参见重置实例访问密码 |
。 |
3.2 修改端口
- webapp/webapp.yml 保持默认配置即可,如果8080端口被其他服务占用,可以修改8080端口为18080
[root@skywalking webapp]# cat /usr/local/skywalking/apache-skywalking-apm-bin-es7/webapp/webapp.yml
server:
port: 18080
collector:
path: /graphql
ribbon:
ReadTimeout: 10000
# Point to all backend's restHost:restPort, split by ,
listOfServers: 192.168.56.65:12800
3.3 开放端口并启动服务
[root@skywalking webapp]# firewall-cmd --list-ports
[root@skywalking webapp]# firewall-cmd --zone=public --add-port=18080/tcp --permanent
[root@skywalking webapp]# firewall-cmd --zone=public --add-port=11800/tcp --permanent
[root@skywalking webapp]# firewall-cmd --zone=public --add-port=12800/tcp --permanent
[root@skywalking webapp]# firewall-cmd --reload
[root@k8s-node1 bin]# cd /usr/local/skywalking/apache-skywalking-apm-bin-es7/bin/
[root@localhost bin]# sh startup.sh # 启动
# 启动sky前要确保es已经正常运行
# jps查看进程,skywalking,有两个进程skywalking-webapp.jar和OAPServerStartUp
[root@skywalking webapp]# jps
26662 skywalking-webapp.jar
46391 Jps
33080 OAPServerStartUp
32379 Elasticsearch
10.skywalking使用
01.skywalking-python
1.0 参考
官网:https://skywalking.apache.org/docs/skywalking-python/v0.7.0/en/setup/intrusive/
包官网:https://pypi.org/project/apache-skywalking/
GitHub:https://github.com/apache/skywalking-python/tree/1d25b08c5a3d6b16719966406fec2c51291b0126
博客案例:https://blog.csdn.net/qq_30355341/article/details/114641498
flask使用skywalking:https://blog.csdn.net/qq_30355341/article/details/114641498
1.1 安装
# Install the latest version, using the default gRPC protocol to report data to OAP
pip install "apache-skywalking"
# Install the latest version, using the http protocol to report data to OAP
pip install "apache-skywalking[http]"
# Install the latest version, using the kafka protocol to report data to OAP
pip install "apache-skywalking[kafka]"
# Install a specific version x.y.z
# pip install apache-skywalking==x.y.z
pip install apache-skywalking==0.1.0 # For example, install version 0.1.0 no matter what the latest version is
1.2 使用测试
- 只需在启动前初始化 skywalking的配置文件
- 官方文档:https://pypi.org/project/apache-skywalking/
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from flask import Flask
from flask import request
from skywalking import agent, config
config.init(collector_address='192.168.56.65:11800', service_name='flask_test02')
agent.start()
agent.start()
app=Flask(__name__)
@app.route('/user/<name>') #设置url传参数 http://127.0.0.1:5000/user/zhangsan?name=aaa
def first_flask(name): #视图必须有对应接收参数
print(name) # zhangsan
print(request.form) # 获取post请求
print(request.values.get('name')) # 获取get请求中参数
print(request.args.get('name')) # 获取get请求中参数
return 'Hello World'
if __name__ == '__main__':
app.run(debug=True,port=8001)
11.ServiceMesh
01.微服务问题
1.1 微服务实现流程
1、服务都注册到服务注册中心
- 要构建微服务体系,首先我们需要独立部署一款实现服务注册/发现功能的组件服务,目前可供选择的主流方案一般有Eureka、Consul、Nacos等
- 搞定服务注册/发现后,我们编写一个Java微服务,此时为了将该服务注册到服务注册中心
- 一般会引入Spring Cloud提供的支持对应注册中心接入的SDK,并在应用入口类中通过@EnableDiscoveryClient注解的方式标注
- 之后SDK中的逻辑就会在应用启动时执行服务注册动作,并提供给注册中心相应地探测接口,以此实现微服务与服务注册中心之间的连接。
- 以此类推,我们可以通过这种方式将一组微服务都注册到服务注册中心!
2、服务之间要互相调用
- 一般我们会通过编写FeignClient接口来实现微服务之间的调用
- 而其底层的逻辑则是通过Feign所集成的Ribbon组件去注册中心中获取目标服务的服务地址列表
- 之后Ribbon根据服务地址列表进行负载均衡调用。
- 至于服务与注册中心之间如何保证连接有效性,则依赖于服务注册中心与其SDK之间的协作机制。
3、负载均衡、熔断、限流、网关
- 而高级一点,服务之间的调用除了实现负载均衡,还要实现熔断限流
- 那么此时可以通过部署服务网关组件(例如Zuul/Spring Cloud GateWay)来实现微服务入口的熔断限流
- 内部服务之间的限流熔断则通过集成Hystrix或Sentinel组件,以客户端本地配置或远程配置中心的方式来实现。
1.2 微服务遇到的问题
**1、框架/SDK太多,后续升级维护困难**
- 在这套体系中,与服务治理相关的逻辑都是以SDK代码依赖的方式嵌入在微服务之中
- 如果某天我们想升级下服务注册中心的SDK版本,或者熔断限流组件Hystrix或Sentinel的版本,那么需要升级改造的微服务可能会是成百上千
- 且由于这些组件都与业务应用绑定在一起,在升级的过程中会不会影响业务稳定,这都是需要谨慎对待的事情,所以对SDK的升级难度可想而知的!
**2、多语言微服务SDK维护成本高**
- 试想下如果构建的微服务体系,也要支持像Go、Python或者其他语言编写的微服务的话
- 那么上述这些微服务治理相关的SDK是不是得单独再维护几套呢?
- 所以在这种体系结构中,对多语言微服务的支持就成了一个问题!
**3、服务治理策略难以统一控制**
- 基于该套体系构建的微服务体系,在对像熔断、限流、负载均衡等服务治理相关的策略管理上,都是比较分散的
- 可能有人会写到自己的本地配置文件,有人会硬编码到代码逻辑中,也可能有人会将其配置到远程配置中心
- 总之对于服务治理策略逻辑都是由对应的开发人员自己控制,这样就很难形成统一的控制体系!
**4、服务治理逻辑嵌入业务应用,占有业务服务资源**
- 在这套微服务体系中,服务治理相关的逻辑都是在微服务应用进程中寄生运行的
- 这多少会占有宝贵的业务服务器资源,影响应用性能的发挥!
**5、额外的服务治理组件的维护成本**
- 无论是服务注册中心、还是服务网关,这些除了微服务应用本身之外服务治理组件,都需要我们以中间件基础服务的方式进行维护
- 需要额外的人力、额外的服务器成本
02.Service Mesh实现
2.1 Service Mesh是什么
- 如果用一句话来解释什么是 Service Mesh,可以将它比作是应用程序或者说微服务间的 TCP/IP,负责服务之间的网络调用、限流、熔断和监控。
- 对于编写应用程序来说一般无须关心 TCP/IP 这一层(比如通过 HTTP 协议的 RESTful 应用)
- 同样使用 Service Mesh 也就无须关系服务之间的那些原来是通过应用程序或者其他框架实现的事情
- 比如 Spring Cloud、OSS,现在只要交给 Service Mesh 就可以了。
Service Mesh 有如下几个特点
- 应用程序间通讯的中间层
- 轻量级网络代理
- 应用程序无感知
- 解耦应用程序的重试/超时、监控、追踪和服务发现
2.2 Service Mesh网格
- 其中绿色的正方形表示正常部署的微服务,而蓝色的正方形表示一个网络代理,也就是大家通常所说的SideCar。
- 在Service Mesh架构下,每部署一个微服务,都需要部署一个与之相对应的代理服务,所有与微服务本身的交互都通过SideCar代理
- 而SideCar之间会形成一张形似网格的交互链路,这就是服务网格名称的来由
- 在Service Mesh中,当我们将一个服务部署在Kubernetes之后
- 安装在Kubernetes中的Service Mesh组件(例如Istio)就会自动在该微服务的同一个Pod之中启动一个与之对应的代理进程(例如istio-proxy)
- 这个保姆式的代理进程会代替微服务本身去实现原先在Spring Cloud体系中需要微服务自身完成的服务注册、负载均衡、熔断限流等微服务治理功能。
- 并且,这些代理进程并不是孤军奋战,而是会通过像xDS协议(Service Mesh中数据面与控制面通信的通用协议)与Service Mesh控制组件保持连接。
2.3 ServiceMesh开源项目
- Linkerd(https://github.com/linkerd/linkerd):第一代 Service Mesh,2016 年 1 月 15 日首发布,业界第一个 Service Mesh 项目,由 Buoyant 创业小公司开发(前 Twitter 工程师),2017 年 7 月 11 日,宣布和 Istio 集成,成为 Istio 的数据面板。
- Envoy(https://github.com/envoyproxy/envoy):第一代 Service Mesh,2016 年 9 月 13 日首发布,由 Matt Klein 个人开发(Lyft 工程师),之后默默发展,版本较稳定。
- Istio(https://github.com/istio/istio):第二代 Service Mesh,2017 年 5 月 24 日首发布,由 Google、IBM 和 Lyft 联合开发,只支持 Kubernetes 平台,2017 年 11 月 30 日发布 0.3 版本,开始支持非 Kubernetes 平台,之后稳定的开发和发布。
- Conduit(https://github.com/runconduit/conduit):第二代 Service Mesh,2017 年 12 月 5 日首发布,由 Buoyant 公司开发(借鉴 Istio 整体架构,部分进行了优化),对抗 Istio 压力山大,也期待 Buoyant 公司的毅力。
- nginMesh(https://github.com/nginmesh/nginmesh):2017 年 9 月首发布,由 Nginx 开发,定位是作为 Istio 的服务代理,也就是替代 Envoy,思路跟 Linkerd 之前和 Istio 集成很相似,极度低调,GitHub 上的 star 也只有不到 100。
- Kong(https://github.com/Kong/kong):比 nginMesh 更加低调,默默发展中。