https://mp.weixin.qq.com/s/UNm8cBw4TKq4OobVKHUBXA

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

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

现实是这样的:

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

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

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

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

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

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

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

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

我们刚开始的服务,其实并没有那么复杂。我只有一台配置非常低的机器,我的应用,我的代码,我的聪明才智,全部在这一个小小的工程里面。

由于我是搞 IT 的,所以我的项目名字就叫 jisuanji。有人说我用中文拼音做项目名,太那个。

我不听,我就是这么命名。我还把公共模块叫 gg,密码字段叫 mm,谁管得着呢。

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

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

我拆成了两个服务

可能是我和我一样二的人有点多,我的项目访问量越来越大,这也许就叫臭味相投吧。

我自己的开发速度,已经追不上头脑里的 Idea,是时候招个人对服务进行拆分了。

不能拆的太过火,所以刚开始,我把 jisuanji 拆成了两个服务。其中的服务 B,仅仅部署了一个节点,因为它的压力还不是太大。

即使这样,我不得不买上 3 台服务器来部署服务节点,真是肉痛。我这么抠门的人,数据库当然也是共用的。虽然有时候机器压力有点大,但暂时还死不了人。

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

由于我搞过一段时间的 Web Service,首先就想到了它。但这玩意太重了,我还不如通过 HTTP 访问来的舒爽。

通过 HTTP Client,或者 Ok Http,我的服务 A,现在可以直接模拟 HTTP 请求访问服务 B 了。

当团队里有第二个人,就开始吐槽我的项目了。以下是他罗列的,我的项目的罪状:

  • 复杂度太高,代码严重耦合。

  • 技术债务多,拍脑袋需求一箩筐。

  • 代码不规范,一坨屎。

  • 技术创新难,一个类几千行…

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

乱成一锅粥了

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

我挑了 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。

一点道理

处理杂乱无章的事情,最有效的途径,就是集权和中心化。集权和中心化的核心就是授权或者认同,否则注定失败。

授权是对上,各位当权者应该同意我的做法,所以我需要用极其易懂的语言,去说服他们接受这个体系;认同,是对下,最好是从人民的抱怨声中,出具的改善措施,所以要权威专业。

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

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

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

一些途径

为了对世界进行初步的了解,我成立了资源统计部,对山川河流进行了初步的勘查,绘制出有章可循的地图。对社会的现状和错综复杂的关系进行了摸底。

我把这些信息出版成图书,遭到藏宝图收藏者们的嫉妒和憎恶。他们躲藏在不为人知的角落,龌龊行事。

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

对于所有的重要商品,进行了集中管控。这个世界贵重的不是黄金,而是食物,所以我还修建了四通八达的道路和无孔不入的交易中心。

从此之后,很少饿死过人。由于这部分是在我的控制范围内,所以进行的很顺畅。

G 国的民风比较彪悍,经常发生暴力事件。这也难免,从刚开始,这个国家就难以驯化。好在缺了他们,这个系统也能循环的下去。

前不久 G 国又发生了重大的事件,所有其他国家联合抵制,禁止 G 国国民入境。但时间是化解伤痛的良药,我估计这样的限制不会持续很久。

但糟粕还是有的。有人的地方,就有江湖,就有压迫。但这样的糟粕我是不想让其他人看到的。

来访的使者,应该只能够看到歌舞升平、安居乐业,这注定了不能让他们和底层接触,否则就发现金玉其外败絮其中的现状了。

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

结语

我清楚的知道,为了建立一个和谐自然的系统,曾经花费了多大的代价。这其中的组成部分,并不能总是完美无缺的运行。

而且,在这个看似平和的整体上,就滋生了其他无数令人头痛的问题 ,不过这是另外一个话题了。

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

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

SpringCloud-粪发涂墙90的更多相关文章

  1. 粪发涂墙-redis1

    redis 核心就是 如果我的数据全都在内存里,我单线程的去操作 就是效率最高的,为什么呢,因为多线程的本质就是 CPU 模拟出来多个线程的情况,这种模拟出来的情况就有一个代价,就是上下文的切换, 对 ...

  2. 粪发涂墙-tomcat

    tomcat 的 JAVA_OPTS 分析设置 快乐生活你我 2019-08-12 06:07:00 JAVA_OPTS ,顾名思义,是用来设置JVM相关运行参数的变量. 1.JVM:JAVA_OPT ...

  3. 粪发涂墙-Redis

    Redis的高并发和快速原因 1.redis是基于内存的,内存的读写速度非常快: 2.redis是单线程的,省去了很多上下文切换线程的时间: 3.redis使用多路复用技术,可以处理并发的连接.非阻塞 ...

  4. 粪发涂墙-java1

    相信很多人和笔者一样,经常会做一些数组的初始化工作,也肯定会经常用到集合类.假如我现在要初始化一个String类型的数组,可以很方便的使用如下代码: String [] strs = {"T ...

  5. RocketMq-粪发涂墙1.0

    角色 说明 Producer 生产者,用于将消息发送到RocketMQ,生产者本身既可以是生成消息,也可以对外提供接口,由外部来调用接口,再由生产者将受到的消息发送给MQ. Consumer 消费者, ...

  6. 回顾一年的IT学习历程与大学生活

    今天是2015年8月27日,距离成为大三狗还有一个多星期,在这个不算繁忙的暑假的下午来总结一下这一年来,在IT方面的学习. 一.入门(2014.3) 我大一的专业是信息工程,信息工程听上去就是信息(I ...

  7. C语言-简单哈希表(hash table)

    腾讯三面的时候,叫我写了个哈希表,当时紧张没写好···结果跪了··· 回来后粪发涂墙,赶紧写了一个! 什么都不说了···先让我到厕所里面哭一会··· %>_<% 果然现场发挥,以及基础扎实 ...

  8. IDF - CTF - 牛刀小试

    找学校CTF好地方,IDF实验室CTF训练营(http://ctf.idf.cn/). . 刚接触CTF.来玩下牛刀小试.AK了. . 好爽好爽.. 1.摩斯password 嘀嗒嘀嗒嘀嗒嘀嗒 时针它 ...

  9. 【搞笑签名】390个qq个性昵称或签名,周末前娱乐一下

    1 来瓶82年的矿泉水 2 名不正则言承旭 3 天涯何处无芳草,还是母乳喂养好 4 她的妈妈不爱我 5 你丫的 6 农夫三拳 7 猪嚼在恋√痛 8 马驴脸猛鹿 9 小白兔兽性大发 10 曰捣一乱 11 ...

随机推荐

  1. 零基础自学Python是看书还是看视频?

    很多人都碍于Python培训班的高昂费用和有限的空余时间都选择自学Python,但是没有老师帮助,显得有些迷茫,不知应该从何处学起,也不知识看书学习还是应该看视频学习.本就来谈谈这个话题.   我们先 ...

  2. vs2019清空输入缓冲区

    发现用cin.sync()在vs2019中不能清空输入缓冲区,以前的vs版本没试过,我看别人在vc中用cin.sync()可以清除,估计是IDE的问题..以下是我学习C++四个多月写的一整段代码 运行 ...

  3. bootstrap资料索引

    中文手册  :  http://w3c.3306.biz/bootstrap_forms/show-25-64-1.html

  4. cocos Studio 特效文件播放失败

    可能是json文件中file的Path不对引起的.因为生成的path是根据文件在项目中的path生成的,即当此特效不是项目的一级目录时,会带有项目中上级目录,导致找不到文件(不会报错). ccs.lo ...

  5. 跨站点请求伪造(CSRF、XSRF)

    相关文章: 1. http://www.cnblogs.com/xiaoqian1993/p/5816085.html  深入解析跨站请求伪造漏洞:原理剖析 2 .http://blog.csdn.n ...

  6. 【音乐欣赏】《Heart Made of Stone》 - The Tech Thieves

    曲名:Heart Made of Stone 作者:The Tech Thieves Yeah It's been years now and I wonder Is it over? Do you ...

  7. how to activate XMind8 to pro version.

    From activate Xmind 8. in step 3: run ./setup.sh in sudo command, and use the following command to r ...

  8. Python中的模块简单认识

    将自己定义的方法,变量存放在文件中,为一些脚本或者交互式的解释器实例使用,这个文件称为模块. 细说的话,模块可以分为四个通用类别: 1 使用python编写的.py文件(自定义模块) 2 已被编译为共 ...

  9. Linux, Nginx - Deepin linux手动安装nginx和出现的问题

    安装步骤 切换至root su 安装依赖库 sudo apt-get install build-essential && sudo apt-get install libtool s ...

  10. AcWing 906. 区间分组

    //1.将所有区间按左端点从小到大排序 //2.从前往后处理每个区间,判断能否将其放到某个现有的组中 //判断某一组的最后一个区间的右端点是否小于该区间的左端点 //如果大于或等于,就开新组,如果小于 ...