软件技术杂谈专栏开场白:我对技术的理解

百家 作者:InfoQ 2017-07-27 10:06:24
作者|周明耀
编辑|小智
写在前面

Amazon 的 CTO Werner Vogels 博士有一句名言“All things Distributed”,我想说的是“All things are Software”。他是我的男神,我以他为榜样、目标。

今天这篇文章是系列文章的开场篇,我不讨论具体的技术问题,只是谈谈对于软件开发的理解。为什么会是杂谈?因为后续的文章,我力争每个月都有一篇发布,话题杂而力求专,很多都是针对单一技术点或者冷僻技术点的深入介绍,而这些技术点又和生活本质相结合,希望您会喜欢。

冷僻技术点

这里说的冷僻技术点并不是真的冷僻,而是会关注到的人不多,或者国内目前没有这一方面的文章。举个例子,JDK 里的 HashMap,JDK7 和 JDK8 的实现有一定差异。

JDK7 中,当 HashCode() 的返回值相等时,HashMap 会在 LinkedList 里存储对象,导致 HashMap 整体的时间复杂度在 O(1) 到 O(N) 之间波动。JDK8 更新了 HashMap 内部的实现,当复杂的 HashCode 数量超过一个临界值后,会以红黑树的形式存放对象,从而将整体的时间复杂度缩小至 O(1) 到 O(log(n)) 的范围内。

我们看一个示例。定义一个 Key 类并实现 Comparable 接口,为了比较最坏情况下的 O(n) 和 O(log(n)),重写 HashCode() 并返回一个定值。

public class Key implements Comparable{
      private int value;

      public Key(int value){
              this.value = value;
      }

      @Override
      public int compareTo(Key key){
              return this.value - key.value;
      }

      @Override
      public int hashCode(){
              return 1;//A poorly written hash function.
      }
}

主调用类:

public class Main {
      public static void main(String[] args){

          int keyNum = 10000;
          HashMap map = new HashMap<>();
          for(int i = 0;i

运行结果:

Java7 的结果:
Min Time:205
Average Time:207.8
Java8 的结果:
Min Time:1
Average Time:1.8

如果将对象数量增加到 50000,即 keyNum=50000,你会看到耗时对应增加:

Java7 的结果:
Min Time:5137
Average Time:5178.5
Java8 的结果:
Min Time:5
Average Time:6.5

如果 key 类没有实现 Comparable 接口,Java 会通过调用 tieBreakOrder(Object a,Object b) 方法来比较键的顺序。tieBreakOrder(Object a,Object b) 方法方法会先通过 getClass().getName() 比较类名大小,再用 System.identityHashCode 决定顺序。这一过程的成本相当高,我们也做了测试,采用这种方式时,JDK7 和 JDK8 基本没有性能差距。

为什么会有这样的结果差异?因为 JDK8 将 HashMap 的内部检索实现改为了红黑树方式,当 Key 类实现了 Comparable 接口时,JDK8 通过红黑树将 HashMap 最坏情况下的时间复杂度降低为 O(log(n))。目前的临界值为,当 HashMap 的容量大于 64,且重复的 HashCode 数量达到 8 的时候,将 LinkedList 转变为红黑树。如果 Key 类不可比较,反而成本会更高。

为什么写专栏

当下的软件从业者,都受过良好的计算机和软件方面的教育,特别是近几年,软件开发发展到一定成熟度之后,对于从业者的学历背景、基础知识、动手能力、学习能力越来越看重。但是现代的计算机和软件方面的教育,基本上都是从科学研究领域脱胎出来的,教育的目的也理所当然的主要是为科学研究领域服务。而随着社会的发展,软件不断地渗透到不同的业务领域,涉及普通人生活的方方面面。以科学研究为目的的软件教育,和日益深入人们生活的软件应用,产生了很大的隔阂。以致很多计算机和软件专业毕业的学生,进入企业工作后,总是感叹学校所学习的知识排不上用场,必须得重新学起,才能够达到企业的要求。

即便在公司站稳脚本,很多公司都有软件工程师,有多少软件工程师理解自己在公司的愿景?我的了解是,不多。很多工程师每隔几年,或者更短的时间,就会迷茫一次。公司有愿景,并不代表软件工程师的个人愿景清晰,希望我的这些文章能够让大家减少迷茫,树立技术愿景,把您拉回技术情怀,不要迷茫,因为那太费时间了。

软件工程师

软件工程师作为一种严肃的职业已经存在 70 多年了(二次大战结束后)。全球目前有至少数百万软件工程师,这还不包括人数众多的学生、编程爱好者(他们非常认真地编写程序,但不以此为谋生之道)。

Gace Hopper 在 1961 年写下了这些文字:“软件工程师是一个古怪的群体,他们崛起的速度很快,很快就形成了独立的职业,并且过早地感染了不愿做出改变的抗性。我曾经听说有些软件工程师因为客户不愿意修改自己的系统而斥责客户,而有时走进我的办公室,要求坚持他的想法。处于这个原因,我在办公室悬挂了一个逆时针走动的时钟。”。

软件工程师是一种有趣的工作,而大多数软件工程师都很享受工作,这样就不难理解了,为什么难以管理他们?如果有人付钱让你开心地玩,你还会愿意受制于人吗?受人管制就会减少工作中的乐趣!

从许多方面看,软件工程师之间的差异非常大,只有很了解程序设计的人才能完全理解这一点,事实上,软件工程师之间的差异主要来自个人内在因素,而不是外在属性。大多数公司的高层管理者对所有软件工程师一视同仁 ,这种看法是片面的。微软公司的 Bill Gates、Adobe 公司的 John Warnock、FaceBook 公司的 Mark Zuckerberg 都没有犯这样的错误,因为他们本质上也都是软件工程师。这也是为什么我觉得某些大型软件企业需要变革的原因,在科技界,你最好不要让非技术出身的人担任 CEO。

也不是计算机专业毕业的人才能做软件工程师。有一个同事,以前是学法律的,后来转行写代码,写出的代码比很多写的年份多得多的人还强的多。她对法律相关的思维的缜密,很好地就转移到了代码逻辑的缜密上,不学自通。这就是软件工程师,你用正常思维理解不了他们的成长路线。

软件是人类行为的模拟

软件的历史,可以说是用机器模拟人的历史的进一步发展。在计算机出现前,人们用机器来代替人进行生产,也就是所谓的机械化生产。软件的出现,也许很多人没有意识到,包括这个历史过程中的参与者,实际上是人类有意无意地在计算机上模拟自己这种原始动机的体现。人们对于时间的恐惧,导致了人们延长自身生命的努力,提升自己的生产力是其中的一种途径。而软件则让人们能够节省大量的工作时间,有更充分的时间去关注并推进自身的核心生命周期。

因此,可以认为软件的主要目的,就是把人类生活的非核心生命周期软件化、虚拟化,以提供更低的成本和更高效率的新生活,让核心生命周期的运行能够更加容易,让非核心生命周期的处理更少地占用人类的时间,变相地延长人类生命。

说到这里,如果 60 年代有社交媒体,我相信大家也会在那里惊呼:“喔!!软件来了,我们都要失业了!”,和现在对于人工智能的恐惧是一样的。

技术和业务的关系

业务和架构,是压在软件从业人员身上的两座大山。软件并不是虚无缥缈的东西,它和现实生活是紧密相关的。业务和架构都是处理人的问题。而技术人员最讨厌处理的就是人的问题,内心厌恶,却又无法逃避。因为这个排斥的心理,工作中始终想避开和人有关系的地方。因此在做技术之前,还需要做一些准备工作,用来连接现实生活,让大家知道处理人的问题并不可怕。建立了这个相关性,每个人就都可以自行思考了。

其实对于做软件的技术人员而言,技术也可以分为两部分:一部分是软件技术,另一部分是业务技术。当前软件行业所说的技术,基本上都是指软件技术和计算机相关的技术,也就是软件的访问生命周期所涉及的技术,比如服务、存储等相关技术。软件技术大部分集中在软件的访问生命周期部分。业务技术这一块,软件工程则较少涉及。这部分主要隐藏在业务逻辑中。因此在谈技术时,只谈软件技术是远远不够的。而不管是软件技术还是业务技术,均来源于对现实生活问题的解决,现实生活才是软件工程师真正的养成来源。

时间恐惧

为什么软件工程师会有时间恐惧和压力呢?其原因是他们把按时完成自己的工作当成了自己的最大利益。人对时间的压力是与生俱来的,并且对业务的不了解也会导致他们没有太大的把握。这一问题在其他行业的表现并不明显,毕竟在其他行业,软件工程师主要处理的是本行业的问题,对业务比较熟悉。软件行业则较特殊,通常是以业务的问题是否解决为判断标准,是在解决另一个行业的问题,这一点提高了对软件工程师的要求。这就要求软件工程师把完成业务的工作当成自己的最大利益,深入到业务中去。随着对业务的熟悉,对时间的恐惧才会慢慢地消失。对业务领域理解得越深入,就越知道如何去发现问题,慢慢就成为业务专家了。只有做到这一点,才能在业务领域建立自信,成为一个合格的软件工程师。

还有另一种很普遍的现象,做技术的软件工程师往往看不上业务。觉得技术更高端,而业务太平凡、太低端,并且业务人员总是给技术挖坑。而业务人员则觉得做技术的眼光高,但总是理解有偏差。技术人员往往对业务一知半解,业务问题总是解决得不圆满。但业务人员对此又无可奈何,因为自己不懂技术。做架构的人必须亲身体验业务,感受业务,才可能真正认识业务的个性,真正认识业务所面临的的问题。在理解业务个性的基础上,才能够谈共性。否则这个抽象就像是无根之木,局限于个人的主观认识,很难长大。

软件技术的发展

从冯. 诺依曼结构开始,程序逻辑开始脱离硬件,采用二进制编码。软硬件两者结合,一个可编程的大脑出现了,这也是为什么我们把计算机叫做电脑的原因。在硬件上运行的程序就是软件,用来控制硬件的行为。通过软件的编写,可以制造出各种各样具备不同能力的“人”,而说花的时间比培训一个真正的人少。

随着半导体技术的进步,硬件的成本越来越低,性能越来越高,摩尔定律:当价格不变时,集成电路上可容纳的元器件数目,约每隔 18-24 个月增加一倍,性能提升一倍。软件方面,为了简化难度,开始采用汇编语言,进一步出现了类似于人类语言的高级语言,比如 C/C++/Java 等,这使得人类可以用类似于人的语言,把人类的知识传递给计算机,训练计算机掌握某种技能。记得 20 年前我刚开始学习编程的时候,学习的是类似于 Basic 的高级语言,等我读大学时,首先接触的是 C 语言,使用 Turbo C 编写 C 语言调试不太方便,后来逐渐学习了 Java、.NET、GO,每一样都更加方便、抽象,学习起来更快。

软件危机之前,我们是没有软件工程理论支撑的,那时出现了对于行业的质疑,也为后续美国的“登月计划”带来了技术上的困难。因此,逐渐出现了瀑布式软件开发流程,并随着时间的推移,形成了 SDLC 等实践性方案,为大公司提供完整的软件开发体系流程。但是随着互联网的出现,中小型的软件公司不断涌现,我们发现瀑布式管理流程已经不符合我们的实际需求,现实世界要求更快的思维变换,因此逐渐出现了迭代开发、敏捷开发、XP,因为他们能够支撑业务对于技术输出、产品实现的需求。

编码的理解

计算机专家在问题求解时非常重视表达式简洁性的价值。Unix 的先驱者 Ken Thompson 曾经说过非常著名的一句话:“丢弃 1000 行代码的那一天是我最有成效的一天之一。”这对于任何一个需要持续支持和维护的软件项目来说,都是一个当之无愧的目标。早期的 Lisp 贡献者 Paul Graham 甚至将语言的简洁性等同为语言的能力。这种对能力的认识让我们把可以编写紧凑、简介的代码作为许多现代软件项目选择语言的首要标准。

软件技术的相通性

Java 语言有自己的垃圾回收器,为了加快 GC 的回收速度,HotSpot 的历代 GC 都有自己的不同的设计方案,Java9 会使用为默认 GC 的是 G1 GC。G1 GC 的 Region 设计方案。区间概念在软件设计、架构领域并不是一个新名词,关系型数据库、列式数据库最先使用这个概念提升数据存、取速度,软件架构设计时也广泛使用这样的分区概念加快数据交换、计算。

看看 HBase 的 RegionServer 设计方式。在 HBase 内部,所有的用户数据以及元数据的请求,在经过 Region 的定位,最终会落在 RegionServer 上,并由 RegionServer 实现数据的读写操作。RegionServer 是 HBase 集群运行在每个工作节点上的服务。它是整个 HBase 系统的关键所在,一方面它维护了 Region 的状态,提供了对于 Region 的管理和服务;另一方面,它与 Master 交互,上传 Region 的负载信息上传,参与 Master 的分布式协调管理。

1

HRegionServer 与 HMaster 以及 Client 之间采用 RPC 协议进行通信。HRegionServer 向 HMaster 定期汇报节点的负载状况,包括 RS 内存使用状态、在线状态的 Region 等信息,在该过程中 HRegionServer 扮演了 RPC 客户端的角色,而 HMaster 扮演了 RPC 服务器端的角色。HRegionServer 内置的 RpcServer 实现了数据更新、读取、删除的操作,以及 Region 涉及到 Flush、Compaction、Open、Close、Load 文件等功能性操作。

Region 是 HBase 数据存储和管理的基本单位。HBase 使用 RowKey 将表水平切割成多个 HRegion,从 HMaster 的角度,每个 HRegion 都纪录了它的 StartKey 和 EndKey(第一个 HRegion 的 StartKey 为空,最后一个 HRegion 的 EndKey 为空),由于 RowKey 是排序的,因而 Client 可以通过 HMaster 快速的定位每个 RowKey 在哪个 HRegion 中。HRegion 由 HMaster 分配到相应的 HRegionServer 中,然后由 HRegionServer 负责 HRegion 的启动和管理,和 Client 的通信,负责数据的读 (使用 HDFS)。每个 HRegionServer 可以同时管理 1000 个左右的 HRegion。

2

再来看看软件系统架构方面的分区设计。以任务调度为例,假设我们有一个中心调度服务,那么当数据量不断增多,这个中心调度服务一定会遇到性能瓶颈,因为所有的请求都会最终指向它。为了解决这个性能瓶颈,我们可以将任务调度拆分为多个服务,即这多个服务都可以处理任务调度工作,那么问题来了,每个任务调度服务处理的源数据是否需要完全一致?根据某通信大厂公布的专利发明,显示他们对于每一个任务调度服务有数据来源区分的操作,即按照任务调度数量对源数据进行划分,比如 3 个任务调度服务,那么源数据按照行号对 3 取余的方式划分,如果运行了一段时间之后,任务调度服务出现了数量上的增减,那么这个取余划分需要重新进行,要按照那个时候的任务调度数量重新划分区间。

这里举这三个貌似不同领域的示例,为的是说明三者的设计思路其实是相同的,或者说是源于同一种算法。

我的理解

我自己从事软件开发行业已经 13 年,真正接触编程是 16 岁,这么算下来就超过 20 年的编程经验。在这 20 年之间里,软件开发方式发生了巨大的变化。瞬息万变是这个时代的特征,固守经验、一成不变是错误的行为。我们看一看云计算的出现就知道了,云计算定义了一种按需索取、实时供应的特性,它本身就是敏捷的,它为软件工程注入了新的活力,如果软件开发人员可以快速、自由地获取开发过程中所需的各种资源,那么软件开发必将迎来一次飞跃式的发展。这也就是为什么公有云服务、Serverless 计算会开始流行的原因。

写在最后

公交车有起点有终点,对我来说,为人民服务只有起点没有终点,对他 (马云) 来讲,美国上市不是终点,也只是一个新的起点。

这句话是杭州公交公司的孔胜东师傅说的。从 1986 年开始,他在中山北路和百井坊巷四岔路口设立义务修车点,无论酷暑严寒,刮风下雨,每个周六晚上 7 点到 10 点,他都在那里。因此孔师傅成了全国劳动模范、浙江道德模范。其实他还有另外的身份,17 大代表、马云同学。无论哪个标签,都没能阻止他继续在路边帮人免费修车,这是出于内心的热爱,无论您怎么看,我觉得他的内心是为人民服务,所以才能坚持。这也是我开这个专题的目的,因为热爱,所以选择。

下一篇写什么,我想针对 Apache Cassandra 内部的一些实现算法写一篇文章,为什么是 Cassandra,因为它在 3.X 版本发布后做了较大程度的改进,而国内缺少这一方面的资料,导致国内技术人员对它的映像不好,我开这个杂谈专栏,就是为了找到灯下黑。

作者介绍

周明耀,2004 年毕业于浙江大学,工学硕士。13 年软件研发经验,近 10 年技术团队管理经验,4 年分布式计算、大数据技术经验。著有《大话 Java 性能优化》、《深入理解 JVM&G1 GC》、《技术领导力 - 如何带领一支软件研发团队》,InfoQ 专栏作家、IBM 开发者论坛专栏作家,个人提交发明专利 16 项。个人微信号 michael_tec,个人公众号“麦克叔叔每晚 10 点说”。

关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接