转载地址:http://www./articles/Q7VNne2 刘思贤(微博@starlight36),爱油科技架构师、PMP。主要负责业务平台架构设计,DevOps实施和研发过程持续改进等,关注领域驱动设计与微服务、建设高效团队和工程师文化培养。 摘要本次分享主要介绍了爱油科技基于Docker和spring Cloud将整体业务微服务化的一些实践经验,主要包括: 单体应用优点
缺点
架构拆分拆分:按行分层,按列分业务 在我们的微服务体系中,所有的服务被划分为了三个层次:
项目中我们主要关注业务服务层和接入层,对于没有足够运维力量的我们,基础设施使用云服务是省事省力的选择。 业务服务层我们给他起名叫作Epic,接入层我们起名Rune,建立之初便订立了如下原则:
业务逻辑层我们主要使用使用Java,接入层我们主要使用PHP或Node。后来随着团队的成长,逐步将接入层全部迁移至Node。 框架选型爱油科技作为一家成品油行业的初创型公司,需要面对非常复杂的业务场景,而且随着业务的发展,变化的可能性非常高。所以在微服务架构设计之初,我们就期望我们的微服务体系能:
目前常见的微服务相关框架:
这些常见的框架中,Dubbo几乎是唯一能被称作全栈微服务框架的“框架”,它包含了微服务所需的几乎所有内容,而DubboX作为它的增强,增加了REST支持。 它优点很多,例如:
不过遗憾的是:
Motan是微博平台微服务框架,承载了微博平台千亿次调用业务。 优点是:
不过:
Apache Thrift、gRPC等虽然优秀,并不能算作微服务框架,自身并不包括服务发现等必要特性。 如果说微服务少不了Java,那么一定少不了Spring,如果说少不了Spring,那么微服务“官配”Spring Cloud当然是值得斟酌的选择。 优点:
不足:
根据我们的目标,我们最终选择了Spring Cloud作为我们的微服务框架,原因有4点:
Spring CloudSpring Cloud是一个集成框架,将开源社区中的框架集成到Spring体系下,几个重要的家族项目:
服务发现和配置管理Spring Cloud Netflix提供了Eureka服务注册的集成支持,不过没选它是因为:
docker作为支撑平台的重要技术之一,Consul几乎也是我们的必选服务。因此我们觉得一事不烦二主,理所应当的Consul成为我们的服务注册中心。 Consul的优势:
也就是说,Consul可以一次性解决我们对服务注册发现、配置管理的需求,而且长期来看也更适合跟不同平台的系统,包括和Docker调度系统进行整合。
最初打算自己开发一个Consul和Spring Cloud整合的组件,不过幸运的是,我们做出这个决定的时候,
因此借助Consul和
当然也踩到了一些坑:
#!/usr/bin/env bash set-e # If service runs as Rancher service, auto set advertise ip address # from Rancher metadata service. if[ -n"$RUN_IN_RANCHER"];then echo"Waiting for ip address..." # Waiting for ip address sleep 5 RANCHER_MS_BASE=http://rancher-metadata/2015-12-19 PRIMARY_IP=`curl -sSL$RANCHER_MS_BASE/self/container/primary_ip` SERVICE_INDEX=`curl -sSL$RANCHER_MS_BASE/self/container/service_index` if[ -n"$PRIMARY_IP"];then exportSPRING_CLOUD_CONSUL_DISCOVERY_HOSTNAME=$PRIMARY_IP fi echo"Starting service #${SERVICE_INDEX-1}at$PRIMARY_IP." fi exec"$@"
我们的容器运行在Rancher中,所以可以利用Rancher的metadata服务来获取容器的IP地址,再通过 另外一些服务中内置了定时调度任务等,多实例启动时需要单节点运行调度任务。通过Consul的分布式锁服务,我们可以让获取到锁的节点启用调度任务,没获取到的节点等待获取锁。 服务集成
为了方便开发人员使用,微服务框架应当简单容易使用。对于很多微服务框架和RPC框架来说,都提供了很好的机制。在Spring Cloud中通过 服务方声明一个Restful的服务接口,和普通的Spring MVC控制器几乎别无二致: @RestController @RequestMapping("/users") publicclassUserResource{ @RequestMapping(value ="{id}", method = RequestMethod.GET, produces ="application/json") publicUserRepresentationfindOne(@PathVariable("id")String id){ User user = this.userRepository.findByUserId(newUserId(id)); if(user ==null|| user.getDeleted()) { thrownewNotFoundException("指定ID的用户不存在或者已被删除。"); } returnnewUserRepresentation(user); } } 客户方使用一个微服务接口,只需要定义一个接口: @FeignClient("epic-member-microservice") publicinterfaceUserClient{ @Override @RequestMapping(value ="/users/{id}", method = RequestMethod.GET, produces ="application/json") User findOne(@PathVariable("id")String id); }
在需要使用
对于错误的处理,我们使用HTTP状态码作为错误标识,并做了如下规定:
对于服务器端,只需要在一个异常类上添加注解,即可指定该异常的HTTP响应状态码,例如: @ResponseStatus(HttpStatus.NOT_FOUND) publicclassNotFoundExceptionextendsRuntimeException{ publicNotFoundException(){ super("查找的资源不存在或者已被删除。"); } publicNotFoundException(String message){ super(message); } publicNotFoundException(String message, Throwable cause){ super(message, cause); } }
对于客户端我们实现了自己的 @Component publicclassFeignClientExceptionErrorDecoderimplementsErrorDecoder{ privatefinalErrorDecoder delegate =newErrorDecoder.Default(); @Override publicExceptiondecode(String methodKey, Response response){ // Only decode 4xx errors. if(response.status() >=500) { returndelegate.decode(methodKey, response); } // Response content type must be json if(response.headers().getOrDefault("Content-Type", Lists.newArrayList()).stream() .filter(s -> s.toLowerCase().contains("json")).count() >0) { try{ String body = Util.toString(response.body().asReader()); // 转换并返回异常对象 ... } catch(IOException ex) { thrownewRuntimeException("Failed to process response body.", ex); } } returndelegate.decode(methodKey, response); } }
需要注意的是,
在 try{ User user = this.userClient.findOne(newUserId(id)); } catch(NotFoundException ex) { ... }
通过 服务质量保证微服务架构下,由于调用需要跨系统进行远程操作,各微服务独立运维,所以在设计架构时还必须考虑伸缩性和容错性,具体地说主要包括以下几点要求:
Spring Cloud中内置的
下面主要介绍一下,各个组件在进行服务质量保证中是如何发挥作用的。 ConsulConsul中注册了一致性的可用的服务列表,并通过健康检查保证这些实例都是存活的,服务注册和检查的过程如下:
这样能够保证Consul中列出的所有微服务状态都是健康可用的,各个微服务会监视微服务实例列表,自动同步更新他们。 HystrixHystrix提供了断路器模式的实现,主要在三个方面可以说明:
图片来自Hystrix项目文档 首先Hystrix提供了降级方法,断路器开启时,操作请求会快速失败不再向后投递,直接调用fallback方法来返回操作;当操作失败、被拒或者超时后,也会直接调用fallback方法返回操作。这可以保证在系统过载时,能有后备方案来返回一个操作,或者优雅的提示错误信息。断路器的存在能让故障业务被隔离,防止过载的流量涌入打死后端数据库等。 然后是基于请求数据统计的断路开关,在Hystrix中维护一个请求统计了列表(默认最多10条),列表中的每一项是一个桶。每个桶记录了在这个桶的时间范围内(默认是1秒),请求的成功数、失败数、超时数、被拒数。其中当失败请求的比例高于某一值时,将会触发断路器工作。
最后是不同的请求命令( 这里包括了Hystrix的一些重要参数的配置项:
Ribbon
Ribbon使用Consul提供的服务实例列表,可以通过服务名选取一个后端服务实例连接,并保证后端流量均匀分布。
在这个过程中,各个组件扮演的角色如下:
Feign负责提供客户端接口收调用,把发起请求操作(包括编码、解码和请求数据)封装成一个Hystrix命令,这个命令包裹的请求对象,会被Ribbon的负载均衡器处理,按照负载均衡策略选择一个主机,然后交给请求对象绑定的HTTP客户端对象发请求,响应成功或者不成功的结果,返回给Hystrix。
总的来看, ZuulZuul为使用Java语言的接入层服务提供API网关服务,既可以根据配置反向代理指定的接口,也可以根据服务发现自动配置。Zuul提供了类似于iptables的处理机制,来帮助我们实现验证权鉴、日志等,请求工作流如下所示: 图片来自Zuul官方文档。 使用Zuul进行反向代理时,同样会走与OpenFeign类似的请求过程,确保API的调用过程也能通过Hystrix、Ribbon提供的降级、控流机制。 Hystrix DashboardHystrix会统计每个请求操作的情况来帮助控制断路器,这些数据是可以暴露出来供监控系统热点。Hystrix Dashboard可以将当前接口调用的情况以图形形式展示出来:
图片来自Hystrix Dashboard官方示例 Hystrix Dashboard既可以集成在其他项目中,也可以独立运行。我们直接使用Docker启动一个Hystrix Dashboard服务即可: docker run --rm -ti -p 7979:7979 kennedyoliveira/hystrix-dashboard
为了实现能对整个微服务集群的接口调用情况汇总,可以使用 日志监控微服务的日志直接输出到标准输出/标准错误中,再由Docker通过syslog日志驱动将日志写入至节点机器机的rsyslog中。rsyslog在本地暂存并转发至日志中心节点的Logstash中,既归档存储,又通过ElasticSearch进行索引,日志可以通过Kibana展示报表。
在rsyslog的日志收集时,需要将容器信息和镜像信息加入到tag中,通过Docker启动参数来进行配置: --log-driver syslog --log-opt tag="{{.ImageName}}/{{.Name}}/{{.ID}}" 不过rsyslog默认只允许tag不超过32个字符,这显然是不够用的,所以我们自定义了日志模板: template (name="LongTagForwardFormat" type="string" string="<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg%") 在实际的使用过程中发现,当主机内存负载比较高时,rsyslog会发生日志无法收集的情况,报日志数据文件损坏。后来在Redhat官方找到了相关的问题,确认是rsyslog中的一个Bug导致的,当开启日志压缩时会出现这个问题,我们选择暂时把它禁用掉。 领域驱动设计我们使用领域驱动设计(DDD)的方法来构建微服务,因为微服务架构和DDD有一种天然的契合。把所有业务划分成若干个子领域,有强内在关联关系的领域(界限上下文)应当被放在一起作为一个微服务。最后形成了界限上下文-工作团队-微服务一一对应的关系:
微服务设计在设计单个微服务(Epic层的微服务)时,我们这样做:
这给我们带来了显著的好处:
事务问题从单体应用迁移到微服务架构时,不得不面临的问题之一就是事务。在单体应用时代,所有业务共享同一个数据库,一次请求操作可放置在同一个数据库事务中;在微服务架构下,这件事变得非常困难。然而事务问题不可避免,非常关键。 解决事务问题时,最先想到的解决方法通常是分布式事务。分布式事务在传统系统中应用的比较广泛,主要基于两阶段提交的方式实现。然而分布式事务在微服务架构中可行性并不高,主要基于这些考虑:
根据CAP理论,分布式系统不可兼得一致性、可用性、分区容错性(可靠性)三者,对于微服务架构来讲,我们通常会保证可用性、容错性,牺牲一部分一致性,追求最终一致性。所以对于微服务架构来说,使用分布式事务来解决事务问题无论是从成本还是收益上来看,都不划算。 对微服务系统来说解决事务问题,CQRS+Event Sourcing是更好的选择。 CQRS是命令和查询职责分离的缩写。CQRS的核心观点是,把操作分为修改状态的命令(Command),和返回数据的查询(Query),前者对应于“写”的操作,不能返回数据,后者对应于“读”的操作,不造成任何影响,由此领域模型被一分为二,分而治之。 Event Sourcing通常被翻译成事件溯源,简单的来说就是某一对象的当前状态,是由一系列的事件叠加后产生的,存储这些事件即可通过重放获得对象在任一时间节点上的状态。 通过CQRS+Event Sourcing,我们很容易获得最终一致性,例如对于一个跨系统的交易过程而言:
我们只要保证领域事件能被持久化,那么即使出现网络延迟或部分系统失效,我们也能保证最终一致性。 实践上,我们利用Spring从4.2版本开始支持的自定义应用事件机制将本地事务和事件投递结合起来进行:
一些小经验
DevOps到目前为止我们已经有数十个微服务运行于线上了,微服务数目甚至多过了团队人数。如果没有DevOps支持,运维这些微服务将是一场灾难。 我们使用Docker镜像作为微服务交付的标准件:
由于时间所限,这里就不展开赘述了。 永不完美
基于 对于非JVM语言的微服务和基于SpringCloud的微服务如何协同治理,这一问题仍然值得探索。包括像与Docker编排平台,特别是与Mesos协同进行伸缩的服务治理,还需要更多的实践来支持。 总结
|
|
来自: WindySky > 《spring cloud》