究竟什么是一个程序?
我的理解:任何一个程序,本质上都是数据处理程序
为了实现数据处理,一个程序需要实现三个功能通信、计算、存储。
通信
从通信的对象来看有
线程间通信:openGauss采用信号量
进程间通信:共享内存
不同机器之间通信
- 前后端交互走HTTP协议
- 微服务调用使用RPC,底层还是socket
- JDBC连接MySQL时走MySQL协议,连接Hive时走Hive协议,其实底层还是socket
从通信的方式来看有
- 直接通信
所有程序之间的通信都有两种方法,一种是pull(数据在response中)一种是push(数据在body中)。
例如在raft中如何确定leader的状态?
push:leader定期向follow发送heartBeat,refresh其electionElapsed(相当于leader向follow传数据)
pull:follow定期向leader发送heartBeat,check其有没有在线(相当于follow从leader拉数据)
- 间接通信
最常见的应该是生产者消费者模型
生产者生产数据打到Broker(Broker中维护了消息队列Queue)
消费者从Broker拉数据
计算
- 并行计算问题
- 高性能计算问题
存储
从生命周期来看程序
显然,从OS被载入内存,到系统shutdown,OS以及被OS所包含的所有软件都有initialize -> running -> shutdown这三个状态。
因此,如果按这个道理来看
从OS -> 程序 -> 进程 -> 线程 -> 组件 -> 函数 -> 变量
各个维度来看,任何一样东西都会有生命周期。
因此,在学习一个陌生知识点的时候,需要不断的问自己这三个问题:
这个组件什么时候初始化,如何初始化,具体哪些变量或者数据结构初始化?
这个组件运行的时候要做什么,上游来的请求是什么样的,偷传给下游的数据是什么样的?
这个组件什么时候被销毁?具体销毁的代码是怎么写的呢?
OO的本质
new对象的本质是什么?申请一块内存,填充对应的数据
什么是对象
一个对象的本质是什么?是存储 + 计算,因此我们在描述某个对象的功能时,一定可以描述为:xx对象记录了xx信息,提供了xxx的方法。由此也可以反推出,任何一个对象,如果没有理解清楚其记录的信息以及提供的方法,那么对这个对象的理解就是不透彻的。
属性
这里有一个很有意思的问题:属性重要还是方法重要?
对于不同的组件来说不一样
- 有的对象方法比属性重要
为什么呢?因为任何需求都可以被抽象成方法,但是不能被抽象为属性。
例如raft为什么需要记录任期term?是因为在选举的过程中,各个节点的状态不是同步的,需要一个任期来判断节点是否滞后,term属性是为了选举这一方法服务的。
也就是说,属性是为了方法服务,方法不需要的属性没必要存在。
- 有的对象属性比方法重要
比如etcd中的RaftLog,最重要的其实是Entires[]、commitedIndex、appliedIndex这几个变量。而其方法不论有多少if else,本质上都是清一色的get/set方法
函数/方法
函数的本质是什么?是计算,是数据的流转。而数据的呈现形式是变量。因此所有的函数都可以看作是对变量的C(new)、R(data = vector[1])和U(num++),D(delete运算,vector中数据的erase)
再进一步,一个函数中的变量有哪些呢?以OO为例
- 对象的成员属性
- 函数的传参
- 全局变量
此外还有中间变量
因此,在写函数的时候,一是需要先弄清楚这四者,否则不要下手。二是,写完函数之后要想一想,这三者中有没有拉下的create,update(这里不需要考虑Read)
对象的组合
一个用OO写出来的程序,一定是若干对象的继承/组合。每一个对象会有属性,方法,这里的方法可以理解为为上层对象准备的API。若干个对象组合成一个模块(注意:继承不能形成模块,继承只能形成单一的个体),每个模块向外暴露一些API。
因此我们在谈说学写代码不能只能只会调包,要懂程序的原理。而从这个角度看来,程序的原来也无非是一些API与一些对象的属性以及一些重要的实现而已。
所以,我认为API才是整个程序设计的灵魂,明白了各个模块之间的API也就能够弄懂整个程序的架构设计。
具体的落地方法,可以使用CLION/VScode左侧边栏的缩略图,可以从整体看到一个对象
如何拆解程序
显然,对于一个庞大的系统来说,要是直接上来扎进源码的实现里,除了浪费时间以及把自己搞得晕头转向以外别无任何其他作用。
因此对于大型的程序,必须要对其进行拆解。
具体落地的措施有
找资料,找到程序的架构图,它可能是在程序的官网(例如tidb),也有可能在某篇paper中(snowflake),还有可能根本没有官方资料,只有民间大佬所整理的图片(ClickHouse)。根据架构图,理解程序的整体架构,以及有哪些大的模块。
针对某个模块,看源代码时,先看左侧的API structure结构(vscode中的outline,idea中的structure),理解这个模块究竟做了哪些工作,有哪些功能,这个结构体有哪些属性。由此便可以简单了解这个模块。
对于raft这种看了接口实现也无法理解其背后原理的程序,先自己yy一个暴力的实现,然后再去看std的实现。实际上这种应用20%的代码就可以实现基本功能,50%在解决corner case, 30%在优化(包括功能扩展与时空优化)