シングルアプリケーション
私達はちょうど複雑ではないことを、実際には、サービスを開始しています。私は唯一のマシンの構成は、小規模なプロジェクトの内部では非常に低く、私のアプリケーションで、私のコードは、私の知性、すべてこれで持っています。
私はITで働くので、ので、私のプロジェクト名がjisuanjiと呼ばれています。一部の人々はあまりにも、私は中国のピンインでプロジェクト名をやると言います。
私は私がそのように命名された、聞いていません。私はまた、そのチューブにそれを着るGGと呼ばれる共通モジュール、ミリメートルと呼ばれるパスワードフィールドを、です。
はい、次のチャートを見ては、そのシンプルです。プロジェクトでは、このステップ、小さな成功をロードバランシングを行うためにnginxのと一緒に暮らすことができます。
この時間は、すべてのコードはそれに全体の、どのようなユーザーアクセス、直接私です。
2つのサービス
私は、私は多分それは同じような考えを持つと呼ばれ、プロジェクトが成長している訪問し、二人より少し、私のようなことがあります。
私自身の開発スピード、心のアイデアの落下短い、サービスを分割するために個人を募集することの時間。
あまりにも遠く除去されていない、最初ので、私は2つのサービスに分割さjisuanji。どこでサービスB、ノードの唯一の展開、それはあまりにも多くの圧力ではありませんので。
それでも、私は本当に心痛、サービスノードを展開する3台のサーバを購入しなければなりませんでした。私は当然のようにケチ男、共有データベースです。時々機械圧力が少し大きいものの、まだの人は死にます。
この時間は、私は選択に直面して:行うサービスAサービスBにアクセスする方法?
私は、Webサービスでのいくつかの時間のために従事していたので、まずそれを考えました。しかし、このことは、私は同様にHTTPを介してアクセス快適かもしれないが、あまりにも重いです。
HTTPクライアント、または[OK]をHTTPで、私のサービスA、シミュレートHTTPリクエストは現在、直接サービスBにアクセスすることができます
また、第2人のチーム、Tucao私のプロジェクトを開始しました。ここで彼は、私のプロジェクトの罪悪感を列挙されたものです:
-
複雑さは、深刻な結合コードが高すぎます。
-
技術的負債及びより、ラッキング私たちの脳は、洗濯物のリストを要求します。
-
コードは、牛の糞を標準化されていません。
-
難しい技術革新、数千行のクラス...
何のために?2、私Tucaoへのサービススプリットから。しかし、何百ものサービスを分割することができるようにするために、私はすべての人々が非常にオープンマインドされた後、この気持ちを我慢します。
乱成一锅粥
このような過去6ヶ月、少年を見て、私は数十にサービスを削除しました。私の仲間は、システム構成図を見せてくれた置くとき、私は無知な力を指示します。
私は9つのサービスを拾って見ることができ、マップを描きました:
首先进行了业务拆分。比如支付业务,订单业务,用户中心,商品中心等,都组建了独立的团队。每个业务又进行了细分,拆分成不同的服务。
在这之间,进行了下面的改动:
-
有小伙伴写了个通用的 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。