Microservice Architecture Design Notes

Microservice Design
Microservice Architecture
背景:我们在开发服务端的企业级应用时,必须提供对各类客户端的支持,比如桌面浏览器,移动浏览器
和原生的移动应用。除此之外还可能需要暴露一些API给第三方使用。
另外,通过web service或者一个消息代理跟其它应用程序集成。
应用程序通过执行业务逻辑处理请求(HTTP请求和消息),访问数据库,跟其它系统交换信息,返回HTML/JSON/XML等类型的答复
应用程序的不同功能区域对应着不同的逻辑组件。

问题:应用程序的部署架构是什么?
考虑因素:
团队开发
新成员必须快速熟悉并形成生产力
应用程序必须易于理解和维护
我们需要熟悉持续部署
我们需要在多个机器上运行多个应用程序副本来满足可扩展性和可用性。

解决方案:
将应用程序架构成一个松耦合的相互协作的服务集合。
每个服务只实现有限的相互关联的功能。比如,订单管理服务,客户管理服务等。
服务之间的交互既可以使用同步协议(比如HTTP/REST)还能使用异步协议(比如AMQP)。
每个服务都能够独立开发和部署。
每个服务都有自己独立的数据存储来跟其它服务解耦。
使用事件驱动架构来维护数据一致性。

示例:虚构一个电子商务应用
我们系统需要从客户那里接收订单,然后检查库存和可用的信贷额度并装配订单。
该应用包括用户界面的StoreFrontUI组件,检查信用的后端服务组件,维护库存和处理订单的组件。

根据业务职能划分定义微服务组件
根据领域驱动设计子领域
根据业务动词划分来定义负责特定行为的服务组件,比如shipping Service负责装运完成的订单
根据名词或者资源定义一个服务专门负责所有跟该entities/resources有关的操作。比如 Account Service负责管理用户账户。

理想状态下,每个服务只应该有一个最小的责任集。SRP(Single Responsibility Principle)
一个类的责任是变化的原因,类的状态应该只有一个原因能够改变。
Unix工具集设计,Unix提供了大量的工具,比如grep,cat和find
每个工具只负责一件事,在执行复杂任务时通过一个Shell脚本来组合这些工具。

如何维护数据的一致性?
为了确保服务之间的松耦合,每个服务都拥有自己独立的数据库。
在服务之间保持数据的一致性是一个挑战。因为 2 phase-commit/distributed transaction 很难满足要求。
这时应用程序就需要使用一个Event-Driven architecture:一个服务在数据发生变化时发布一个事件 ,
其它服务消费这个事件并更新自己的数据。
这个事件发布和消费方式可以是 Event Sourcing 或者 Transaction Log Tailing

如何实现查询?
另一个挑战是如何实现需要同时从多个服务获取数据的查询。
一个通用的方案是使用Command Query Responsibility Segregation 并维护一个或者多个通过预定在数据发生变化时产生的事件流来保持更新的视图具体实现。

相关的设计模式:
Motivating Pattern -----> Solution Pattern
Solution A <-------> Solution B
General <------ Specific

客户端访问微服务时使用API Gateway Pattern
使用Client-side Discovery和Server-side Discovery模式来设计为客户端请求路由到可用的微服务上。
服务间交流的两个不同方式是消息机制和Remote Procedure Invocation模式
部署策略分为Single Server per host和Multiple Services per Host
Microservice chassis pattern和Externalized configuration模式用于跨切面内容。
Service Component Test和Service Integration Contract Test用于测试的模式
Circuit Breaker
Access Token
观察者模式:Log aggregation,Application metrics,Audit logging,Distributed tracing,Exception tracing,Health check API
UI模式:
Server-side page fragment composition
Client-side UI composition

根据商业能力定义服务。商业能力的概念来自于商业架构模型。
它是指一个商业为获取价值而做的事情。
商业能力往往跟商业对象有关。
比如
订单管理负责订单对象
客户管理负责客户对象

商业能力通常会被组织到多层架构里,比如一个企业级应用可能有顶层分类:
Product/Service 开发
Product/Service 交付
Demand generation 需求产生

比如一个在线商店的业务能力分析:
产品分类管理Product Catalog
库存管理Inventory
订单管理Order
交付管理Delivery

每一个能力都可以对应一个微服务
如何来识别商业能力business capability?
识别商业能力并上马服务需要对商业有一个全面的理解。
一个组织的商业能力识别包括分析组织目的,架构,商业流程,和特长领域来完成。
划定内容编辑不断的重复优化流程。
可以从组织架构入手:组织中不同分组负责的商业能力
高层级的域对象模型:商业能力通常跟领域对象相关联。

=============
Scale Cube 规模数据集

X axis - horizontal duplication 横向复制
Scale by cloning 复制扩展

Y axis - functional decomposition 功能分解
Scale by splitting different things 分解不同的事物扩展

Z axis - data partitioning 数据分区
Scale by splitting similar things

X-axis scaling:在一个加载均衡器背后运行应用程序的多个拷贝。
如果有N个拷贝运行,那么每个拷贝负载1/N的请求。通常用于应用程序扩展。
该模式的缺点是由于每个拷贝都潜在的访问所有数据,占用太多的内存从而影响性能。
另一个问题是该方式并不能减轻开发量和应用复杂性带来的问题。

Y-axis scaling:跟X-axis和Z-axis需要运行多个拷贝不同,它只运行一个应用拷贝。
它是通过将应用程序分割到多块,不同的服务来进行扩展的。
每个服务负责一个或者多个密切相关的功能。
将应用程序解耦到服务两种方式,一个是基于动词的分解,定义服务来实现单一的用例,比如checkout
另一种是通过名词来创建服务负责关于特定实体类的所有相关操作,比如客户管理
我们通常会使用这两种混合来实现。

Z-axis scaling:在该模式下每个服务运行一个代码单一的拷贝,在这方面它类似于X-axis扩展,
最大的不同在于每个服务只负责一个数据子集。
一些系统组件负责路由每个请求到合适的服务器,一个通用的路由标准是请求的一个属性比如实体主键。
另外一种路由是自定义类型,比如一个应用程序 可能提供为支付客户路由到能力更强的服务器,将免费用户
路由到普通服务器。
Z-axis通常用于数据库的扩容。 数据基于每条记录的一个属性被分割到跨服务器上。
RESTAURANT表的主键用于在两个不同数据库中划分行。
注意这里X-axis克隆可能通过部署一个或者多个服务成replicas/slaves模式应用到每个分区里。
Z-axis扩容也可以应用到应用程序中。
比如 由多部分组成的查询服务,一个路由器发送每个内容想到相应的分区,在分区里这些内容被索引并保存。
一个查询聚集器发送对所有分区的查询的汇总,将每部分的结果组合起来。

Z-axis扩容的好处是:
每个服务器只处理数据的一个子集。
提高了服务器缓存的利用率并减少了内存使用和I/O阻塞
提高了事务的扩展性,应为请求通常是跨多服务器分布式的
还能提高容错性,因为错误发生只是在部分数据上,不会影响其它服务的数据。

缺点在于:
增加的应用程序的复杂性
我们还需要实现一个分区范式,如果我们需要重新分配数据的话比较麻烦。
增加开发量和应用程序复杂程度


Event-driven architecture:
我们为每个服务配置独有数据库,一些夸多服务的业务事务存在,所以我们需要一种机制来确保跨服务的数据一致性。
比如,我们创建一个电子商务商店,客户有信贷额度,应用程序必须确保一个新的订单提交时不会超过客户的信贷额度。
由于订单和客户信息分属不同的数据库,应用程序不能简单的使用本地ACID事务。

问题:如何维护数据跨服务的一致性?
2PC 两阶段提交已经不再适用

方案:使用事件驱动,最终达到数据一致。
每个服务在它更新数据时都发布一个事件,其它服务预定这些事件,接受到这些事件后执行相应的数据更新操作。

比如一个电子商务系统:
1.Order Service创建一个状态为Pending的订单Order并发布一个OrderCreated事件。
2.Customer Service接收到事件并试着去为该订单保持信贷额度,然后发布一个Credit Reserved事件或者一个CreditLimitExceeded事件
3.Order Service 从Customer Service接收到事件后,执行改变订单状态到approved批准接受或者cancelled取消拒绝。

相关的设计模式:
Database per Service pattern
Event Sourcing
Application events
Database triggers
Transaction log tailing

==================
Application publishes events:
在事件驱动的微服务架构中,为了可靠性,服务必须自动发布任何有关它们状态发生变化的事件。
跨数据库和消息代理来使用分布式事务是不太可能的。

那么我们如何在状态发生变化时自动可靠的发布事件呢?
2PC 不是一个可用的选项。
解决方案:应用程序向EVENTS表插入数据作为本地事务的一部分,另外一个单独进程从EVENTS表中获取这些
记录并发布它们到一个消息中间件。
好处是,高水平的域事件,非2PC实现。
我们需要在实现时注意追踪避免重复发布事件,追踪管理事件发布的正确顺序。
===================
Event Sourcing:
事件发射源保存一个业务实体的状态,比如一个订单或者一个客户作为一系列状态变化事件。
当业务实体状态发生变化时,一个新的事件被添加到该事件列表。因为保存一个事件是一个单一的操作,
所以它是内在原子性。应用程序依赖事件来重构实体当前状态。

应用程序保存事件到一个事件存储数据库,该存储装置应该有一个API用于添加和获取实体的事件。
事件存储应该有类似消息中间件的行为。提供一个API让服务来预定事件,当服务将一个事件保存到事件存储时,
它会将事件分发给所有预定该事件的预订者。

有些实体,比如客户,可能会有大量的事件存在。为了优化事件的加载,应用程序可以周期性的保存一个
实体当前状态的快照,用于重构当前状态时使用。应用程序在重构实体当前状态时,会查找该实体的最近的快照
和自从该快照以后发生的事件执行即可。这样可以避免太多的事件被回放。

实例:
Customer 和 Orders ,我们使用Event Sourcing模式和CQRS。
应用程序使用Spring Boot和Java开发,使用Eventuate编译,它是一个机遇事件源和CQRS的应用程序平台。

Order Service 操作ORDER table,将每一次状态改变的操作都产生相应的事件存储到事件存储装置,
该数据库以Order 2345 为记录记录其OrderCreated,OrderApproved...OrderShipped等事件。
该事件存储装置提供API接受Order Service的 add和find事件操作,和来自Customer Service的预定事件。

不只是简单的将每一个订单的当前状态存储为ORDERS表的一行,应用程序还会持久化每一个Order为一个事件序列。
CustomerService会预定这些订单事件并更新它自己的状态。

public class order extends ReflectiveMutableCommandProcessingAggregate<Order,OrderCommand>{
    private OrderState state;
    private String customerId;
    public OrderState getState(){
        return state;
    }
    
    public List<Event> process(CreateOrderCommand cmd){
        return EventUtil.events(new OrderCreatedEvent(cmd.getCustomerId(),cmd.getOrderTotal()));
    }
    
    public List<Event> process(ApproveOrderCommand cmd){
        return EventUtil.events(new OrderApprovedEvent(customerId);
    }
    
    public List<Event> process(RejectedOrderCommand cmd){
        return EventUtil.events(new OrederRejectedEvent(customerId);
    }
    
    public void apply(OrderCreatedEvent event){
        this.state = OrderState.CREATED;
        this.customerId = event.getCustomerId();
    }
    
    public void apply(OrderApprovedEvent event) {
        this.state = OrderState.APPROVED;
    }


    public void apply(OrderRejectedEvent event) {
        this.state = OrderState.REJECTED;
    }
}

在CustomerService里预定Order事件的事件处理器:
@EventSubscriber(id="customerWorkflow")
public class CustomerWorkflow{
    @EventHandlerMethod
    public CompleteFuture<EntityWithIdAndVersion<Customer>> reserveCredit(
        EventHandlerContext<OrderCreatedEvent> ctx){
            OrderCreatedEvent event = ctx.getEvent();
            Money orderTotal = event.getOrderTotal();
            String customerId = event.getCustomerId();
            String orderId = ctx.getEntity();
            
            return ctx.update(Customer.class,customerId,new ReserveCreditCommand(orderTotal,orderId));
    }
}
上面的代码通过试着保持信用额度来处理一个OrderCreated事件。

事件源模式在事件驱动架构中解决了一个关键问题,使得它可靠的发布事件成为可能。
因为它保存的是事件而非域对象,最大可能的避免了对象关系数据库不统一问题。
它提供了100%可靠的发生在业务实体上的变化audit log。
它使得在任何时间和任意点上实现临时状态查询成为可能

==================================================
API Gateway/Backend for Front-End 
我们在是用微服务架构创建一个在线商店时,为了实现一个产品详细页面,我们需要开发多个
版本的产品详细内容用户界面。
HTML5/JavaScript开发的桌面和移动浏览器使用的页面,它需要有服务端的Web应用提供生成HTML。
原生的Android和苹果客户端,则需要跟服务器通过REST APIs交换
同时我们的在线商店还应该考虑通过REST API暴露一些产品详细介绍给第三方应用程序。

假设我们卖一本书,则需要涉及的详细内容有:
关于书的基本信息,比如书名,作者,定价等。
用户购买的历史信息
可购买数
购买选项
其它跟本书一起可购买的选项
买了这本书的人还会买什么
客户预览
卖家评级
。。。

我们采用微服务架构设计时,制作这样的详细界面需要多个微服务来实现。
产品信息服务,提供产品的基本信息
定价服务,提供产品价格
其它服务提供产品历史
库存服务,提供产品可供购买数
浏览历史服务,提供客户回顾页面
。。。

那我们的微服务架构的应用程序如何来访问这些单独的微服务呢?
我们知道微服务提供的内容通常是跟我们页面需要的内容是不完全一致的。
微服务通常提供充分授权的API,这也就意味着客户端需要跟多个微服务交互。
不同的客户端需要不同的数据,比如桌面浏览器版产品详细页面通常比移动版的包含更多内容。
不同的客户端网络性能也不同。
微服务的实例和它们运行位置(主机+端口)可能是动态变化的
随着时间的变化,服务分区也会发生变化,这些必须对于客户端来说是透明的。

解决方案:
实现一个API网关作为所有客户端的访问入口。
API网关处理请求,有些请求只是被proxied/routed代理并路由到相应的服务,
通过它将请求扩散给其它处理器。
 
 API 网关会提供各种不同的服务API样式供不同的客户端使用。比如Netflix API网关
 它会运行各种客户端不同的适配器代码,提供不同客户端适合的API调用。
 
 API网关还会实现安全内容,比如检查客户端请求的身份认证执行。
 
 API网关的变种:Backend for front-end
 它为每一个客户端定义了各自的API 网关。
 比如 Web app API 网关,Mobile API网关,Public API网关等。
 
 典型的实例是Netflix API gateway
 http://techblog.netflix.com/2013/01/optimizing-netflix-api.html
 https://github.com/cer/event-sourcing-examples/tree/master/java-spring/api-gateway-service
 https://github.com/cer/event-sourcing-examples
 
 如此一来,可以将客户端和应用程序的微服务分离设计隔离开来
 在决定使用哪个位置的微服务实例时也不需要客户端关心
 为每个客户端提供了更加优化的API
 减少请求和来回请求的次数
 简化了客户端对微服务的调用逻辑
 
 如何实现API网关呢?
 现在基于JVM,NIO的类库比如Netty,Spring Reactor等都是可用的。另外NodeJS也是一个选项。
 
 关联的设计模式:
 它是创建微服务架构的必须模式
 API网关必须使用Client-side Discovery 模式或者Server-side Discovery模式来路由请求到可用的微服务实例。
 API网关可能授权用户并传递包含用户信息的Access Token给服务。
 API网关将使用Circuit Breaker模式调用服务。
 
 ============================
 Publish events using database triggers
 使用数据库触发器来发布事件:
这是解决如何可靠稳定的发布状态变化事件问题。
使用一个或者多个数据库事件触发器来向EVENTS表里,该表会有单独的流程来处理发布存储的事件。
===============================================
Transaction log tailing
事务日志追踪:
追踪数据库事务日志来将其每个变化都发布成一个事件。
Eventuate Local(https://blog.eventuate.io/2016/10/06/eventuate-local-event-sourcing-and-cqrs-with-spring-boot-apache-kafka-and-mysql/)
使用了事务日志追踪方式。
该模式是非2PC方式,不需要应用程序变化追踪,保证了较高的准确性。
缺点在于相对晦涩,只能针对特定数据库开发,需要追踪避免重复发布,因为它发生在底层数据库
很难判断对业务层级的事件影响。
==============================================
Audit Logging 审计日志
将用户活动记录到数据库。

=============================================
Circuit Breaker 断路器模式
微服务架构中服务之间有时候需要协作处理请求,当一个服务同时调用其它服务时总是可能存在要调用的服务不可用或者显示高延迟最后不能使用。
前面的资源比如线程可能可能在等待其它服务回复时被调用者使用。
这些都会导致资源被耗尽,使得被调用服务无法再为其它请求服务。
一个服务的失败可能潜在的影响到整个应用程序的其它服务。

那么如何阻止一个网络或者服务的失败级联影响到其它服务呢?

方案是:每个服务客户端必须通过一个代理调用一个远程的服务。
这种代理的功能类似断路器。electrical circuit breaker
当一个连环的consecutive失败穿越一个阈值时(threshold),断路器断开,在一个周期内让所有这期间试图调用
该远程服务的请求都立刻失败。在超出一定时间后,断路器允许有限数量的测试请求通过。
如果这些测试请求成功,断路器会回复正常。否则,如果还有失败发生,则再次断开。

实例:
RegistrationServiceProxy 就是一个使用Scala写的处理远程服务调用失败的断路器。
@Component
class RegistrationServiceProxy @Autowired()(restTemplate: RestTemplate) extends RegistrationService {

  @Value("${user_registration_url}")
  var userRegistrationUrl: String = _

  @HystrixCommand(commandProperties=Array(new HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="800")))
  override def registerUser(emailAddress: String, password: String): Either[RegistrationError, String] = {
    try {
      val response = restTemplate.postForEntity(userRegistrationUrl,
        RegistrationBackendRequest(emailAddress, password),
        classOf[RegistrationBackendResponse])
      response.getStatusCode match {
        case HttpStatus.OK =>
          Right(response.getBody.id)
      }
    } catch {
      case e: HttpClientErrorException if e.getStatusCode == HttpStatus.CONFLICT =>
        Left(DuplicateRegistrationError)
    }
  }
}

这里的@HystrixCommand 指定使用断路器来调用registerUser()服务
@EnableCircuitBreaker声明用于启动断路器功能:
@EnableCircuitBreaker
class UserRegistrationConfiguration {

使用该模式的挑战在于 选择合适的超时时间值 打到不产生误报(false positive)
或者带来执行的延迟问题。

与其相关的模式
Microservice Chassis可能实现该模式
API Gateway会使用该模式调用服务
Server-side discovery 路由器会使用该模式调用服务

Netflix Hystrix 是实现该模式的一个类库。
===================================================================
Microservice Chassis 微服务底架
我们在开发一个应用程序时总是花费大量的时间来处理跨切面的内容。比如
Externalized configuration外部配置,包括credentials证书,外部服务的网络地址,比如数据库和消息中间件。
Logging 日志-配置系统日志框架,比如log4j 或者 logback
Health checks健康检查,一个监控服务可以执行“ping”的url,来判断应用程序的健康状况。
Metrics-矩阵,能够提供一些测量维度来从内部说明应用程序做了什么以及如何做的。
Distributed tracing分布式追踪,使用设备服务代码可以为外部请求在服务之间穿行时添加唯一标识。

与这些跨切面内容对应的另外一些跨切面内容是跟应用程序使用的特定的技术关联的。
应用程序使用的基础架构服务比如数据库或者消息中间件需要对其引用进行配置才能使用。
比如我们应用程序如果要使用关系型数据库,则必须配置一个数据库连接池。Web应用程序要处理HTTP请求
也需要引用配置。

要建立这种机制可能需要花费一天或者两天甚至更长时间。
Microservice Chassis 框架有:
Java语言方面:Spring Boot + Spring Cloud
Dropwizard(http://www.dropwizard.io/1.1.0/docs/)
Go语言方面:Gizmo,Micro,Go kit等

关联的模式:
Self Registration--微服务的底盘通常需要负责注册服务和服务寄存器registry
Client-side discovery--微服务的底盘框架通常要负责客户端服务发现
Circuit Breaker--微服务的底盘可能需要实现断路器模式
Distributed tracing--微服务的底盘框架可能设备代码

=========================================================
Client-side service discovery:
应用系统中服务通常需要调用其它的服务,在一个大型集成应用程序中,服务调用通过语言级别的方法或者过程来实现调用。
在传统的分布式应用系统部署中,服务是运行在固定的,众所周知的位置上(主机+端口),很容易通过HTTP/REST或者一些RPC机制调用。
但是现在基于微服务的应用通常运行在一个虚拟的或者容器化的环境里,有大量的服务实例同时运行,并且他们的位置都是在动态变化的。
这时就需要我们必须实现一种机制来让客户端服务将请求发送给一个动态变化的短暂存在的服务实例。

问题是:客户端服务-API 网关或者其它服务如何发现一个服务实例的位置?
每个服务暴露一个远程API比如HTTP/REST,或者 Thrift等在特定的位置(主机+端口)
这些服务的实例和它们的位置是动态变化的
运行这些服务的虚拟机和容器通常被赋予动态IP地址
服务实例的数量也是动态变化的,比如EC2 Autoscaling Group会基于加载来调整实例的数量。

解决方案:
在请求一个服务时,客户端会首先查询Service Registry来获取一个服务实例的位置。
服务寄存器(Service Registry)是存放所有服务的地方。
首先服务会注册到Service Registry,包括主机和端口信息。
客户端服务 Registryaware HTTP客户端首先会 查询它,然后通过负载均衡去调用具体服务。

实例:使用Spring Boot和Spring Cloud作为微服务应用底盘,使用Scala语言开发
这里RegistrationServiceProxy是应用程序的一个组件,为了注册一个用户我们使用SpringFramework的RestTemplate
来调用其它服务。
@Component
class RegistrationServiceProxy @Autowired()(restTemplate: RestTemplate) extends RegistrationService{
    @Value("${user_registration_url")
    var userRegistration_url=_
    
    override def registerUser(emailAddress:String, password:String):Either[RegistrationError,String]={
        val response = restTemplate.postForEntity(userRegistrationUrl,RegistrationBackendRequest(emailAddress,password),classOf[RegistrationBackendResponse])
        ...
}

在该类里我们注入了RestTemplate和指定REST 终端的user_registration_url
当应用程序部署后,user_registration_url被设置到URL http://REGISTRATION-SERVICE/user
查看docker-compose.yml文件。
REGISTRATION-SERVICE是本地服务名,它会被客户端发现解析成一个网络位置。
服务发现使用Netflix OSS组件实现,该组件提供Eureka和Ribbon,Eureka是一个Service Registry。
Ribbon是一个HTTP客户端用于查询Eureka以将请求路由到可用的服务实例上。

使用各种Spring Cloud声明来配置客户端服务发现:
@Configuration
@EnableEurekaClient
@Profile(Array("enableEurable"))
class EurekaClientConfiguration{
    @Bean
    @LoadBalanced
    def restTemplate(scalaObjectMapper:ScalaObjectMapper):RestTemplate = {
        var restTemplate = new RestTemplate()
        restTemplate.getMessageConverters foreach{
            case mc: MappingJackson2HttpMessageConverter =>
                mc.setObjectMapper(scalaObjectMapper)
            case _=>
        }
        restTemplate
    }
}

@EnableEurekaClient 指定Eureka客户端
@LoadBalanced 配置RestTemplate使用Ribbon,我们需要为Ribbon配置使用Eureka客户端来执行服务发现。
结果就是RestTemplate将通过查询Eureka来找到可用的服务实例的网络位置,然后用于处理对http://REGISTRATION-SERVICE/user端点的请求。

占用更少的网络流量
该模式会使客户端和Service Registry产生耦合
我们需要为每种编程语言或者框架都实现客户端服务发现逻辑,比如Java/Scala,JavaScript/NodeJS等
Netflix Prana(https://github.com/Netflix/Prana)为非JVM客户端提供了一个基于HTTP 代理发现服务。

关联模式:
Service Registry--服务注册表,是服务发现的一个组成部分
Microservice chassis--客户端发现是微服务底架的主要功能
Server Side Discovery--是解决同一个问题的另一方案
==========================================================
Server-client service discovery
当我们请求一个服务时,客户端会发送一个请求给我们众所周知的一个路由地址,路由器会查询服务注册表,这个服务注册表有可能是嵌入在路由器内部的。
然后将我们的请求路由到可用的服务实例上进行处理。

实例:
AWS Elastic Load Balancer(ELB)就是一个服务端服务发现的典型例子。当客户端发送HTTP请求(或者打开TCP链接)到ELB时,它会在一堆的EC2实例中间平衡载入。
ELB可以平衡载入来自互联网的外部接入,当我们部署到VPC时,还能平衡载入内部通信量。 ELB还充当了服务注册表的作用。
EC2实例注册到ELB后,既可以通过显式API调用,也可以作为自动扩容的一部分。

一些集群方案比如Kubernetes和Marathon都是在每个节点主机上运行一个代理程序来负责服务端发现路由。
为了访问一个服务,客户端首先使用配置给该服务的端口连接其本地代理,代理将请求发送给位于集群中某处节点上的服务实例。

跟Client-side discovery机制相比,客户端发现机制相对比较简单,因为它不需要处理发现。 一些云环境都实现这些服务发现功能。
除了作为云环境的一部分,否则路由器是必须另外安装配置的一个系统组件,它也需要根据需要复制扩容。
路由器必须支持必须的通讯协议比如HTTP,gRPC,Thrift等,当然基于TCP的路由除外。

===============================================================

Service Registry 服务注册表
一个服务的客户端可以使用客户端发现也可以使用服务端发现来断定要调用的服务实例的位置信息。

我们实现一个服务注册表,它其实是一个服务的数据库,保存服务的实例和位置。
服务实例为在启动时注册到该数据库,并在关闭时注销。
客户端服务发现 或者 服务端路由发现都会查询该服务注册表来查找可用的服务实例。
服务注册表会调用每个服务实例的健康检查API(http://microservices.io/patterns/observability/health-check-api.html)
来检验服务能否被用来处理请求。

实例:
Eureka Server 是一个简单的Spring Boot应用程序

@SpringBootApplication
@EnableEurekaServer
public class EurekaServer{
    public static void main(String[] args){
        new SpringApplicationBuilder(EurekaServer.class).web(true).run(args);
    }
}

它使用Docker进行部署:
eureka:
 image: java:openjdk-8u91-jdk
 working_dir: /app
 volumes:
    - ./eureka-server/build/libs:/app
 command: java -jar /app/eureka-server.jar --server.port=8761
 ports:
    - "8761:8761"

其它实现了服务注册表的例子包括
Apache Zookeeper
Consul(https://www.consul.io/)
Etcd(https://github.com/coreos/etcd)
另外像Kubernetes,Marathon和AWS ELB都隐式的实现了服务注册表模式。

除非服务注册表被基础架构实现,它是一个重要的基础组件。尽管客户端需要缓存有服务注册表提供的数据,
如果服务注册表失败了,数据最终就将过期,总之,服务注册表必须具备高可用性。

我们需要考虑如何在服务注册表中注册服务。这里有两个选项
Self Registration pattern--服务实体注册它们自己
3rd party registration pattern -- 第三方注册服务实例

注册服务的服务客户端需要知道服务注册表实例的位置,服务注册表必须部署到固定的服务器拥有众所周知的IP。
服务客户端需要配置这一地址。

Netflix Eureka服务实例通常使用弹性IP地址部署,我们在服务端配置是使用属性文件或者DNS来指定弹性IP地址。
当Eureka实例启动时,它会询问配置来觉得哪个弹性地址可用。
Eureka 客户端也需要配置 弹性地址池。

关联模式:
Client-side discovery 和Server-side discovery 的创建都需要服务注册表
Self registration 和 3rd party registration是服务注册表两种不同的服务实例注册方式
Health Check API 服务注册表调用它的一个实例来检查服务是否可以用来处理请求。

==================================================================

Log aggregation 日志集成
微服务架构中,应用程序有多个服务和服务实例构成,它们运行在多台服务器上,请求通常活跨越多个服务。
每个服务实例都会生成关于它所有操作的日志到一个标准格式的日志文件中。这些日志中包含错误,警告,提示和调试信息等。
如何理解应用程序的行为以及排除问题呢?
使用收集日志服务从各个服务实例上收集日志,用户可以查询和分析这些日志。还可以配置当特定的消息在日志中出现时出发警报。

比如 AWS Cloud Watch

关联模式:
Distributed tracing -- 在日志消息里包含外部请求的id
Exception tracking -- 也就是日志异常,将它们报告给一个异常追踪服务。

=======================================================
Self Registration
无论我们采用Client-side Service Discovery 还是Server-side Service Discovery模式,我们都必须在启动时将服务实体注册到Service registry,
让它们能够找到并在关闭时撤销。
那么服务实体是如何注册到服务注册表又是如何从其上面注销的呢?
服务实体必须在启动时注册到服务注册表,在关闭时从服务注册表注销。
服务实体崩溃时必须从服务注册表中注销。
服务实体依然在运行但已无法提供请求处理必须从服务注册表注销

解决方案:
服务实例自己负责将自己注册到服务注册表。在服务实体启动时,将自己的主机IP和端口信息注册到服务注册表。服务实体的必须定期的重新注册自己
让服务注册表知道自己还活着。在服务实体关闭时,需要将自己从服务注册表中注销。

实例:使用SpringBoot和SpringCloud作为Microservice Chassis,用Scala语言编写,该应用程序使用Eureka Service Registry,它是Netflix OSS的一个组件。

在Java配置类上使用@EnableEurekaClient 来让服务实体注册到Eureka
@Configuration
@EnableEurekaClient
class EurekaClientConfiguration {}
============================================================
Health Check API
我们在使用微服务架构时,有时候一个服务实体已经没有处理请求的能力了但是还依然在运行,比如它可能连接数据库超时,当这样的事情发生时,监控系统应该
生成一个警报,载入平衡或者服务注册表不应该再路由请求给它。
那么我们如何判断一个服务实例是否可以处理请求呢?
当服务实体失败时应该发出警报
所有的请求都应该发送给可用的服务实例。
解决方案:
服务有一个健康监测API终端,可以返回服务的健康状况给客户端。
服务实例跟基础服务的连接状态
主机的运行状态,比如硬盘空间等
应用程序的特定逻辑是否正常

监控服务,服务注册表或者负载平衡会周期性的调用这些健康监测内容来检查服务实例的健康状况。

实例:该实例的终端由SpringBoot Actuator模块实现,它配置了/health HTTP端点调用扩展健康检查逻辑。
首先添加对actuator的依赖
dependencies {
  compile "org.springframework.boot:spring-boot-starter-actuator"
}
然后,启动Spring Boot 自动配置
@SpringBootApplication
class UserRegistrationConfiguration{}
至此,我们应用程序已经具备了默认行为的健康监测终端
我们可以通过定义一个或者多个实现了HealthIndicator接口的 Spring beans来自定义监测行为
class UserRegistrationConfiguration{
    @Bean
    def discoveryHealthIndicator(discoveryClient: EurekaClient) : HealthIndicator = new DiscoveryHealthIndicator(discoveryClient)
}
HealthIndicator 接口定义了一个必须实现的方法health(),该方法返回一个Health值。

服务注册表会调用健康监测终端。
==================================================================
Exception Tracking
在微服务架构中,运行在多个机器上的服务实例在发生错误时,会抛出异常,异常为包含错误消息和追踪堆栈信息
如何理解应用程序并排除异常?
异常信息必须是非重复的,被记录,开发者可以检查以及底层问题解析。
任何方案都必须有最小的运行负载。

解决方案:
将所有的异常都上报给指定的集中异常追踪服务,由该服务来汇总并追踪异常然后上报给开发者。

一般采用日志汇总的方案来实现异常汇总。
================================================================
Distributed Tracing
分布式追踪

在微服务架构中,请求通常会跨多个服务,每个服务通过执行一个或多个操作来处理请求,比如数据库查询,发布消息等。
那么如何理解应用程序的行为并排除问题呢?
外部监控只能告诉我们整体的回复时间和调用次数,不能看到每个操作的情况
解决方案:服务仪器代码
为每一个外部请求赋予一个外部请求ID
将外部请求ID传递给所有服务包括处理该请求的服务
将外部请求的ID包含到所有相关日志信息里
记录关于请求的信息(比如开始时间,结束时间)和操作执行。

实例:
我们使用Spring Cloud Sleuth(https://cloud.spring.io/spring-cloud-sleuth/)
该组件提供分布式追踪支持。它测量Spring组件来收集追踪信息并提交给一个Zipkin Server,该服务负责收集和展示追踪内容。

添加相关依赖:
dependencies {
  compile "org.springframework.cloud:spring-cloud-sleuth-stream"
  compile "org.springframework.cloud:spring-cloud-starter-sleuth"
  compile "org.springframework.cloud:spring-cloud-stream-binder-rabbit"
}
RabbitMQ用于交付追踪信息到Zipkin
在docker-compose.yml文件中配置Spring Cloud Sleuth相关的环境变量
environment:
    SPRING_RABBITMQ_HOST: rabbitmq
    SPRING_SLEUTH_ENABLED: "true"
    SPRING_SLEUTH_SAMPLER_PERCENTAGE: 1
    SPRING_SLEUTH_WEB_SKIPPATTERN: "/api-docs.*|/autoconfig|/configprops|/dump|/health|/info|/metrics.*|/mappings|/trace|/swagger.*|.*\\.png|.*\\.css|.*\\.js|/favicon.ico|/hystrix.stream"
该属性文件配置启用Spring Cloud Sleuth并配置它来简化所有请求,同时告诉它通过运行在rabbitmq主机上的RabbitMQ中间件分发trace到Zipkin。

Zipkin定义
@SpringBootApplication
@EnableZipkinStreamServer
public class ZipkinServer {
  public static void main(String[] args) {
    SpringApplication.run(ZipkinServer.class, args);
  }
}

Docker部署配置
zipkin:
  image: java:openjdk-8u91-jdk
  working_dir: /app
  volumes:
    - ./zipkin-server/build/libs:/app
  command: java -jar /app/zipkin-server.jar --server.port=9411
  links:
    - rabbitmq
  ports:
    - "9411:9411"
  environment:
    RABBIT_HOST: rabbitmq
    
它能够提供一些有用的深入到系统内部的行为,找到延迟的来源。
让开发这能够看到单个请求是如何被处理。
汇集和存储这些追踪信息需要大量的基础设备。

Open Zipkin 用于记录和显示追踪信息
Open Tracing 分布式追踪的标准API
https://cloud.spring.io/spring-cloud-sleuth/
======================================================
Database per service

转载于:https://my.oschina.net/u/924064/blog/900161

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值