MoreRSS

site iconShaun修改

https://cniter.github.io/about/
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Shaun的 RSS 预览

HTTP 超时浅见

2024-05-12 10:26:32

前言

  最近业务调用方反馈接收到服务连接中断的错误(python requests 请求抛出异常 raise ConnectionError(err, request=request) \n ConnectionError: ('Connection aborted.', BadStatusLine("''",))),但从 golang 服务日志中看,服务应该是正常处理完成并返回了,且抛出异常的时间也基本和服务返回数据的时间一致,即表明在服务响应返回数据的那一刻,请求方同时抛出异常。

  这个问题很奇怪,起初拿到一个 case 还无法稳定复现,最初怀疑是网络抖动问题,但后续一直会偶发性出现,直到拿到了一个能稳定复现的 case,深入跟踪排查后才发现与网络问题无关,是服务端框架应用设置不合理的问题。

问题篇

  从网上搜索 python ConnectionError: ('Connection aborted.'),错误种类非常多,有网络问题,服务端问题(关闭连接,拒绝服务,响应错误等),客户端关闭连接,超时设置不合理,请求参数/协议错误等等,但若带上 BadStatusLine("''",) ,错误就相对比较明确了(BadStatusLine Error in using Python, RequestsPython Requests getting ('Connection aborted.', BadStatusLine("''",)) error),主要是由于收到了一个空响应(header/body),空响应可以明确是服务端返回的问题,一般可能有以下几个原因:1. 服务端反爬;2. 服务端超时(比如 nginx 默认 60s 超时);3. 网络错误。

  由于是内部服务,所以反爬策略是没有的,而反馈的 case 都带有明显的特征(请求数据量大,处理耗时长),没有网络抖动那种随机性,所以应该也不是网络问题,剩下的只能是超时问题,由于业务方在前置策略上已经识别该 case 数据量大,所以不经过 nginx 网关,直连服务请求,所以也不会有 nginx 超时问题,只能是服务端自己超时。于是直接在代码中查找 timeout 关键字,发现在服务启动时设置了 ReadTimeout 和 WriteTimeout,进一步深挖之后,才对 go 服务的超时有了浅显的认识。

超时篇

参考资料:1. 你真的了解 timeout 吗?,2. i/o timeout , 希望你不要踩到这个net/http包的坑,3. net/http完全超时手册

  由于 HTTP 协议规范并未提及超时标准,而为保证服务稳定性,一般的 HTTP 服务请求都会设置超时时间,各 HTTP 服务端/客户端对于超时的理解大同小异,而这次的问题又起源与 go 服务,所以以 go 为例,分析一下超时。

客户端超时

http.Client.Timeout

  客户端超时,即 GET/POST 请求超时,这个很好理解,就是客户端发送请求到客户端接收到服务器返回数据的时间,算是开发的一般性常识,控制参数一般也特别简单,就是一个 timeout,当然 go 服务客户端支持设置更精细化的超时时间,一般也没啥必要。当客户端感知到超时时,会正常发起 TCP 断开连接的“四次挥手”过程。

服务端超时

http.Server Timeouts

  服务端超时,这才是引发问题的根本原因,go 服务端的超时,主要有两个参数,ReadTimeout 和 WriteTimeout,从上图可以看出,ReadTimeout 主要是设置服务端接收请求到读取客户端请求数据的时间(读请求的时间),WriteTimeout 是服务端处理请求数据以及返回数据的时间(写响应的时间)。GoFrame 框架的 ReadTimeout 默认值是 60s,在请求数据正常的情况下 ReadTimeout 也不可能超时,这次的问题主要出在 WriteTimeout,GoFrame 的默认值是 0s,代表不控制超时,但之前的开发者也同样设置为了 60s,导致服务端在处理大量数据时,发生了超时现象。

  更深挖之后,才发现 WriteTimeout 的诡异之处,当 WriteTimeout 发生之后,服务端不会即时返回超时消息,而是需要等服务端真正处理完之后,返回数据时,才会返回一个空数据,即使服务端正常写入返回数据,但都会强制为空数据返回,导致请求客户端报错。这种表现,看起来就像是 WriteTimeout 不仅没有起到应有的作用,在错误设置的情况下,还会起到反作用,使服务响应错误。WriteTimeout 无法即时生效的问题,也同样有其他人反馈了:1. Diving into Go's HTTP server timeouts;2. net/http: Request context is not canceled when Server.WriteTimeout is reached。可能是网上反馈的人多了,go 官方推出了一个 TimeoutHandler,通过这个设置服务端超时,即可即时返回超时消息。仿照官方的 TimeoutHandler ,即可在 GoFrame 框架中也实现自己的超时中间件。

  至于 WriteTimeout 为啥不起作用,个人猜测主要原因在于 go 服务每接收到一个请求,都是另开一个协程进行处理,而 goroutine 无法被强制 kill,只能自己退出,通常是要等到 goroutine 正常处理完之后才能返回数据,WriteTimeout 只是先强制写一个空数据占位,返回还是得等 goroutine 正常处理完。

  所以正常的 go 服务,在使用类似于 TimeoutHandler 中间件的时候,也最好让 goroutine 尽可能快的退出,一种简单的方法是:1. 设置请求的 context 为 context.WithTimeout;2. 分步处理数据,每一步开始前都先检查请求传入的 context 是否已经超时;3. 若已经超时,则直接 return,不进行下一步处理,快速退出 goroutine。

后记

  这次问题排查,碰到的最大障碍在于,前几次反馈的 case 难以复现,客户端请求报错和服务器返回的时间一致也不会让人往超时的角度去想,在拿到一个能稳定复现的 case 之后,才死马当活马医,先调一下超时参数试试。

  关于 go 服务超时的文章,其实之前也看过,但没碰到具体问题,名词也就仅仅只是名词,很难理解背后的含义和其中的坑点,实践才能出真知 ╮(~▽~)╭。

附录

长连接超时

  关于超时问题,也曾看到过有人碰到一个长链接服务的问题,现象是这样的:后端服务宕机之后,客户端可能需要很久才会感知到,原因在于 tcp 的超时重传机制,在 linux 中,默认会重传 tcp_retries2=15 次(即 16 次才会断开连接),而 TCP 最大超时时间为 TCP_RTO_MAX=2min,最小超时时间为 TCP_RTO_MIN=200ms。即在 linux 中,一个典型的 TCP 超时重传表现为:

重传次数 发送时间 超时时间
-1(原始数据发送) 0s 0.2s
0 (第 0 次重传) 0.2s 0.2s
1 0.4s 0.4s
2 0.8s 0.8s
3 1.6s 1.6s
4 3.2s 3.2s
5 6.4s 6.4s
6 12.8s 12.8s
7 25.6s 25.6s
8 51.2s 51.2s
9 102.4s 102.4s
10 204.8s 120s
11 324.8s 120s
12 444.8s 120s
13 564.8s 120s
14 684.8s 120s
15 804.8s 120s
断开连接 924.8s(≈15min)

所以客户端需要在 15 分钟之后才能感知到服务端不可用,如此,仅靠 TCP 自身的超时机制,很难发现服务端是否宕机/不可用,长链接不释放,进而可能导致客户端不可用且无感知,所以在长链接服务中,需要有其他的手段来保障服务稳定/可用性(eg:心跳探活)。

服务端 context canceled

Refer to: context canceled,谁是罪魁祸首

  从官方的 net/http 包中可以知道,go 服务在接收请求时,会同时生成一个协程监控连接状态,当发现连接有问题(eg:客户端设置请求超时主动断开)时,会将该请求对应的 context cancel 掉,这时服务端如果再继续使用该 context 时,就会报错「context canceled」。当然,如果服务端发生错误,也同样会导致请求对应的 context cancel 掉。

  服务端主动 cancel context 的好处在于可以快速释放资源,避免无效的请求继续执行(当然也得业务代码上主动去感知 context 是否 cancel,从而及时退出);坏处在于,如果服务端需要上报这个请求发生的错误(一般在后置中间件中进行错误上报),这个时候上报错误的请求需要另外生成一个新的 context,绝不能直接使用现有的 context,因为已有的这个 context 已经 cancel 掉了,继续使用会导致上报错误的请求发送失败,达不到上报的目的。

关于中学的学习方法

2024-03-03 12:26:15

前言

  前些日子,小叔说堂弟的学习有点不太能跟上了,让 Shaun 和堂弟聊聊,回想十几年前, 父亲也是这样找堂哥的,仍记得那年的寒暑假,算是 Shaun 进步最快的一年,也是奠定 Shaun 后续学习方法的一年,现在轮到小叔来找 Shaun ,虽说不能当面聊,指导效果会大打折扣,而且当年堂哥教的具体方法也早已忘记,转化为自己的思想和方法,所以 Shaun 也只能把自己的东西说给堂弟,也算是某种意义上的传承。

序篇

   Shaun 一直认为学习是有天赋,这种天赋体现在学习某一方面的事特别长记性,看个几眼就能完全记在脑海里,还能灵活变通记得的东西。同时,学习也需要方法的,在天赋不够的情况下,有个好的学习方法也能事半功倍。最后,学习是需要积累的,所谓的积累,就是增长见识,多练习,就中学而言,积累就是多做不同的题,同一类但举一反三的变题,在积累的足够多的情况下,考场上同样的题至少都是见过的,没有太多的心理压力,自然会好解一些。

  当然中学的学习毕竟是通过考试来验证结果的,而这个结果才是最重要的(也算是一种唯结果论,不过现实如此,社会如此,没人能逃过,只以成败论英雄,唯一需要注意的是英雄很多时候是有保质期的,扯远了 😅),所以应试技巧也很重要,考试是一个在一定时间内如何得分最多的任务,即使是所有的题都能解,但超时了也没用,更何况大部分人只能解一部分,所以对于这种任务,最好是先快速扫一下卷子,心里先有个数(大概都是些啥题),后面再按部就班的的做,性价比低(要花费大量时间,得分又低)的后面再解。当然在绝对的实力面前,所谓的应试技巧都是虚幻,打铁还是得自身硬,应试本质上是一个熟练的事,需要大量的练习,简而言之就是多刷题 🤪。

  闲话说完了,下面就是正文了,由于 Shaun 是理科生,仅记录 Shaun 还能记得的当年理科六门学科的学习经验。

正篇

语文

  语文一直是 Shaun 的弱项,不过从 Shaun 现在的经验再回过头去看语文,感觉语文考验的更多是对人生和社会的一种感悟,这种感悟不仅仅只是对于自身的体验,也是对别人人生经历和当时社会的一种体会。在学生时代,大部分人受限于家庭和外部环境因素,自身体验很少有丰富的,只能多体会别人的人生,别人的人生只能依赖多看书(小说传记历史都可以),最重要的是在读的时候能有自己的一些思考,假如自己在别人的处境下会是一种什么心态,会有什么行动,一些好的文章,作者为什么会那样描写,遣词造句。当然语文也有直接需要记忆的,字词拼音,古诗文这种,就全靠记忆背诵了。

数学

  中学数学最重要的两个分支就是代数和几何,以及介于两者之间的解析几何,于是也有了数学中最重要的思想——数形结合,抽象的数字有了形状,就不再那么枯燥。熟练使用函数图像以及对应的特点,数学及格就没啥问题了,至于几何,立体空间想象力不够的情况下,也可以加坐标系当解析几何计算了,不过就是时间花的多些。

  导数算是函数中最核心的概念(导数以及对应的微分也会在高等数学中贯穿始终),函数导数的几何意义就是对应点切线的斜率,当在实际的物理场景下,导数也有其实际意义,比如路程关于时间的导数就是速度,速度关于时间的导数就是加速度。

  数列可以认为是一种纯数字游戏,虽然通项公式或者递推式可以认为是某种函数,但数列本质还是数字自身的规律,这种更多的是经验和一种直觉,发现不了就是不能发现,无从下手也无法计算。

  集合和数理逻辑,不等式,极值,推理与证明,对应的反证法。概率与排列组合,这类问题熟记公式,太难的问题,不会就是不会了 🙃。

  向量计算,数形结合完美的体现,中学物理的利器,三角函数,向量的内外积,单位向量的意义,这些东西还是只能在练习中画图理解。向量这个数学工具的美,也只能在实际应用中体会,角度,投影,面积,距离(点点/点线/点面距离),坐标变换(旋转/平移/缩放)等等。

英语

  英语也是 Shaun 不太在行的,尤其是现在回想 Shaun 整个中学,英语及格的次数都屈指可数,初中英语最后一次考试能及格还是靠初三下死命的记单词,而高中英语也是到高三才能稳定的及格,原因也是单词和语法记少了,更重要的原因是对死记硬背很是反感,甚至由于这个原因还和高二的英语老师对着干,一上英语课 Shaun 就直接出去了,后来还好高三换了个英语老师,给 Shaun 稍微开了一段时间的小灶,就是让 Shaun 每天写篇英语作文,然后针对这篇作文进行指导批改,这种方式很适合 Shaun ,从此也算是踏上了英语及格之路, Shaun 现在依然很感激高三的英语老师。至于英语听力,这个没办法,只能靠多听,以 Shaun 现在的经验看来,每天都有一定的时间处在英语环境下,确实能提高听力水平,多听的频率很重要,不然过一段时间就没那种感觉了。

物理

  尤记得高一的物理也有很多次没及格,后来在堂哥的指导下,物理好歹也算是入门了,每次考个 80 分都还算轻松。目前还能记得堂哥教的物理学习方式就是手推公式,当然手推公式同样能应用到数学和化学上。所谓的手推公式就是利用一些基础的公式推导出一个复杂的公式,或者是两个复杂的公司来回推导,能够熟练的手推公式,圆周运动和电磁场问题公式层面的问题就能比较清楚了。至于受力分析,支撑力与面垂直,摩擦力与面平行,杆提供支撑力或许也有拉力,绳只提供拉力,可以假设圆周运动的离心力真实存在,与向心力平衡。至于能量守恒和动量守恒,这个只能多刷题了。

  物理是和数学强绑定的一门学科,数学不行,物理不可能会好的,所以要学好物理得先学好数学。

化学

   Shaun 算是有一定天赋的,看几遍书上的内容,就基本上都能记住了,不管是无机还是有机化学实验也基本都很清晰会有啥现象,每个元素的性质当时也都能记得,以至于看到一些常见的物质大概就能知道会有啥反应。不过还记得当时对于化学方程式配平, Shaun 还只能靠眼睛看,没啥方法,后来堂哥教了个得失电子法,同时针对性的做了大量的题,让 Shaun 领先全班一个学期熟练使用这个方法,在配平这类问题上基本没怎么丢过分。在刷题的过程中,也可以活用一些书上没见过的公式,曾经有次看到一个理想气体状态方程的公式,发现用这个公式可以很轻松的解释一些化学平衡的移动问题。化学在 Shaun 这里没怎么太刷过题,感觉就靠多看书了,熟记元素和物质的物理化学性质。

生物

  生物感觉没太多好说的,就实验而言和化学有点像,但需要记忆的东西更多,最需要计算的题也就是染色体概率和群落数量估计问题了,不过就算不会算,丢分也不多。

总结

  刷题是一种很有效的应试技巧,国内的大部分考试都能通过刷题解决,如果解决不了,那就多刷几遍,针对性的刷题会更有效果。

  死记硬背也是一种方式,但能活学活用更重要,在使用中记忆会更好,理科有个很重要的思想就是推理,大部分结论或公式都能通过一些简单前提或公式推导出来,可以试试自己推导一些常用的公式(关于推导,数学科普领域有本书叫「天才引导的历程」可以看看),注重平时的练习,不要怕麻烦,熟才能生巧。

  至于错题本,得看收集错题的方式,最好是一类题收集在一起,每种解题方式各收集一个经典的题型,后续有时间就翻翻回顾下,就 Shaun 个人的经验,记得很杂的错题本,往往起不到应有的效果,针对性的学习很重要,需要注意的是错题集不要做成了难题/怪题集。

  独立思考,本意是指不要人云亦云,需要有自己的思考和看法(这本应该是每个人的必备技能,但没有的人确实不少)。在学习领域,特指在寻求问题答案的过程中,一定先得有个自己的思考过程,苦思不得的问题会更深刻,同时思考的过程也是自己串通知识点的过程,更容易知道自己的盲区。

  因材施教,同样也因人学习,每个人在不同的学习环境下学习效率是不同的,有些人需要被人催促,需要更有压力一点才能学的好,而有些人更主动一些,在宽松的环境下学习更有效果。而目前的学校都是填鸭式教育,一视同仁,虽说每个学校的教学风格不太一样,但不一定适合学校内的每个学生,所以需要找到适合自己的方式。

后记

  回顾整个高中生涯,对 Shaun 影响最大的其实还是堂哥和高三的英语老师,当时的班主任虽然对 Shaun 也很好,但对 Shaun 的学习和做事方式影响就没那么大,只记得当时班主任常说的一句话——读书是能改变命运的。对于大部分人,读书确实是最可行的出路,其他的路不确定性会更多,虽说读书需要一定的天赋,但国内应试教育的本质注定了努力刷题是能弥补这一部分天赋的,当然,如果有个人能在刷题的路上再稍微指导一下,会走很多弯路,也更容易找到适合自己的学习和思考方式。

VNSWRR 算法浅解

2024-02-07 22:31:58

前言

  最近偶然在公司内网看到一篇文章「负载均衡算法vnswrr改进——从指定位置生成调度序列」。正好 Shaun 一直觉得调度类算法很有意思,就认真看了下,顺便写下自己的一些理解。

预备篇

  通俗来讲负载均衡解决的是「在避免机器过载的前提下,多个请求如何分发到多台机器上」的问题,本质上是一个分布式任务调度的问题,在机器性能相同的情况下,最简单的策略就是轮询,多个机器依次轮流处理请求。Nginx 官方的 SWRR 算法解决的是「在机器性能不同的情况下,如何使请求分布更均匀,更平滑,避免短时间大量请求造成局部热点」的问题。

SWRR篇

  在 SWRR 算法中,有两个权重,一个是初始实际权重(effective weight, ew),一个是算法迭代过程中的当前权重(current weight,cw),在负载均衡过程中,每次请求分发都选择当前权重最大的机器,同时更新每台机器的当前权重,当前权重更新策略如下:

  1. 若设定 n 台机器各自的初始权重为 \((ew_1,ew_2,...,ew_n)\),同时 \(ew_1 \le ew_2 \le ... \le ew_n\) ,且 \(W_{total}=\sum_{i=1}^n ew_i\)

  2. 第一个请求来时,n 台机器各自的当前权重 \(cw_i=ew_i, 1 \le i \le n\) ,由于此时 \(cw_{max}=\max(cw_i)=cw_n\) ,则请求分发给第 n 台机器处理,同时更新机器各自的当前权重 \(cw_1=cw_1+ew_1, cw_2=cw_2+ew_2,...,cw_{n-1}=cw_{n-1}+ew_{n-1},cw_n=cw_n+ew_n-W_{total}\),记为 \((2*ew_1,2*ew_2,...,2*ew_{n-1},2*ew_n-W_{total})\)

  3. 第二个请求来时,此时 n 台机器的各自权重为 \((2*ew_1,2*ew_2,...,2*ew_{n-1},2*ew_n-W_{total})\) ,选取权重值对应的机器进行处理,假设为第 n-1 台,则更新后权重为 \((3*ew_1,3*ew_2,...,3*ew_{n-1}-W_{total},3*ew_n-W_{total})\)

  4. \(W_{total}\) 个请求来时,此时 n 台机器的各自权重应该为 \[(W_{total}*ew_1-m_1*W_{total},W_{total}*ew_2-m_2*W_{total},...,W_{total}*ew_{n-1}-m_{n-1}*W_{total},W_{total}*ew_n-m_n*W_{total}) \\\text{s.t.} \quad \sum_{i=1}^n m_i=W_{total}-1 \\\quad 0 <= m_i <= ew_i\] 由于每次调度都是权重最大值减权重和,重新分配权重后权重和无变化,所以理论上此时除第 k 台机器外,每台机器的权重都为 0,第 k 台机器的权重为 \(W_{total}\) ,所以这次调度处理之后,每台机器的权重又会重新回到初始权重。

VNSWRR 篇

  VNSWRR 算法是阿里针对 Nginx 官方的 SWRR 算法实际运行中对于部分场景下(瞬时流量大,权重更新等)均衡效果不太理想的改进算法,其最大的改进点在于预生成调度序列,以空间换时间减少调度时间,同时在权重更新后随机选取调度序列的起点,使初次请求就调度在不同的机器上,减少高权重机器的局部热点问题。具体流程如下:

  1. 首先使用 SWRR 算法生成前 n 个调度序列;
  2. 再随机选取一个位置作为调度起点,后续的请求依次从调度序列中选取;
  3. 若调度序列用完,则继续用 SWRR 算法生成后 n 个调度序列;
  4. 如此循环,直到调度序列的长度为 \(W_{total}\),即一个周期内的全部调度序列,用完后,从头开始调度即可;
  5. 若有权重更新,则从 1 开始重新生成调度序列;

正文

  从上面的逻辑中,可看出 SWRR 算法调度序列是以 \(W_{total}\) 为周期的一个循环序列,只需要知道一个周期内的调度序列,就可以推算出后续的调度机器(除非权重有变更或者有机器增删)。计算一个周期内的调度序列也比较简单,取当前调度权重中最大值对应机器,同时更新每台机器的当前权重,作为下次调度的权重,简而言之,就是从上次调度结果推出下次调度结果,是一个递推式。那有没有办法不从上次结果推下次结果,直接计算当前的调度结果,简化 VNSWRR 的第一步每次都从头开始预生成前 n 个调度序列,直接从任意位置开始生成调度序列,内网中这篇文章就给出了一个看似“可行的”解决方案,直接计算第 q 个请求的调度结果,具体方案如下:

在 SWRR 算法中,第 q 个请求时,全部机器的当前权重序列应该为 \[(q*ew_1-m_1*W_{total},q*ew_2-m_2*W_{total},...,q*ew_{n-1}-m_{n-1}*W_{total},q*ew_n-m_n*W_{total}) \\\text{s.t.} \quad \sum_{i=1}^n m_i=q-1 \\\quad 0 <= m_i <= ew_i\] 即权重序列中共减去了 \(q-1\)\(W_{total}\) ,平均上 \(m_i=ew_i/W_{total}*(q-1)\),区分 \(m_i\) 的整数部分 \(mz_i\) 和小数部分 \(mx_i\)\(\sum_{i=1}^n m z_i\) 代表减去的 \(W_{total}\) 个数,计算差值 \(d=q-1-\sum_{i=1}^n mz_i\),即还剩 d 个 \(W_{total}\) 待减,对小数部分 \(mx_i\) 从大到小排序,取前 d 个对应的机器再减 \(W_{total}\),即可得到第 q 个请求时的当前权重序列,取最大权重对应的机器即为调度结果,后续调度结果可通过递推式得出。


  初次看到这个方案的时候,就想动手实现一下,因为思路也比较清晰简单,实现完之后,简单测试一下,也确实没啥问题,后面再深度测试了一下,就发现该方案确实有点小小的问题,在大部分情况下,该方案确实能得到很正确的结果,但还是存在一些错误结果,就因为有少量错误结果,所以该方案不要在生产环境下应用。该方案错在了将 \(q*ew_i\) 看成最后一个整体进行处理排序,忽略了分步执行结果,导致小部分场景下的错误排序结果,进而生成错误调度权重,调度错误。

  现在再回到初始问题「如何生成 SWRR 算法中指定轮次的调度结果?」,抽象来看,该问题是个数学问题「如何从数列的递推式计算数列求通项公式」, 但 SWRR 的递推式相对复杂,中间还有取最大值这个不稳定变量,实际很难得到通项公式,直接计算指定调度解果,Shaun 问了 ChatGPT,也自己想了很久,搜了很久,但都没有答案,内网中的这个方案算是最接近的一个答案。

后记

  在内网中看到这个方案的思路很有意思,将整数和小数部分拆开,再单独对小数部分排序,所以就自己测试了一下,顺便学习了下负载均衡 SWRR 算法,虽然问题依旧还在,但总归是有点收获。

附录

  附代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import random


def ouput_schedule(rs_arr, schedule_num):
all_rs_weight_str = ";\t".join(["rs:%s,cw:%s" % (rs["rs_name"], rs["cw"]) for rs in rs_arr])
schedule_rs = max(rs_arr, key=lambda x:x["cw"])
print("%s:\t%s\t===>\trs:%s,cw:%s" % (schedule_num, all_rs_weight_str, schedule_rs["rs_name"], schedule_rs["cw"]))

return schedule_rs

def swrr(rs_arr, weight_total):
schedule_rs = rs_arr[0]
max_weight = schedule_rs["cw"]
for rs in rs_arr:
if rs["cw"] > max_weight:
schedule_rs = rs
max_weight = rs["cw"]

rs["cw"] += rs["ew"]

schedule_rs["cw"] -= weight_total

return schedule_rs

def swrr_test():
real_servers = [{"rs_name": chr(i+64), "ew": i, "cw": i} for i in range(1, 6)]
weight_total = sum([rs["ew"] for rs in real_servers])
schedule_count = weight_total
swrr_seq = []
for i in range(1, schedule_count+1):
ouput_schedule(real_servers, i)
schedule_rs = swrr(real_servers, weight_total)

swrr_seq.append(schedule_rs["rs_name"])

print(swrr_seq)

# swrr_test()
# print("---------")

def swrr_n(rs_arr, weight_total, schedule_num):
ms = [(rs["ew"] / float(weight_total)) * (schedule_num-1) for rs in rs_arr]
mzs = [int(m) for m in ms]
mxs = [(i, m-int(m)) for i, m in enumerate(ms)]
mxs = sorted(mxs, key=lambda x:x[1], reverse=True)
for i, rs in enumerate(rs_arr):
rs["cw"] = schedule_num * rs["ew"]
rs["cw"] -= mzs[i] * weight_total

d = (schedule_num-1) - sum(mzs)
for i in range(d):
rs_arr[mxs[i][0]]["cw"] -= weight_total

schedule_rs = ouput_schedule(rs_arr, schedule_num)

return schedule_rs

def swrr_n_test():
real_servers = [{"rs_name": chr(i+64), "ew": i, "cw": i} for i in range(1, 6)]
weight_total = sum([rs["ew"] for rs in real_servers])

schedule_rs_seq = []
for i in range(1, weight_total+1):
schedule_rs = swrr_n(real_servers, weight_total, i)

schedule_rs_seq.append(schedule_rs["rs_name"])
# swrr_n(real_servers, weight_total, 9) # err schedule rs
print(schedule_rs_seq)

# swrr_n_test()

def vnswrr_preschedule(rs_arr, weight_total, N, schedule_rs_seq):
for i in range(1, N+1):
schedule_rs = swrr(rs_arr, weight_total)
if len(schedule_rs_seq) >= weight_total:
break
schedule_rs_seq.append(schedule_rs)

def vnswrr(rs_arr, rs_count, weight_total, prev_schedule_idx, schedule_rs_seq):
N = min(rs_count, weight_total)

schedule_idx = prev_schedule_idx + 1
schedule_idx %= weight_total

if schedule_idx >= len(schedule_rs_seq)-1:
vnswrr_preschedule(rs_arr, weight_total, N, schedule_rs_seq)

return schedule_idx

def vnswrr_test():
all_schedule_rs_seq = []
real_servers = [{"rs_name": chr(i+64), "ew": i, "cw": i} for i in range(1, 6)]
rs_count = len(real_servers)
weight_total = sum([rs["ew"] for rs in real_servers])

N = min(rs_count, weight_total)
schedule_rs_seq = []
# 预生成调度序列
vnswrr_preschedule(real_servers, weight_total, N, schedule_rs_seq)
# 随机取调度结果
prev_schedule_idx = random.randint(0, N-1)-1

for i in range(1, 2*weight_total+1):
schedule_idx = vnswrr(real_servers, rs_count, weight_total, prev_schedule_idx, schedule_rs_seq)
all_schedule_rs_seq.append(schedule_rs_seq[schedule_idx]["rs_name"])
prev_schedule_idx = schedule_idx

print([rs["rs_name"] for rs in schedule_rs_seq])
print(all_schedule_rs_seq)

vnswrr_test()

参考资料

1、QPS 提升60%,揭秘阿里巴巴轻量级开源 Web 服务器 Tengine 负载均衡算法

2、Nginx SWRR 算法解读

记一次资源不释放的问题

2023-05-01 22:16:58

前言

  最近发现一个 GoFrame 服务即使空载 CPU 使用率也很高,每次接受请求后资源没有被释放,一直累积,直到达到报警阈值,人工介入重启服务,于是压测排查了一下。

问题篇

  先新增代码启动 go 自带的 pprof 服务器:

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"net/http"
_ "net/http/pprof"
)

func Pprof(pprof_port string) {
go func(pprof_port string) {
http.ListenAndServe("0.0.0.0:"+pprof_port, nil)
}(pprof_port)
}

压测以及 profile 命令:

1
2
3
4
5
6
7
8
9
10
11
12
# 压测命令
wrk -t8 -c1000 -d60s --latency --timeout 10s -s post_script.lua http://host:[srv_port]/post

# profile 整体分析
go tool pprof -http=:8081 http://host:[pprof_port]/debug/pprof/profile?seconds=30

# 查看函数堆栈调用
curl http://host:[pprof_port]/debug/pprof/trace?seconds=30 > ./pprof/trace01
go tool trace -http=:8081 ./pprof/trace01

# 查看内存堆栈
go tool pprof -http=:8081 http://host:[pprof_port]/debug/pprof/heap?seconds=30

  在压测 30 次后,即使服务空载 CPU 也被打满了,查看服务此时的 profile,发现 goroutine 的数目到了百万级别,查看 cpu 堆栈发现集中调用在 gtimer 上,但遍寻服务代码,没有直接用到 GoFrame 的定时器,问题出在哪也还是没想太明白。吃完饭后偶然灵光一现,既然 CPU 看不出啥,那再看看内存,查看内存发现,内存对象最多的是 glog.Logger,看代码也正好有对应的对象,可算是找到问题真正的元凶了。

  log 对象一般都是全生命周期的,不主动销毁就会一直伴随着服务运行,所以 log 对象一般都是程序启动时初始化一次,后续调用,都是用这一个对象实例。而这次这个问题就是因为在代码中用 glog 记录了数据库执行日志,每次请求都会重新生成一个 glog 对象,又没有主动释放造成的。

  知道问题的真正所在,解决问题就相对很简单了,只在程序启动时初始化一个 glog 对象,后续打印日志就用这一个实例,其实更好的方式是生产环境不打印数据库日志,毕竟影响性能。

后记

  CPU 资源的占用往往伴随着内存资源的占用,当从调用堆栈以及线程资源上看不出问题的时候,可以转过头来看看内存堆栈,毕竟内存堆栈更能指示有问题的对象出在哪,知道内存对象是谁,也相当于提供了排查问题代码的方向。

附录

  在排查过程中发现 goroutine 数目异常的高,于是想限制一下 goroutine 数目,在网上搜索的时候发现当用容器部署 go 服务时,go 默认最大的 goroutine 数目为宿主机 cpu 核数,而不是容器的 cpu 核数,从而并发时 goroutine 数目可能比容器 cpu 核数高很多,造成资源争抢,导致并发性能下降,可以通过设置环境变量 GOMAXPROCS 指定 goroutine 最大数目,也可以使用 go.uber.org/automaxprocs 库自动修正最大核数为容器 cpu 核数。

自适应设置 GOMAXPROCS 上下限代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
_ "go.uber.org/automaxprocs"

"runtime"
)

func main() {
procsNum := runtime.GOMAXPROCS(-1)
if procsNum < 4 {
procsNum = 4
} else if procsNum > 16 {
procsNum = 16
}

runtime.GOMAXPROCS(procsNum)

// todo something...

}

python 内存泄漏排查

※注:python 的默认参数是全局变量,若默认参数为一个引用类型(eg:字典对象),且函数中会对该参数进行写操作,就极有可能发生内存泄漏,所以 python 默认参数最好是值类型。

方法一是线上程序直接排查,通过 pyrasite 和 guppy 直接对应 python 程序:

step1:绑定 python 程序 pid,开启 pyrasite shell 窗口,执行 pyrasite-shell <pid>

step2:使用 guppy 查看 python 程序内存情况,

1
2
3
>>> from guppy import hpy
>>> h = hpy()
>>> h.heap()

step3:间隔一定时间后,再次使用 h.heap(),对比两次内存变化

该方法一般只能粗略查看内存泄露的数据对象,可能无法精确定位到指定位置,这时需要用方法二,手动插入代码查看程序运行日志:

Python标准库的gc、sys模块提供了检测的能力

1
2
3
4
5
6
import gc
import sys

gc.get_objects() # 返回一个收集器所跟踪的所有对象的列表
gc.get_referrers(*objs) # 返回直接引用任意一个 ojbs 的对象列表
sys.getsizeof() # 返回对象的大小(以字节为单位)。只计算直接分配给对象的内存消耗,不计算它所引用的对象的内存消耗。

基于这些函数,先把进程中所有的对象引用拿到,得到对象大小,然后从大到小排序,打印出来,代码如下:

1
2
3
4
5
6
7
8
9
10
11
import gc
import sys

def show_memory():
print("*" * 60)
objects_list = []
for obj in gc.get_objects():
size = sys.getsizeof(obj)
objects_list.append((obj, size))
for obj, size in sorted(objects_list, key=lambda x: x[1], reverse=True)[:10]:
print(f"OBJ: {id(obj)}, TYPE: {type(obj)} SIZE: {size/1024/1024:.2f}MB {str(obj)[:100]}")

找到内存占用稳定增长的对象,调用 gc.get_referrers(*objs),查看该对象的引用信息,即可快速定位泄漏位置

该方法更加灵活精确,不好的地方是有侵入性,需要修改代码后重新上线,同时获取这些信息并打印,对性能有一定的影响,排查完之后,需要将该段代码下线。

参考资料

1、python内存泄露问题定位:附带解决pyrasite timed out

2、技术 · 一次Python程序内存泄露故障的排查过程

社畜三年,风雨兼程

2023-04-02 10:06:28

前言

  财富和幸福算是绝大部分人的毕生追求,所以在读这本书时,更容易让人有一种思想上的共鸣,但一个人的成功总是独一无二的,需要依靠天时地利人和,正所谓,学我者生,似我者死,可以学习借鉴成功者的一些思想,但不要想着沿着成功者的路继续走下去就能成功。 ——读『纳瓦尔宝典』

感想篇

  财富并不代表金钱或者地位,而是某种可以自然增长的东西,是一种追求共赢的东西,是一种可以长期存在的东西。最好的投资总是学习,成本低且有效,但接受新知识总是需要消耗更多的能量,而人总是想尽量减少能量的消耗,所以很多人都沉迷于快餐文化中,不需要过多的思考,又消磨了时光,一举两得 ( ̄ε(# ̄)☆╰╮( ̄▽ ̄///)。

  人生总有选择,也总要承担选择所带来的后果。选择永远比努力重要,光凭运气去选择,或许能瞎猫碰上死耗子,但狗屎运总归会用完,下一次就不好说了。如何选择是需要去学习的,有从自身过去的经验去学习,有从别人身上去学习,更多的是依靠平时积累的各种信息,这就是努力了,努力或许有结果,也或许没有,但不努力,一定没结果。努力的目的是为了有更多的选择,在有更好的选择时能及时判断,不要单纯的为了结果而努力,很有可能得不偿失。付出和回报看对人对事吧,对自己的付出当然总是会有回报的,对事情付出同样会有,但对其他人付出,就别太指望一定要有啥回报,可以设个底线,在没有任何回报的情况下,能付出多少。

  选择也从来不是一竿子买卖,局部最优也不代表全局最优,人生的选择更是如此,十年前的当时最优放在现在看可能选错了,放在十年后再看可能又选对了,时代在发展,当然倘若十年前真选对了,可能就不需要十年后再回过头来看了 :P。绝对最优这个词在人生道路上就不存在,只能说是相对更优,这个更优不是选项之间的对比,而是选项与自身的对比,哪个选项最能提升自己就选哪个就行,长远来看,投资自己不一定能飞黄腾达,但总能有口饭吃。投机是选择,稳扎稳打也是选择,本身无优劣,回报和风险总是共存的,能承受的起就行,别光见贼吃肉,不见贼挨打。

  获取金钱的方式有很多,最快的方式都写在刑法里 ๑乛◡乛๑,而创造财富的方式也有很多,最直接的是去资本市场做投资,也可以想方设法积累自己的名气(不管是好是坏),然后走流量变现。创造财富都有其风险,就算是大航海时代,不也一大批人沉在海底,打工人虽然是在给别人创造财富,但对自身而言,也算是一种资本的原始积累,而且是相对最稳定的一种,对于技术人来说,技术经验也算是一种时间杠杆,所以边打工,边在资本市场中学习,也不失为一种创造财富的方式。

  至于幸福,「哈佛大学公开课:幸福课」中指出幸福是不可持续的,永远幸福是不可能的。幸福的人都是相似的,不幸的人各有各的不幸,穷人的不幸比富人更不幸,借用一句伪科学的话:所有的不幸的都是对现状的一种不满足感,满足是一种幸福,适应也是一种幸福,总而言之就是看开,释放。「银河系漫游指南」中宇宙终极问题的答案是 42,或许本身就是无意义的事,为啥一定要有意义,无聊或许才是人生的常态主题。当欲望得到满足,或许能得到短暂的幸福,但随之而来的空虚感也得忍受,更关键的在于去选择下一件事,绝大部分人总还是要为了生计奔波,吃饱了撑得就容易胡思乱想,忙点啥,哪怕随便干啥,都能体会到闲暇时的幸福 :P。

  曾经在 TED 上看到不要把梦想告诉别人,告诉了就很难实现,因为能得到的只有嘲笑,以 Shaun 个人经验来说是没错的,梦想还是放在心里比较好,或许等哪天实现了再放出来会更加畅快,有且仅有自己做的梦才是真正的梦想,这才是人最珍爱的东西,就像「来自深渊:烈日的黄金乡」中真正的挚爱是不会让任何人知道的,哪怕是自己最后的传承者。「把时间当作朋友」中有这样一句话,“来自外部的恐惧在于过分在意外界的评价”,Shaun 本身不是个太在意别人看法的人,在意别人的评价,无疑会让自己活得更累,尤其是大部分人的评价没有丁点儿建设性,只是一种优越感作祟,当然,自己真有问题而不自知也是不行的。很喜欢暗杠「童话镇plus」里唱的“很多人一辈子忙忙碌碌不会懂得:有个被嘲笑的梦想万一有天实现了呢?”,人活着,总得有个念想。奔波一世,虽说是为了这一日三餐,但能留下点脚印,哪怕是些许痕迹,好像也还不错。

  老子云:知人者智,自知者明;邓宁-克鲁格效应也指出认知有四大境界:不知道自己不知道,知道自己不知道,知道自己知道,不知道自己知道;人贵有自知,可惜不知为知者有之,过而不改者有之,好为人师者有之。人之患在好为人师,道不可轻传,薪尽火传在很多时候也只是妄想,自以为是,人以为非,自以为厉害不算厉害,别人认为厉害才是真厉害。自知,知道自己几斤几两,自尊自爱,力所能及,道阻且长,持续学习,徐徐图之。

  常言道:种一棵树最好的时机是十年前,其次是现在。做事情从来不嫌晚,就怕不行动,人人都会做梦,有些梦不切实际,有些梦有迹可循,空想无用,再周密的计划也比不过实际执行,计划赶不上变化,诚然,试错有成本,但这成本也同样是经验,按部就班的迭代执行,总会走向通往梦想之路,不过大部分人可能在路上就迷失了,甚至有些人走不上路,能真正到达终点的人,终究只是少数。

工作篇

  一位长者曾经说过:一个人的命运啊,当然要靠自我奋斗,但是也要考虑到历史的进程。工作不是事业,对于大部分的人目标都只是简单的钱而已。三年期的目标算是达成了,五年期的目标的应该是完不成了,不过总归要尽力,大环境如此,个人的努力显得格外苍白。在时代的洪流之下,众人皆是蝼蚁,按部就班,做好自己该做的就行,至于回报,还是得看外面的环境调整预期。

  Shaun 这三年来,从小公司跳到大公司,体会最深的就是,就单纯的论做事而言,大公司和小公司没啥区别,能接触到的人也就是自己组内和上下游的几十个人而已,唯一的区别可能也就是履历背景了,但这个也只能在跳槽时才能体现其作用,当然或许有人说人的能力强弱不同,但就 Shaun 的感觉来看,不是不同,而是术业有专攻,不同的公司对人的侧重点要求不同,大公司小公司都有工作能力很强的,不过都跑路了 ¯\_(ツ)_/¯,不过有一点是真的有区别的,就是数据量,这是实打实的区别,不过这更多的是对经验的要求,对人做事的本质能力并没有更高的要求,还是用原来的思维方式尝试新的方案和实验,真正难的问题还是交给学术界去解决吧。

  无论大小公司,拥抱变化或许才是永恒的话题。这种变化,不只是裁员(只要是补偿没问题的裁员,若被裁,那便被裁,找个吃饭的地方,总还是不难的),更多的是体现在做事方面。打工人常听人说,事情是做不完的,没必要这么加班加点的干,没必要加班干是真的,但事情是会做完的,只是做完了又会有新的事过来,事情做完又两层含义:一层是事情可以告一段落了,做到这到此为止了,不要再投入大量人力做这个事,只是日常维护;另一层是这个事情不做了,现有人力要么换岗要么走人。对于公司上层来说,更关心的事是做完这个事之后下一个事做什么,如果没有下一个事,那留下这么多人就没必要了,体现到下层大头兵上,就是要么被裁,要么换个事情做,至于换个的事情可能是类似的事,也可能是全新的事,这就对人的学习能力或知识迁移能力有一定的要求。从上层的角度来说基本上很难让一个大头兵一直持续深入的干某件事,浪费人力,投入和产出完全不成比例,所以持续学习,拥抱变化才是真理。就 Shaun 个人的经验而言,基本上每半年换个新的活儿,有的活能照之前的经验完全迁移,有的活完全没接触过,得重新开始学,不过好在大多数的事都还能胜任,差强人意。

  换活,有的是开辟新业务,有的是填坑,开辟新业务一般都算是好活,从 0 到 1,事情不一定轻松,但收益可是实打实的,而且越前期的收益越容易拿,除非新业务黄了,不然光啃老都能啃几年,这是真好活。至于填坑,就看坑是什么样的了,若是核心的坑,那当然是极好的,怕就怕是陈年老坑,还需要不断维护更新,经受无数人的东西还有去维护,也是个烂坑;另一种坑是,前人写了系统原型,需要沿着这个原型继续开发,可惜原型一般只是实现核心功能,更可怕的是还是个初版,代码也是一团糟的那种,这样的坑接收恐怕是只能重写了,但收益一般也就不好说了。

  收益,是企业最核心的两个字,没有收益,一切都是免谈,收益有显而易见的,比如节省成本,开拓市场,也有不明显的,比如为以后节省人力,为将来快速解决问题。显式的收益很容易感知,也很好评估,隐式的收益就基本无法感知,将来的事谁能说的清,况且将来还有没有这事都很难说,员工的人力成本也容易忽视,这些收益就很难评估,也就基本不可能定为上下一心的 kpi,当下面的 kpi 不是上面的 kpi,那下面做的一切工作都是白干,虽然对自身可能有益,但也只能面试时体现了。每离职一个核心人员,至少需要调派 3 个人来填补空缺,为啥企业宁愿后面补充,而不是提前安排一个人来分担工作,主要原因在于企业在赌核心人员不会轻易离职,也不会安排太多人手干维护性质的工作,但当熟手离职,再要去维护的代价就很大了,后来者一边填坑,一边在心里骂街,就这样一轮一轮的后来者,一轮一轮的骂街,屎山也就这样炼成了,直到推倒重来,形成上下一心的 kpi,又开始新一轮的屎山炼成。

  提问从来不是件丢人的事,只要自己尝试过解决且保持应有的礼貌,毕竟人非生而知之者,不知道才是常态。很多人喜欢闷头干活,遇到问题只会冥思苦想,独立思考固然是一种品质,但思考也需要讲究基本法,思考也分交流前中后,当完全没有交流,就开始想解决方案,这是呆子,没有任何背景,能想出好的方案,那可能是真正的天才,那也没必要和打工人混在一起了。软件系统的问题更多的可能是人的问题,遇到问题,最好的是先了解背景原因后果,了解一下来龙去脉,再和有经验的人交流讨论一下,可以怎么解决,最后再决定具体做法,怎么执行,真梳理完了之后,执行一般是一件比较简单的事了。遇事多沟通,想开点,对人愚钝,对事精明。

  如无必要,勿增实体,漂亮话人人都会说,可真在具体实施中,一般都是怎么快怎么来,优先增加一个实体再说,而不是去想有没有必要;当然也有的会过度放大这个事,明明增加这个实体能给各方都便利很多,从成本考虑就是不增,孰不知在另一方面反而是增加了成本。软件开发行业中,有很多定律法则,但人是活的,定律是死的,每个人有每个人的理解,在不同的业务场景下,定律的体现都有不同,一般情况下,围绕定律走不会出太大的问题,但有时也需要灵活变通,就像设计模式一样,不是完全一层不变的,有那层意思在就行,Shaun 认为唯一恒定的定律就是「简单」,不管是设计还是开发,越简约,越直观,就越稳定,越正确。

  年龄歧视在职场中是确实存在的,国内职场普遍认为,当到一定年龄的时候,就应该到一定的职级,到一定的职级就不会再在一线干活了,这就导致了在招干活的人,就不会招年龄大的,这真的就很畸形,职级高就不会也不需要在一线干活,就目前国内职场的环境,技术从来不是主流的上升手段,所谓的技术路线是不存在的,不需要真正的架构师,文档架构师更受欢迎,说的好才能活的好。做完和做好从来都是两回事,但相比较而言,做完更重要些,毕竟做的好与不好,很难量化评估,所以更多的要求是能不能做出来,不关心好与坏,有东西出来这更重要,而一般都能做出来,所以不重视技术人员,而更重视管理人员,这就是互联网 35 岁失业的本质,就算 35 岁以上的技术人员能做的更好,但 ROI 不成正比。管理者掌握是组织技能,执行者掌握的是生存技能,当经济环境下行时,生存技能有优势,当经济环境上行时,组织技能更有优势。

  跟对人,做对事,简简单单六个字,做起来可太难了,首先怎么定义对,在合适的时间做合适的事,这个合适怎么把握。曾以为只要努力做事,就能得到应有的回报,可在人类世界,老实做事有回报的都上新闻了。做好事不如做对事,但不知道事情做的对不对的时候,也只能选择做好,至少这是对个人能力的要求,少留点骂名。当然,善战者无赫赫之功,做的好了,也没法突出个人的重要性,甚至给上面的感觉是有你没你没区别,只要还在这个位置上,能力就体现不了,相反,经常出问题的人,得一堆人围着转,这看起来就很重要 ๑乛◡乛๑,如果上面再分配不公,能力强的人自然就加速走了,某种程度上的死海效应,劣币驱逐良币。

  工作也是讲方法论的,做事的出发点和方向错了,即使能解决一部分问题,但解不干净,最后还得推到重来,所以项目启动前的分析非常重要,一定得先抓头部问题,当然穿插一些能快速解决的问题也行,其次就是明确哪些问题一定要解,哪些问题可以不解,最后就是一定要留下文档纪要,这是工作量的体现,也是未来追溯问题的依据。汇报也一般可分为五步:背景,需求和目标,解决方案和排期,进展和依赖项,问题和风险。

  工作自由,有两种境界,一种是选择做什么的自由,另一种是选择不做什么的自由。大部分或许能选择做什么,但却无法拒绝一些事,有些事情只能被动接受,但有些却是可选的,不要一直被牵着走,事有轻重缓急,要有自己的认知,学会拒绝,哪怕是上级的需求,现如今找个养家糊口的工作还是不难的,此处不留爷自有留爷处。了解并学习职场中一些常用的话术,分辨并挑出真正对自身有益的,别把别人太当回事,别把自己不当回事,事实是事实,话术是话术,主人翁意识是每个领导都希望下属有的,但解释权归领导所有,越上层就越不可能落到实处。尽心做事,尽力做好每一件接手的活,无论喜欢与否,真不想做,就不要接,既然做了,就尽职尽责,算是在工作中积德了,败人品的事还是尽量不要做。放宽心态,大方待人,不要把工作当生活,可以享受工作,但更应该享受生活。勇于分享,分享讨论可能不见得是件好事,但绝不是坏事,分享同样也是一种总结。灵感是易逝的,当有灵感时,就尽快行动起来,优秀的产品需要时间来打磨,对结果需要有更多的耐心。

  在 22 年一片开源节流的浪潮中,人人自危。「浪潮之巅」中,科技的发展史就是一批企业的兴亡史,或因为自身的原因,尾大不掉,或因为更上层的力量,没有哪家企业能一直辉煌下去,打工人能做的,只有选条赛道,厚积薄发,尽量少换或不换行业,剩下的也就只有听天由命了,毕竟将来的事谁也说不好,潮起潮落,又有谁能一直屹立浪潮之巅 ╮(╯▽╰)╭。

生活篇

  这三年,最大的主题就是“新冠”,新冠时代,每个人都是历史的见证者,这次疫情,足以在人类历史上留下浓墨重彩的一笔。这期间,也能真正在现实里体会一把魔幻现实主义,有大规模封城的,有叫嚣着他的软肋是儿子的,也有恶意返乡有家不能回的。天赋🧑权,疫赋🐶权,肉食者的政策总是不食人间疾苦。又或许是上面的政策出发点是好的,但奈何下面的执行者大部分是一帮饭桶蛀虫,能站在布衣角度去落实政策的又有多少。解封之后,对 Shaun 而言,最大快人心的就是不用再看看门大爷的嘴脸。黑色的眼睛可能是黑夜给的,也可能是白天给的,有人用它寻找光明,也有人用它寻找黑暗,西游记里描述的狮驼国从历史角度看,也不是什么神话传说,虽说如今的时代论惨烈规模没那么大,但苦难并没有完全消失,太阳底下也没有新鲜事。

  「动物庄园」里有句话,“所有动物生来平等,但有些动物比其他动物更平等”,疫情三年,对这句话体会更加深刻,有的人生在罗马,也有的人生来就是骡马,人人平等的乌托邦世界或许只是个伪命题。连科学也只是为政治服务的工具而已,社会的科技发展也从不依赖于上层阶级的想法,往往只是下层某个灵光一现的思路,人类社会有个很奇怪的现象就是,本来一个人在下层成果迭出,当跃迁到上层时,思维就好像僵化了,深层原因可能有很多,但至少表面现象是这样。普罗大宗也很容易受到各种言论的影响,尤其是某些专业人士的公开发言,所以完全的言论自由也意味着完全混乱,需要管控,但也有知情权,不然就像一氧化二氢实验一样,隐瞒一面,重点宣传另一面,就很容易受到别有用心的一些误导。

  未经他人苦,莫劝他人善,这三年,在网上见过太多的牛鬼蛇神,一把键盘走天下,自以为站在大义之上,实际上只是些吃着人血馒头既蠢又坏的看客,以自己幸福的生活站在道德制高点上去肆意抨击他人,并因此而洋洋得意。诚然无能是最大的罪恶,哀其不幸,怒其不争,但匹夫之怒,也能血溅五步,自救者天救,自助者天助,自弃者天弃。当感叹世事无常的时候,都可以去看看「活着」,有的人觉得蝼蚁尚且偷生,好死不如赖活着,也有的人觉得宁为玉碎不为瓦全,读书毕竟是个很私人的事,一千个人心中有一千个哈姆雷特。

  生存还是生活,这是一个永恒的话题。苏轼曾感叹道,寄蜉蝣于天地,渺沧海之一粟,人这一辈子,说长也长,年轻的时候往前看,感叹还要这样过几十年,说短也短,回首往事,转瞬即逝。也曾踌躇满志,誓要走出山村,而今跨长江越黄河,问一句,有必要吗?行路难!行路难!多歧路,今安在?生活的方式有很多种,或许平淡才是真,人活一世,顺心而已。22 年,偶然发现北京有很多的户外徒步组织,Shaun 也参加了好几次活动,感觉确实很有意思。有的人把徒步当成一种极限运动,挑战自我,也有的人把徒步作为一种休闲运动,纯纯放松心情,每个人徒步的目的都各不相同,重点在于量力而行,对大自然有敬畏之心。周末去外面走走,听风观景阅人看故事,虽然身体上并不轻松,但能极大的消除一周心理上的劳累感。寄情于山水,逍遥于世间,打工人花点小钱,就能有心灵上的放松,整挺好。

  至于对象,就感觉自己一直很佛系,或许得等到真正成为大魔法师的那一天,才会转变心态。爱情,Shaun 是从来不奢望的,爱情是咋样,相信一千个人心中有一千个哈姆雷特,古今中外有无数的作品的描述了作者心中认为的模样,共同点在于都有风花雪月,嘻嘻哈哈,哭哭啼啼,而生活,好像优秀的作品不多,一个可能的原因是爱情短暂,更有戏剧性,作品也能更有张力,而生活一般时间跨度较长,再惊天动地的事在时间的长河里或许也只是一朵小水花,看起来很平淡,贾科长或许是个很善于观察生活的人,22 年的「隐入尘烟」或许也描绘了生活的一部分,平平淡淡才是真,哪有那么多跌宕起伏,哪有那么多真善美。

  『紫阳』中有句话,「男女之情并不深奥,感情的发生有两种诱因,一是源于阴阳交合本能的驱使,以阴阳交合为目的。还有一种是喜欢对方身上的优良本格,愿意与之长相厮守。这两种诱因都可以引发情感,没有高下清浊之分,两种诱因也往往彼此掺杂,很难明确区分。这两者唯一的不同就是后者更容易被世人传颂赞美,但后人传颂和赞美的其实也并不是情感本身,而是少数人身上的优良品格」。爱情或许值得被赞美,但更多的是恋爱过程中经历的事,爱情或许从来都是想象中的产物。「三体」中有描述,大部分人的爱情对象也只是存在于自己的想象之中。他们所爱的并不是现实中的 ta,而只是想象中的 ta,现实中的 ta 只是他们创造梦中情人的一个模板,他们迟早会发现梦中情人与模板之间的差异,如果适应这种差异他们就会走到一起,无法适应就分开,就这么简单。两个人真正在一起的时候,往往只看到对方坏的一面,分开的时候,回忆时却想着好的一面,人生就是这么反复无常。「疑犯追踪」有这样一句台词:有一天你嫁给了自己的灵魂伴侣,然后眼睁睁看着他们变为另一个人,一旦深爱这人曾经的样子,便无法接受他们改变后的样子。最爱的人或许只存在于想象中,毕竟,随环境变化最大的也是人。

  有人说,两个人在一起就是为了分担风险,当一个人能够足够承担风险的时候,或许就不需要两个人。「在云端」中有讨论过结婚的意义:可以有依靠的人?但能真白头偕老的人又有多少,计划永远赶不上变化,就算能碰到自己的理想型,但你大概率不会是 ta 的理想型,况且人都是善变的;不会孤独终老?如今的时代养儿防老风险也不小,比父母更有能力的孩子基本不可能待在父母身边;或许正如男主后期的思想转变,结婚的意义或许就是找个能分享倾听理解陪伴的人。Shaun 理想的两人关系应该是一种战友之上的关系,相互信任理解尊重,虽有小分歧,但大目标一致。人与人之间的相处哪要那么多心思,顺其自然,求同存异,自尊自爱,人待以诚,待人以诚。庸人为了忘却烦恼,一般以智者不入爱河来自我安慰。人啊,简单又复杂,简单在合心意,复杂在人心难测,所以不需要强求,顺心不香吗?

  回想起以前高中的时候,就有同学对 Shaun 说:“你这看样子以后就是要靠相亲找对象的”,现在想来,那同学看人真准。有人说相亲是让一个不懂人的去搞定一个难懂的人,其实哪有什么不懂或难懂,有的只是一群彷徨的可怜人罢了,很少有人能确切的明白自己真正想要的是什么。数学中有个最优停止理论,得出了一个 1/e 的数值。通俗来说就是在苏格拉底的麦穗故事里前 1/3 的路程,什么都不摘,只是用来估计自己的预期,在后 2/3 的路程里,一旦有接近甚至超越的预期的麦穗出现,立即摘下。满足 1/e 概率的前提之下是麦穗大小均匀分布,可惜现实世界很少有均匀分布,所以这个东西对于相亲虽说有一定的借鉴作用,但还是不要太盲从。相亲本来就带着强烈的不信任感,而信任的建立一般又是个长期过程,在不信任甚至有些防备的状态下进行交流,自然很难发现别人的闪光点,更多的是在不经意间暴露出的缺点(相对而言是缺点,毕竟善于发现美的人不多,挑刺的人会更多些),从这点而言,相亲和面试差不多了,更重要的是遇上对的人,幸运值很重要。

  相亲的交流无非就两种方式,一种是直截了当,开始就问对方想找个啥样的;另一种迂回战术,从工作学习生活爱好方面按流程开始话题,虽然很平淡,但是一般也没太大的问题。交流是相互的,就光一个人问,这不是聊天,而是面试。当然如果感觉聊不到一块儿,确实话不投机半句多,就尽早结束;当犹豫要不要开启话题的时候,就可以长痛不如短痛了,当断不断,反受其乱。出来相亲的人都抱着不同的目的,有的人是被迫的,有的人只是想接触一下外人,有的人是想给别人一个机会,也有的人确实很实诚的想找个相守一生的对象。不同的目的造就了不同的人,有的人就是想出来玩玩,享受那种若即若离的朦胧感,Shaun 一贯的态度是抱着一颗平常心,观其行而知其心,得之我幸,失之我命,求而不得,何必强求,弃得失心,方可自在,若不成,那便不成,也不需要刨根问底,在没有结果的事情面前,真相都显得没有任何意义。

  很多人对别人的看法在第一次交流的时候,就基本已经确定了,先入为主的思想根深蒂固,孰不知交流本身可以算是一件很随意的事,不同的交流方式,不同的语言文字,在不同的人生背景下,都有不同的含义,除非带着很强的目的性,非常明确的知道这次交流的主题是啥,不过这就和工作中开会没啥区别了。生活中的交流还是随意些比较好,一般都是想到啥就说啥,不用费脑子,巴适。

理财篇

  关于理财的言论有很多,在经济上行的时候,网络上流传的话是,你不理财财不理你,经济下行的时候,说的更多的就是,你一理财财离开你。理财,其实是一个很私人的事,分享交流可以,但安利就算了,赚钱的时候只会觉得自己眼光不错,亏钱发泄情绪的时候就正好能找到一个出气口。理财的手段也有很多,最稳妥的当然是不理财,直接把现金都放家里,毕竟在这个银行都会爆雷的时代,钱放银行都不太保险。有人说,乱世买黄金,盛世买古董,灾荒屯余粮,这话说的也有一定的道理,毕竟黄金是整个人类文明的硬通货,钱或许不一定是钱,但黄金总还能代表钱,古董更多的是一种精神文明象征,当衣食无忧的时候,自然也会想有点更高层次的追求,当衣不蔽体食不果腹的时候,自然填饱肚子才是最高优先级。对应到国内市场,当银行爆雷的消息传来,黄金大涨,这三年疫情,食品消费行业涨,其他都跌,市场总是跟随人的需求变化而变化,不管是看衰还是看好,总有人能从中发现商机,毕竟资本总是会从一个地方转移到另一个地方,除非整个市场崩了。

  理财本质上是一个买与卖的哲学,就算是存银行,为方便取用,就用活期,暂时不用,就用定期,这也是存款收益的一种买卖权衡,至于市场里的交易,就是更直接的买卖了。每个人都想从这种买卖中获益,不管是买方还是卖方,但是这种买卖不是简单的零和,有可能是正和,有时甚至会是负和,主要看买卖的东西的是啥。有些人一次两次的交易都获利了,就想着下次能不能获利更多,但随着等待的时间拉长,收益都变成亏损了,最后只能忍痛割肉。不管是盈利还是亏损,都有其背后的逻辑,不长记性,迟早哪天会栽沟里。在「赌城风云中」有类似这样一句台词:让人去赌场,并一直待在赌场,堵的越多总会输的越多,赌场总是赢家。赌场赢的关键在于如何让赌徒一直待在赌场,就算出去,也依然会回到赌场,股票投资市场虽然和赌场差别很大,但本质上有些地方是相通的,对于普通人,一直待在股市中,却不去学习证券金融市场运转规则,不去了解国家政策导向,迟早有翻车的一天,虽然对于整个市场来说不一定有赢家,但赌徒肯定会是输家。

  有人说,人只能赚到自己认知范围内的钱,但没人说,会亏损到什么程度。所有的亏损都来自于赚钱的欲望,越想赚钱可能亏损就越大,对于理财,止损线和止盈线同等重要,这两条线每个人心里都应该有自己的尺子,当然也得根据当前的市场环境和自我认知进行动态调整。见好就收,见衰则退,说起来容易,但做起来何其艰难,早早退出的懊恼,越陷越深的后悔,这些事情只有亲身体验过,痛过之后长了记性,才能有自己的认知,光凭运气挣的钱,指不定哪天就会连本带息的还回去。亏损是个无底线的事情,短时间大亏,很多人可能就直接退场不玩了,怕就怕在钝刀子割肉,割几天又喂点好的养几天,亏麻了又还有点希望,碰到这种情况更加需要慎重,需要费心费力收集更多的信息做决策,所以与其在后期劳心劳力,不如前期就先做好各项调查准备工作,而且做事的心态也不一样,每一笔投资都应该有充分的理由,不然不投会更好。

  在这个人人都想赚快钱的时代,国内市场都是很浮躁的,都堵的是自己不是最后一批入场的。很多人也知道快和慢都是相对的,投机倒把拼运气是快,但总归不能长久,除非完成资本的原始积累之后立即转型,不然总是要还回去的。在股市里遨游,不亏就是赚,能保住不亏,稳扎稳打,从长时间维度上来讲,不一定就会比“快”方法慢。Shaun 这三年的投资收益不能说一点没有,只能说和存银行差不多,相当于是玩了三年,也还算能接受,就当是白嫖了些股市经验吧 :P。这三年可以说是入市即巅峰了,经历过连续几个月的万亿交易量,也经历过上证 2800 以下,算是过了一波小牛熊,不说掌握了多少金融相关的专业知识,但一些常识性的经验还是积累了一些。Shaun 总结的经验主要有:1、万亿成交量的市场,需要慎入;2、当不确定甚至不知道买啥的时候,就不要动;3、当一个热点被广泛讨论的时候,可以考虑退出了;4、尽量不要去买大股东在减持的股票,买家不如卖家精;5、两会前一个月的市场,慎入,两会期间可以酌情考虑入场;6、10 月份可以考虑一波白酒,年后卖掉;7、最重要的是,拿来投资的钱一定得是可预见的未来三年内不会动的钱。

后记

  难得写万字长篇,这次看完『纳瓦尔宝典』,又兼之正好工作三年(拖延症拖到快四年了 😅),算是跨过了人生的一道小槛,所以难免会有些想记下来的一些东西,也算是总结一下这三年来的工作生活思考。或许下一篇万字长文是十年回顾,也或许没有,毕竟自古苦难多诗文,天降大任于人,天不降大任于人,经历一样,唯一的区别在于是不是天命人。道阻且长,且行且顾。

22 年获得技能:接活达人
22 年获得成就:三年已到

2021 年小结

2022-02-20 09:46:12

  纵观宇宙史,生物史,人之一生,不过沧海一粟,弹指灰飞,若有重来,何必重来。人生一字,莫过于拼,为私欲者有之,为利他者有之,为后代者有之,为权利者有之,为名声者有之,为理想者有之,。。。不拼之人,难存于世,众生皆苦,苦中作乐。  ——鲁迅没说过文集

前言

  21 年,工作上第一阶段的目标算是提前半年完成了,非常感谢前领导的赏识,至于生活上第一阶段的目标感觉还是遥遥无期。

工作篇

  21 年,同样一直在学习,感觉全年都在用新事物完成工作,从学习 Scala,Go 到 OSS,K8S,再到熟悉 macOS,Vim。用这些新学的东西从 0 到 1 完成了一个半项目,一个项目是地图切片系统,将 GIS 数据以 S2 网格的形式进行重新分组管理,这个系统算是优化到了 Shaun 能优化的极致,内存和性能之间达成的 trade-off,单机版可以最大程度的利用多核 CPU,集群版同样可以充分发挥多台机器的作用,这个项目算是 Shaun 花大力气做的第二个项目了,同样的满意与自豪,希望能继续发光发热。至于那半个项目,只能说是开了个头,算是 shp 数据的版本管理系统,支持正常的 CRUD,空间查询以及分析能力,初步的属性和几何信息版本管理,这个项目没有做完,算是留下了一点小遗憾。不过 22 年再回过头去看,继续做下去的话,会碰到很多难点,有些问题,对 21 年的 Shaun 来说可能是无解的,甚至可能是导致项目做不下去的关键问题。

  21 年,人生中第一次跳槽,要说原因,可能也就是想换个环境,接触不同的人,当然也有一部分钱的原因,更重要的还是想出来看看,看看其他的一些流程方案,加快自己的成长速度,正如 Shaun 在学生时代说的,换个环境能使人成长的更快。确实,跳槽了之后能明显感觉到自己做事的一些变化,每个环境对人的要求是不一样的,不谈孰高孰低,只是不同的方面而已,综合这些方面,才能更好的应对后续碰到的一些困难以及有更好的发展前途。

  21 年,工作上最大的收获不是做了多少项目,学了多少新技术,更不是跳槽涨了多少薪,而是跳槽后心态和做事方面的一些转变。以前虽然嘴上说着社畜社畜,但总还是一种学生心态,年轻气盛(年轻人不气盛还叫年轻人吗 ๑乛◡乛๑ ),做事钻牛角尖,只想尽最大的努力做好一件事,一心多用就比较烦躁,有时也大手大脚的,还好是 toB 的行业,有足够的时间来打磨和优化,也是真的感谢前领导的赏识和放任。跳槽之后,感觉自己做事的心态一下就放开了。在新公司,学到了一个新词语——确定性。

  向上管理又同时不唯上,却是不简单,保障确定性就是一种比较好的做法,所谓的确定性就是能够完全把控一件自己负责的事。确定性,说到底也就是数据,有些什么事,分别是什么,分别有多少,工作量多少,计划排期,现在的进度,成果如何,剩余情况,预期情况,风险情况,牵扯的上下游安排,碰到的问题与困难,可能的解决方案。并不是说一定要有阶段性的成果才算确定性,每次汇报,都能把上面这些问题说清楚,也是一种确定性,能够确定这些东西也是自己能力的一种体现。领导关心的也是这些数据,向上负责,同时也是对工作负责,对自己负责。

生活篇

  对目前的 Shaun 来说,生活和工作基本没啥区别,工作在 Coding,生活有时也会 Coding,唯一的区别在于,工作是为了生存,生活是为了兴趣。生活算是工作之余的放松,所以关于生活能写的确实不多。

  21 年,虽然疫情还在持续,但常年待在租房里还是有一些出去玩的冲动,遂去了一趟西湖,人确实很多,风景也没有让人耳目一新的感觉,有点名不副实了,还没有旁边的龙井村好玩,杭州的交通也是一场不太美好的出行体验。出去玩,主要是为了散心,这个目的算是达到了。

  21 年,本来想去一趟黄山的,但由于自己懒得动,还是没去成,这不得不说是一种遗憾了,换了个城市,再想提起勇气去,就不知道是猴年马月了。换城市这件事,Shaun 也认真思考过,代价确实比较大,或许将来有一天,Shaun 会因为这个决定后悔,当然也或许不会,Shaun 一贯的认知就是有钱在哪都舒服,没钱在哪都难受,最终还是 follow my heart,决定趁着年轻,多出去看看,毅然决然的走出过去两年多舒适的工作和生活环境,来到这个陌生的环境重新开始,这件事,算是为平淡的生活增加了些许起伏。

  刚来到新城市,虽然觉得一切都比较新鲜,但还是被新城市恶劣的天气环境给搞的很不爽,不过还算运气不错,只看了一家就找到了 Shaun 还算满意的房子,新城市的房租确实要高一些,而且中介费居然要一个月的房租,这着实是有些高。新城市的防疫政策对底层打工人没有丁点儿人文关怀,部分小区的看门大爷是真大爷,就像菜鸟程序员写的低级 robot,逻辑写的死死的。防疫软件也是垃圾中的战斗机,纳税人的血汗钱也不知道有多少进了个人口袋。

  理财方面也开始接触一些更专业的知识,国内金融从业资格考试主要有四个:证券从业资格考试,基金从业资格考试,银行从业资格考试,期货从业资格考试,都有对应的统编教材,并不是说要一定通过这几个考试,而是可以从这几门考试中,对国内金融市场有一定的认识,不是完全的小白(真要参加考试的话可以看看这个 证券资格证考试要准备多久? ,刷题的话就找个 app 就行,刷题学习法 😅),一般看完前两个从业资格的备考资料就差不多了,有个基础的认识,后期的交易策略或计划就只能根据个人的情况慢慢摸索了。至于 21 年的理财成果就不是很好了,把 20 年赚的又亏回去了,主要是出于 20 年的乐观心态,觉得互联网还能再涨点,就一直没卖,没想到 21 年的国家政策对互联网这么不友好,想要挣钱,还是得跟着政策走 ¯\_(ツ)_/¯。

总结

  前路漫漫,不问对错,不求利弊。每个公司做事的风格是不一样的,这种差异性才是需要学习的地方,也是能让人快速成长的基础。年纪越大,越觉得人生若只如初见是一种奢侈。独行备艰难,莫忘守初心。

21 年获得技能:学无止境
21 年获得成就:重新开始