一、微服务的诞生背景及相关原则
1.单体架构带来的问题
- 项目过度复杂
比如一个原本很简单的单体应用,经过不断的扩展,功能越来越多,经手的人也在不断的变化,可能会变成一个非常复杂的大系统。功能模块之间相互耦合,难以维护。一旦某个需求变更,影响面难以评估,可能会出现牵一发而动全身的尴尬情况。Bug修复也可能出现打地鼠的现象,这里改好了,那里又出问题了。 - 开发、代码提交、测试、交付
都因为项目过大而花费很多时间,严重影响团队的工作效率。 - 难以扩展
部署的时候服务器性能要满足所有功能的要求,内存、CPU资源要求越来越高 - 技术栈难以更新
更新技术栈风险太大,可能被困在一个被废弃的技术体系中
2.微服务设计原则
- 高内聚低耦合
紧密关联的业务应该放在一起,每个服务是针对一个单一职责的业务的封装,专注做好一件事。同时避免服务太过于分散,减少不必要的服务
单库原则,每个服务有自己的数据库,避免在服务与服务之间共享数据,不允许跨库访问。只能通过rest接口访问其它服务的数据 - 高度自治
独立部署运行和扩展
独立开发和演进
独立的团队和自治 - 以业务为中心
每个服务处理特定的业务,有明显的边界
围绕业务组织团队
能快速响应业务变化
隔离实现细节,让业务逻辑实现代码得以复用 - 容错设计
设计可容错的系统,增强系统的自我保护能力
服务隔离
服务降级
限制资源使用
防止故障蔓延
3.服务如何拆分
首先,服务的拆分不应该是一成不变的,应该随着业务的变化而新增服务或者将体量大的服务细分为几个新的服务,对于体量很小又相互关联的服务可以聚合为一个服务。
- 指导原则
- 单一职责原则
我们在设计微服务架构时应该SRP原则,设计小的、内聚的、仅仅含有单一职责的服务 - 闭包原则
如果对包做出修改,需要调整的类应该在这个包之内。
在微服务架构下采用CCP原则,这样我们就能根据同样原因进行变化的服务放在一个组件内。这样做可以控制服务的数量,当需求变化时,变更和部署也更加容易。理想情况下,一个变更只会影响一个团队和一个服务。CCP是解决分布式单体的法宝。
- 单一职责原则
4.微服务的优势和带来的问题
微服务架构有以下好处:
- 使大型的复杂应用可以持续交付和持续部署
- 每个服务都相对较小并且容易维护
- 服务可以独立扩展
- 更好的容错性
- 更利于技术更新
微服务架构的弊端:
- 服务的拆分
- 分布式系统带来的挑战。容错防止故障蔓延、分布式事务、数据跨库查询如何处理
- 团队之间的协调
二、微服务所需要的组件
1.服务注册与发现
-
eureka基本原理
-
eureka的常用配置
eureka: instance: hostname: localhost metadata-map: 自定义的元数据 心跳间隔,默认30秒 eureka.instance.leaseRenewallIntervalInSeconds 故障摘除等待时间,默认90秒 eureka.instance.leaseExpirationDurationInSeconds 注册表抓取间隔,默认30秒 eureka.client.registryFetchIntervalSeconds 是否开启自我保护模式 eureka.server.enable-self-preservation: false
如果在eureka控制台看到下面的东西:
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
这就是eureka进入了自我保护模式,如果客户端的心跳失败了超过一定的比例,或者说在一定时间内(15分钟)接收到的服务续约低于85%,那么就会认为是自己网络故障了,导致人家client无法发送心跳。这个时候eureka注册中心不会立即把失效的服务实例摘除,在测试的时候一般都会关闭自我保护模式。
-
eureka服务发现的时效性
默认配置下,服务A注册完成以后,服务B最长多久可以感知到?
客户端每隔30秒会更新一次,服务端的多级缓存每30秒更新一次缓存,所以最久需要1分钟。 -
服务故障的自动感知的时效性
默认配置下,当某个实例故障了,其它实例多久能感知到?
首先服务端evict自动摘除每个60秒执行一次。去判断一下当前所有的服务实例,是否有的服务实例出现了故障,一直没有发送心跳过来,是否要将故障的服务实例下线。如果上次的心跳到现在间隔了90s x 2 = 180s,才会认为是故障了。然后就是客户端更新注册表和服务端刷新缓存的时间都是30秒。可能要3、4分钟才能感知到。。。 -
除了eureka还有哪些服务注册发现组件可以用?
另外2018年7月Eureka 2.x停止开源计划了,具体如下:
2.负载均衡
2.1 ribbon的基本原理
2.2 ribbon支持哪些负载均衡算法
- RoundRobinRule:ribbon默认负载均衡算法,round robin轮询,从server
list中,不断的轮询选择出来一个server,每个server收到的请求基本上是平均的 - AvailabilityFilteringRule:这个rule就是会考察服务器的可用性
如果3次连接失败,就会等待30秒后再次访问;如果不断失败,那么等待时间会不断边长 如果某个服务器的并发请求太高了,那么会绕过去,不再访问 - WeightedResponseTimeRule:带着权重的,每个服务器可以有权重,权重越高优先访问,如果某个服务器响应时间比较长,那么权重就会降低,减少访问
- ZoneAvoidanceRule:根据机房和服务器来进行负载均衡,适合多机房场景使用
- BestAvailableRule:忽略那些请求失败的服务器,然后尽量找并发比较低的服务器来请求
- RandomRule:随机找一个服务器,尽量将流量分散在各个服务器上
- RetryRule:可以重试,就是通过round robin找到的服务器请求失败,可以重新找一个服务器
3.声明式调用
3.1 feign
feign是springCloud体系中用来做服务间调用的组件。基于jdk动态代理构造了feignClient的代理对象,代理对象中封装了下面几个步骤的实现:
- 从DiscoveryClient中获取服务实例列表
- 使用ribbon选择一个服务实例
- 构造完整的http请求
- 将http请求封装到hystrix中执行
3.2 项目中feign如何使用
在实际项目里,服务提供方会将自己提供的接口打成jar包然后上传到maven仓库,调用方引入这个jar包调用jar包里面提供的Api接口即可。示例见demo
3.3 Feign的httpClient客户端替换
Feign原生的httpClient是jdk中的HttpURLConnection,不支持连接池。可以Apache的HTTP client替换feign默认的client或者使用OKHTTP替换feign默认的client。
feign:
httpclient:
enabled: true/false
okhttp:
enabled: true/false
4.提高服务可用性
4.1 高可用系统架构
资源隔离、限流、熔断、降级、运维监控
- 资源隔离:让你的系统里,某一块东西,在故障的情况下,不会耗尽系统所有的资源。比如线程资源,可以将对其它的服务调用放在线程池里面执行,每个服务都对应一个线程池,互不影响。
- 限流:高并发的流量涌入进来,比如说突然间一秒钟100万QPS,10万QPS进入系统,其他90万QPS被拒绝了
- 熔断:系统后端的一些依赖,出了一些故障,比如说mysql挂掉了,每次请求都是报错的,熔断了,后续的请求过来直接不接收了,拒绝访问,10分钟之后再尝试去看看mysql恢复没有
- 降级:mysql挂了,系统发现了,自动降级,从内存里存的少量数据中,去提取一些数据出来
- 运维监控:监控+报警+优化,各种异常的情况,有问题就及时报警,优化一些系统的配置和参数,或者代码
4.2 微服务的可用性问题
假如将一个单体应用拆分成30个微服务,每个服务的可用性做到99%。
一个请求极端情况下可能需要调用30个服务,那么这个功能的可用性就是99%的30次方,也就是73.97%。所以微服务拆分得越多,系统的可用性就可能会下降得越明显。
4.3 Hystrix是什么?
在分布式系统中,每个服务都可能会调用很多其他服务,被调用的那些服务就是依赖服务,有的时候某些依赖服务出现故障也是很正常的。
Hystrix可以让我们在分布式系统中对服务间的调用进行控制,加入一些调用延迟或者依赖故障的容错机制。
Hystrix通过将依赖服务进行资源隔离,在某个依赖服务出现故障的时候,阻止这种故障在整个系统所有的依赖服务调用中进行蔓延,同时Hystrix还提供故障时的fallback降级机制
总而言之,Hystrix通过这些方法帮助我们提升分布式系统的可用性和稳定性。
4.4 Hystrix是如何实现它的目标的?
(1)通过HystrixCommand或者HystrixObservableCommand来封装对外部依赖的访问请求,这个访问请求一般会运行在独立的线程中,资源隔离
(2)对于超出我们设定阈值的服务调用,直接进行超时,不允许其耗费过长时间阻塞住。这个超时时间默认是99.5%的访问时间,但是一般我们可以自己设置一下
(3)为每一个依赖服务维护一个独立的线程池,或者是semaphore,当线程池已满时,直接拒绝对这个服务的调用
(4)对依赖服务的调用的成功次数,失败次数,拒绝次数,超时次数,进行统计
(5)如果对一个依赖服务的调用失败次数超过了一定的阈值,自动进行熔断,在一定时间内对该服务的调用直接降级,一段时间后再自动尝试恢复
(6)当一个服务调用出现失败,被拒绝,超时,短路等异常情况时,自动调用fallback降级机制
(7)对属性和配置的修改提供近实时的支持
4.5 Hystrix的执行原理
4.6 Hytrix的自动恢复机制
断路器的状态切换到open状态,在经过一段时间以后,会进入half-open半开状态。意思就是说会放少量请求经过断路器,看能不能正常调用。如果调用成功了,那么就自动恢复,转到close状态。
5.Api网关
5.1 为什么需要Api网关?
设计模式,facade门面思想,对于一套复杂的类体系和接口,全部暴露一个统一的门面,门面给别人调用即可,别人就不用去熟悉你几十个类和几百个接口。比如与前端或者其它系统对接,别人只需要调用Api网关中暴露的接口就行了。
-
请求路由
屏蔽复杂的后台系统的大量的服务,然后让前端工程师调用的时候非常的简单 -
统一处理
把所有后台服务都需要做的一些通用的事情,挪到网关里面去处理- 统一安全认证
- 统一限流
- 统一降级
- 统一异常处理
- 统一请求统计
- 统一超时处理
5.2 Zuul
Zuul是Netflix开源的微服务网关组件,它本质上是一个Servlet。通过一系列的Filter来实现请求路由、错误统一处理等功能,支持自定义Filter来增加一些统一处理的逻辑。
- pre过滤器
-3:ServletDetectionFilter
-2:Servlet30WrapperFilter
-1:FromBodyWrapperFilter
1:DebugFilter
5:PreDecorationFilter - routing过滤器
10:RibbonRoutingFilter
100:SimpleHostRoutingFilter
500:SendForwardFilter - post过滤器
1000:SendResponseFilter - error过滤器
0:SendErrorFilter
5.3 Zuul的执行流程
6.链路追踪
在微服务中,如果需要定位某个异常是哪个服务导致,或者做性能优化的时候怎么知道整个链路里面哪里耗时最久?可以利用链路追踪快速定位
Skywalking截图
7.配置中心
在项目中,每个环境的配置文件都不一样,如何管理?临时修改了某个配置不想重启服务。都可以用配置中心解决。
Spring Cloud Config支持将配置文件保存在git/svn上,每个环境对应一个分支。
在启动参数中设置环境就可以使用对应的配置文件。这样项目代码和配置文件就可以分开管理。
官方文档
https://cloud.spring.io/spring-cloud-config/reference/html/
8.服务接口文档
Swagger -UI
见demo
9.服务监控
三、微服务demo
参考资料:
《微服务架构设计模式》
Spring Cloud官网
skywalking中文文档