Disposable speak to understand the core components of Spring Cloud

Single application

We have just started the service, in fact, not that complicated. I have only one machine configuration is very low, my applications, my code, my intelligence, all this in a small project inside.

Because I work in IT, so my project name is called jisuanji. Some people say I do the project name in Chinese pinyin, too that.

I do not listen, I was so named. I'm also a common module called gg, the password field called mm, whose tube wear it.

Yes, look at the following chart, is that simple. Project can live with Nginx to do load balancing this step, even a small success.

This time, all the code is a whole, what user access, direct me to that.

Two services

I may be like me, a little more than two people, I visited the project is growing, perhaps it is called like-minded.

My own development speed, falling short of mind Idea, it's time to recruit individuals to split the service.

Not too far removed, so the beginning, I jisuanji split into two services. Where the service B, only the deployment of a node, because it's not too much pressure.

Even so, I had to buy 3 servers to deploy the service node, really heartache. I am so stingy man, of course, shared database. Although sometimes the machine pressure a bit big, but yet people die.

This time I was faced with a choice: how to access the service A service B do?

Since I had been engaged for some time in the Web Service, first thought of it. But this thing is too heavy, I might as well be comfortable accessed via HTTP.

By HTTP Client, or Ok Http, my service A, simulate HTTP requests can now directly access the service B.

When the second person team, began Tucao my project. Here's what he listed, guilt of my project:

  • Complexity is too high, severe coupled code.

  • Technical debt and more, racking our brains demand a laundry list.

  • Code is not standardized, bull feces.

  • Technological innovation difficult, a class of thousands of lines ...

For what? From a service split into two, so I Tucao. But in order to be able to split out hundreds service, I do put up with this feeling, after all my people are quite open-minded.

乱成一锅粥 the

Such as a look at the past six months, boy, I removed the service to dozens. When my companion put a system configuration diagram showed me, I direct the ignorant force.

I picked up nine service can see, drew a map:

首先进行了业务拆分。比如支付业务,订单业务,用户中心,商品中心等,都组建了独立的团队。每个业务又进行了细分,拆分成不同的服务。

在这之间,进行了下面的改动:

  • 有小伙伴写了个通用的 HTTP Client 调用组件,自己的负载均衡策略。

  • 有另外一个小伙伴,习惯 Protobuf,所以选了 gRPC。

  • 事实证明 SOA 还是有市场的,这不,就有几个服务的交互引入了 Web Service。

  • 有人想要用 RMI,被我及时发现、否决,腹死胎中了。

  • 每次建个新服务,都需要更新一下 Excel,然后将这个 Excel 周知出去。

现在的整个系统,简直是个四不像。什么通信方式都有,什么交互格式都不缺。

拿最要命的 D 服务来说,光通讯模块,就引入了 20 几个 Jar 包。如果应用扩展到上千个…My God…

更要命的是,这么多服务,每次上线一个模块都胆战心惊,因为他不知道到底会有什么连锁反应。

是时候叫出超级飞侠了。哦不,叫出微服务了。

微服务来袭

目前,最火的微服务框架,就是 Spring Cloud 了。虽然 Netflix 公司对某些组件的维护经常爽约,但有些核心组件还是非常经典的。

注册中心:Eureka

服务 A,怎么找到服务 B,有很多种方式。比如你生活在一个小镇上,你问 xjjdog 是谁,老王可能认识他,但小李可能并不知晓;但小李认识老王,所以通过他最终也能找到 xjjdog,只不过麻烦一些。

你可以随便拉小镇上的一个人,来问 xjjdog 是谁。你还会变戏法一样拿出一个小本本,把你认识的人,都告诉他们。当你脑残式的问了一个遍,到最后所有人都知道 xjjdog 了。

上面说的就是 Gossip 协议。最终,你们都能够知道彼此,因为都是大嘴巴。

比如小郑生了个孩子,过不了多少时间,全镇子的人都把这个孩子记录在本子上了。用这种方式,服务都能够知道彼此,完成通信。

可惜这并不美好,从小镇的东头跑到西头,需要很长时间。在这个时间里,小郑刚生的孩子可能因为先天疾病夭折了。我们需要一种信息集中度和实效性更高的方式。

这就需要一个中心,那里的信息就是权威。 在 Spring Cloud 体系中,最常用的注册中心就是 Eureka。

任何服务启动以后,都会把自己注册到 Eureka 的注册表中;当服务死亡的时候,也会通知 Eureka。

这样,当服务 A 想要找服务 B 的时候,只需要问一下 Eureka Server 就可以了,它什么都知道。

为了达到这个目的,还是要有一部分工作量的。且看下图。这个注册动作,是由一个叫做 Eureka Client 的组件来完成的。

服务启动和关闭的时候,会通过这个组件注销自己;而当服务 A 想要调用服务 B 的时候,直接问 Eureka Server 就可以了。服务 A 拿到结果后,会把结果缓存在本地的注册表里。

你可以认为是一个拷贝。所以 Eureka Server 死掉后,并不影响服务 A 找到服务 B。

负载均衡组件:Ribbon

现在问题来了。服务 A 拿到服务 B 的实例列表以后,发现有两台。

10.0.0.12
10.0.0.16

接下来麻烦了,该调哪台机器呢?这就是 Spring Cloud 中组件 Ribbon 的作用。

其实 Round Robin 是一个通用的计算机术语。它是最常用的负载均衡策略,请求会均匀的分配给后面的每台服务器。

Ribbon 工作时,会做下面四件事:

  • 优先选择在一个 Zone 且负载较少的 Eureka Server,进行连接。

  • 定期从 Eureka 更新、过滤服务和实例列表。

  • 根据负载均衡策略,从注册表中选择一个真正的实例地址。

  • 通过 Rest Client 对服务发起调用。

可以看到,Ribbon 背后,还是采用的 HTTP 协议进行交互。看以下代码,就可以直接实现对远端服务的调用:

@Bean
@LoadBalanced
RestTemplate restTemplate(){
    return new RestTemplate();
}
...

 @Autowired
RestTemplate restTemplate;
public String test() {
    return restTemplate.getForObject("http://test-service/test", String.class);
}

Ribbon 的 Filter 会查找 Test-Service,并替换成相应的实例地址。

Ribbon 不仅仅提供了轮询的策略,还有其他的,比如:

  • 随机 Random

  • 根据响应时间加权

  • 自定义

拿轮询来说,最终的选择逻辑就在 RoundRobinRule 类中:

private int incrementAndGetModulo(int modulo) {
        for (;;) {
            int current = nextServerCyclicCounter.get();
            int next = (current + 1) % modulo;
            if (nextServerCyclicCounter.compareAndSet(current, next))
                return next;
        }
}

为简化代码而生:Feign

可以看到,Ribbon 需要自己构建 HTTP 请求,模拟 HTTP 请求然后使用 RestTemplate 发送给其他服务,步骤相当繁琐。而且返回类型不安全,也表达不出什么语义。

其实,通过 Ribbon 方式,已经能够完成微服务之间的调用了。但 Spring Cloud 的开发语言是 Java,肯定要进行更加高级的封装,才能体现它的逼格。

Feign 得益于 Java 的动态代理机制,最终封装出一套简洁的接口调用方式,将需要调用的其他服务的方法定义成抽象方法即可,不需要自己构建 HTTP 请求。

首先,Feign 会根据 @FerignClient 注解,通过动态代理,创建一个动态代理类。

接下来,你只要通过调用接口的方式,就可以构造上面提到的 Ribbon 调用参数,这个过程会自动填充。最后,通过构造的 Ribbon 请求,发起真正的调用,并通过反射组装返回值。

所以,Feign 只是一层皮,最终还是要通过 Ribbon 进行调用。在我看来,把 Ribbon 和 Feign 合成一个组件,也是合理的。

它们有一个比较通用的名词,就叫做 RPC(远程调用)。

异常的保护伞:断路器 Hystrix

下面以一个支付请求为例,说一下不是风平浪静的情况下,服务会有什么反应。

每一个真正的支付请求,都会调用其他四个服务。首先,使用鉴权服务,获取用户的支付权限;然后,风控服务会做一些规则验证。

为了更好的推销产品,会调用营销业务,获取一些推荐信息;最后,调用聚合支付服务,进行真正的支付。

其中,营销业务其实是可有可无的。让用户首先把钱花出去,是我们的首要任务。

考虑下面一种场景,营销业务由于系统故障或者负载问题,发生了大面积的不可用或者超时。然后,所有的请求都卡在了获取营销信息的代码上。

如图所示,鉴权和风控都已经通过了。因为一个旁路功能:营销业务,导致真正的支付无法进行。这个时候,如果有人调用支付请求,会发现支付请求也出错了。

因为它们最终都卡在了营销这一段小代码上:

所以,对于营销业务这种不是链路上必备的服务提供者,要有一个手段,让它在发生问题的时候,隔离它一段时间。

负责这个功能的组件,就叫做 Hystrix。以我们编程的思维来说,这就是个 if 条件:

if(服务发生问题){
    return "暂时不要处理";
}

但我们不能这么编码在业务代码里。所以 Hystrix 对每个服务开了一个线程池,并有比较复杂的规则,来控制这些出问题的服务的行为。

比如,在2分钟内,直接返回营销业务的默认结果,而不是一直卡在那里。

这个过程,就叫熔断。就像电源一样,出了问题,先切断保险丝,别把电器给烧了。

此网关非彼网关:Zuul

API 网关是一个反向的路由,它屏蔽了内部的细节,为调用者提供了统一的入口。

网关,其实是一堆过滤器的几何,可以实现一系列和业务无关的横切面功能。

熟悉 Spring 的都知道 AOP,路由的一个功能,就是针对于分布式服务的一个 AOP。

还是先说下网关的职责吧。简单罗列几个:

  • 安全认证。提供统一的认证方式和鉴权功能,避免重复开发。

  • 熔断,限流。针对问题服务,进行熔断操作;对流量进行预估,限制访问。

  • 日志监控。统一流量入口,进行流量分析和监控。

  • 屏蔽内部细节,对外提供一致的接口。

  • 实现灰度。使用自定义策略实现分流,达到测试的目的。

网关的位置,大体就如下图:

可以看到,我们平常用的 Nginx,就可以当作网关。但对于微服务来说,Nginx 的配置实在是太麻烦了。

不是说 Nginx 功能不够强大,而是因为它们不是一个体系的,就存在整合成本(比如 Kong)。

Zuul 就不一样了,它和 Spring Cloud 的其他组件,是一家子的。一家子的,当然会特殊照顾。

Zuul 本身就是一个 Servlet,外部请求经过一系列 Filter 后,会达到真正的服务。上面说的熔断器,就是高度集成的。

一张聚合图

有了上面关键组件,事情就明了的多了。我们把它放在一张图中,就是下面的样子:

我们将其简化一下,就可以得到一张更简洁的图。可以看到,只需要 3 个关键点:

  • 服务注册中心,统一管理所有服务的信息,默认组件是 Eureka。

  • RPC,网络通信组件,服务 A 怎么调用服务 B。在 Spring Cloud 中,就是 Ribbon+Feign。

  • 网关,拆分的服务怎么暴露接口,最终见人的样子。默认组件是 Zuul。

 

发布了1 篇原创文章 · 获赞 15 · 访问量 1万+

Guess you like

Origin blog.csdn.net/a1405/article/details/100127304