大话微服务架构
简介
微服务架构:是一种架构概念,旨在通过将功能分解到各个离散的服务中以实现对解决方案的解耦。你可以将其看作是在架构层次而非获取服务的。微服务架构是个很有趣的概念,它的主要作用是将功能分解到离散的各个服务当中,从而降低系统的耦合性,并提供更加灵活的服务支持。
2:好处:
在传统的IT行业软件大多都是各种独立系统的堆砌,这些系统的问题总结来说就是扩展性差,可靠性不高,维护成本高。到后面引入了SOA服务化,但是,由于 SOA 早期均使用了总线模式,这种总线模式是与某种技术栈强绑定的,比如:J2EE。这导致很多企业的遗留系统很难对接,切换时间太长,成本太高,新系统稳定性的收敛也需要一些时间。最终 SOA 看起来很美,但却成为了企业级奢侈品,中小公司都望而生畏。
首先,通过分解巨大单体式应用为多个服务方法解决了复杂性问题。在功能不变的情况下,应用被分解为多个可管理的分支或服务。每个服务都有一个用RPC-或者消息驱动API定义清楚的边界。微服务架构模式给采用单体式编码方式很难实现的功能提供了模块化的解决方案,由此,单个服务很容易开发、理解和维护。
第二,这种架构使得每个服务都可以有专门开发团队来开发。开发者可以自由选择开发技术,提供API服务。当然,许多公司试图避免混乱,只提供某些技术选择。然后,这种自由意味着开发者不需要被迫使用某项目开始时采用的过时技术,他们可以选择现在的技术。甚至于,因为服务都是相对简单,即使用现在技术重写以前代码也不是很困难的事情。
第三,微服务架构模式是每个微服务独立的部署。开发者不再需要协调其它服务部署对本服务的影响。这种改变可以加快部署速度。UI团队可以采用AB测试,快速的部署变化。微服务架构模式使得持续化部署成为可能。
最后,微服务架构模式使得每个服务独立扩展。你可以根据每个服务的规模来部署满足需求的规模。甚至于,你可以使用更适合于服务资源需求的硬件。比如,你可以在EC2 Compute Optimized instances上部署CPU敏感的服务,而在EC2 memory-optimized instances上部署内存数据库。
3:不足:
其中一个跟他的名字类似,『微服务』强调了服务大小,实际上,有一些开发者鼓吹建立稍微大一些的,10-100 LOC服务组。尽管小服务更乐于被采用,但是不要忘了这只是终端的选择而不是最终的目的。微服务的目的是有效的拆分应用,实现敏捷开发和部署。
另外一个主要的不足是,微服务应用是分布式系统,由此会带来固有的复杂性。开发者需要在RPC或者消息传递之间选择并完成进程间通讯机制。更甚于,他们必须写代码来处理消息传递中速度过慢或者不可用等局部失效问题。当然这并不是什么难事,但相对于单体式应用中通过语言层级的方法或者进程调用,微服务下这种技术显得更复杂一些。
另外一个关于微服务的挑战来自于分区的数据库架构。商业交易中同时给多个业务分主体更新消息很普遍。这种交易对于单体式应用来说很容易,因为只有一个数据库。在微服务架构应用中,需要更新不同服务所使用的不同的数据库。使用分布式交易并不一定是好的选择,不仅仅是因为CAP理论,还因为今天高扩展性的NoSQL数据库和消息传递中间件并不支持这一需求。最终你不得不使用一个最终一致性的方法,从而对开发者提出了更高的要求和挑战。
测试一个基于微服务架构的应用也是很复杂的任务。比如,采用流行的Spring Boot架构,对一个单体式web应用,测试它的REST API,是很容易的事情。反过来,同样的服务测试需要启动和它有关的所有服务(至少需要这些服务的stubs)。再重申一次,不能低估了采用微服务架构带来的复杂性。
另外一个挑战在于,微服务架构模式应用的改变将会波及多个服务。比如,假设你在完成一个案例,需要修改服务A、B、C,而A依赖B,B依赖C。在单体式应用中,你只需要改变相关模块,整合变化,部署就好了。对比之下,微服务架构模式就需要考虑相关改变对不同服务的影响。比如,你需要更新服务C,然后是B,最后才是A,幸运的是,许多改变一般只影响一个服务,而需要协调多服务的改变很少。
部署一个微服务应用也很复杂,一个分布式应用只需要简单在复杂均衡器后面部署各自的服务器就好了。每个应用实例是需要配置诸如数据库和消息中间件等基础服务。相对比,一个微服务应用一般由大批服务构成。例如,根据Adrian Cockcroft,Hailo有160个不同服务构成,NetFlix有大约600个服务。每个服务都有多个实例。这就造成许多需要配置、部署、扩展和监控的部分,除此之外,你还需要完成一个服务发现机制(后续文章中发表),以用来发现与它通讯服务的地址(包括服务器地址和端口)。传统的解决问题办法不能用于解决这么复杂的问题。接续而来,成功部署一个微服务应用需要开发者有足够的控制部署方法,并高度自动化。
4:传统开发模式与微服务的区别
对接互联网所面临的最大的问题,就是巨大的用户量所带来的请求量和数据量,会是原来的N倍,能不能撑得住,大家都心里没底。虽然已经用了Dubbo实现了服务化,但是没有熔断,限流,降级的服务治理策略,有可能一个请求慢,高峰期波及一大片,或者请求全部接进来,最后都撑不住而挂一片。
一旦到了互联网大促级别,Oracle数据库是肯定扛不住的,需要从Oracle迁移到DDB分布式数据库
从SOA到微服务化,拆分粒度非常的细
微服务解决的问题:
1:快速迭代
第一一统天下,第二被第一收购,其他死翘翘。
2:提交代码频繁大量冲突
3:小功能上线独立,不需要等待其他模块开总监大会。
4:高并发
5:横向扩展,主要业务进行扩容,次要业务保持
5:服务熔断降级:保证次要业务不影响核心业务
微服务化的需要措施
持续集成
1:拆分如何保证功能不变,不引入Bug——持续集成
静态缓存
2:静态资源要拆分出来,缓存到接入层或者CDN,将大部分流量拦截在离用户近的边缘节点或者接入层缓存——数据中心,CND,静态缓存。
容器化
3:应用的状态要从业务逻辑中拆分出来,使业务无状态,可以基于容器进行横向扩展——容器,Docker和K8S管理
拆分与服务发现
4:核心业务和非核心业务要拆分,方便核心业务的扩展以及非核心业务的降级 —— 微服务拆分与服务发现
库的分离
5:数据库要读写分离,要分库分表,数据库要具有横向扩展能力,不能成为瓶颈。
缓存
6:要层层缓存,只有少量的流量到达数据库——缓存的设计
缓存设计有CDN动静资源隔离,Tomact本地缓存,但是本地缓存存放在JVM中,会面临FullGC问题,还有就是分布式缓存,在Tomact和数据库中间添加一层,常用的有Redis和memcached,
缓存架构设计
1:多层次:某一层挂了,还有一层可以撑着。
2:分场景:要明确要存储大的无格式的数据,还是要存储小的有格式的数据,还是要存储一定需要持久化的数据。
3:要分片:多个实例,达到负载均衡,防止单个实例成为瓶颈或者热点。分片机制可以使用Redis的Cluster方式,分片的算法往往是哈希取模或者一致性哈希。
使用
1:和数据库结构一样,原样缓存
2:列表排序分页的缓存
如果我们想获取点赞最多的评论,或者最新的评论,然后列出来,一页一页的翻下去。
3:计数缓存
将计数作为结果放在缓存里面,当数据有改变的时候,调用计数服务增加或者减少计数,而非通过异步数据库count来更新缓存。
4:重构维度缓存
需要为了查询方便,将数据重新以另一个维度存储一遍,从而不用每次查询的时候都重新聚合,如果还是放在数据库,比较难维护,放在缓存就好一些。
例如一个商品的所有的帖子和帖子的用户,以及一个用户发表过的所有的帖子就是属于两个维度。
在这种场景下,数据量相对比较大,因而单纯用内存缓存memcached或者redis难以支撑,往往会选择使用levelDB进行存储,如果levelDB的性能跟不上,可以考虑在levelDB之前,再来一层memcached。
问题
1:实时性与一致性问题
2:缓存的穿透问题:没有读到怎么办?
为什么会出现缓存读取不到的情况呢?
第一:可能读取的是冷数据,原来从来没有访问过,所以需要到数据库里面查询一下,然后放入缓存,再返回给客户。
第二:可能数据因为有了写入,被实时的从缓存中删除了,就如第一个问题中描述的那样,为了保证实时性,当数据库中的数据更新了之后,马上删除缓存中的数据,导致这个时候的读取读不到,需要到数据库里面查询后,放入缓存,再返回给客户。
第三:可能是缓存实效了,每个缓存数据都会有实效时间,过了一段时间没有被访问,就会失效,这个时候数据就访问不到了,需要访问数据库后,再放入缓存。
第四:数据被换出,由于缓存内存是有限的,当使用快满了的时候,就会使用类似LRU策略,将不经常使用的数据换出,所以也要访问数据库。
第五:后端确实也没有,应用访问缓存没有,于是查询数据库,结果数据库里面也没有,只好返回客户为空,但是尴尬的是,每次出现这种情况的时候,都会面临着一次数据库的访问,纯属浪费资源,常用的方法是,讲这个key对应的结果为空的事实也进行缓存,这样缓存可以命中,但是命中后告诉客户端没有,减少了数据库的压力。
无论哪种原因导致的读取缓存读不到的情况,该怎么办?是个策略问题。
一种是同步访问数据库后,放入缓存,再返回给客户,这样实时性最好,但是给数据库的压力也最大。
另一种方式就是异步的访问数据库,暂且返回客户一个fallback值,然后同时触发一个异步更新,这样下次就有了,这样数据库压力小很多,但是用户就访问不到实时的数据了。
3:
解决的刷新策略
1:实时策略
读先从cache读,没读到在从数据库读然后再放入cache。写入是先写数据库,再删缓存
2:异步策略
读不到时,直接返回一个fallback数据,往消息队列放入数据加载的事件,可以削峰。
更新的时候:如果先更新数据库再更新缓存,实时性较差;先更新缓存在更新数据库,这种缓存完全挡在数据库前面了。实时性好,但是需要持久化机制和主备策略。
3:定时策略
异步化消息队列
熔断限流降级
配置中心
日志
链路追踪
压测
拆分规范
1:拆分最多三层,两次调用
拆分是为了横向扩展,因而应该横向拆分,而非纵向拆分成一串的。如:应该讲商品和订单模块进行拆分,而非将下单的十多个步骤进行拆分,然后一个调用一个。
2:单向调用,严禁循环调用
如果循环调用,升级就很头疼
3:串行改并行,异步化
如果有的组合服务处理流程的确很长,需要调用多个外部服务,应该考虑如何通过消息队列,实现异步化和解耦。
例如下单之后,要刷新缓存,要通知仓库等,这些都不需要再下单成功的时候就要做完,而是可以发一个消息给消息队列,异步通知其他服务。
4:接口实现幂等
幂等一般需要设计一个幂等表来实现,幂等表中的主键或者唯一键可以是transaction id,或者business id,可以通过这个id的唯一性标识一个唯一的操作。
5:接口数据定义严禁内嵌,穿透
接口对象应该
6:规范化工程名
服务发现选型
Dubbo注册到zookeeper里面的是接口,而springcloud注册到Eureka或者consul里面的是实例,
在规模比较小的情况下没有分别,但是规模一旦大了,例如实例数目万级别,接口数据就算十万级别,对于zookeeper中的树规模比较大,而且zookeeper是强一致性的,当一个节点挂了的时候,节点之间的数据同步会影响线上使用,而springcloud就好很多,实例级别少一个量级,另外consul也非强一致的。
很多springcloud可以做的事情,kubernetes也有相应的机制,而且由于是容器平台,相对比较通用,可以支持多语言,对于业务无侵入,但是也正因为是容器平台,对于微服务的运行生命周期的维护比较全面,对于服务之间的调用和治理,比较弱
因而实践中使用的时候,往往是kubernetes和springcloud结合使用,kubernetes负责提供微服务的运行环境,服务之间的调用和治理,由springlcoud搞定。
运维
有了镜像容器,开发交给运维的是一个容器镜像,容器内部的运行环境,应该体现在Dockerfile文件中,这个文件应该是开发写的。
容器镜像有个特点,就是ssh到里面做的任何修改,重启都不见了,恢复到镜像原来的样子,也就杜绝了原来我们部署环境,这改改,那修修最后部署成功的坏毛病。