我打算先写跟技术相关的内容,然后再详细说换工作的想法。
我在生活篇里面提到了以下几点:
- 阅读 PHP 函数注册、函数调用、垃圾回收、PHP 对象相关的源码,加深了对脚本语言的理解。
- 阅读 JAVA、PHP 5、PHP 7、Redis、Go 的 HashMap 的源码,比较及整理到博客,并在分享会做了分享。
- 阅读一部分 Linux 内核的 Socket 相关源码,以及 Golang 的 HTTP Server 跟 TCP 相关的源码。
- 阅读 InfluxDB 查询和写入相关的源码,并用文字描述出来。
- 阅读跟内存分配相关的文章以及源码,主要是 jemalloc 、 TCMalloc 和 ptmalloc。
- 整理和翻译一小部分 HTTP 1.1 的 RFC 内容。
- 还有为了面试去深入学习的一些内容。
这些大多数都是只了解了一部分,没有完全深入去理解。凡是我没有输出到博客的技术内容,我都一律认为自己没有完全掌握。就算是输出到博客,也很有可能只是有了片面的理解。
2020 年总共创建了 63 篇博客。大多数是技术博客,但通常只有几句话。另外其中有一些是我自己捣鼓服务器时的记录。仅发布了少数几篇博客。不过我会把一些只有骨架的半成品发表到 Github 博客上,只是国内大多数时候没法访问。其他的都躺在草稿箱。2021 年的任务是把这些博客补充完整然后发出来。
Github 博客:
https://schaepher.github.io
技术栈的变更
这次我对自己非常狠,新工作几乎把我技术栈换掉了。
主力编程语言从 PHP 换到 Golang
刚毕业的时候还想着写 JAVA,后来第一份工作用的是 PHP。虽然现在换成 Golang 了,但我并不排斥 PHP。
用 PHP 搭配上 Laravel 后,写业务非常舒服。Laravel 借鉴了 Rails 的理念。它很全面,可以从它那里学到很多设计模式和最佳实践,现在偶尔还会去参考 Laravel 的实现。它也很自由,可以用第三方库替换框架自带的库。
The Rails Doctrine - Rails 信条
https://ruby-china.org/wiki/the-rails-doctrine
PHP 和 Laravel 是在 WEB 业务方向特化的语言,这是它们的优点。而再加上 Swoole ,PHP 就可以用来编写高性能高并发的服务了。有人说 Laravel 的学习成本高,但我没有什么感觉,反倒是感觉用起来很舒服。
但是我仍然选择了 Golang。原因有很多。
主要原因:
PHP 能做的东西有限
我见识比较少,想不起来有什么重要的组件是用 PHP 写的。我特地到 Github 的 Trending 看了一圈,基本都是跟业务系统或者基础组件管理页面的项目。没有数据库、消息队列或者中间件。
毕竟 PHP 就不是用来做这些的,它的定位是写 WEB 应用,因此它在其他方向上的上限就比较低。
我认为写业务是非常普遍但很重要的事情,毕竟没有那么多的基础组件可以写,大多数人都是在写业务代码。
但我更希望自己能够参与到更加基础的组件中去,这样我才能更多地借现实场景的需要去接触和学习核心的技术,更多地满足自己对底层技术的好奇心。
我曾经想过在工作之余去深入学习另一门语言,并参与到开源的基础组件的发展中。方法不是没有,而是我目前还没突破这方面的局限性。而且这也只是其中一个原因。大公司的 PHP 岗位有限
很现实的一点。
PHP 和 Laravel 由于其自身的局限,很少被用于大型项目中,因此很难成为大多数中大公司的主力编程语言。
比如腾讯虽然有些项目在用 PHP,但也只是因为是老项目,需要维护。新项目应该不会考虑 PHP。
由于以前 Laravel 在国内的接受度不是很高,所以老项目大多不使用 Laravel。甚至有的项目不使用框架,就算用了框架也可能是多年前的版本。
光是想想就能感受到维护这种项目的痛苦。PHP 岗位对前端知识有要求,也会被要求去做前端的活
我只想暂时当个纯粹的后端。平常我自己想要做点什么的时候,也不需要有什么界面,用命令行就够了。
前端也有很多理念和知识可以深挖学习,前后端不一定孰优孰劣。问题在于一旦前后端都涉及,对我这种普通人来说就是灾难。
所以我只学习必要的前端知识,把重心放在后端。
不太重要的原因:
部署
PHP 部署起来相对比较复杂,需要安装 PHP 解释器。如果是 WEB 服务,还需要 PHP-FPM。
如果是用在工作上,这其实不是什么问题。但是我还想让自己写的程序在更多平台上跑,有些系统没法装。例如路由器和 Wifi 模块。
而 Golang 直接编译成一个可执行文件丢上去就行了。像路由器这种 ROM 小的设备,还可以压缩二进制文件减小可执行文件的体积。而像 Wifi 模块这种更小存储的设备,还可以使用像 Tiny Go 这种简化版。并发
有些任务需要并发执行。如果用 PHP,那么得开多进程,或者转成多个 HTTP 请求,或者使用 Swoole,比较麻烦。
其他的原因以后想起来了再补充。
其实没有运行时和垃圾回收的 Rust 也是一个很好的选择,但显然 Go 更适合这个阶段的我。我会在空闲时间去实践 Rust,但目前不会将其作为主力工作语言。
另外如果我现在再去学习 C++ ,感受到的难度肯定和以前不一样。然而我觉得没有多大必要,如果能做到看懂开源项目(例如 MySQL)的源码,那我就很满意了。
数据库加入 MongoDB 和 InfluxDB
虽然我还没完全掌握 MySQL 的底层原理,但对 InnoDB 的部分原理还是有一定的了解。但是对于其他的存储服务的了解就很浅,比如列式存储的 InfluxDB,和文档型的 MongoDB。
大多数场景使用 MySQL 是最合适的。在一些特殊的场景中,才会需要用到其他数据库。
例如我们有个项目主要是存储服务器的统计数据,统计数据以分钟为粒度存储。我们使用 InfluxDB 存储服务器维度的数据,使用 MongoDB 存储业务维度的数据。最开始业务维度的数据存储在 MySQL 上,后来业务量上来了,延迟变高才切换到 MongoDB。
如果要用 MySQL 存储一天中每分钟的数据,有多种方式。
- 每分钟一行,但数据量大
- 每一行存储一天所有时间点。
但 InnoDB 对列数有限制,就算在最新的 MySQL 8.0 里面也最多只能是 1017 列,而一天有 1440 分钟。https://dev.mysql.com/doc/refman/8.0/en/innodb-limits.html MyISAM 可以支持超过 1440 列,但它是表锁,我们碰到了数据延迟高的问题。同时它还有只能存储其中一种数据的问题。
- 每行存储半天的时间点。这个可以。但仍然没有解决只能存储一种数据的问题。
一种是每行加个字段表示数据类型,另一种同时也能解决上面的问题。 - 使用 json 存储。这样一行就能存储一整天的所有类型的数据。
会不会引入其他问题?这个我暂时没去考虑。
在我入职的时候,同时存在两个版本。MySQL 的老版本和 MongoDB 的新版本。不过新版本有几个 bug 导致数据没有对上。我最初的主要工作之一就是帮忙修复 bug,把数据对齐。另外一个是实现新的数据上报协议,使上报的数据字段可以根据需要添加,同时兼容旧协议。
不过这个版本还存在一个严重的隐患。它让服务在内存中合并一段时间的数据再每隔一定时间刷到数据库。如果服务发生故障,或者需要升级程序,重启的那段时间的数据会丢失。
为了解决这个问题,我重写了这个服务。让上传的数据先存储到 Kafka ,再从 Kafka 消费。这样就算升级也不会影响到数据,而且可以启动多个消费者,提高可用性和处理效率。
这个版本还没有完全上线。一个是因为在我升级上报协议之后,直到现在都没有必须升级的功能点。另一个原因是有另外一个项目需要做。所以准备到春节后再升级。
一开始我不知道这个服务已经有提供接口给其他一个服务使用。因为这个服务的数据没校准,基本都是使用老系统的数据。结果在修复 bug 的过程中,某次升级导致数据往下掉。触发了那个服务的告警,出现了个问题。影响到了我的绩效。血的教训。
通过这个项目,让我熟悉了 MongoDB 的使用,后续就靠我自己去研究它的实现原理了。使用起来的感受是 MongoDB 的查询非常强大。我用得比较多的是 Aggregation。甚至可以在里面用 javascript 处理数据。不过它占用的内存实在是太多了
由于 InfluxDB 是用 Go 写的,源码看起来就方便多了。它的开源版本已经不再提供集群模式,我就想自己实现个集群功能的子集,只用于查询数据。我下载了包含集群功能的版本的源码,看了功能的实现,但是在最底层的数据通信这块停下了。看起来新版本和老版本的通信方式有所变化,后续找机会再试试看。后来另外看了它的数据存储过程和单机查询过程。目前只有简略地列出关键点,后面找个时间补充完整,然后发出来。
数据结构
第一次使用小顶堆。这个是因为项目中有个功能需要算出特定业务一个月的 95 峰值带宽。具体可以去搜索引擎搜索 95 峰值(或者 ninety-fifth percentail)这个词,用于跟机房计费用的。每五分钟一个点,从小到大排序,然后从小到大数到第 95% 那个点作为数据。
如果一次性查一个月的数据进行合并,数据量太大,又占内存又慢。所以就分开查,每次查一天的数据。然后把数据丢到小顶堆里面,给小顶堆设置为从大到小 95 点所需的个数。这样每次都保留极少数的数据。
这里有个问题:为什么不先把数据合并起来?这样就只需要对 8640 个点排序了。原因有好几个,一个是要查上千种情况下的数据,预先合并的话,数据量太大。
发表的博客及花
C 语言初步实现面向对象的三个基本特征
发表在博客园:
https://www.cnblogs.com/schaepher/p/12498512.html
我刚毕业那会儿,有个面试要求我用 C 语言实现面向对象,我没答好。过了将近三年才想到把它整理出来。
这次是由于看了 PHP 的源码,想到了以前遗留了这么个问题,就尝试写了个简化版的面向对象。
查找资料的过程中发现没什么人发过相关的博客。有的也是只有部分代码,缺少完整的例子,提高了理解难度。
我这个虽然是简单版,但已经提供了应有的思路。如果要实现更多,需要添加 HashMap 的内容。我在犹豫是否花时间把它做成一个系列。
其实继续深入下去就可以直接用 PHP 或者 Python 的实现了。直接看它们的源码也行,只是它们一开始就是完整的实现,内容比较多,容易乱。
我现在又去读了一遍,发现很多地方不通顺,增加了理解难度。后面再找时间改一改,反正我现在已经找到写博客的节奏,不怕鸽。