关于微服务的介绍目前已经有不少文章作了介绍,本文再也不对微服务的概念再作进一步阐述,重点将介绍微服务架构具体开发运维方面的经验总结,侧重于落地实践。前端
目前业界比较热门的微服务开发框架是SpringCloud和dubbo,因为前期一些项目已经使用了SpringBoot进行快速开发,天然就平滑地升级到SpringCloud进行微服务实践。另外,按照微服务不断演进的思路,咱们首先对非核心业务和新业务进行了重构,不断积累微服务开发运维经验,待后续时机成熟,再对核心业务进行微服务重构治理。java
和大部分企业实施微服务流程同样,首先就是肯定微服务重构的原则和目标。因为证券行业的自身特色,肯定了以下几个原则:nginx
1. 安全放在首位。安全是第一位的非功能性需求,在设计之初,就应该考虑到,涉及服务器、容器、应用、接口、数据等层面。程序员
2. 核心业务与普通业务分离。交易和行情服务是券商交易APP的核心业务,应该和其余业务进行有效隔离,避免其余应用对核心业务产生任何影响。例如一个运营活动的高峰流量,就不该该对核心交易行情服务产生任何影响。spring
3. 按照业务和主数据进行合理拆分。能够按照各种业务模块来进行服务拆分,例如资讯、理财、增值服务、业务办理、帐户帐单等,每一个业务模块天然拆分红一个微服务组件。另外,按照主数据属性来分类也是一个不错的维度,例如在CRM中的各种数据维度,能够按照主数据属性来进行划分,例如资产类、帐户类、交易流水类等。sql
按照上述拆分原则进行划分,原有的单体应用已经拆分红多个微服务业务集群。以下图1所示。除了按照业务划分的业务组件以外,还有一些通用的功能性组件和非功能性组件,例如统一权限校验、日志统一处理服务、报警服务等,将在后续详细说明。docker
图一 微服务业务梳理组件分布图数据库
SpringCloud的整体组件架构图以下图2所示。
图2 SpringCloud微服务技术架构组件图后端
开箱即用的特性使得SpringCloud比较容易上手,须要哪一个功能,就经过maven引入响应子系统组件,符合各个层次的使用者,也符合各种不一样应用场景特色。微服务改造是一个渐变的过程,没必要一开始就使用全部功能。按照目前自身的技术条件与保障能力,同时参照应用自身特色,咱们使用了以下几类组件,下面分别进行详细说明。缓存
目前SpringCloud支持的服务注册有Consul和Eureka。Consul须要独立部署,脱离Spring框架;而Eureka自然集成在SpringCloud中,自己就是一个SpringBoot项目。这2者特色能够经过以下表1展现。
表1 Consul和Eureka对比表
能够看出,Consul在总体功能上比Eureka更加丰富和完备,但同时也增长了必定的复杂性;Eureka相对来讲更加轻量,自然溶于SpringCloud体系,虽然缺失一部分功能,可是对于通常性的微服务集群来讲已经足够。所以,咱们采用了Eureka来做为服务发现与注册组件。下图为目前已经接入Eureka组件的全部拆分的微服务实例。目前来看,数量还不算太多,后期随着业务的发展,会不断增长微服务实例。以下图3所示:
图3 Eureka管理视图
从业务量上来讲,一个数据中心站点上,通常挂载2个实例作负载均衡便可知足通常业务需求,但在一些请求量较大的服务上,咱们会增长微服务的实例数量。例如对于咱们的一个行情增值微服务组件,因为早高峰tps接近6000,就挂载了6个实例。所以经过实际业务量的请求数据监控,咱们能够动态调配微服务组件的挂载实例,从而知足业务需求,保障服务的HA,同时提升了系统资源的使用效率。
对于Eureka服务自己来讲,也是挂载了2个实例,经过客户端的Failover协议同时配置2个实例,来实现服务注册的高可用。须要在每一个注册到Eureka集群中微服务组件均增长此配置便可:eureka.client.serviceUrl.defaultZone=http://ip1:20001/eureka/,http://ip2:20001/eureka/。
若是超过2个Eureka实例,则经过继续以“,”分隔连上。这点来讲,和ActiveMQ的链接使用殊途同归。
从咱们实际运行来看,目前Eureka组件应用逻辑简单、稳定。当一台Eureka Server实例down机后,全部client实际均不受影响。固然从Eureka自身来讲,也存在弱一致性的问题,特别是对于微服务节点在重启时候,可能会存在某服务在一个心跳周期不可用的状况,待心跳超时隔离之后,就恢复正常了。其实这也是为了最大程度保证可用性,可见确实只知足了AP。官方目前Eureka2.X版本将再也不开源,基于此考虑,后续咱们也会持续关注替代方案Consul,或者其余服务注册框架。
所谓“一夫当关,万夫莫开”,统一接入网关组件做为服务的统一入口,地位足够重要。Zuul网关主要实现了请求服务路由、负载均衡、统一校验等功能。在服务路由和负载均衡方面,和nginx至关相似。Zuul经过自身注册到Eureka,经过url匹配的serviceID配置,便可实现服务路由。
目前咱们在Zuul网关中实现了校验token功能,经过ZuulFilter,能够轻松作到全部外部请求的token统一校验。目前尚未把其余校验类逻辑放到Zuul中,一些请求流量统计的功能,咱们在nginx层面作了拦截实现。
Zuul2版本已经与今年5月份发布,相比于Zuul1,其强大的事件驱动和异步非阻塞调用特性,将支持超高并发接入并转发。其核心是引入了高性能netty框架,从请求接入到调用外部组件,所有采用netty事件驱动模式来实现。而Zuul1则采用传统servlet同步请求的方式进行处理,每一个链接分配一个线程,全部的线程均从线程池中获取,一旦外部链接并发量很大的话,线程数将急剧上升,一旦处理线程阻塞,则最终将耗尽容器中的线程池内的线程,形成容器没法继续接受新的请求,所以才有了Hystrix熔断组件来解决服务耗尽资源的解决方案。
虽然Zuul1的同步阻塞带来了吞吐量的瓶颈,可是整个处理模块比较简单,从请求接入处处理,再到响应,整个流程都在一个线程中处理,方便了开发和调试跟踪。而Zuul2则将接入请求所有放入netty的事件队列中,并分别由不一样的线程进行接入、处理、响应,从维护和跟踪上面来看,更加复杂和难维护。
其实对于通常的应用场景来讲,并发量不会太大,其实用Zuul1就可以知足要求,并且开发维护更简单;可是若是面对高并发的应用场景,例如QPS>1000时,则建议采用Zuul2,其异步非阻塞特性,将轻松接入高并发链接,并配合其EventLoop和pipeline机制,能够保证全部的链接都能被接收和处理,基本不存在链接被拒绝访问的状况。
从Netflix给出的性能结论来看,Zuul2的性能比Zuul1大体提高了20%,可见从吞吐量上面来讲,提高能力有限;可是比较明确的是,Zuul2的链接数管理方面要明显好于Zuul1,能够支持超大规模的并发链接处理。从这点上面来讲,其性能应该接近于Nginx。可参考以下Zuul演进图4.
图4 从Zuul1到Zuul2演进图
目前实际项目中,生产系统仍是以Zuul1来做为API网关使用。由于目前咱们只是对于内部系统API、外部一些核心低频API调用走Zuul,并发量不太大,还能扛住;对于外部应用的一些高并发API调用,例如行情相关的接口QPS>6000,仍是从Nginx接入,直接转发到SpringBoot微服务组件。
从将来趋势来看,为了实现微服务的全覆盖和完整性,后期将把nginx的接入请求所有转到Zuul上来,到时将必须升级到Zuul2上来,没的说。
Feign是一种http方式的声明式调用,目前SpringCloud默认使用java原生的HttpURLConnection进行http调用,同时经过jar包引入还能够支持Apache的httpclient、OKhttp等。Feign底层依靠Ribbon实现了调用各个实例的负载均衡,从效率上面来讲,不占太大优点,可是从简单易用上面来看绝对是一个重要的亮点;并且和自动熔断降级组件Hystrix天热融合,优点明显。使用Feign很是简单,只须要建立一个调用服务的接口方法,标记@FeignClient便可像本地方法同样调用。
在微服务集群中一般会有不少个服务之间的调用,例如经过Feign调用。各个服务之间的调用最终造成了网状模型。若是某一些基础服务不可用,而上游服务持续不断调用此基础服务,可能致使整个系统故障,也就是“雪崩效应”。下图5展现了常见的场景。
图5 Hystrix服务降级示例图
断路器很好理解,当Hystrix Command请求后端服务失败数量超过必定比例(默认50%),断路器会切换到开路状态(OPEN)。这时全部请求会直接失败而不会发送到后端服务。断路器保持在开路状态一段时间后(默认5秒),自动切换到半开路状态(HALF-OPEN)。这时会判断下一次请求的返回状况,若是请求成功, 断路器切回闭路状态(CLOSED),不然从新切换到开路状态(OPEN)。Hystrix的断路器就像咱们家庭电路中的保险丝,一旦后端服务不可用,断路器会直接切断请求链,避免发送大量无效请求影响系统吞吐量,而且断路器有自我检测并恢复的能力。
Fallback至关因而降级操做。对于查询操做,咱们能够实现一个fallback方法,当请求后端服务出现异常的时候,可使用fallback方法返回的值。 fallback方法的返回值通常是设置的默认值或者来自缓存。例如在一个热门推荐股票榜单功能中,若是Feign调用失败,则自动在Fallback中返回本地缓存中的默认榜单便可,成功实现了推荐榜单服务的降级。
在Hystrix中,主要经过线程池来实现资源隔离。一般在使用的时候咱们会根据调用的远程服务划分出多个线程池。例如调用产品服务的Command放入A线程池,调用帐户服务的Command放入B线程池。这样作的主要优势是运行环境被隔离开了。就算调用服务的代码存在bug或者因为其余缘由致使本身所在线程池被耗尽时,不会对系统的其余服务形成影响。可是带来的代价就是维护多个线程池会对系统带来额外的性能开销。若是是对性能有严格要求并且确信本身调用服务的客户端代码不会出问题的话,可使用Hystrix的信号量(Semaphores)来隔离资源。
例如,在咱们实际生产系统中,若是服务调用不太频繁时,减小线程数量,节约系统资源,能够在配置文件中显示设置隔离线程池的线程规模=2:hystrix.threadpool.default.coreSize=2。
因为Feign组件集成了Ribbon和Hystrix,其服务降级和熔断的参数配置较为复杂,若是不看完源码,则只能靠系统测试来一一验证了。例如服务调用超时这类场景,其默认的服务超时设置为1秒,这对一些外部较复杂的服务调用来讲是不太合适的,所以须要在配置文件中显式声明hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000,将其扩大到5秒超时。另外对于服务的重试次数,也须要进行谨慎设置,一旦被调用方服务没有作好幂等性防御,可能会带来意想不到的破坏。
Zipkin 是一个开放源代码分布式的跟踪系统,由Twitter公司开源,它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展示。每一个服务向zipkin报告计时数据,zipkin会根据调用关系经过Zipkin UI生成依赖关系图,显示了多少跟踪请求经过每一个服务,该系统让开发者可经过一个 Web 前端轻松的收集和分析数据,例如用户每次请求服务的处理时间等,可方便的监测系统中存在的瓶颈。
Zipkin提供了可插拔数据存储方式:In-Memory、MySql、Cassandra以及Elasticsearch。下图6为咱们实际场景中的一个接口的调用链分析。
上方的图形展现了3个微服务之间的调用关系;下方的甘特图则展现了调用链的各个服务耗时分布。对于分析调用链层次多的复杂接口具备很好的分析做用。
在咱们实际系统中,对于接口的采样率有个配置:spring.sleuth.sampler.percentage=0.2,表示只采样20%的接口入库存储可展现,对于接口请求量巨大的应用来讲,避免接口量太大消耗太多内存。
SpringBootAdmin提供了开箱即用的监控工具箱,能够看各个注册在Eureka下个微服务实例的运行全貌,以下图7所示。首先在面板上面展现了全部能监控到的应用实例,具体到IP和端口。Status包括UP和DOWN,分别表示正常启动和未启动。
图7 SpringBootAdmin Dashboard
点击detail能够进入应用的详细监控页面。在详情页面概览中包含了应用的全貌信息,包括JVM占用的内存状况、类加载、GC、DataSource数据库链接占用状况快照,以下图8所示。还提供有实时刷新功能,每隔一秒动态展现系统的快照,比JavaVisualVM和jConsole更加方便和直观。咱们用的较多的是DataSource链接数占用状况分析,若是Active链接数长期占用率较高,就必须考虑应用对数据库的优化了。
图8 SpringBootAdmin 应用内全貌图
在监控详情页面中,还能够看到Metrics、Environment、Logging、JMX、Threads、Trace、Heapdump几个栏目。
Metrics主要展现了接口调用次数和耗时统计(Counter和Gauges),便于查看微服务内部接口的运行状况。以下图9所示。
图9 SpringBootAdmin 应用内Metrics图
主要提供该服务的全部配置项。包括系统层级和应用的配置列表。
提供动态改变应用log打印级别的功能。在系统出现异常而没有查到有效日志的状况下,能够动态调整logging的日志级别,例如调整为debug级别,可让应用当即以debug级别进行日志打印,方便查看系统详细异常状况。其实底层仍是使用了JMX实现,须要在logback配置中增长jmx支持配置,以下图10所示。
图10 SpringBootAdmin 应用内Logging图
JMX栏目将显示应用全部JMX对象,若是有一些自定义的JMX MBean,能够经过此处操做动态执行操做,实现应用配置的动态热变动,以下图11所示。
图11 SpringBootAdmin 应用内JMX图
主要提供当前线程堆栈的快照信息。经过查看线程全貌快照,能够看到当前全部线程的运行状态,若是发现某类线程组所有都属于running,可能须要扩容线程池。若是发现大部分线程高峰期一直都是waiting状态,可能须要缩减线程池规模。若是发现大部分线程都在等待某一类资源对象锁,则表示该类资源存在瓶颈,须要进行优化调整。合理的调整各种线程池规模,可让系统运行的更高效,提升系统资源利用率,以下图12所示。
图12 SpringBootAdmin 应用内Thread快照图
主要提供对外接口的实时运行快照。在此不作详细说明。
主要提供了实时获取内存堆栈快照的下载功能。若是发现应用占用内存一直很高,GC释放效率低,则能够经过查看内存堆栈快照,做进一步分析,哪些大对象没有及时释放。
微服务的正常运转离不开配套的监控体系和敏捷DevOps,大量的微服务实例须要自动化的监控和运维的支持。以下图13为最终微服务系统的部署图。
图13 微服务部署架构图
对于部署的微服务来讲,实现了中间件和数据库的彻底独立,大部分微服务组件都拥有独立的数据库和缓存资源。固然有些缓存可能存在公用的状况,这个具体场景具体分析。目前部署的环境仍是VM,后续为了提升部署速度和资源动态升缩能力,会尝试用docker来进行替代。
目前对于服务器资源的监控时zabbix,对于微服务应用的监控是SpringBootAdmin,另外对于接口API的监控咱们还自研了一套接口可用性监控系统,基本上覆盖了监控的大部分需求。从架构演进角度来看,后续监控会引入Prometheus+Grafana结合使用。
微服务的快速部署和迭代,须要持续集成CI与持续部署CD的支持。目前在测试环境已经实现了CI与CD的全流程自动化运行,因为公司网络的监管要求,目前生产系统网段没法直接连通代码仓库,所以目前CI与CD实际上是分离的,当前仍然须要人工去作部署包的上传,以下图14所示。
图14 CI&CD现状图
目前已经在jekins上实现了大部分的微服务实例的持续部署,大大提高了部署的效率和准确性。并且经过Jenkins上面的部署历史,也实现了投产的审计和可追溯,提高投产管理水平,以下图15所示。
图15 Jenkins自动部署图
从目前微服务的搭建和治理来看,总体还处于一个入门级水准,但考虑到咱们实际的技术水平和保障能力,也没法一步作到很完善的规模。微服务治理是不断演进的一个过程,正如任何系统架构同样,永远没有最完美的,只有最合适的。
目前整套系统只是使用了SpringCloud中的很小一部分组件,后续可能会增长统一配置中心ConfigServer,但其前提是生产系统网络须要开通到Git库访问,仍须要和机房部门沟通。考虑到目前咱们的服务器仍是实体机+VM组合,后续仍然须要引入docker容器技术,为之后迁移到云平台作好准备。
本文主要从实际开发运维的角度阐述了基于SpringCloud的微服务体系,但愿能够为各位同行快速搭建微服务系统提供一些参考和帮助。
欢迎工做一到五年的Java工程师朋友们加入Java程序员开发: 854393687
群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用本身每一分每一秒的时间来学习提高本身,不要再用"没有时间“来掩饰本身思想上的懒惰!趁年轻,使劲拼,给将来的本身一个交代!