微服务介绍
每个微服务都是个小的应用程序,有自己的六边形结构、业务逻辑和不同的适配器。一些微服务可能暴露API给别的微服务用或者给客户用。其他的微服务实现UI。运行时,每个实例都是个虚拟机或者Docker容器。
微服务的优势
- 解决复杂性问题 。分解了一个怪物成为一组服务,功能不会受影响,但应用已经被分解成可管理的服务。每个服务都以RPC或者消息驱动的API来定义明确的边界。微服务也强制使用模块化,通常在单体应用代码中比较难以实现。因此,单个服务开发要快的多,易于理解和维护。
- 使每个服务由专注于该服务的团队独立开发 。开发则可以随意选择技术和框架,只要符合API。创建新服务就可以使用新的技术框架了。
- 每个微服务都能独立部署 。由于每个服务相对独立并且比较小,修改测试部署都是非常方便的,同时便于持续集成,使持续部署成为可能。
- 微服务架构使每个服务都可以独立调整 。你可以为每个服务部署满足它的需求的实例。此外,你可以使用最匹配服务需求的硬件。比如,你可以在EC2上选择CPU密集型处理图像,内存优化型上部署内存数据库服务。
微服务的缺点
就像Fred Brooks在30年前写到,没有银弹。像其他技术一样,微服务架构也有缺点。
- 一个就是它的名字,Microservice过于强调服务规模。实际上,一些开发者主张构建极细粒度的10-100LOC服务。虽然小服务是最好的,但是这只是手段而不是微服务的目标。目标 是充分分解应用,以便敏捷应用程序的开发和部署 。
- 分布式的复杂性 。开发人员需要选择和实现基于消息或者RPC的内部交流机制。更有甚者,需要些代码去处理请求目标慢或者不可用的错误。虽然这些都不难,但是它比通过语言级别的方法/过程调用要复杂的多。
- 分区数据库架构 。更新多个业务实体的业务交易是相当普遍的。因为有单体数据库,所以在单体应用程序中微不足道。但是,在基于微服务的应用中,你需要更新不同服务的多个数据库。用分布式事务通常不是一个选择,不仅是因为CAP定理。这根本不被现在很多NOSQL数据库和邮件代理支持。你最后不得不用基于一致性(注:放弃一致性,保证可用性和分区容错性。由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的。可用性是数据库的容灾,NOSQL有支持,如MongoDB的Replica Sets,需要自行配置)的方法,这对开发者来说更具有挑战性。
- 测试微服务应用更加复杂 。例如使用一个现代框架就像Spring Boot,写一个启动代替应用的REST API测试是简单的。相反,一个类似的测试类需要启动这个服务依赖的其他服务(或者配置一个虚拟的)。
- 跨多个服务的修改更复杂 。例如,让我们想象你在实现一个需要改变服务A、B、C的故事卡片,A依赖B,B依赖C。在单体应用你可以简单修改对应模块,并与其他集成。相反,在微服务架构,你需要很小心计划和协调每个服务的更改。比如你可能需要更改C,C被B使用,最后服务与A。幸运的是,很多修改通常只影响一个服务,需要协调多个服务的比较少。
- 部署更复杂 。单体应用在相同的服务和负载均衡器上部署是很简单的。每个应用实例都配置有基础架构(数据库和消息代理)的访问入口(主机和端口)。相反,微服务通常包括很多服务。比如,Hailo有160个不同的服务,Adrian Cockcroft说Netflix有超过600个。每个服务端都有很多运行时实例。很多可移动组件需要被配置,部署,扩展和监控。另外,你需要实现一个服务发现机制(稍后讨论)使服务可以发现任何其需要通信的其他服务的位置(主机和端口)。传统的基于故障单子和手动操作的方式无法扩展到这种复杂程度。所以,成功部署一个微服务应用程序需要更高水平人员实现的的部署方法和自动化。
微服务发现
定义
在微服务应用中,运行的服务实例集会动态更改。实例能动态分配网络位置。所以为了使客户端向服务端发送请求它必须使用服务发现机制。
客户端应用进程向注册中心发起查询,来获取服务的位置。服务发现的一个重要作用就是提供一个可用的服务列表。
原理
- 当User Service启动的时候,会向Consul发送一个POST请求,告诉Consul自己的IP和Port。
- Consul 接收到User Service的注册后,每隔10s(默认)会向User Service发送一个健康检查的请求,检验User Service是否健康(Consul其实支持其他健康检查机制)。
- 当Order Service发送 GET 方式请求/api/addresses到User Service时,会先从Consul中拿到一个存储服务 IP 和 Port 的临时表,从表中拿到User Service的IP和Port后再发送GET方式请求/api/addresses。
- 该临时表每隔10s会更新,只包含有通过了健康检查的Service。
微服务网关
定义
API网关。它是系统的单个入口服务器。它类似于面向对象的外观模式。它封装了内部系统架构并且提供了每个客户端定制的API。它可能还有其他的职责比如身份验证、监控、负载均衡、缓存、请求整理和管理还有静态相应处理。
优点
流量统一管理、路由
它用于解决微服务过于分散,没有一个统一的出入口进行流量管理、路由映射、监控、缓存、负载均衡的问题。
设备适配
还可以针对不同的渠道和客户端提供不同的API Gateway,对于该模式的使用由另外一个大家熟知的方式叫Backend for front-end, 在Backend for front-end模式当中,我们可以针对不同的客户端分别创建其BFF。
协议转换
客户端直接调用微服务可能会使用对网络不友好的协议。内部服务之间可能使用Thrift二进制RPC、gRPC协议。两个协议对浏览器不友好并且最好是在内部使用。应用应该在防火墙之外使用HTTP或者WebSocket之类的协议。
缺点
API网关是一个必须被开发、部署、管理的高可用组件。还有一个风险是它变成开发瓶颈。开发人员必须更新API网关来暴露每个微服务接口。因此更新API网关的过程越少越好。然而,对于大多数现实世界的应用,使用API网关是有意义的。
微服务容错保护
服务降级
服务降级类似女生旅行:在用户访问量高峰期,整体资源面临不足的时候,将一些重要优先程度相对较低的服务先关掉,等到过了高峰期再恢复。比如京东商城在双十一期间,可能会对评论服务进行服务降级。
回到微服务系统,服务A调用服务B,当我们对服务B进行降级后,服务A将直接调用预定义的降级逻辑(即方法调用代替跨服务请求),从而快速获取返回结果,而降级方法逻辑的返回结果与真实服务B的返回结果的区别 就好比 残次品与良品的区别,此时我们认为服务B所提供的服务质量降低了,即我所说的降级。
// 主逻辑
@FeignClient(value = "mst-goods-service", fallback = GoodClientFallback.class)
public interface GoodsClient {
@RequestMapping(method = RequestMethod.GET, path = "/api/goods/{goods_id}")
GoodsDTO getOne(@PathVariable("goods_id") Long goodsId);
}
// 降级逻辑
@Component
public class GoodClientFallback implements GoodsClient {
@Override
public GoodsDTO getOne(Long goodsId) {
return new GoodsDTO(1l, 12.3, 2l, "name");
}
}
服务熔断
在分布式架构中,断路器模式的作用也是类似的,如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不再调用目标服务,直接返回结果,快速释放资源,避免最终因为服务不可用蔓延导致系统雪崩灾难。
断路器什么时候会打开
这里涉及到断路器的三个重要参数:
- 快照时间窗:断路器确定是否需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认10秒。
- 请求总数下限:在快照时间窗内,必须满足请求总数下限才会启用熔断。默认20,意味着在10秒内,如果调用不足20次,即便所有的请求都失败,断路器都不会打开。
- 错误百分比下限:当请求总数在快照时间内超过了下限,比如发生了30次调用,如果在这 30次调用中,有16次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%下限的情况下,断路器就会打开。
断路器打开之后发生什么
熔断打开之后,再有请求调用的时候,将不会调用主逻辑,而是直接调用降级逻辑,这个时候就会快速返回,而不是等待5秒后才返回fallback。通过断路器实现了自动发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。
主逻辑如何恢复
Hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器就进入半开状态,释放一次请求到原来的主逻辑上。如果此次请求正常返回,那么断路器将会关闭,主逻辑恢复正常。否则,断路器继续保持打开状态,而休眠时间窗会重新计时。