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. 与大神聊天1h

    与大神聊天1h 啊,与大神聊天1h真的是干货满满 解bug问题 之所以老出bug是因为我老是调用别人的包啊,在调参数的时候,并不知道内部机制 其实就自己写一个函数,然后能把功能实现就好了. 问题是,出 ...

  2. 使用Python发送、订阅消息

    使用Python发送.订阅消息 使用插件 paho-mqtt 官方文档:http://shaocheng.li/post/blog/2017-05-23 Paho 是一个开源的 MQTT 客户端项目, ...

  3. MySql 中关键字 case when then else end 的用法

    解释: SELECT case -------------如果 ' then '男' -------------sex='1',则返回值'男' ' then '女' -------------sex= ...

  4. 在多租户(容器)数据库中如何创建PDB:方法4 克隆远程Non-CDB

    基于版本:19c (12.2.0.3) AskScuti 创建方法:克隆远程Non-CDB(从 Non-CDB 中进行远程克隆).将 非CDB数据库PROD1 远程克隆为 CDB1 中的 PDB7 对 ...

  5. android toolbar 显示返回按钮并改变按钮颜色

    <android.support.design.widget.AppBarLayout android:id="@+id/about_appbar" android:layo ...

  6. Python爬虫之post请求

    暑假放假在家没什么事情做,所以在学习了爬虫,在这个博客园里整理记录一些学习的笔记. 构建表单数据(以http://www.iqianyue.com/mypost 这个简单的网页为例) 查看源代码,发现 ...

  7. 大数据-redis

    redis 分布式缓存数据库 单节点安装 tar -zxvf redis-3.2.9.tar.gz cd /opt/sxt/redis-3.2.9 yum -y install gcc tcl (依赖 ...

  8. 文件分割合并DOS版

    这个从163邮箱里翻出来的程序,2004年的修改日期,放这另存一下. 当时拿了一本C++的书来学,学了一阵就琢磨着做一个东东,然后就想起一个以前印象深刻的软件,叫做笨笨狗分割器. 当时主要还是靠3.5 ...

  9. 响应国家号召,AI助力疫情防控!顶象AI防疫方案获得国家人工智能标准化总体组认可

    当前,打赢新型冠状病毒感染的肺炎疫情是最重要的使命任务.而这场疫情的拉锯战,不仅要有全国人民共同努力.医护人员的无私奉献,还要积极运用现代科技的力量,用科学来战胜病魔.工信部也发文倡议:充分发挥人工智 ...

  10. Educational Codeforces Round 76 (Rated for Div. 2) C. Dominated Subarray

    Let's call an array tt dominated by value vv in the next situation. At first, array tt should have a ...