为 Istio 做了点微小的贡献
Istio 1.7.0
最近 Istio 1.7.0 发布了,这是一个令人非常激动的版本,因为这个版本解决了大量我们遇到的 Bug。
更激动的是,其中几个是我提交的代码修复的。
所以想在这把这几个 Bug 过一遍,顺便也介绍一下如何 debug Istio 代码。
最近 Istio 1.7.0 发布了,这是一个令人非常激动的版本,因为这个版本解决了大量我们遇到的 Bug。
更激动的是,其中几个是我提交的代码修复的。
所以想在这把这几个 Bug 过一遍,顺便也介绍一下如何 debug Istio 代码。
Kubernetes 内置HorizontalPodAutoscaler
可以很方便地根据 CPU 和内存做水平扩容缩容。
Kubernetes 在启动Pod
和销毁Pod
的时候对生命周期的控制也做的非常好,并不会对一个服务有太大的影响。只要把优雅启动和优雅关闭做好,并且把 Requests 和 Limits 配置到合适的数值,这一切会变得非常便捷。
另外再配合 Kubernetes Cluster Autoscaler,还可以实现整个集群机器的自动扩容缩容。
Cluster Autoscaler 可不是简单地根据机器剩余多少资源来判断是否要扩容缩容的。一般的机器自动扩容缩容都只是判断一下 CPU 用了多少,内存用了多少,如果剩余很多就尝试缩减机器。但 Cluster Autoscaler 的判断逻辑没这么简单,它还会和 Kubernetes 污点,污点容忍性,Pod
亲和性,Node 亲和性相结合。例如你有一台机器有特殊的 Label,上面有一个Pod
只能跑在有这个 Label 的机器上,占用的资源非常少。如果是别的程序,看到这台机器占用资源少,就直接把它干掉了,但实际上它不能去掉,因为上面的这个Pod
只能在这台机器上跑。
不可否认,当你把一个单体程序拆成微服务后,单次请求的延迟也必定会增加。
然后再做 Service Mesh 改造后,每个服务外层还要再加一层 Proxy,那延迟又要增加了。
如果是一个复杂点的接口,内部产生 10 次远程调用是很常见的。Service Mesh 的核心 Sidecar 可以认为是一层反向代理。在这种情况下你数数一个请求经过了多少层反向代理?就算他们都是 C 写的,把极致性能放到首位,但单次反向代理的损耗在 1ms 左右还是很常见的。
也就是说单体程序做 Service Mesh 改造后,简单的 API 延迟增加 5ms 左右,复杂的 API 延迟增加 20ms 左右都是很正常的。
不仅如此,因为远程调用的网络因素,P99 也会比单体程序高很多,这也没办法,经过了多次网络调用后稳定性肯定是不如程序内部函数调用的。
产品经理突然来了个需求,希望在一些事件打点的地方记录一下用户的各种信息:IP,User Agent,Accept Language 等。
但数据打点是分散在各个地方的,而且需求变化非常快,我们怎么样才可以随时随地拿到这些信息呢?
一种笨办法就是在所有函数调用的地方把相关信息一层层往下传,但你应该没见过这样的代码,实在是太麻烦了。
另外一种方式就是直接从一个静态方法里拿到当前的 Request 对象,并从中拿到各种信息。例如 C# 中是这样的:HttpContext.Current.Request
,Python Flask 中是这样的:from flask import request
。如果进去看看源码的话就会发现一般它们都是通过 Thread Local 来实现的。大部分的 HTTP Server 都是一个 Request 只由一个线程处理,所以这么做没什么问题。
而 Golang 的并发模型不一样,所以 Golang 无法这么做,Golang 需要显示地传播 Context。同样,用 Netty 做 HTTP Server 的话,并发模型也是完全不一样的,同样无法直接使用 Thread Local,只能显示传播 Context。还有基于 RxJava 实现的也无法这么做。
上下文本质上是一种隐式传播的信息,简化工作量。上面提到的这些都是程序内部的上下文,如果把这个隐式的信息传播扩展到微服务之间,那么它就变成分布式上下文了。
CI / CD 是我们整个 Service Mesh 转型过程中最艰难的一个环节。
为什么最艰难?
CI / CD 本质上要解决的问题是:标准 + 自动化脚本 + 可视化 + 通知。
不同的公司情况不同,标准也会不同,因为在没有经验的情况下,很难制定出合理的标准。
没有标准也就不能有足够抽象好用的自动化脚本,为一个项目写一些脚本不难,写出抽象好用的脚本很难。
可视化和通知优先考虑开源产品,这两块在两年前也没什么可选的。
对于一个国际化的 App 来说,UI 和各种文案的国际化是必须的。
一般来说,开发只要根据用户的语言,然后调用对应方法,就可以拿到对应的文案了。
String errorMessage = getErrorMessage("exception.HTTP_404", "en-US,en;q=0.5")
以前的微服务架构中优雅启动和优雅关闭其实不难,这些东西本身就是自己实现的。
启动后先打点流量预热一下,然后把实例注册一下就行了;要关闭的时候就先取消注册,等待一段时间尽量让所有请求结束后再关闭。
另外,传统微服务架构下的实例启动关闭的频率也远低于 Service Mesh,做了 Service Mesh 这个问题也会更突出一点。
那具体遇到了哪些问题又怎么解决呢?
Ingress 的相关概念可以直接看 Kubernetes 的文档,讲的很清楚了:
简单的来说,它和传统服务器架构中的负载均衡器是类似的,本质上就是把集群内部的服务暴露给集群外。
internet
|
[ Ingress ]
--|-----|--
[ Services ]
这块技术方案非常多,要开发一个自己的 Kubernetes Ingress 也不难。看 Ingress Controllers 这篇文章,Ingress Controllers 的意思就是 Kubernetes Ingress 的具体实现。
两年前比较靠谱的方案主要是 Nginx Ingress 和 Istio Gateway,而现在技术方案已经非常多了。所有传统负载均衡厂商基本都为 Kubernetes 开发了 Ingress。
从严格的定义看,Istio Gateway 不能算是一个 Ingress Controller,因为它并不是根据 Kubernetes 里的Ingress
资源来定义路由规则的。
Kubernetes Ingress 的理念是想做一层抽象,配置和实现解耦,所有的配置都是配置Ingress
,而不需要关心具体的技术实现。
Istio Gateway 不用Ingress
来配置,而是使用了自己的一套资源来配置,实际的功能上也比 Kubernetes Ingress 更丰富。因为技术实现脱离了 Kubernetes Ingress,所以我觉得严格的定义来看它不是一个 Ingress Controller。
严格的来说,数据库中间件的选择和 Servic Mesh 无关,一般公司很早就应该上数据库中间件了。
数据库中间件一般有两个方案:SDK 模式或者 Proxy 模式。SDK 模式性能更好,Proxy 模式兼容性更好。
既然我们都在往 Service Mesh 方向走了,就是不想在业务代码去接 SDK 了,所以 Proxy 模式是我们优先选择的方案。虽然延迟会高一点,但还是那句话,不要只盯着单次调用的延时。
那数据库中间件到底解决了哪些问题?一般来说,利用数据库中间件可以实现如下功能:
之前在大众点评做 Zebra 的时候,主要的技术方案就是 SDK 模式,因为整个大众点评是 Java 技术栈,没有多语言的问题,所以用 SDK 模式可以尽量提高性能。
Istio 的架构设计让人看着非常舒服,分工明确,扩展性强。
特别是 Mixer 模块,包含Telemetry
和Policy Check
两个模块,数据平面的 Envoy 会把所有请求异步发送给 Mixer 用作遥测,也会定时检查对应规则判断是否可以调用目标服务。
数据平面会把所有的请求上报到 Mixer,如果想要扩展任何功能,只需要扩展 Mixer 就行了。Istio 也把这一层做成了 CRDs,只需要创建对应的 CRDs 就可以了,而不需要对数据平面做任何改动。
按照 Istio 的理念,遥测和规则检查是属于控制平面的,从解耦的角度看,这样的设计很棒。
如果仅从易用性这个角度看,kops 是完胜 EKS 的,下面有一个官方的演示视频。
所有的操作都可以在命令行里完成,包括建集群,改集群。
每次变更都需要如下几个步骤:
kops update cluster
,这一步相当于dry run
,你可以检查即将产生的变更。kops update cluster --yes
,这一步才是把配置推到线上。kops rolling-update cluster
,这里也是dry run
,上一步虽然把配置推送到线上了,但有些机器的配置需要重建机器。kops rolling-update cluster --yes
,最后它会按顺序一台台更新机器。整个集群升级过程非常直观可控,再配合 Kubernetes 的PodDisruptionBudget
,整个升级过程会变得非常安全。PodDisruptionBudget
可以控制一个Deployment
至少存活多少Pod
,保证服务可用。否则如果一个Deployment
的两个Pod
凑巧在一台机器上,没有PodDisruptionBudget
的话它就直接干掉了。
然而, kops 在 Master 节点的可靠性上出了点问题。
刚来创业公司的时候,被这简单粗暴的“架构”设计震惊了,准确的说,这里并没有什么设计。整个后端就一个单体程序,整体结构和大学里写的三层架构差不多,好吧,好歹还做了一些分层。设计模式就别想找到了,面向对象都很少见。
作为初创员工加入后,发现其实这种代码在人少时还挺高效的。本地搞点配置写个脚本,几秒内就可以把新代码发布到线上。
此时我们大致的架构是这样的:
但随着团队的扩张,2人、5人、10人,当 10 个人往同一个没有良好设计的项目里 commit 代码的时候,问题也慢慢地开始显现了。
代码冲突,发布依赖,线上雪崩… 是时候开始微服务化了。
2018年7月,我们的转型之路正式开始。
很早之前,在咸鱼买了一套罗技 G29 赛车游戏方向盘,全新的一套2500以上,用1600的价格买到一套二手的,算是捡了个便宜。
卖家顺便送了一套简易的支架,放在自己的椅子前就可以玩。但是那坐姿,根本不是开车啊,简直是一种煎熬。
于是在网上寻觅赛车座椅,价格倒不是问题,可以接受,但这个体积实在是…
公司内容由用户生成的话,不可避免地会遇到各种色情图片的问题。虽然我们有了举报系统,但是靠人工处理也是挺累的。
正好在半年前,Yahoo 发布了一个开源的基于 Caffe 的色情图片模型。https://github.com/yahoo/open_nsfw
这部分经验我会另起一篇文章来介绍一下。
而本文,主要是我在接触到深度学习相关知识后,发现可以用它来解决自己的一个痛点。
所以就有了此文和一些开源小代码。
人是一种很奇怪的东西,以前家里科学上网速度很慢的时候,只要能打开就很满意了。
现在换了联通,Google 明明已经秒开了,却又开始不满足现状了。
恰巧最近看到了各种 KCP, BBR 技术的介绍,就想给自己的梯子折腾折腾了。
记得在一年前的某一天,电信宽带的国际链路突然变龟速,国外的网站甚至打都打不开。本来去年年底就想换联通宽带的,但是看到电信有200M体验活动,而联通只有50M,所以又被电信骗过去了。
然后安慰自己,电信国际联路只是一时的问题,总有一天会变好的。然后我又被坑了一年…
公司的很多服务都部署在 Amazon 云服务上,每次查看简直崩溃。
电信为什么这么慢?目前最合理的解释就是电信大规模宽带提速(这个是好事),国内网络互访没有问题,但国际链路带宽却增长缓慢。再加上近几年海淘兴起,最终导致电信的国际链路拥堵不堪。
而联通为什么“快”?其实联通的国际链路只有电信的 1/3,但是联通宽带发展太慢,而这太慢反而成了优势。特别是在上海,几乎没人用联通,很多老小区也没有通联通宽带。最后的结果就是,联通的国际链路访问速度比电信快很多。
网友的数据分析:https://oldcat.me/post/Number-Never-Lie-ChinaTelecom-Shanghai-Network
期间多次向电信和工信部投诉,并没有什么用。曾经还加入过某个QQ群,大家实名制签名,律师帮大家一起起诉电信。同样并没有什么用。
那怎么办?我只能选择联通了。
常说车开多了胆子会越来越小,写代码也是。其实不是老司机胆子小了,而是新手无知无畏罢了。
最近一个很简单的功能,我做了2-3天,要是在我刚毕业的是时候把这个任务交给我,啪啪啪,不是我吹牛,2-3小时我就搞定了!
直接看产出的结果可能没觉得怎么样,甚至还会觉得这么做不对,但我觉得其中的思考过程还是非常有价值的,所以想在这记录下来。
环形缓冲区:https://zh.wikipedia.org/wiki/環形緩衝區
维基百科的解释是:它是一种用于表示一个固定尺寸、头尾相连的缓冲区的数据结构,适合缓存数据流。
底层数据结构非常简单,一个固定长度的数组加一个写指针和一个读指针。
只要像这张图一样,把这个数组辦弯,它就成了一个 RingBuffer。
那它到底有什么精妙的地方呢?
我最近做的项目正好要用到类似的设计思路,所以翻出了以前在点评写的 Puma 系统,看了看以前自己写的代码。顺便写个文章总结一下。
本文为我和同事的共同研究成果
当跨语言的时候,有些东西在一门语言中很常见,但到了另一门语言中可能会很少见。
例如 C# 中,经常会关注拆箱装箱,但到了 Java 中却发现,根本没人关注这个。
后来才知道,原来是因为 Java 中没有真泛型,就算放到泛型集合中,一样会装箱。既然不可避免,那也就没人去关注这块的性能影响了。
而 C# 中要是写出这样的代码,那你明天不用来上班了。
同样的场景发生在了学习 Python 的过程中。
什么?数据库连接竟然没有连接池!?
完全不可理解啊,Java 中不用连接池对性能影响挺大的。
Python 程序员是因为 Python 本来就慢,然后就自暴自弃了吗?
突然想到一个笑话
问:为什么 Python 程序员很少谈论内存泄漏?
答:因为 Python 重启很快。
😂 说多了都是泪,我之前排查 Java 内存泄漏的问题,超高并发的程序跑了1-2个月后就崩溃。我排查了好久,Java GC 参数也研究了很多,最后还是通过控制变量法找到了原因。
如果在 Python 中,多简单的事啊,写一个定时重启脚本,解决…
我这人对待事物有一个习惯,一个东西只有把原理搞明白了,才会去相信它,才会去用它。
反之就会对它嗤之以鼻。因为国内无良商家实在太多,不去刨根问底,就容易交智商税。
例如朋友圈的微商,本质就是类似传销的金字塔模式,那些三无产品真的有人买?朋友圈见一个屏蔽一个。
还有之前流行的“大麦若叶”,功效被吹得那么神,还是来自于日本的正规品牌。
它还真让我困惑了很久,因为理论上,在日本、美国等地方相关法律健全,不太会有这种“神奇”的产品出现。
后来知乎上的解答让我豁然开朗,简单地说一下结论:
这东西的确有一定功效,而日本农产品不发达,所以产生了这个东西。
但是在中国不一样啊,蔬菜这么便宜,你为什么要花大价钱买这个呢?而且还这么难喝。
而且它的很多所谓的功效都是到了国内被吹出来的。
然后,偏光太阳镜也是一个传说中很神奇的东西,带上它开车就可以抗反光,让视野更清晰。嗯,一般价格至少100以上。
这不是逗我玩吗?电影院那种3D偏振眼镜5元就可以买一副了,不都是偏振吗?还能有什么区别?
于是,我就这样带着家里的3D偏振眼镜开了一年多的车…
我之前也一直以为那些偏光太阳镜是无良商家的虚假宣传,为了揭露他们的邪恶本质,我当然要去研究一下!
于是,就有了去年我在知乎提的一个问题:为什么偏光太阳镜可以抗反光?