41

这次要是讲不明白Spring Cloud核心组件,那我就白编这故事了

 4 years ago
source link: https://www.tuicool.com/articles/nueiauj
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

e6JBVrU.gif

这几天可真是热啊,泡个海澡是再好不过了。玩的正起劲,突然脚底绊上一股暗流,然后我就一直在水里旋转旋转旋转…终于眼前一黑。

爱的魔力转圈圈。我穿越了。

V7bMN3Z.gif

邻国相望,鸡犬之声相闻,民至老死不相往来。这个世界被小诸侯给切的七零八落,一锅乱麻。

而现实是,我的国家因为常年打仗,剩下的女人很多,需要打通远嫁他方的通道;而A国盛产长得和猪一样大的耗子,卖的很好。它们可以做成皮大氅,用来取暖。所以交流是在所难免的。

现实是这样的:

一、 A国不知道B国身处何方 ,经常有牧民捧着藏宝图一样的破布,葬身在崎岖的山路上。

二、 B国听不懂C国含糊不清的吐词 ,感觉他们在求救,等跑近一看,却发现其实是在骂娘。

三、 C国生产的南瓜就知道卖给D国 ,剩下的都烂在了地里,E国都开始吃树皮了。

四、F国倒是远近闻名,但四面八方蜂拥而至的难民,让他们非常苦恼。 其中,G国的难民,最是恶劣

五、曾有其他大陆板块的使者,5年不得要领。见神粥大地现状,作诗一首:《真TM乱》。

作为一个穿越者,一个怜悯众生的剩人。我要留给这个世界一张蓝图,好让后人记住我的名字:xjjdog。同时,我也想起了,我为什么有这种这种强大的自信。

”回忆“的片段将我带回到21世纪。

一、我要聊点技术了:单体应用

我们刚开始的服务,其实并没有那么复杂。我只有一台配置非常低的机器,我的应用,我的代码,我的聪明才智,全部在这一个小小的工程里面。由于我是搞it的,所以我的项目名字就叫 jisuanji 。有人说我用中文拼音做项目名,太那个。我不听,我就是这么命名。我还把公共模块叫 gg ,密码字段叫 mm ,谁管得着呢。

对,看下面的图,就是这么简单。 项目能活到用nginx来做负载均衡这一步,就算是小成功了。

iaIVjq6.png!web

这个时候,所有的代码就是一个整体,用户访问什么,我直接给就是。

二、我拆成了两个服务

可能是我和我一样二的人有点多,我的项目访问量越来越大,这也许就叫臭味相投吧。我自己的开发速度,已经追不上头脑里的idea,是时候招个人对服务进行拆分了。

不能拆的太过火,所以刚开始,我把 jisuanji 拆成了两个服务。 其中的服务B,仅仅部署了一个节点,因为它的压力还不是太大。即使这样,我不得不买上3台服务器来部署服务节点,真是肉痛。我这么抠门的人,数据库当然也是共用的。虽然有时候机器压力有点大,但暂时还死不了人。

fQvymmZ.png!web

这个时候我就面临了一个选择问题:服务A要怎么访问服务B呢?

由于我搞过一段时间的webservice,首先就想到了它。但这玩意太重了,我还不如通过Http访问来的舒爽。通过HttpClient,或者OkHttp,我的服务A,现在可以直接模拟Http请求访问服务B了。

当团队里有第二个人,就开始吐槽我的项目了。 以下是他罗列的,我的项目的罪状:1、复杂度太高,代码严重耦合;2、技术债务多,拍脑袋需求一箩筐;3、代码不规范,一坨屎;4、技术创新难,一个类几千行…

至于么?从一个服务拆成两个,就这么吐槽我。不过为了以后能拆出成百上千个服务,这口气我暂时忍了,毕竟我这人还是比较虚心的。

三、乱成一锅粥了

等过去半年一看,好家伙,服务给我拆了了几十个。当我的同伴把系统结构图拿给我看,我直接懵逼了。我挑了9个能看的服务,画了张图。

z2mYzuM.jpg!web

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

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

一、有小伙伴写了个通用的HttpClient调用组件,自己的负载均衡策略。

二、有另外一个小伙伴,习惯protobuf,所以选了gRPC。

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

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

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

现在的整个系统,简直是个四不像。 什么通信方式都有,什么交互格式都不缺。 拿最要命的D服务来说,光通讯模块,就引入了20几个jar包。如果应用扩展到上千个…My god…

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

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

四、微服务来袭

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

1、注册中心:Eureka

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

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

上面说的就是gossip协议。最终,你们都能够知道彼此,因为都是大嘴巴。比如小郑生了个孩子,过不了多少时间,全镇子的人都把这个孩子记录在本子上了。

用这种方式,服务都能够知道彼此,完成通信。

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

这就需要一个中心,那里的信息就是权威。在SpringCloud体系中,最常用的注册中心就是Eureka。任何服务启动以后,都会把自己注册到Eureka的注册表中;当服务死亡的时候,也会通知Eureka。

qYfAvaI.jpg!web

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

为了达到这个目的,还是要有一部分工作量的。且看下图。这个注册动作,是由一个叫做Eureka Client的组件来完成的。服务启动和关闭的时候,会通过这个组件推销自己;而当服务A想要调用服务B的时候,直接问Eureka Server就可以了。服务A拿到结果后,会把结果缓存在本地的注册表里。

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

z6Rbuu7.jpg!web

2、负载均衡组件:Ribbon

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

10.0.0.12
10.0.0.16

接下来麻烦了,该调哪台机器呢?这就是SpringCloud中组件Ribbon的作用。其实 Round Robin 是一个通用的计算机术语。它是最常用的负载均衡策略,请求会均匀的分配给后面的每台服务器。

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

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

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

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

4、通过RestClient对服务发起调用。

miYfimr.jpg!web

可以看到,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 ,并替换成相应的实例地址。

BFZv6fz.jpg!web

策略

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

1、随机Random

2、根据响应时间加权

3、自定义

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

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

3、为简化代码而生:Feign

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

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

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

VFVnaea.jpg!web

首先,Feign会根据@FerignClient注解,通过动态代理,创建一个动态代理类。接下来,你只要通过调用接口的方式,就可以构造上面提到的Ribbon调用参数,这个过程会自动填充。最后,通过构造的Ribbon请求,发起真正的调用,并通过反射组装返回值。

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

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

4、异常的保护伞:断路器

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

每一个真正的支付请求,都会调用其他四个服务。首先,使用鉴权服务,获取用户的支付权限;然后,风控服务会做一些规则验证;为了更好的推销产品,会调用营销业务,获取一些推荐信息;最后,调用聚合支付服务,进行真正的支付。

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

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

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

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

NfiuaqU.jpg!web

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

负责这个功能的组件,就叫做Hystrix。

以我们编程的思维来说,这就是个if条件。

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

但我们不能这么编码在业务代码里。所以Hystrix对每个服务开了一个线程池,并有比较复杂的规则,来控制这些出问题的服务的行为。比如,在2分钟内,直接返回营销业务的默认结果,而不是一直卡在那里。

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

5、此网关非彼网关:zuul

API网关是一个反向的 路由 ,它屏蔽了内部的细节,为调用者提供了统一的入口。网关,其实是一堆 过滤器 的几何,可以实现一系列和业务无关的横切面功能。

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

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

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

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

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

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

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

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

Bf2i2eM.jpg!web可以看到,我们平常用的nginx,就可以当作网关。但对于微服务来说,nginx的配置实在是太麻烦了。不是说nginx功能不够强大,而是因为它们不是一个体系的,就存在整合成本(比如kong)。

zuul就不一样了,它和SpringCloud的其他组件,是一家子的。一家子的,当然会特殊照顾。Zuul本身就是一个Servlet,外部请求经过一系列Filter后,会达到真正的服务。上面说的熔断器,就是高度集成的。

6、一张聚合图

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

YvM3YjV.png!web

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

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

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

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

qaqERr2.png!web

征途

一点道理

处理杂乱无章的事情,最有效的途径,就是集权和中心化。 集权和中心化的核心就是授权或者认同,否则注定失败。 授权是对上,各位当权者应该同意我的做法,所以我需要用及其易懂的语言,去说服他们接受这个体系;认同,是对下,最好是从人民的抱怨声中,出具的改善措施,所以要权威专业。

和微服务一样,需要给一些陈旧的概念,强行赋予看起来比较自然的新意义。比如我把统一语言,叫做文化融合,就显得高大上一些。

第二个,就是把职责拆的足够细。够细才能够精,每个位置上的人才能各司其职。

还有一点,整个过程,要能够系统化,能够进行推演。如果一件事有着不可预料的后果,那是冒险家干的事情。

一些途径

为了对世界进行初步的了解,我成立了资源统计部,对山川河流进行了初步的勘查,绘制出有章可循的地图。对社会的现状和错综复杂的关系进行了摸底。 我把这些信息出版成图书,遭到藏宝图收藏者们的嫉妒和憎恶。 他们躲藏在不为人知的角落,龌龊行事。

我还选了一个自己觉得好听的方言,统一了每个诸侯国的语言。在推行的过程中,多次受到土著们强烈的反对,拒不改正。 被我强行斩首了几个之后,以后的推行,就快的多了。

对于所有的重要商品,进行了集中管控。这个世界贵重的不是黄金,而是食物,所以我还修建了四通八达的道路和无孔不入的交易中心。从此之后,很少饿死过人。由于这部分是在我的控制范围内,所以进行的很顺畅。

G国的民风比较彪悍,经常发生暴力事件。这也难免,从刚开始,这个国家就难以驯化。好在缺了他们,这个系统也能循环的下去。前不久G国又发生了重大的事件,所有其他国家联合抵制,禁止G国国民入境。但时间是化解伤痛的良药,我估计这样的限制不会持续很久。

但糟粕还是有的。有人的地方,就有江湖,就有压迫。但这样的糟粕我是不想让其他人看到的。来访的使者,应该只能够看到歌舞升平、安居乐业, 这注定了不能让他们和底层接触,否则就发现金玉其外败絮其中的现状了。

他们和外交官打的不亦乐乎,我很欣慰。

End

我清楚的知道,为了建立一个和谐自然的系统,曾经花费了多大的代价。这其中的组成部分,并不能总是完美无缺的运行。而且,在这个看似平和的整体上,就滋生了其他无数令人头痛的问题 ,不过这是另外一个话题了。

就是这样。 我所做的一切,我所有的期望,只不过是为了: 当新的机会在我身后,我能够从容的、华丽的转身。

这就是我为了有一天能够穿越,所做的准备。

还不过瘾?这里有个姊妹篇

微服务不是全部,只是特定领域的子集

FBf2AnA.jpg!web

更多精彩文章

ErmqMrV.jpg!web

欢迎关注交流。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK