我眼中的微服务架构

24次阅读

共计 3365 个字符,预计需要花费 9 分钟才能阅读完成。

引言

随着业务的发展,无数公司开始面向服务开发,转向微服务架构。

说句大实话,今年秋招,算法和 Java 岗人才爆炸,不容易。

忙着找工作,好久没写过博客了。华软新版本基本完成,趁着有时间正好给大家讲一讲传说中的微服务架构。

微服务

这里的微服务,并不是单指我们用的 spring-cloudspring-cloud 只是微服务的一种最佳实践。对于创业来说,在没有稳定的资金支撑自研中间件的情况下,spring-cloud是我们最好的选择。

在提出微服务架构之前,我们先分析一下我们以往的系统有哪些问题。

单体架构

单体架构,就像我们之前写得系统一样,一个 spring-boot 项目实现了所有的业务逻辑,部署的时候 mvn packagejava -jar 就算跑起来了一个后台项目。

缺点很明显,我们也遇到过。这里我列出我的几个观点:

可用

如果这个实例宕机了,因为单体应用的强依赖性,整个系统都变为不可用的状态。

因为系统对 API 的强依赖,一旦 API 实例宕机了,整个系统就完了。

对于计量和学校的业务,要求还没有这么高,但是如果对于银行的项目,就没这么容易接受了。

并发

除了加服务器,我们没有办法有针对性地解决高并发。

假设,Alice的学生管理项目遇到了高并发导致的性能问题,我们只能这么干。

部署多个 API 节点,通过配置 nginx 分发策略进行负载均衡。

但是,可能有 100 接口,往往只有 5-10 个接口是学生 / 老师们常用的,像综合查询之类的不存在并发问题。这样部署属实浪费资源。

我们无法针对项目中某些常用的接口在硬件部署级别上实施优化。

语言

这个是我在参考甲方提供的华软另一版本的 Web 项目得出的思考。

项目架构如下所示:

一个前后端不分离的 JavaEE 项目,去调用另一个专门提供华软接口的 .NET 项目。

这是不是看起来很酷,一个项目的后台,采用两种语言,两个服务之间通过 HTTP 调用进行通信。

如果服务之间是通过 @Autowired 在容器中进行依赖,那所有功能都需要使用 Java 实现,无法发挥各种语言的优势。

微服务架构

服务高可用

假设我们要保证验证码微服务的高可用,我们可以使用服务注册与发现:

服务注册中心,存储服务到实例地址的映射。

当系统启动时,右侧的三个验证码微服务会向服务注册中心进行注册,表示自己要提供服务。这个过程称为服务注册。

服务注册中心就存储着类似这样的数据:

VerificationCode:- code1.instrument.yunzhiclub.com
  - code2.instrument.yunzhiclub.com
  - code3.instrument.yunzhiclub.com

表示验证码有三个微服务提供,地址分别是code1.instrument.yunzhiclub.comcode2.instrument.yunzhiclub.comcode3.instrument.yunzhiclub.com

如果前台要使用验证码服务,Gateway会向服务注册中心询问,有哪个微服务提供验证码的功能,获取到列表,再进行转发。

这个向服务注册中心获取其他服务实例的过程,称为服务发现。

同理,其他微服务想调用该服务,执行的也是与网关相同的服务发现。

为什么能保证高可用?

大家思考一下,为什么这样设计就能保证高可用呢?

微服务和服务注册中心之间是通过心跳来续约的,如果超过阀值,服务没有向注册中心发送数据,中心就认为这个实例已经死亡,将其从可用列表中剔除。

假设某天,验证码微服务 1 宕机了,不再发送自己的心跳信息,服务注册中心中的维护的数据就变成了(实例 1 被剔除):

VerificationCode:- code2.instrument.yunzhiclub.com
  - code3.instrument.yunzhiclub.com

这样能保证如果有实例宕机,可以让系统以最快的速度恢复。

哪怕用户目前正在请求 code1 实例,本次失败了,在一段时间的心跳过后,数据更新,用户重试,也能保证服务可用。

既然这么依赖服务注册中心,那服务注册中心宕机了怎么办?

在系统部署时,服务注册中心往往是三台实例以上的集群。所以大可不必考虑这个问题。

服务注册中心之间会进行数据同步,我觉得这个和路由器之间交换路由表很类似。

上面的诸多技术都是理论,落实到的是 spring-cloud 中的一个个组件。

网关:spring-cloud-netflix-zuulspring-cloud-gateway。这里推荐使用spring-cloud-gateway

zuul采用阻塞 API 实现,spring官方认为其存在性能问题,遂放弃维护 zuul,启用新项目spring-cloud-gateway,采用非阻塞API 实现。

https://stackoverflow.com/questions/47092048/how-is-spring-cloud-gateway-different-from-zuul

服务注册中心:spring-cloud-netflix-eureka

又是 netflix 家的,我看慕课网的实战课用的基本上都是eureka

但是我看新闻好像是由于 netflix 公司的闭源,eureka项目停止维护,替代方案:spring-cloud-consul

知识都学会了,换个组件还不是轻而易举?

服务雪崩

微服务带来便利的同时,也为我们带来了服务雪崩问题。

本图片摘抄自博客,感谢原作者的分享:https://segmentfault.com/a/1190000005988895

就像这个图一样,因为服务之间相互依赖,如果 A 服务宕机了,那依赖它的 B 服务也不可用,同理,依赖 BCD服务都不可用。

一个服务的崩溃,导致了整个系统的瘫痪,这就是服务雪崩。

我们发现雪崩的原因是虽然 B 可用,但是因为 A 的瘫痪,导致了 B 不可用。所以问题出在服务调用上,我们需要在服务调用时进行错误处理。

我们可以使用这种策略:当检测到 A 崩溃时,B就不去请求A(反正请求也是失败),使用本地的降级策略。这个过程称为熔断降级。

降级可以理解为一种兜底策略,差强人意的意思。

比如查询用户数据,用户服务又去调用朋友圈服务的接口。假设朋友圈的接口宕机了,无法获取最新的数据,我们可以使用缓存策略返回上一次缓存的数据。

保证了后台出错,但是给用户的体验也不会很差。

熔断器组件:spring-cloud-hystrix

微服务认证

这个是从我刚开始学微服务就一直带着的一个问题。这么多服务,不可能每一个都搭一个 spring-security 吧?

直到上次计量项目中学习了潘老师对用户认证的实现,才解决这个问题。

所有用户认证放在网关,网关对前台的 COOKIE / X-AUTH-TOKEN 进行用户信息的认证,查询出用户后,根据用户信息签发一个JWT Token

微服务之间的认证,使用网关签发的JWT Token

在《重新定义 Spring Cloud 实战》一书中,也是推荐网关结合 jwt 的认证方式。

通信成本

微服务架构还需要考虑的一个就是微服务之间的通信成本。

@Autowired
private CourseService courseService;
/**
 * fallback 兜底策略
 */
@FeignClient(value = "course",
        fallback = CourseClientHystrix.class)
public interface CourseClient {@GetMapping(value = "{id}")
    CourseInfo getCourseInfo(@PathVariable Long id);
}

看看代码就是成本的区别,过去就是 @Autowired 直接调,现在得用 feign 通过网络调,网络通信肯定比本地慢。

所以服务拆分要合理,如果一个服务要求高可用,并且可以接受通信成本的话,就可以单独拆分微服务。

服务拆分对架构师要求极高,从实现到性能,需要考虑周全。我这种小菜鸡只能写写博客吹吹牛了,现在我还不敢服务拆分呢(就怕拆的不好,把性能拆坏了)。

总结

吹了一篇理论,让大家先了解了解微服务架构,到了自己学的时候也少走些弯路。

看着挺简单的,真正搭 spring-cloud 的时候没有看上去那么简单。照着慕课网搭过一个小的,花了好几天时间。

书到用时方恨少,事非经过不知难。

正文完
 0