Go微服务: 隔离设计


0. 写在前面

隔离设计源于船舶行业,一般而言无论大船还是小船,都会有一些隔板,将船分为不同的空间,这样如果有船舱漏水一般只会影响这一小块空间,不至于把整个船都给搞沉了。

同样我们的软件服务也是一个道理,我们要尽量避免出现一个问题就把这个业务给搞挂的情况出现

那什么是「服务隔离」呢?

顾名思义,它是指将系统按照一定的原则划分为若干个服务模块,各个模块之间相对独立,无强依赖。当有故障发生时,能将问题和影响隔离在某个模块内部,而不扩散风险,不波及其它模块,不影响整体的系统服务。

1. 服务的演进

1.1. 最简单的架构

如下图,今天天气不错,我们开始创业(天气和创业有啥关系???),搞了一个电商网站,由于前期人手不足技术也不够,就一个服务和一个数据库就开始对外提供服务了

随着货物的不断上架,我们发现产品相关介绍的图片、视频等信息占用了我们服务的大部分带宽,并且也不太好管理,用户访问呢也比较慢,影响了剁手的体验。

1.2. 动静隔离

这时候我们做了一波优化,把 静态资源的数据使用云服务商提供的对象存储给保存了起来,然后在前面接入了一个 CDN 给用户提供更好的体验。

使用对象存储和 CDN 将静态资源和动态 API 进行了隔离

然后突然有一天我们发现,用户大量投诉说什么垃圾网站,突然访问这么慢,进过紧锣密鼓的排查发现,原来是我们的运营同学在后台进行数据统计准备出报告的时候影响了生产的数据库,导致影响了我们的用户

1.3. 数据库主从隔离

所以我们有进行了一波优化,我们对数据库进行了主从分离, 然后将运营后台和我们的商城主服务做了拆分,后续所有的统计查询请求我们都从从库查询,其他请求才会去修改主库。

是滴,我们又做了一次隔离,一个是将数据库做了主从隔离,另外一个按照不同的用户属性,做了用户隔离。当然这是比较宏观的在这过程中我们肯定也会对数据中的表进行一些拆分设计,例如将经常变化的数据和不太经常变化的数据分配到两张表等。

怎么给用户分类?

可以用按照用户是否VIP、用户等级、用户IP等等,方法很多,要结合自己实际业务的特性来做。

其实这也是一种「多租户架构」,在SaaS服务中用得比较多。

多租户模式有三种形式:

  • 完全的隔离,即服务和数据都是完全独立的。
  • 公共服务、独立数据源,即多个租户是用的同一台服务程序,但是底层的数据源是独立的。
  • 公用服务、公用数据源,即多个租户的服务程序与数据库源都是共享的,不同数据可能会做分区分表来独立。

上述三种方式,从下到上,独立性和安全性越来越高,资源利用率越来越低,根据业务特性去选择,一般选择折中方案。

1.4. 微服务架构

不知道是随着一些爆款活动的推出,以及不知名大 V 的推广使得我们的业务欣欣向上,还是我们新来的技术总监为了 KPI 我们进行了轰轰烈烈的微服务改造的活动,最后经过长达一年多的改造,我们的服务架构改造成了下面这个模样。

请求在访问之前都会先经过 WAF 防火墙,然后再到对应的 CDN 节点然后经过我们的 API 网关到 BFF 层。然后 BFF 层再去调用各种服务聚合成业务数据并且返回。

这里 其实又做了一层按照服务的隔离,我们将一个单体服务拆分成了一个个的小服务,就不会出现评论挂掉了导致整个服务挂掉无法下单的情况。

1.5. 服务优先级隔离

微服务改造完成之后我们发现,的确整体的服务质量都好了很多,但是突如其来的一个 bug 导致我们的监控大量告警,这是为什么呢,原来是我们的推荐服务出现了一个内存泄漏的问题,然后我们的服务限制做的不够好根本没有设置任何限制,这就导致它占用了资源池中的大量资源,让我们的其他服务资源紧缺

然后我们就又做了一个改造,我们把支付和商城这种最重要的业务单独放在了一个池子里面,对于像评论推荐这种没有那么重要的业务放在共享的资源池当中。

所以这一次我们按照服务的优先级进行了隔离

1.6. 热点缓存

突然有一天,我们的一个商品成为了爆款,大量用户涌入访问,成功将我们的 cache 打挂,后续我们做了什么改动呢,我们将 remote cache 提升为了 local cache,在 sdk 当中自动识别出热点流量,然后将其缓存,大大减少了 redis 的压力

这一次我们就将热点数据进行了隔离

2. 隔离设计要点

2.1. 隔离种类

  • 服务隔离
    • 动静隔离:上面讲到的CDN
    • 读写隔离:如上面讲到的主从, 除此之外还有常见的CQRS模式,分库分表
  • 轻重隔离
    • 核心隔离:例如上面讲到的将核心业务独立部署,非核心业务共享资源
    • 热点隔离:例如上面讲到的 remote cache 到local cache
    • 用户隔离:不同的用户可能有不同的级别,例如上面讲到的外部用户和管理员
  • 物理隔离
    • 线程:常见的例子就是线程池,这个在Golang中一般不用考虑过多, runtime已经帮我们管理好了
    • 进程: 现在一般使用容器化部署,跑在k8s上就是一种进程级别的隔离
    • 机房:我们目前在k8s基础上做一些开发, 常见的一种做法就是将服务的不同副本尽量的分配在不同的可用区,实际上就是云厂商的不同机房,避免机房停电或者着火之类的影响
    • 集群:非常重要的服务我们可以部署多套,在物理上进行隔离,常见的有异地部署,也可能就部署在同一个区域

2.2. 注意事项

  • 不可越界:能在隔离模块内完成的逻辑,就尽量不要跨模块调用,减少依赖。
  • 不可共享:数据和资源能独享的就尽量不要共享,不然很容易造成隔离失效。
  • 考虑效率:设计隔离模块的时候,要根据业务情况而定,充分的考虑到未来的拓补结构,减少调用效率的损失。
  • 考虑颗粒度:隔离模块设计的大小问题,过大和过小都不合适,需充分考虑。
  • 服务的全面监控:既然服务或用户进行隔离了,那么系统的复杂度肯定是比之前要高了,那么针对多服务的全链路监控是必不可少的。

2.3. 隔离的弊端

  • 当我们某个功能操作需要关联多个服务模块或者同时查询所个模块数据的时候,代码写起来就会相对麻烦一些了,其中涉及到多模块调用的性能问题、数据一致性问题、事物问题等。
  • 不同服务模块之间的交互也会比较复杂一些,因为要做服务隔离,避免服务强依赖,所以模块之间的交互调用最好是走异步模式,需要通过异步线程或消息中间件来传递实现。
  • 在进行运营大数据分析的时候,由于数据是散落在不同服务模块的,因此需要做额外的汇聚操作,还得有唯一字段保证数据在不同模块产生的先后顺序。

3. 参考

[1]一. Go微服务–隔离设计

br>


文章作者: Alex
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Alex !
  目录