从单体架构到微服务架构演进
简介
随着互联网的发展,互联网企业的业务也在不断的飞速发展,进而导致系统的架构也在不断的发生着变化。总体来说,系统的架构大致经历了:单体应用架构—>垂直应用架构—>分布式架构—>SOA架构—>微服务架构的演变。当然,很多互联网企业的系统架构已经向Service Mesh(服务化网格)演变;
单体应用架构
在企业发展的初期,一般公司的网站流量都比较小,只需要一个应用,将所有的功能代码打包成一个服务,部署到服务器上就能支撑公司的业务。这样也能够减少开发、部署和维护的成本。
比如,大家都很熟悉的电商系统,里面涉及的业务主要有:用户管理、商品管理、订单管理、支付管理、库存管理、物流管理等等模块,初期我们会将所有模块写到一个Web项目中,然后统一部署到一个Web服务器中。
优点
- 架构简单,项目开发和维护成本低。
- 所有项目模块部署到一起,对于小型项目来说,维护方便。
缺点
- 所有模块耦合在一起,虽然对于小型项目来说,维护方便。但是,对于大型项目来说,却是不易开发和维护的。
- 项目的各模块之前过于耦合,如果一旦有一个模块出现问题,则整个项目将不可用。
- 无法针对某个具体模块来提升性能。
- 无法对项目进行水平扩展。
垂直应用架构
随着企业业务的不断发展,发现单节点的单体应用不足以支撑业务的发展,于是企业会将单体应用部署多份,分别放在不同的服务器上。但是,此时会发现不是所有的模块都会有比较大的访问量。如果想针对项目中的某些模块进行优化和性能提升,此时对于单体应用来说,是做不到的。于是乎,垂直应用架构诞生了。
垂直应用架构,就是将原来一个项目应用进行拆分,将其拆分为互不想干的几个应用,以此来提升系统的整体性能。
这里,我们同样以电商系统为例,在垂直应用架构下,我们可以将整个电商项目拆分为:电商交易系统、后台管理系统、CMS管理系统等。
我们将单体应用架构拆分为垂直应用架构之后,一旦访问量变大,我们只需要针对访问量大的业务增加服务器节点即可,无需针对整个项目增加服务器节点了。
优点
- 系统进行了拆分,可根据不同系统的访问情况,有针对性的进行优化。
- 能够实现应用的水平扩展。
- 各系统能够分担整体访问的流量,解决了并发问题。
- 一个系统发生了故障,不应用其他系统的运行情况,提高了整体的容错率。
缺点
- 拆分后的各系统之间相对比较独立,无法进行互相调用。
- 各系统难免存在重叠的业务,会存在重复开发的业务,后期维护比较困难。
分布式架构
我们将系统演变为垂直应用架构之后,当垂直应用越来越多,重复编写的业务代码就会越来越多。此时,我们需要将重复的代码抽象出来,形成统一的服务供其他系统或者业务模块来进行调用。此时,系统就会演变为分布式架构。
在分布式架构中,我们会将系统整体拆分为服务层和表现层。服务层封装了具体的业务逻辑供表现层调用,表现层则负责处理与页面的交互操作。
优点
- 将重复的业务代码抽象出来,形成公共的访问服务,提高了代码的复用性。
- 可以有针对性的对系统和服务进行性能优化,以提升整体的访问性能。
缺点
系统之间的耦合度变高,调用关系变得复杂,难以维护。
SOA架构
如果大家看过动漫《工作细胞》,一定会对萌萌哒的血小板妹妹印象深刻,《工作细胞》通过动漫拟人的方式讲述了人体中不同细胞的职责和工作方式。每种细胞都有自己独特的功能,血小板负责止血和修复创伤,而T细胞负责吞噬异物和发现入侵者。
软件系统变得越来越复杂,参与的开发者就越多,应用系统的分离和分化就变得很有意义了。面向前端的API应用服务器不再真正的处理业务逻辑而是调用专门的服务器来完成。
单点登录(SSO)是一个典型的面向服务的架构,在互联网公司中被广泛使用。国内互联网巨头往往拥有多个系统,例如腾讯的QQ音乐、空间都可以使用同一个QQ号登陆。于是用户服务和认证服务被剥离开来,各个系统之间通过统一登录和管理用户信息,用户的体验得到了极大的提升,这就是面向服务架构的一个例子。
在分布式架构下,当部署的服务越来越多,重复的代码就会越来越多,对于容量的评估,小服务资源的浪费等问题比较严重。此时,我们就需要增加一个统一的调度中心来对集群进行实时管理。此时,系统就会演变为SOA(面向服务)的架构。
优点
使用注册中心解决了各个服务之间的服务依赖和调用关系的自动注册与发现;
缺点
- 各服务之间存在依赖关系,如果某个服务出现故障可能会造成服务的雪崩;
- 服务之间的依赖与调用关系复杂,测试部署的困难比较大
缓存穿透
首先,我们来说说缓存穿透。什么是缓存穿透呢?缓存穿透问题在一定程度上与缓存命中率有关。如果我们的缓存设计的不合理,缓存的命中率非常低,那么,数据访问的绝大部分压力都会集中在后端数据库层面。
什么是缓存穿透?
如果在请求数据时,在缓存层和数据库层都没有找到符合条件的数据,也就是说,在缓存层和数据库层都没有命中数据,那么,这种情况就叫作缓存穿透。
我们可以使用下图来表示缓存穿透的现象。
造成缓存穿透的主要原因就是:查询某个Key对应的数据,Redis缓存中没有相应的数据,则直接到数据库中查询。数据库中也不存在要查询的数据,则数据库会返回空,而Redis也不会缓存这个空结果。这就造成每次通过这样的Key去查询数据都会直接到数据库中查询,Redis不会缓存空结果。这就造成了缓存穿透的问题。
如何解决缓存穿透问题?
既然我们知道了造成缓存穿透的主要原因就是缓存中不存在相应的数据,直接到数据库查询,数据库返回空结果,缓存中不存储空结果。
那我们就自然而然的想到了第一种解决方案:就是把空对象缓存起来。当第一次从数据库中查询出来的结果为空时,我们就将这个空对象加载到缓存,并设置合理的过期时间,这样,就能够在一定程度上保障后端数据库的安全。
第二种解决缓存穿透问题的解决方案:就是使用布隆过滤器,布隆过滤器可以针对大数据量的、有规律的键值进行处理。一条记录是不是存在,本质上是一个Bool值,只需要使用 1bit 就可以存储。我们可以使用布隆过滤器将这种表示是、否等操作,压缩到一个数据结构中。比如,我们最熟悉的用户性别这种数据,就非常适合使用布隆过滤器来处理。
缓存击穿
如果我们为缓存中的大部分数据设置了相同的过期时间,则到了某一时刻,缓存中的数据就会批量过期。
什么是缓存击穿
如果缓存中的数据在某个时刻批量过期,导致大部分用户的请求都会直接落在数据库上,这种现象就叫作缓存击穿;
我们可以使用下图来表示缓存击穿的线程;
造成缓存击穿的主要原因就是:我们为缓存中的数据设置了过期时间。如果在某个时刻从数据库获取了大量的数据,并设置了相同的过期时间,这些缓存的数据就会在同一时刻失效,造成缓存击穿问题。
如何解决缓存击穿问题?
对于比较热点的数据,我们可以在缓存中设置这些数据永不过期;也可以在访问数据的时候,在缓存中更新这些数据的过期时间;如果是批量入库的缓存项,我们可以为这些缓存项分配比较合理的过期时间,避免同一时刻失效。
还有一种解决方案就是:使用分布式锁,保证对于每个Key同时只有一个线程去查询后端的服务,某个线程在查询后端服务的同时,其他线程没有获得分布式锁的权限,需要进行等待。不过在高并发场景下,这种解决方案对于分布式锁的访问压力比较大。
缓存雪崩
如果缓存系统出现故障,所有的并发流量就会直接到达数据库。
什么是缓存雪崩
如果在某一时刻缓存集中失效,或者缓存系统出现故障,所有的并发流量就会直接到达数据库。数据存储层的调用量就会暴增,用不了多长时间,数据库就会被大流量压垮,这种级联式的服务故障,就叫作缓存雪崩。
我们可以用下图来表示缓存雪崩的现象。
造成缓存雪崩的主要原因就是缓存集中失效,或者缓存服务发生故障,瞬间的大并发流量压垮了数据库
如何解决缓存雪崩问题
解决缓存雪崩问题最常用的一种方案就是保证Redis的高可用,将Redis缓存部署成高可用集群(必要时候做成异地多活),可以有效的防止缓存雪崩问题的发生。
为了缓解大并发流量,我们也可以使用限流降级的方式防止缓存雪崩。例如,在缓存失效后,通过加锁或者使用队列来控制读数据库写缓存的线程数量。具体点就是设置某些Key只允许一个线程查询数据和写缓存,其他线程等待。则能够有效的缓解大并发流量对数据库带来的巨大冲击。
另外,我们也可以通过数据预热的方式将可能大量访问的数据加载到缓存,在即将发生大并发访问的时候,提前手动触发加载不同的数据到缓存中,并为数据设置不同的过期时间,让缓存失效的时间点尽量均匀,不至于在同一时刻全部失效。
微服务架构
随着业务的发展,我们在SOA架构的基础上进一步扩展,将其彻底拆分为微服务架构。在微服务架构下,我们将一个大的项目拆分为一个个小的可以独立部署的微服务,每个微服务都有自己的数据库。
优点
- 服务彻底拆分,各服务独立打包、独立部署和独立升级。
- 每个微服务负责的业务比较清晰,利于后期扩展和维护。
- 微服务之间可以采用REST和RPC协议进行通信。
缺点
- 开发的成本比较高;
- 涉及到各服务的容错性问题;
- 涉及到数据的一致性问题;
- 涉及到分布式事务问题;
如何从单体架构过渡到微服务
架构师们最想通过微服务化取代的部分,往往是架构中的核心功能,经常用飞行中更换引擎来比喻。
为使微服务能顺利的应用,不应该幻想一蹴而就,可以分阶段采取行动。
培训先行
技术人都善于把面临的问题变成技术问题,然后在自己最擅长的领域去把它解决。
这就造成一个悖论:能用技术解决的问题就不是问题,真正的问题在受限的情景下仅靠技术是解决不了的,实施微服务最大的拦路虎也不是技术本身。
最大的问题不是如何做好微服务,而是就微服务应该是什么达成一个一致的看法。
因此,可以在实施前通过多数人参与大讨论或培训,使认知达成一致。这类似编码规范中的命名规范,使用那种命名方法不重要,重要的是人人都使用同一种命名方法。
绞杀者模式
绞杀者模式是指,对于无法通过修缮者模式改进的系统,在系统外重新构建新功能来逐步剥离重构,对功能服务逐个绞杀。
好处是不影响原来的环境,一旦条件成熟就能快速切换。而缺点是可能需要一段时间同时维护两套系统,付出额外的开发维护成本。
监狱模式
允许一些短期无力改动的系统通过监狱窗口(MicroProxy)接入微服务平台并委托 Proxy 将其暴露成微服务,单体架构往往拥有庞大的服务接口梳理, 往往需要开多个监狱窗口。
每个监狱窗口都会被包装分割成微服务,条件成熟了能很方便的替换成原生微服务,称为刑满释放。