你好呀,我是歪歪。
我最近其实在思考一个问题:
对于程序员来说,怎么才算是在写有“技术含量”的代码?
为什么会想起思考这个看起来就很厉(装)害(逼)的问题呢?
因为这就是知乎上的一个问题:
https://www.zhihu.com/questio…
第一次看到这个问题的时候,我很快的就划过去了,齐全就没有关注这个问题。然而就是看了那么一眼,这个问题就偶然不经意间在脑海中浮现进去。
而后隔了一段时间,中午刷知乎的时候这个问题又冒出来了。
好巧不巧,也是那天中午,我看到了这样的一个面试题:
看到这个面试题的第一眼,我就想起了 Dubbo 服务中的一个预热性能。
在联合知乎这个问题,我过后就感觉:Dubbo 服务的预热源码在我看来就是一个“有技术含量”的代码呀。
这一块性能编码的确一点也不简单,次要是能体现出编码的人对于 JVM 和 RPC 方面的“内功”,可能意识到,因为 JVM 的编译特点,再加上 Dubbo 在架构中充当着 RPC 框架的角色,所以为了服务最大水平上的稳固,能够在编码的层面做肯定的服务预热。
然而写完相干答复之后,从评论区来看,基本上是清一色的吐槽,说我举得这个例子和问题相悖。
比方我截取点赞最高的两个评论:
看完这些吐槽之后,我感觉这些吐槽是有情理的,我的例子举得的确不好,十分的全面。
为了更好的引出这个话题,我先搬运并裁减一下我过后的答复吧。
顺便也算是答复一下刚刚说的那个面试题。
服务预热
上面这个办法,只有两行,然而这就是 Dubbo 服务预热性能的外围代码:
org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance#calculateWarmupWeight
看一下这个办法在框架外面调用的中央:
当咱们不指定参数的状况下,入参 warmup 和 weight 是有默认值的:
也就是在用默认参数的状况下,下面的办法能够简化为这样:
static int calculateWarmupWeight(int uptime) {//int ww = (int) (uptime / ((float) 10 * 60 * 1000 / 100));
int ww = (int) (uptime / 6000);
return ww < 1 ? 1 : (Math.min(ww, 100));
}
它的入参 uptime 代表服务启动工夫,单位是毫秒。返回参数代表以后服务的权重。
基于这个办法,我先给你搞个图。
上面这个图,x 轴是启动工夫,单位是秒,y 轴是对应的权重:
从图上能够看出,从服务启动开始,每隔 6 秒权重就会加一,直到 600 秒,即 10 分钟之后,权重变为 100。
比方当 uptime 为 60 秒时,该办法的返回值为 10。
当 uptime 为 66 秒时,该办法的返回值为 11。
当 uptime 为 120 秒时,该办法的返回值为 20。
以此类推 …
600 秒,也就是十分钟以及超过十分钟之后,权重均为 100,代表预热实现。
那么这个权重是干啥用的呢?
这个就得联合着负载平衡来说了。
Dubbo 提供了如下的五种负载平衡策略:
- Random LoadBalance :「加权随机」策略
- RoundRobin LoadBalance:「加权轮询」策略
- LeastActive LoadBalance:「起码沉闷调用数」策略
- ShortestResponse LoadBalance:「最短响应工夫」策略
- ConsistentHash LoadBalance:「一致性 Hash」策略
除了一致性哈希策略外,其余的四个策略都得用到权重这个参数:
权重,就是用来决定这次申请发送给哪个服务的一个关键因素。
我给你画个示意图:
A、B、C 三台服务,A,B 的权重都是 100,C 服务刚刚启动。
作为一个刚刚启动的服务,是不适宜承受突发流量的,认为运行在服务器上的代码还没有通过充沛的编译,主链接上的代码可能还没有进入编译器的 C2 阶段。
所以按理来说 C 服务须要一个服务预热的过程,也就是刚刚启动的前 10 分钟,应该有逐渐承受越来越多的申请这样的一个过程。
比方最简略的加权随机轮询的负载平衡策略中,要害代码是这样的:
org.apache.dubbo.rpc.cluster.loadbalance.RandomLoadBalance#doSelect
看不明确没关系,我再给你画个图。
在 C 服务启动的第 1 分钟,它的权重是 10:
所以代码中的 totalWeight=210,因而上面这行代码就是随机生成 210 之内的一个数字:
int offset = ThreadLocalRandom.current().nextInt(totalWeight);
在示意图中有三个服务器,所以 for 循环中的 lenght=3。
weights[] 这个数组是个啥玩意呢?
看一眼代码:
每次循环的时候把每个服务器的权重汇总起来,放到 weights[] 外面。
在下面的例子中也就是这样的:
- weights[0]= 100(A 服务器的权重)
- weights[1]= 100(A 服务器的权重)+100(B 服务器的权重)=200
- weights[2]= 100(A 服务器的权重)+100(B 服务器的权重)+10(C 服务器的权重)=210
当随机数 offset 在 0-100 之间,A 服务器解决本次申请。在 100-200 之间 B 服务器解决本次申请。在 200-210 之间 C 服务器解决本次申请:
也就是说:C 服务器有肯定的概率被选上,来解决这一次申请,然而概率不大。
怎么概率能力大呢?
权重要大。
权重怎么才大呢?
启动工夫长了,权重也随之增大了。
比方服务启动 8 分钟之后,就变成这样了,C 服务器被选中的概率就大了很多:
最初到 10 分钟之后,三台服务器的权重统一,承当的流量也就简直统一了。
C 服务器承当的申请随着服务启动工夫越来越多,直到 10 分钟后达到一个峰值,这就算是经验了一个预热的过程。
后面介绍的就是一个预热的伎俩,而相似于这样的预热思维你在其余的一些网关类的开源我的项目中也能找到相似的源码。
然而预热不只是有这样的一个实现形式。
比方阿里基于 OpenJDK 搞了一个 Alibaba Dragonwell,其实也就是一个 JDK。
https://github.com/alibaba/dr…
其中之一的个性就是预热:
除了预热这个点之外,我还在知乎的答复中提到了起码沉闷数负载平衡策略的实现 LeastActiveLoadBalance.java:
从初始化提交之后,一共就没批改过几次。
你也能够比照一下,初始版本和以后最新的版本,外围算法、外围逻辑根本没有发生变化:
除了这个策略之外,其余的几个策略也是差不多相似的“稳固”。
从评论说起
我在知乎答复这个问题的时候,没有下面这一大节写的那么多,然而核心内容大略就是下面这些。
在答复说提到预热,我是想表白看似不起眼的两行代码,背地还是蕴含了十分多的深层次的起因,我感觉这是有“技术含量”的。
而提到负载平衡策略的实现,多年来都没有怎么变动,我是想表白这些重要的、底层的、根底的代码,写好之后,长年没动,阐明最开始写进去的代码是十分稳固的。能写出这样稳固的代码,我感觉这也是有“技术含量”的。
接着带你看看评论区:
评论简直是清一色的不认可这个答复。然而我后面说了,在答复这个问题的时候,的确感觉我的答复是比拟贴近主题的。
然而看了评论之后我想明确了,为什么这是一个不好的答案,正如评论区说的:
例子举得不行,只不过是因为要解决的问题始终没有产生扭转,所以解决方案也就绝对稳固。
首先这样的代码原本就和绝大部分程序员理论工作中写的代码差距过大,框架的源码值得学习,然而在理论开发中的借鉴意义不大。
而且评论区也提到了,绝大多数程序员基本就没有机会去写这样的比拟考验“技术能力”的代码。
这也的确是事实,少部分中间件的开发和绝大部分业务逻辑的开发,是两个思维模式齐全不一样的程序员群体。
而后我看了一下这个话题下的高赞答复:
其实高赞答复就这一句话:
一个优良的程序员,在接到一个要编写“覆灭地球”的工作的时候,他不会简略的写一个 destroyEarth() 的办法;而是会写一个 destroyPlanet() 的办法,将 earth 作为一个参数传进去。
这才是比拟贴近咱们理论工作的一个例子。
就着这个例子,我换个惯例一点的需要来说,比方让你接入一个微信领取的需要:
你可能会这样去定义一个类:
public class WechatPayService {public void wechatPayment(){// 微信领取相干逻辑}
}
当要应用的时候,就把 WechatPayService 注入到须要应用的中央,没有任何故障。
然而随着而来的一个需要是让你接入支付宝领取。
你当然是自然而然的搞了一个相似的类:
public class AliPayService {public void aliPayment(){// 支付宝领取相干逻辑}
}
然而你写着写着发现:诶,怎么回事,感觉支付宝的这套逻辑和微信的有很多相似之处啊,开发的关键步骤感觉都截然不同?
于是你定义了一个接口,应用策略模式来专门干“领取”相干需要:
public interface IPayService {
/**
* 领取形象接口
*/
public void pay();}
在我看来,这是一个十分惯例的开发计划,我甚至在拿到“微信领取”这个需要的时候,我就驾轻就熟的晓得应该应用策略模式来做这个需要,为了不便当前的开发。
然而,我这个“驾轻就熟”也是有一个相熟的过程的,我也不是一开始,一入行,一工作就晓得应该这样去写的。
我是在工作之后,看了大量的理论我的项目外面的代码,看到我的项目在用,感觉这样很实用,我的项目构造也很清晰,才在其它的相似的需要中,刻意的模拟学习、了解、使用、打磨,缓缓的融入到了本人的编码习惯中去,因为太过相熟,我慢慢的认为这是没有技术含量的货色。
直到起初,有一次我带着一个实习生做一个我的项目,我的项目中有一个排行榜的性能,排行榜须要反对各个维度,前端申请的时候会通知我以后是须要展现哪个排行榜。
在需要剖析、零碎设计以及代码落地阶段我都自然而然的想到了后面说到的策略模式。
起初实习的同学看到了这一段逻辑,给我说:这个需要的实现形式真好。如果让我来写,我相对想不出这样的落地计划。
然而我感觉这就是个惯例解决方案而已。
我举这个例子是想表白的意思就是对于“技术含量”这个货色,每个人,每个阶段的了解是截然不同的。
与我而言,站在我当初正在写这篇文章的工夫节点上,我感觉有技术含量的代码,就是他人看到后违心应用,违心模拟,违心通知前面来的人:这个货色真不错,你也能够用一用。
它能够小到一个我的项目外面的只有寥寥几行的办法类,也能够大到一套行业内问题的残缺的技术解决方案。
除了这个例子外,我还想举我刚刚加入工作不久,遇到过的另外一个例子。
需要说来也很简略,就是针对一个表的增删改查操作,也就是咱们经常吐槽的没有技术含量的 crud。
然而,我过后看到他人提交的代码时我都震惊了。
比方一个新增操作,所有的逻辑都在一个 controller 外面,没有所谓的 service 层、dao 层,一把梭间接把 mapper 注入到了 controller 外面,在一个办法外面从数据校验到数据库交互全部包圆了。
性能能用吗?
能用。
然而这样代码是有“技术含量”的代码吗?
我感觉能够说是毫无技术含量了,用当初的流行语来说,我甚至感觉这是程序员在“摆烂”。
我要基于对于这一段代码持续开发新性能,我能做什么呢?
我无能为力,原来的代码切实不想去动。
我只能保障在这堆“屎山”上,我新写进去的代码是洁净的、清晰的,不持续往里面扔垃圾。
起初我读了一本书,叫做《代码整洁之道》,外面有一个规定叫做“童子军军规”。
军规中有一句话是这样的:让营地比你来时更洁净。
类比到代码上其实就是一件很小的事件,比方只是改好一个变量名、拆分一个有点过长的函数、打消一点点反复代码,清理一个嵌套 if 语句 …
这是让我的项目代码随着工夫流逝而越变越好的最简略的做法,继续改良也是专业性的外在组成部分。
我感觉我对于这一点“规定”落实的还是挺好的,看到一些不是我写的,然而我感觉能够有更好的写法时,而且改变起来非常简单,不影响外围性能的时候,我会被动去改一下。
我能保障的是,这段代码在通过我之后,我没有让它更加凌乱。
把一段凌乱的代码,拆分的清晰起来,再起初的人违心依照你的构造持续往下写,或者持续改良。
你说这是在写“有技术含量”的代码吗?
我感觉不是。
然而,我感觉这应该是在谋求写“有技术含量”的代码之前,必须要具备的一个能力。而且是比写出“有技术含量”的代码更加重要的一个根底能力。
延长
以上就是我集体的一点观点,然而我还想延长出一些别的货色。
比方在写文章之前,我也在其余网站上提出了这个问题:
https://segmentfault.com/q/10…
大家见仁见智,从各个角度给出了不同的答复。
这也再次印证了后面我说的观点:
对于“技术含量”这个货色,每个人,每个阶段的了解是截然不同的。
我把大家给我的回复贴过来,心愿能对你也有帮忙:
再比方我最近在知乎上看到了这样的一个视频:
https://www.zhihu.com/zvideo/…
外面的主人公黄玄,说了这样的一段话:
这曾经是另外一个维度的程序员,对于“什么是有技术含量的代码”的另外一个维度的解答了。
我远远达不到这个高度,然而我喜爱这个答复:
一直的传承上来,成为下一代软件,或者说下一代人类文明的基石。我感觉可能去参加这样的货色,对我来说,可能是程序员的一种浪漫。
所以你呢,对于这个问题,你会给出什么样的答案呢?