GitChat 做者:曾小波
更多IT技术分享,尽在微信公众号:GitChat技术杂谈html
随着公司业务量的飞速发展,平台面临的挑战已经远远大于业务,需求量不断增长,技术人员数量增长,面临的复杂度也大大增长。在这个背景下,平台的技术架构也完成了从传统的单体应用到微服务化的演进。前端
这是平台最开始的状况,当时流量小,为了节约成本,并将全部应用都打包放到一个应用里面,采用的架构为.net+sqlserver:java
表示层linux
位于最外层(最上层),最接近用户。用于显示数据和接收用户输入的数 据,为用户提供一种交互式操做的界面,平台所使用的是基于.net的web形式。nginx
业务逻辑层git
业务逻辑层(Business Logic Layer)无疑是系统架构中体现核心价值的部分。它的关注点主要集中在业务规则的制定、业务流程的实现等与业务需求有关的系统设计,也便是说它是与系统所应对的领域(Domain)逻辑有关,不少时候,也将业务逻辑层称为领域层。
业务逻辑层在体系架构中的位置很关键,它处于数据访问层与表示层中间,起到了数据交换中承上启下的做用。因为层是一种弱耦合结构,层与层之间的依赖是向下的,底层对于上层而言是“无知”的,改变上层的设计对于其调用的底层而言没有任何影响。若是在分层设计时,遵循了面向接口设计的思想,那么这种向下的依赖也应该是一种弱依赖关系。对于数据访问层而言,它是调用者;对于表示层而言,它倒是被调用者。github
数据层web
数据访问层:有时候也称为是持久层,其功能主要是负责数据库的访问,能够访问数据库系统、二进制文件、文本文档或是XML文档,平台在这个阶段使用的是hibernate.net+sqlserver。redis
第一代架构看似很简单,却支撑了平台的早期业务发展,知足了网站用户访问量在几万规模的处理需求。可是当用户访问量呈现大规模增加,问题就暴露出来了:算法
为了解决第一代架构面临的问题,团队制定了以下的策略,并造成了第二代应用架构(垂直应用架构)
Sticky就是基于cookie的一种负载均衡解决方案,经过cookie实现客户端与后端服务器的会话保持, 在必定条件下能够保证同一个客户端访问的都是同一个后端服务器。请求来了,服务器发个cookie,并说:下次来带上,直接来找我!。在项目中,咱们使用了taobao开源的tengine中的session_sticky模块。
能够看到第二代架构解决应用级别的水平扩展扩展,通过优化后,该架构支撑了几十万用户的访问需求,在这一阶段有部分应用已经使用java 完成了mvc架构的重写。固然也存在一些问题。
咱们曾经尝试使用sql server AlwaysOn 解决扩展问题,可是实验发如今复制过程当中出现至少10s的延迟,所以放弃了这个方案。
为了解决第一代与第二代架构存在的问题,咱们对平台进行了梳理优化。根据平台业务须要以及对第一二代架构的总结,咱们肯定了第三代架构的核心需求:
并以此为基础进行了平台的第三代架构的重构工做。
看第三代架构里面的组成,主要分为八个部分:
平台在选择CDN厂商时,须要考虑经营时间长短,是否有可扩充的带宽资源、灵活的流量和带宽选择、稳定的节点、性价比;综合前面几个因素平台采用了七牛的CDN服务。
选择哪一种负载,须要综合考虑各类因素(是否知足高并发高性能,Session保持如何解决,负载均衡的算法如何,支持压缩,缓存的内存消耗),主要分为如下两种:
LVS:工做在4层,Linux实现的高性能高并发、可伸缩性、可靠的的负载均衡器,支持多种转发方式(NAT、DR、IP Tunneling),其中DR模式支持经过广域网进行负载均衡。支持双机热备(Keepalived或者Heartbeat)。对网络环境的依赖性比较高。
Nginx:工做在7层,事件驱动的、异步非阻塞的架构、支持多进程的高并发的负载均衡器/反向代理软件。能够针对域名、目录结构、正则规则针对http作一些分流。经过端口检测到服务器内部的故障,好比根据服务器处理网页返回的状态码、超时等等,而且会把返回错误的请求从新提交到另外一个节点,不过其中缺点就是不支持url来检测。对于session sticky,咱们经过基于cookie的扩展nginx-sticky-module来实现。这种也是平台目前所采用的方案。
redis 集群:以高响应速度、内存操做为上层提供缓存服务。
mongodb集群:因为mongodb具备灵活文档模型 、高可用复制集 、可扩展分片集群等特性,平台基于此为上层提供如文章、帖子、链路日志等存储服务。mongodb集群采用了复制+分片的架构解决可用性与扩展性问题。
MySQL集群:存储会员、商品、订单等具备事务性要求的数据。
Kafka:支撑了平台的全部的消息服务。
ES(elasticsearch):提供了平台的商品、会员、订单、日志等搜索服务。
康威定律:任何组织在设计一套系统时,所交付的设计方案在结构上都与该组织的沟通结构保持一致。
在实践第三代架构时,咱们对团队组织作了几个调整:
专门创建了一个架构师部门来负责第三代架构的推行工做。一般对于一个的架构师团队有系统架构、应用架构、运维、DBA、敏捷专家五个角色组成是一个比较合理的结构。那么又如何控制好架构组的产出,保证架构工做的顺利推行呢?
再谈谈整个团队的交付流程与开发模式,若是没有预先定义好,则很难让微服务架构发挥出真正的价值,下面咱们先来看看微服务架构的交付流程。
使用微服务架构开发应用程序,咱们其实是针对一个个微服务进行设计、开发、测试、部署,由于每一个服务之间是没有彼此依赖的,大概的交付流程就像上图这样。
设计阶段:
架构组将产品功能拆分为若干微服务,为每一个微服务设计 API 接口(例如 REST API),须要给出 API 文档,包括 API 的名称、版本、请求参数、响应结果、错误代码等信息。
在开发阶段,开发工程师去实现 API 接口,也包括完成 API 的单元测试工做,在此期间,前端工程师会并行开发 Web UI 部分,可根据 API 文档造出一些假数据(咱们称为“mock 数据”),这样一来,前端工程师就没必要等待后端 API 所有开发完毕,才能开始本身的工做了,实现了先后端并行开发。
测试阶段:
这一阶段过程全自动化过程,开发人员提交代码到代码服务器,代码服务器触发持续集成构建、测试,若是测试经过则会自动经过Ansible脚本推送到模拟环境;在实践中对于线上环境则是先要走审核流程,经过以后才能推送到生产环境。提升工做效率,而且控制了部分可能由于测试不充分而致使的线上不稳定。
在以上交付流程中,开发、测试、部署这三个阶段可能都会涉及到对代码行为的控制,咱们还须要制定相关开发模式,以确保多人可以良好地协做。
因为第三代架构跨度较大,而且面临了没法修改的.net遗留系统,咱们采用绞杀者模式,在遗留系统外面增长新的Proxy代理微服务,而且在LB控制upstream的方式,而不是直接修改原有系统,逐步的实现对老系统的替换。
经验代表,咱们须要善用代码版本控制系统,我曾经遇到一个开发团队,因为分支没有规范,最后一个小版本上线合代码竟然化了几个小时,最后开发人员本身都不知道合到哪一个分支。拿 Gitlab 来讲,它很好地支持了多分支代码版本,咱们须要利用这个特性来提升开发效率,上图就是咱们目前的分支管理规范。
最稳定的代码放在 master 分支上,咱们不要直接在 master 分支上提交代码,只能在该分支上进行代码合并操做,例如将其它分支的代码合并到 master 分支上。
咱们平常开发中的代码须要从 master 分支拉一条 develop 分支出来,该分支全部人都能访问,但通常状况下,咱们也不会直接在该分支上提交代码,代码一样是从其它分支合并到 develop 分支上去。
当咱们须要开发某个特性时,须要从 develop 分支拉出一条 feature 分支,例如 feature-1 与 feature-2,在这些分支上并行地开发具体特性。
当特性开发完毕后,咱们决定须要发布某个版本了,此时须要从 develop 分支上拉出一条 release 分支,例如 release-1.0.0,并将须要发布的特性从相关 feature 分支一同合并到 release 分支上,随后将针对 release 分支推送到测试环境,测试工程师在该分支上作功能测试,开发工程师在该分支上修改 bug。待测试工程师没法找到任何 bug 时,咱们可将该 release 分支部署到预发环境,再次验证之后,均无任何 bug,此时可将 release 分支部署到生产环境。待上线完成后,将 release 分支上的代码同时合并到 develop 分支与 master 分支,并在 master 分支上打一个 tag,例如 v1.0.0。
当生产环境发现 bug 时,咱们须要从对应的 tag 上(例如 v1.0.0)拉出一条 hotfix 分支(例如 hotfix-1.0.1),并在该分支上作 bug 修复。待 bug 彻底修复后,需将 hotfix 分支上的代码同时合并到 develop 分支与 master 分支。
对于版本号咱们也有要求,格式为:x.y.z,其中,x 用于有重大重构时才会升级,y 用于有新的特性发布时才会升级,z 用于修改了某个 bug 后才会升级。针对每一个微服务,咱们都须要严格按照以上开发模式来执行。
咱们已经对微服务团队的架构、交付流程、开发模式进行了描述,下面咱们聊聊概括一下微服务开发体系。
Martin Flower的定义:
In short, the microservice architectural style [1] is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies
简单的说,微服务是软件系统架构上的一个设计风格,它倡导将一个本来独立的系统分红多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间经过基于HTTP的RESTful 轻量级API进行通讯协做。被拆分的每一个微服务围绕系统中的某项或一些耦合度较高的业务进行构建,而且每一个服务都维护着自身的数据存储、业务开发、自动化测试案例以及独立部署机制。因为有了轻量级通讯机制,这些微服务间可使用不通的语言来编写。
微服务到底拆分到一个多大的粒度,更多时候是须要在粒度与团队之间找到一个平衡点,微服务越小,微服务独立性带来的好处就越多。可是管理大量微服务也会越复杂。基本上拆分须要遵循如下几个原则:
为了搭建好微服务架构,技术选型是一个很是重要的阶段,只有选择合适的"演员",才能把这台戏演好。
咱们使用 Spring Cloud 做为微服务开发框架,Spring Boot 拥有嵌入式 Tomcat,可直接运行一个 jar 包来发布微服务,此外它还提供了一系列“开箱即用”的插件,例如:配置中心,服务注册与发现,熔断器,路由,代理,控制总线,一次性令牌,全局锁,leader选举,分布式 会话,集群状态等,可大量提升咱们的开发效率。
功能 | Spring Cloud |
---|---|
路由与负载均衡 | Ribbon |
注册中心 | Eureka |
网关 | Zuul |
断路器 | Hystrix |
分布式配置 | Config |
服务调用跟踪 | sleuth |
日志输出 | elk |
认证集成 | oauth2 |
消息总线 | Bus |
批量任务 | Task |
工程结构规范
上图是咱们实践中每一个服务应该具备的项目组成结构。
其中:
为对内其它微服务提供服务调用。服务名+api模块为服务间定义的接口规范,使用swagger+rest接口定义。服务名+server模块包含了能直接启动该服务的应用与配置。
供上层web应用请求的入口,该服务中通常会调用底层微服务完成请求。
API 网关实践
API网关做为后端全部微服务和API的访问入口, 对微服务和API进行审计,流控, 监控,计费等。经常使用的API网关解决方案有:
最有名的固然是Netflix的zuul, 但这不意味着这种方案就最适合你, 好比Netfilx是由于使用AWS,对基础设施把控有限, 因此才不得不在应用层作了zuul这样的方案,若是通盘考虑, 这种方案不是最合适或者说最有的方案。
但若是本身的团队对总体技术设施把控有限,且团队结构不完善,应用层方案也多是最适合你的最佳方案。
也是咱们采用并认为最合适的方案,OpenResty和Kong是比较成熟的可选方案, 不过Kong使用Postgres或者Cassandra, 国内公司估计选择这俩货的很少,但Kong的HTTP API设计仍是很不错的。
使用nginx+lua+consul组合方案,虽然咱们团队大可能是java,选择zookeeper会是更加天然的选择,但做为新锐派,对压测结果进行了分析, 咱们最终选择使用consul。
良好的HTTP API支持, 能够动态管理upstreams, 这也意味着咱们能够经过发布平台或者胶水系统无缝的实现服务注册和发现, 对服务的访问方透明。
在以上的方案里:
consul做为状态存储或者说配置中心(主要使用consul的KV存储功能);nginx做为API网关, 根据consul中upstreams的相关配置,动态分发流量到配置的upstreams结点;
nginx根据配置项, 链接到consul集群;
启动的API或者微服务实例, 经过手工/命令行/发布部署平台, 将实例信息注册/写入consul;
nginx获取到相应的upstreams信息更新, 则动态变动nginx内部的upstreams分发配置,从而将流量路由和分发到对应的API和微服务实例结点;
将以上注册和发现逻辑经过脚本或者统一的发布部署平台固化后,就能够实现透明的服务访问和扩展。
链路监控实践
咱们发现,之前在单应用下的日志监控很简单,在微服务架构下却成为了一个大问题,若是没法跟踪业务流,没法定位问题,咱们将耗费大量的时间来查找和定位问题,在复杂的微服务交互关系中,咱们就会很是被动,此时分布式链路监控应运而生,其核心就是调用链。经过一个全局的ID将分布在各个服务节点上的同一次请求串联起来,还原原有的调用关系、追踪系统问题、分析调用数据、统计系统指标。
分布式链路跟踪最先见于2010年Google发表的一篇论文《dapper》。
那么咱们先来看一下什么是调用链,调用链其实就是将一次分布式请求还原成调用链路。显式的在后端查看一次分布式请求的调用状况,好比各个节点上的耗时、请求具体打到了哪台机器上、每一个服务节点的请求状态,等等。它能反映出一次请求中经历了多少个服务以及服务层级等信息(好比你的系统A调用B,B调用C,那么此次请求的层级就是3),若是你发现有些请求层级大于10,那这个服务颇有可能须要优化了
常见的解决方案有:
github地址:GitHub - naver/pinpoint: Pinpoint is an open source APM (Application Performance Management) tool for large-scale distributed systems written in Java.
对APM有兴趣的朋友都应该看看这个开源项目,这个是一个韩国团队开源出来的,经过JavaAgent的机制来作字节码代码植入(探针),实现加入traceid和抓取性能数据的目的。
NewRelic、Oneapm之类的工具在java平台上的性能分析也是相似的机制。
官网:OpenZipkin · A distributed tracing system
github地址:GitHub - openzipkin/zipkin: Zipkin is a distributed tracing system
这个是twitter开源出来的,也是参考Dapper的体系来作的。
Zipkin的java应用端是经过一个叫Brave的组件来实现对应用内部的性能分析数据采集。
Brave的github地址:https://github.com/openzipkin...
这个组件经过实现一系列的java拦截器,来作到对http/servlet请求、数据库访问的调用过程跟踪。而后经过在spring之类的配置文件里加入这些拦截器,完成对java应用的性能数据采集。
github地址:GitHub - dianping/cat: Central Application Tracking
这个是大众点评开源出来的,实现的功能也仍是蛮丰富的,国内也有一些公司在用了。不过CAT实现跟踪的手段,是要在代码里硬编码写一些“埋点”,也就是侵入式的。
这样作有利有弊,好处是能够在本身须要的地方加埋点,比较有针对性;坏处是必须改动现有系统,不少开发团队不肯意。
前面三个工具里面,若是不想重复造轮子,我推荐的顺序依次是Pinpoint—>Zipkin—>CAT。缘由很简单,就是这三个工具对于程序源代码和配置文件的侵入性,是依次递增的。
针对于微服务,咱们在spring cloud基础上,对微服务架构进行了扩展,基于Google Dapper的概念,设计了一套基于微服务架构的分布式跟踪系统(WeAPM)。
如上图所示,咱们能够经过服务名、时间、日志类型、方法名、异常级别、接口耗时等参数查询响应的日志。在获得的TrackID能够查询到该请求的整个链路日志,为重现问题、分析日志提供了极大方便。
断路器实践
在微服务架构中,咱们将系统拆分红了一个个的微服务,这样就有可能由于网络缘由或是依赖服务自身问题出现调用故障或延迟,而这些问题会直接致使调用方的对外服务也出现延迟,若此时调用方的请求不断增长,最后就会出现因等待出现故障的依赖方响应而造成任务积压,最终致使自身服务的瘫痪。为了解决这样的问题,所以产生了断路器模式
咱们在实践中使用了Hystrix 来实现断路器的功能。Hystrix是Netflix开源的微服务框架套件之一,该框架目标在于经过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具有拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能。
断路器的使用流程以下:
启用断路器
@SpringBootApplication @EnableCircuitBreaker public class Application { public static void main(String[] args) { SpringApplication.run(DVoiceWebApplication.class, args); } }
代用使用方式
@Component public class StoreIntegration { @HystrixCommand(fallbackMethod = "defaultStores") public Object getStores(Map<String, Object> parameters) { //do stuff that might fail } public Object defaultStores(Map<String, Object> parameters) { return /* something useful */; } }
配置文件
资源控制实践
聊到资源控制,估计不少小伙伴会联系到docker,docker确实是一个实现资源控制很不错的解决方案,咱们前期作调研时也对是否使用docker进行了评审,可是最终选择放弃,而使用linux 的libcgroup脚本控制,缘由以下:
为何要有cgroup?
Linux系统中常常有个需求就是但愿能限制某个或者某些进程的分配资源。也就是能完成一组容器的概念,在这个容器中,有分配好的特定比例的cpu时间,IO时间,可用内存大小等。因而就出现了cgroup的概念,cgroup就是controller group,最初由google的工程师提出,后来被整合进Linux内核中,docker也是基于此来实现。
libcgroup使用流程:
安装
yum install libcgroup
启动服务
service cgconfig start
配置文件模板(以memory为例):
cat /etc/cgconfig.conf
看到memory子系统是挂载在目录/sys/fs/cgroup/memory下,进入这个目录建立一个文件夹,就建立了一个control group了。
mkdir test echo "服务进程号">> tasks(tasks是test目录下的一个文件)
这样就将当前这个终端进程加入到了内存限制的cgroup中了。
总结一下,本文从咱们微服务实践的背景聊起,介绍了微服务实践的工做方式,技术选型,以及相关的一些微服务技术。包括:API网关、注册中心、断路器等。相信这些技术会给你们在实践中带来一些新的思路。固然整个微服务实践之路内容繁多,一篇文章不可能所有包括,你们有兴趣能够在chat中提出来,咱们chat见!
实录:《七夜:微服务用Spring Cloud搭建实战解析》