之前查看 NodeJS 项目的磁盘占用空间一直在增长,而且每次重启之后磁盘占用空间又急剧下降,然后又开始随时间增长。这个现象非常不符合预期。
du
大法首先是使用了 du
命令,查看了这个项目容器中,到底是哪些文件占用了磁盘空间。然而一无所获,根本没有找到大量占空间的文件/目录。
lsof
大法既然 du
查不到,那试着查下当前打开的文件列表。不查不知道,一查吓一跳,发现了一堆已经删除但未正确释放的文件,大概像下面这样(从网上找的示例)。
1 | mysqld2660mysql4uREG8,20524290/tmp/xxxx (deleted) |
到这里,问题就已经很明确了:项目中某些地方打开了这些文件,在没有正常关闭的情况下,被其它逻辑给删除了。
首先找到了几处删除的逻辑,定位到被操作的文件,然后再顺着往上找,找出所有可能操作这个文件的地方,然后就找到了 isomorphic-pkg-reader 这个库。这个库的源代码挂在了 TencentWSRD 这个 OWNER 下,但已经 8 年没有更新过了,估计也没有什么人用了。
这个库使用了 yauzl
来懒解压 zip
文件,从中读取需要的内容。但是并没有按照 yauzl
的官方文档正确关闭文件,也没有暴露接口给用户手动关闭(真的是造孽啊)。
If close() is never called, then the zipfile is “kept open”. For zipfiles created with fromFd(), this will leave the fd open, which may be desirable. For zipfiles created with open(), this will leave the underlying fd open, thereby “leaking” it, which is probably undesirable. For zipfiles created with fromRandomAccessReader(), the reader’s close() method will never be called. For zipfiles created with fromBuffer(), the close() function has no effect whether called or not.
在定位到关键问题之后,我尝试着给这个库提了 PR,但已经快一个季度了,都没有任何回应。也许连之前的维护者也已经放弃这个库了?
无奈之下,我只好把库的源代码拷贝到自己的项目中,然后进行一点点「魔改」,解决这次的「磁盘泄露」问题。
如果你也恰好用了这个库,也可以参考我的 PR 内容处理一下。
相信大部分开发或运维同学,都有接触和使用过 .env
文件,不少的框架和工具都支持从 .env
文件中加载环境变量,比如 PHP 语言的 Laravel 框架或者是 Docker 的系列工具。
但是 .env
文件的读取,基本都依赖特定的语言库或者是特定的工具,如果希望在任何一个 Linux 或者类 Linux 环境中,加载 .env
定义的环境变量,并给后续的程序使用,又要怎么做呢?
在尝试开发这个功能的时候,我有想过利用 dotenv
作为关键词去搜索,看看前辈们是如何解决的。
但很不幸地发现,并没有找到非常官方的介绍,也就没有官方标准,而是每个语言库对其各自理解和实现。
好在各自实现的功能相差无几,基本都能正常理解以下的定义方式,也就没有太多转换的困难。
1 | KEY_1=VAL_1 |
对于各种语言库,它们大部分可以利用高级语言去编程,配合各种高效的库,可以很方便地加载 .env
文件的内容并处理。但对于纯粹的 Shell 来说,就没有那么容易了。
使用 Shell 加载环境变量,一般考虑直接使用 source .env
或者 export KEY_1=VAL_1
的方式。但无论是哪种,都不得不考虑一些特殊情况。
首先第一个问题就是 KEY 的合法性,只能允许英文字母和数字以及下划线等字符,且不允许以数字开头。对于不合法的 KEY,在加载环境变量前需要进行过滤,否则可能导致脚本异常。
用户在使用引号时,可能会出现五花八门的情况:
要如何正确的理解用户意图,或者引导用户按我们的处理原理去使用引号,是非常困难的事情。
如果变量值没有被引号包裹,但包含了空格或者其它特殊符号,在使用 source
和 export
时,也会出现如 syntax error
的错误,导致后续的其它变量无法正常设置。
对于变量值是多行文本的情况要如何处理,也是一个比较棘手的问题。比如用户可能是这样定义的变量:
1 | KEY_A=any |
又或者用户在 .env
文件中是这样写的:
1 | KEY_B=any\nmultiple\nline\nvalue |
要怎么样做,才算是理解了用户的真正意图?
在互联网上一番搜寻之后,得到的答案无外乎 source
、export
、eval
几种方式,但无论哪种都没有完全解决上述的几种问题。
于是,我去对比了 GitHub Actions 和 GitLabCI 的做法:
鉴于 GitLabCI 的不靠谱行为(文档和实际行为不一致),而 GitHub Actions 给了一个相对完美的解决方案,而且 GitHub Actions 作为业界标杆,按照它的方式来实现,也是比较可以接受的。
模仿的目标确定了,接下来就是要用 Shell 来实现 GitHub Actions 的逻辑,同时要尽量兼容 sh
、bash
、zsh
的不同语法。废话不多说,直接上代码:
1 | ENV_FILE=".env" |
以上就是本次折腾的心历路程,如果对正在冲浪的你有所帮助,那便是极好的。
本文主要是方便科研及工作上有需求的同学,请在遵守相关法律法规前提下使用网络加速服务。
最近跑路的机场有点多,之前一直在用的 BlinkLoad 自从关站之后,再也没有消息。在接触 BlinkLoad 的同时,也接触的另外一家 WavesLink,本来觉得用得挺稳的,现在也疑似跑路。
跑路的机场一个接一个,而现存机场的套餐价格也基本在 20+ RMB 起步,对于轻度用户和想购买备用机场的朋友,实在是不太友好。
向朋友打听了下,发现有一家叫「万城」的新机场还不错,虽然才开了不久,但试用了几周下来,机场节点丰富而且稳定,而且套餐比较灵活(最低月付 12 元),无论是作为轻度用户的入门机场,还是作为老鸟们的备用机场,都是一个不错的选择。
万城专线是运营在温哥华的全专线高端机场,全域企业级跨境专线,特殊时期依然稳定,高峰时段网速保障。
节点丰覆盖近40全球边缘地区小众节点,有24小时实时在线客服和定制客户端。
万城机场的团队在温哥华,除非机场主长期不盈利,否则大概率不会因监管问题而关停。
目前提供了按周期和按流量两种计费方式:
按周期套餐和其它机场的计费模式相差不大,都是每个月固定流量上限,用完即止。
目前机场总共提供了迷你、入门、标准、专业、独享几个套餐,价格从 12 元到 138 元每月,流量从每月 20GB 到 760GB。
按流量的则比较适合于月流量不稳定的用户,分为迷你、入门、标准、专业四档,价格从 68 元到 598 元。
其中比较有意思的是,迷你和入门是按年付费,而标准和专业则是一次付费永久使用。
套餐灵活是万城机场的一大特色,无论是轻度还是重度用户,都能找到合适的套餐。
比如博主本人,则对「按周期的迷你套餐」和「按流量的标准套餐」比较感兴趣,非常适合作为备用机场,以防哪天其它机场连不上,可以救急用。
和其它机场一样,万城同样无障碍解锁 Netflix/TikTok/Hulu/HBO/TVB/Disney+ 等流媒体。
除此之外,机场还支持访问和使用当前火热的 ChatGPT/BingAI 等前沿 AI 平台,不用担心被防火墙拒之门外。
万城的节点是我目前见过覆盖地区最多的一个,有着非常多的小众地区节点,对于需要海淘或者特殊地区的访问 IP 的同学,简直是福音了。
目前支持的节点:香港、台湾、美国、日本、新加坡、马来西亚、菲律宾、韩国、印度、澳大利亚、以色列、加拿大、墨西哥、阿根廷、巴西、智利、英国、法国、德国、俄罗斯、西班牙、瑞典、意大利、荷兰、波兰、南非、土耳其、新西兰、泰国等 30+ 国家和地区。
万城支持使用支付宝付款,对国人友好。除此之外,它还支持虚拟货币支付,对于想在这个裸奔的互联网环境下依然保留一些隐私的同学,也是一个不错的支付选项。
万城机场虽然刚成立不久,但在节点资源、套餐设定、支付方式等方式上,都做得比较不错。目前,TG 群也已经是千人规模,也从侧面反映出它的受欢迎程度。
本人测试了几周,在稳定性和冲浪流畅度方面,它的表现也都还是不错的,所以在这里推荐一下。
如果你也恰好有类似需求,不妨戳 这里 注册个账号体验体验。另外,下单时可以从以下优惠码中选择使用:
vcity80zs
:8 折优惠,适用于所有套餐年付及以上(不包括「计量包」)vcity90zs
:9 折优惠,适用于所有套餐(包括「计量包」)nobody
:88 折优惠(专属优惠),适用于所有套餐和支付方式launchd is an init and operating system service management daemon created by Apple Inc. as part of macOS to replace its BSD-style init and SystemStarter. There have been efforts to port launchd to FreeBSD and derived systems.
如 维基百科 所说,launchd 是 MacOS 上所使用的一套服务管理框架。它作为系统启动的第一个进程,系统的其它进程均由它直接或间接创建。
1 | ➜ hunterx.xyz git:(master) ✗ ps -ef | grep launchd | grep -v grep |
在 Linux 系统下,有一个与之对应的进程为 systemd,它的对应管理工具为 systemctl,launchd 的管理工具就是下文所要介绍的 launchctl。
launchd 对一个服务的管理行为需要记录在以 .plist
为后缀的 xml
文件中,通常我们将这类文件称为 plist
文件。plist
的定义语法可以参考 文档,以下是一个简单的示例:
1 |
|
不同类型的 plist
文件存放的目录和运行用户有所不同:
类型 | 路径 | 说明 |
---|---|---|
User Agents | ~/Library/LaunchAgents | 为当前登录用户启动 |
Global Agents | /Library/LaunchAgents | 为当前登录用户启动 |
Global Daemons | /Library/LaunchDaemons | root 或者通过 UserName 配置指定的用户 |
System Agents | /System/Library/LaunchAgents | 当前登录用户 |
System Daemons | /System/Library/LaunchDaemons | root 或者通过 UserName 配置指定的用户 |
使用 launchctl
可以非常方便地对查询/添加/删除服务,以下是我经常使用到的一些命令:
1 | 列出当前加载的服务 |
如果一个服务之前已经被加载,使用 launchctl load
命令时会报错,可以尝试先对服务进行卸载:
1 | launchctl list | grep xyz.hunterx.plist && launchctl unload xyz.hunterx.plist |
如果启动的服务需要一些环境变量(如用户登录后自动 source 引入的变量),需要在配置中使用 EnvironmentVariables
定义:
1 |
|
ChatGPT —— OpenAI 训练的大型语言模型
由于 OpenAI 平台的限制了国人使用,普通小白想要体验 ChatGPT 的难度不是一丁半点!
一边是炒得火热的 ChatGPT,一边是难以绕过的平台限制,想体验又没有办法,真是恼火!
前段时间,就有朋友找到我,问能不能把 ChatGPT 接入到 AI Pocket 小程序,让他们也体验体验,感受下高科技。
于是我就研究了下相关文档,找到现成的 API,以及热心网友提供的 Hack 操作。
然后,就在一个月黑风高的夜晚,小程序接入 ChatGPT 就有了雏形。
经过最近一两周的试用,整体还算稳定,所以也在这里分享给网友。
废话不多说,直接扫码即可体验,但需要注意:
最早接入 ChatGPT 模型的 Hack 方式已失效,现在使用的是 GPT-3,效果类似
接口有限流,提问请慢点
小程序有广告(用广告发电)
菜刀再锋利,也需要刀法
ChatGPT 功能强大是无须置疑的,如果你在对话过程中,没有得到想要的答复,可能是使用姿势不对!
这里列举一下“调教方式”,在对话开始前输入,或许会有意想不到的收获!
更多调教方式:https://github.com/PlexPt/awesome-chatgpt-prompts-zh
我想让你充当 Linux 终端。我将输入命令,您将回复终端应显示的内容。我希望您只在一个唯一的代码块内回复终端输出,而不是其他任何内容。不要写解释。除非我指示您这样做,否则不要键入命令。当我需要用英语告诉你一些事情时,我会把文字放在中括号内[就像这样]。我的第一个命令是 pwd
我想让你充当英语翻译员、拼写纠正员和改进员。我会用任何语言与你交谈,你会检测语言,翻译它并用我的文本的更正和改进版本用英语回答。我希望你用更优美优雅的高级英语单词和句子替换我简化的 A0 级单词和句子。保持相同的意思,但使它们更文艺。我要你只回复更正、改进,不要写任何解释。我的第一句话是“istanbulu cok seviyom burada olmak cok guzel”
我想让你担任Android开发工程师面试官。我将成为候选人,您将向我询问Android开发工程师职位的面试问题。我希望你只作为面试官回答。不要一次写出所有的问题。我希望你只对我进行采访。问我问题,等待我的回答。不要写解释。像面试官一样一个一个问我,等我回答。我的第一句话是“面试官你好”
我想让你做一个旅游指南。我会把我的位置写给你,你会推荐一个靠近我的位置的地方。在某些情况下,我还会告诉您我将访问的地方类型。您还会向我推荐靠近我的第一个位置的类似类型的地方。我的第一个建议请求是“我在上海,我只想参观博物馆。”
我想让你扮演讲故事的角色。您将想出引人入胜、富有想象力和吸引观众的有趣故事。它可以是童话故事、教育故事或任何其他类型的故事,有可能吸引人们的注意力和想象力。根据目标受众,您可以为讲故事环节选择特定的主题或主题,例如,如果是儿童,则可以谈论动物;如果是成年人,那么基于历史的故事可能会更好地吸引他们等等。我的第一个要求是“我需要一个关于毅力的有趣故事。”
我需要有人可以推荐美味的食谱,这些食谱包括营养有益但又简单又不费时的食物,因此适合像我们这样忙碌的人以及成本效益等其他因素,因此整体菜肴最终既健康又经济!我的第一个要求——“一些清淡而充实的东西,可以在午休时间快速煮熟”
我希望你表现得像个数学家。我将输入数学表达式,您将以计算表达式的结果作为回应。我希望您只回答最终金额,不要回答其他问题。不要写解释。当我需要用英语告诉你一些事情时,我会将文字放在方括号内{like this}。我的第一个表达是:4+5
]]>作为一名营养师,我想为 2 人设计一份素食食谱,每份含有大约 500 卡路里的热量并且血糖指数较低。你能提供一个建议吗?
已经流逝的时光总是如此快,就好像一眨眼便过完了一年,现在也已经是二零二三年了!
尽管疫情封控伴随着过去一年,但生活一样很精彩。
一月与二月,我们还沉浸在春节的喜乐氛围中,第一次在深圳看了灯会。
三月,深圳疫情严重,大部分时间居家办公,没有了公司食堂,我们开始了买菜做饭的日常生活。
四月,我下定决心离开腾讯,递交了辞职申请后,开始给自己放假:重游深圳著名景点——世界之窗、再次光顾珠海长隆、感受日月贝的大风吹、清远漂流一日游。
五月,给我们的爱情中长跑画上一个句号,来了一场只有两个人的求婚仪式。
六月,在深圳南山区民政局的见证下,在一个大雨天,我们领了证。然后就开始打包行李,向成都出发!长途搬家总是会让人翻出来一些老物件,看着伴随着我五年的水杯,不得不感叹我真是一个不(非)离(常)不(贫)弃(穷)的人。
七月与八月,是与成都及周边逐渐熟悉的两个月:在环城绿道骑行领略公园城市之美,也游览黄龙、九寨沟、熊猫基地等成都及周边景点。
九月,不禁夸的成都也封控了。宅家无聊的我们,开始玩起了奥德赛和健身环,常常沉迷游戏到凌晨。
十月,是筹备婚礼的季节。在亲朋好友的见证下,我们举办了一场低调但不失喜庆的婚礼。在疫情政策频频变化的时间段,还能踩着点把婚礼办好,真的是走了大运:本应是阴雨的婚礼当天却是意外的好天气、婚礼第二天政府下发不允许办婚礼的通知。
十一月,在电车与油车、领克与吉利之间反复横跳,最终顺利提了车上了牌,开始和交警斗智斗勇的挪车游击战。很不幸的是,在这场游击战中,我方最终因吃了三张罚单败下阵来,灰溜溜地租了个地下车位。
十二月,主题是杨(阳)过与杨(阳)康。不出意外地感染了新冠,又非常幸运地只是轻症,在药物缺乏的情况下,顺利过阳关。年末最后一天,我们去成都 IMAX 天花板影院看了阿凡达,又驾车在马路上看了一眼双子塔灯光秀(平平无奇),然后给二零二二年画上了一个不圆满的句号。
专家建议阳康后至少休息一个月,所以我也暂时不去计划未来的事情了!
二零二三也许会更差,但不管怎样,能好好活着,有家人朋友的陪伴就是最大的幸福。
最后,再祝大家新年快乐!
]]>故事背景:自防疫政策变化后,大家不需要再凭绿码/核酸进出各种公共场合
那本来可以是一个风和日丽的、宅家保平安的周末下午,却因为一次即兴的外出乱逛破灭了。
那是一个不太大、不太新的球馆:总共5/6块场地、光线一般、通风看起来也一般。前台没有人,连老板也混在球场里打球。场馆里人倒是不少,无论是打球的还是等着打球的,很少看到戴着口罩的。
因为没有空场了,社牛家属就现场挑了个对手,在别人的场地上打了 20 分钟。离开球馆的时候,家属就一直在打喷嚏,那个时候我就在想——完了,毒株躲不掉了。
后来我们还去了商场,但商场人少得可怜、且几乎都戴着口罩,相比较之下,感染的可能性似乎小一些。
这天起床之后,我感觉到右手臂莫名酸痛,然而最近并没有做过手臂的锻炼。
接近中午的时候,开始有些腹泻,呼吸也开始有些许热感,而且整个人略感疲惫。
下午接近晚上的时候,好像一切又短暂恢复了正常,精气神不错,还可以自己做饭炒菜,晚饭后还用健身环锻炼了半个多小时。
多年的生病经验告诉我,此事并不简单。所以晚上洗了澡之后,我就换上了那套最厚的睡衣,为未来的发热排汗做准备。不过,当晚除了莫名醒来几次之外,并没有发烧。
这天依然是轻微腹泻,不同之处在于:喉咙的不适感加重、偶尔头晕头疼。
下午,本来一直是站立办公的我,突然感觉站不住了,就坐在了凳子上想休息一会。强行坐着办公了一会,身体的疲惫感一下指数级别地提升,头和眼皮都很重,再也撑不住了。
然后匆忙在系统提了病假申请的流程,再告知了家属,就火速往被窝里钻了。因为家里也没有体温计,不知道烧到了多少度,只能凭呼吸的热感判断发烧了。
家属下班回家后,就一直伺候我喝冰糖加盐水,还有给我贴冰湿巾降体温,一直折腾到凌晨才有些降到正常体温。而我喝了那么多的水,也都在一次次的排汗降温中起到了关键作用。
起床之后,感觉人好多了,除了饿了一晚有些晕之外,我都可以自己做点早饭来吃。
不过整个人还是比较虚弱和疲惫,体温偶尔会上蹿下跳,所以这一天也是需要在家休息,防止再次高烧。
这一天基本就没有再发烧了,剩下的更多是普通感冒的症状:鼻塞、咳嗽、听力变弱。
听说感染新冠发烧过后,一般都能用试纸测出来阳性,所以我也用了家里唯二的试纸测了下。一测果然是阳性:T 线又深又快地显现,一开始我还有点怀疑,以为这是参照线,还特地去群里问了下朋友再敢确认。
既然确诊了,就打算给自己放一天假,好好休息一下。好巧不巧,家属早上剧烈咳嗽,下午就开始无力然后发烧,我也刚好在家照顾她。
第五天的我,已经没有非常明显的不适感了,也能再次站立办公,虽然偶尔还是需要坐下来休息。同一天,家属发烧也降下来了,开始体验我第三天的反复烧。
宅着的时候没有明显不适,晚上睡觉有时感觉需要用力呼吸,就好像空气不够吸的感觉。期间出去做了次核酸检测,走路太多的话,容易感到累。
小林对于阳一直跃跃一试,在开放政策后出去兜了一圈,然后阳了。
大聪明阳了,但他买的药还在路上,等药到的时候,他的病已经好了。
小林拉着大聪明去球馆,结果大聪明阳了,需要小林照顾他;大聪明退烧后,小林阳了,又轮到大聪明照顾小林。
如果不是买了车,大聪明和小林就不会想着周末要去打球,如果不是去打球,也许就不会在买的药还没到就阳了。
如果不是买了车,也不会机缘巧合地加入了小区群,如果不是加入了小区群,也不会在小林发烧前求到几粒救命的布洛纷。
]]>从腾讯离职后,在深圳狗了半个月后,我来到了成都这座有人吹捧上天、也有人频繁劝退的城市……
飞机降落在成都天府机场后,我们「乘站」了 18 号线地铁,开始了驶向市区的七七四十九分钟之旅。
很不幸的是,上车时就满座了,而路上又一直没啥人下车,所以我们站了一路。万幸的是,地铁上设置了像高铁一样的放置行李的空间和架子,也算是给减了负。
如果要给成都起个别名,我觉得叫「熊猫城」一点都不过分。下了地铁就能看到无处不在的熊猫形象,一种被熊猫包围了的感觉。
在深圳南山区待久了,每次出了地铁站的时候,映入眼帘的总是狭小的空间和拥挤的人流。
而当我从世纪城地铁站出来后,真的有一种「大草原」般的冲击感:平坦、宽阔、几乎没有行人。
PS:当然,在早高峰/晚高峰时,人潮汹涌也是另外一种壮观。
在成都也勉强算待了两个月了,大概也是有一点发言权,针对我觉得几个比较关键的点,分别聊聊我的见闻。
都说成都很少能看见太阳,包括之前给我找房的中介也说「成都每次出太阳,大家都要发朋友圈」以及「成都几乎看不到蓝天白云」。
实际上,也许没有网上传的这么糟糕。起码在夏天的这两个月里,太阳的出勤率还是相当高的,出门不打伞就等着晒伤吧!
但是,成都的空气质量真的比不过深圳、厦门等沿海城市。即使天气预报的空气质量为优,也总有一种雾蒙蒙的感觉。
只有大雨过后的晴天,或者是盛夏的傍晚能看到比较通透的蓝天白云。尤其在有晚霞的时候,一片红云半边天,极具观赏性。至于冬天的天气如何,我们拭目以待。
那么多打工人往成都跑的一个重要成因便是成都的房价了,但我觉得成都的房价相比它的平均工资来说,诱惑力也并没那么高。
下图是从网上截的 2022 年 7 月的成都平均房价数据,几个中心城区的数据(成华区、锦江区、金牛区、青羊区、武候区)相对准确,其它区域暂时忽略。
IT 从业者聚集的比较多的是高新区(或者武候区),目前高新区较新的房子均价在 3 万一平左右,单价稍低的小区又都是 140 起步(很多 200 平起步)的大户型,总的购房压力也不小。尤其现在房地产企业爆雷的消息频出,掏空钱包买期房的心理压力也大。
我想说,打工人来了就不可能躺平
都说成都是一个悠闲的城市,但我认为那是对于住在老城区的老成都人而言的,找个茶馆安静地坐着,享受他的岁月静好。
刚来成都的那几天,早上出门去金拱门买早餐时,路过世纪城地铁站,那密密麻麻的人流,并不比春运逊色多少。取餐时,因为手机问题有点慢,后面的打工人就用一种极其不耐烦的语气催促着。
PS:上班早/下班早的人很多,也许「下班早的人多」可以让你觉得成都没有那么卷
作为一个不称职的「吃货」,爱吃的就那两三样,所以对于大家所说的「成都美食多」,我的感受并不强烈。
想在大众点评上找几家餐馆探探店,看来看去都是「兔头」、「自贡菜」、「蛙」等关键词,兴趣全无的我就把 APP 关了。也许是我住的地方太偏僻,离真正的美食中心太遥远。
不过话说回来,相比深圳厦门,大型商超负一楼的面食店倒是比较多,又可以愉快地吃西北的特色面食了!
四川及周边可以游玩的景点倒是很丰富,光成都市内就有好几种玩法:
出了成都,还可以走川西,去周边的甘孜州、阿坝州旅游。截止到目前,我也只不过去逛了下九寨沟,在成都看了下大熊猫,还有很多景点没有打卡。
在深圳南山区,行人、自行车、电动车一起挤在那小小的人行道上,行人和骑行的人都难受。难以想象,如果有视障者出行,该是多艰难。
成都这点做得很不错,自行车道和人行道分开,自行车道很宽,我甚至都觉得比机动车道还要宽,简直是骑行者的福利。
除此之外,成都修有一个总长度 100 公里的环城绿道(又称成都天府绿道),把桂溪生态公园、青龙湖公园、锦城湖、三圣乡等 121 个特色生态公园串联起来。找个太阳快下山的下午,沿着绿道骑行,可以一路感受不同的田园风光,令人心旷神怡。
来成都之前,在深圳已经体验了几个月的「每两三天核酸检测一次」的痛苦。来成都之后,除了刚入川的三天两检,基本摆脱了保质期只有 48 小时的噩梦。
PS:当然,现在疫情又反复了,打工人又开始了三天两检的要求!
其实,我觉得这个话题基本可以不用讨论。
我在厦门、福州、深圳这么多城市待过,也未曾觉得线下购物买的东西会有明显的价格差异,即使有差异也不会影响到正常的消费。大件商品基本都靠网购,就更不用受所住地区的物价影响。
所以日常消费的大头还得看房租水电
拿打工人聚集地高新区和深圳南山的房租做个简单的对比:以深圳南山万象新园为例,3500 能勉强租到一个合租房的独卫独阳台的 30 平左右的主卧;在成都世纪城附近的小区,3500 能租到一个 70/80 平甚至更大的套二/套三。
说起水电消费,另外有个值得一提的点就是四川电网每年 6-10 月的「丰水期电费返还」政策:在四川地区河流水量充沛的季节,将产生的富余电能以价格补贴的形式返还给广大居民,是四川人民的专属福利政策。
截止到目前的感受而言,我认为成都是不排外的。
不过这里的人习惯性地讲四川话,即使你一直在用普通话和他沟通,除非你主动要求讲普通话。
说实话,我是慌的。
来之前,我特地看了看网上的分析,基本的结论是「成都中心城区发生大地震的可能性比较小」。而且看到一些在成都生活了几年的朋友,他们似乎一点都不担心,我也就慢慢地放轻松了。
PS:毕竟过去的这两个月里,我还没有被地球震过
回到开篇的「有人吹捧上天,也有人频频劝退」的疑惑,如果要给成都打分,我给八十分(满分一百分):
FIRE
(Financial Independent Retire Early)的人群而言,成都定居的吸引力尚可(我更希望选择一个离海不远的小城市定居)To be continued…
如果我将来在成都定居了,或者打算离开成都了,再更新「在成都生活是一种什么体验(下)」吧!
]]>最近因为排查一个日志采集问题,和 docker logs
杠上了,然后便有了本文。
最近,我们发现线上的容器日志会莫名丢失最后一行,经过不断地尝试,最近确认了复现路径:如果日志最后一行不以换行符结尾,则最后一行日志会丢失。
于是我就尝试在本地起服务打断点排查,然而无论怎么尝试,本地都没法复现线上问题。硬着头皮把可能出问题的代码挨个查阅,我也没找到可能和换行有关的逻辑。
就在一筹莫展的时候,我把关注点放在了 docker logs
的 API 上,然后发现了问题所在。感兴趣的朋友可以在本地用以下命令验证起来:
1 | # run a test container |
上述命令在不同平台的行为均有不同(Docker 版本均为 20.10):
同样的代码,在不同操作系统下的行为不一致,这是我万万没想到的。带着这样的迷惑,我向官方提了个 Issue,提供了完整的复现路径。
Docker 的开发很快给了响应,确认这是 docker logs -f
的问题,可能原因是某种竞态问题。开发在 22 版本中对 log follow 的功能重写(该 Bug 被修复),其中较关键的一次 提交 可能将用于修复 v20.10 版本的 Bug。
这里附上该提交的说明,感兴趣的同学可以看看变更代码:
The LogFile follower would stop immediately upon the producer closing. The close signal would race the file watcher; if a message were to be logged and the logger immediately closed, the follower could miss that last message if the close signal (formerly ProducerGone) was to win the race. Add logic to perform one more round of reading when the producer is closed to catch up on any final logs.
Docker 日志一般存储在 /var/lib/docker/containers/xxx/xxx-json.log
文件中,每一行日志存储时会通过 stream
区分 stdout/stderr
,如:
1 | {"log":"2021/08/17 15:17:23 server: Listening on 0.0.0.0:8000...\n","stream":"stdout","time":"2021-08-17T15:17:23.104438582Z"} |
如果我们通过 Docker 提供的 SDK(或者直接和 docker.sock
通讯),调用 Docker API 去获取容器日志,要如何区分 stdout/stderr
呢?这里有个示例命令行,可以参考着在本地执行看看:
1 | docker run --rm --name test alpine /bin/sh -c "sleep 5; echo normal; echo error > /dev/stderr" |
如果用 vi
打开 out
文件,会发现在 normal
和 error
前还有一些奇怪的符号,而这些不可见的字符恰好是 Docker 用来区分当前行是 stdout
还是 stderr
的方式。
1 | # cat out 的显示 |
Docker 输出日志前,使用 StdWriter
对标准输出和错误输出混流,如果需要对混流后的内容分流,则可以使用 StdCopy
实现。我们来简单分析下 StdCopy
的 代码,人工对混流后的内容分流看看效果。
把不可见字符转换成 16 进制
1 | \x01\x00\x00\x00\x00\x00\x00\x07normal\n\x02\x00\x00\x00\x00\x00\x00\x06error |
取第一个字节的内容作判断:normal
一行首字节为 1,表示 stdout
;error
一行首字节为 2,表示 stderr
1 | func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) { |
细心的读者可能发现了,在分流时并不是想当然地根据换行符来分割,而是通过日志前的「长度信息」来判断:\x00\x00\x00\x07
对应的十进制数为 7,表示后方的日志长度为 7 个字节,与 normal\n
长度一致;\x00\x00\x00\x06
对应的十进制数为 6,与 error\n
长度一致。
1 | frameSize = int(binary.BigEndian.Uint32(buf[stdWriterSizeIndex : stdWriterSizeIndex+4])) |
此染色并非大家熟悉的日志染色(即给日志加上跟踪 ID,便于后续的错误定位),而是单纯地希望给 stderr
的输出加上颜色。
关于 Shell 的颜色标识原理和语法大全,此处不便展开,感兴趣的同学可参考 了解 ANSI 转义码的 color 设置。
基于上面对 Docker 日志的研究,一个显而易见的方式则是自定义 StdCopy
方式,然后加上颜色标识。但其时有一种方式更简单直接:拦截进程的错误输出,添加上颜色标识再重定向到错误输出。
1 | # 例子 |
使用这种方法也有一种的局限性,比如 Ubuntu
默认的 shell
是 Dash
,而 Dash
不支持这种重定向,会直接报重定向错误,所以谨慎使用。
本篇完…
]]>最近有一个流水线引擎相关的需求,希望能够按需控制一个 Pod 内的容器创建或者启停,以此来实现流水线步骤执行的控制。
本文主要记录之前研究的一些结论,备忘的同时也给后续有需要的同学一个思路。
熟悉 k8s 的同学应该知道,一个 Pod 在创建的时候,需要把内含的容器指定好,在 Pod 运行之后,无法再新增或者删除内部的容器。
通过研究 k8s 的官方 API 发现,k8s 提供了临时容器(Ephemeral Containers)这么一个玩意,可以在现有的 Pod 中临时运行,以便完成用户的操作(如故障排查)。
通过 k8s 提供的 API,可以非常方便地往一个已有 Pod 追加临时容器(不能修改/删除临时容器),以此实现流水线中特定步骤的执行。
另外一个思路就是像 TEKTON 的实现一样(详见 说明文档):在创建 Pod 时,一次性创建好所有容器,不过每个容器的 entrypoint 都使用定制过的脚本,脚本循环检测「固定路径」的文件,判断是否要执行业务逻辑还是直接退出。
如果觉得这样的控制权不够,还可以考虑修改 Pod 的 labels/annotations,容器监听 labels/annotations 值的变化再发生指定动作(附:通过文件将 Pod 信息呈现给容器)。
总的来说,无论是「临时容器」还是「定制 Entrypoint」都可以达到目的。
前者由于是「新特性」,只有最新的几个 k8s 版本才会默认集成该特性,不一定适用于每个集群。而且由于该特性自带 Debug 属性,临时容器的运行资源可能没有办法保障,对于稳定性/可用性要求极高的场景,不一定适用。
后者的操作比较正统,目前想到的劣势主要在于资源的一次性分配、entrypoint 的定制化这两点:对于串行执行步骤较多的流水线,在分配 Pod 时没法充分利用上现有宿主机的剩余资源;entrypoint 的定制则需要考虑得比较全面,比如后续的日志收集,以及业务自行定制的镜像的兼容性。
PS:这里不考虑多 Pod(每个 Pod 一个容器执行一个步骤),主要是因为不同步骤之前如果需要共享数据会比较困难(需要 NFS 等手段)。
]]>对于使用大仓模式开发的团队,项目代码按目录存放在同一个仓库下,代码和历史提交日积月累,每次跑流水线时如果克隆全量代码,效率非常低。
所以需要一个机制,可以只克隆部分我们关心的文件,本文主要记录之前研究和尝试的结果。
1 | git clone --depth 1 --filter=blob:none --sparse https://github.com/HunterXuan/wx-tfjs-demo.git |
--depth
用于控制克隆的历史版本次数,对于 CICD 的场景,一般只关心最新版本,所以这里指明克隆深度为 1 即可--filter
在部分克隆的模式下,用于指明要克隆的文件,这里排除了所有的二进制文件--sparse
指定使用 sparse-checkout
模式,该模式下仅克隆根目录下的文件,后续可使用 git-sparse-checkout
命令新克隆文件1 | git sparse-checkout set file1 dir1 |
使用上述命令可检出关心的文件和目录,多个文件/目录用空格隔开即可。
]]>我们有大量使用 Nginx 作为反向代理的场景,为了尽量不去修改固化的配置,通常在配置中使用域名而非硬编码 IP。
1 | server { |
本来以为这样就可以万无一失,直到发现待裁撤的机器还一直被调用的情况。
通过一番调研,发现这其实是 Nginx 的一个特性(或者说 Bug):Nginx 在启动时,会对域名做 DNS 解析操作并缓存,直到下一次重启或配置重载。
这个问题的原因和解决办法,在 Nginx 官方文章中均有提到:Using DNS for Service Discovery with NGINX and NGINX Plus。
不过,文中提到的解决办法仅限于商业版本的 Nginx(即 Nginx Plus)。对于开源版本的 Nginx,就还是老实使用 nginx -s reload
重载配置吧。
对于无法登录机器执行命令的环境,建议开发配套的脚本下发/执行的工具和平台,或者增加 cron 脚本定时重载 Nginx 配置。
]]>2022 年 5 月 24 日,星期二,阴转小雨
腾讯是一家好公司,微信支付是个好平台
不过,我最欣赏的还是 38 楼的金钱蛋炒牛肉
早上退了电脑,下午还了工卡,领了离职证明,还在 KM 研究离职 FAQ 时,突然提示账号需要登录却怎么也登录不上……
独自维护一个被频繁使用的历史项目是一种什么体验
处理企业微信/工位电话轮番轰炸是家常便饭,面对各种咨询和问题排查需求,常常需要处于多线程高负荷运行模式。
还会偶尔接受到来自一线开发「你这个系统早该淘汰/重构了」的挑战,以及「今天的问题是因为被 XXX 平台坑了一波」的甩锅。
除了身体上的累,内心也一直有种「工作永远都没有结束」的焦虑感。
随着技术和规范的演进,野蛮生长时期留下来的技术债务总要有人来还,这个重担自然就落在了我这个现任的肩上。比如数据库连接规范改造、PHP 业务上云、开发框架重新设计等。
Each coin has two sides
有时候,维护一个使用者众多的历史项目也并不是那么一无是处:
由于要经常排查疑难杂症,需要熟练掌握基于 Linux/Docker 的问题排查方法,也必须花时间了解公司/部门的各项基础设施的实现原理,比如监控上报、日志上报、服务发现、故障屏蔽等。当然,还有 PHP 生命周期、扩展编译及 core 问题溯源等。
另外,由于接触到的都是一线的业务开发,维护者更清楚一线开发的痛点和诉求,然后打造出更好用的开发框架和平台。
还有就是,作为这样一个平台的维护者,会在组织内部有一定的知名度(哪怕是骂名)。
日常救火的工作模式令人烦恼,尤其是看到其他开发做一些小而美或者参与到大而全的新项目(利于晋级)时,那种沮丧感更加强烈。
组织内外对 PHP 技术栈的一种「不积极态度」,让人自然而然地将自己代入成「孤军奋战的炮灰」,不过我还没有那么无私 —— 愿意牺牲个人帮他人完成人生目标。
不管资本家是出于什么目的,腾讯在生活上的方方面面把员工照顾得很好,而员工只需要好好工作。
除了这些硬件配置,鹅厂的瑞雪文化也做得很不错,在道德、礼仪、职业操守等方面规约员工行为,引导大家和谐相处。在「阿里味」已经成「贬义词」的当下,腾讯的「瑞雪文化」确实还不错。
部门多了之后,在公司标准执行的一致性方面,似乎有点令人唏嘘。其中之一便是脉脉上讨论热度最高的定级定薪问题,比如培训班应届生 siza 事件。
既然说到脉脉,就再扯一下脉脉上另一个热门关键词「嫡系文化」。身处「嫡系文化」氛围浓重的组织时,如果不能想办法加入,那基本上如同打入冷宫的存在了。很庆幸自己虽然没喝到嫡系文化的汤,但也不算吃过其中的亏。
另外一个感触较深的就是「甩锅习惯」问题,当然这只是个人感受,可能并不适用于他人,就不展开了。
WXG 是腾讯的头部事业部之一,由于张小龙向来喜欢小团队作战,所以事业部的人员增长比较克制。相比于其它事业群,在这里能更直接到感受到高层的一些决策思路。比如,小龙偶尔会在工作日的下午搞个线上直播,和大家一起聊天。
微信支付作为国民支付工具,其业务所依赖的各项基础设施在安全、稳定等方面要求很高。所以在这两年里,我还是有学到不少最佳实践,包括但不限于 DDD 的思想与落地、领域建模方法和工具、微服务架构下的服务发现/鉴权/监控告警等。
在「低代码」思想主要在 UI 战场发挥作用的时候,微信支付已经开始用低代码思想来构建研发流程和工具。用工业化、流程化的思维做软件开发,尽管会抹杀业务开发的一些创造力,但某种程度上却可以大幅提升研发效率,达到四两拨千斤的效果。
两年前,脉脉上微信支付的口碑可以用好评如潮来形容,而现在基本上成了「内卷」的代名词。
老板们认为「不卷」,因为还有很多有价值的事情可以做,还有很多事情没做好,远没达到「无效内卷」的境地。
而员工们认为的「卷」却在于,在蛋糕无法做得更大的前提下,即使把这些「有价值的事情」做得更好,个人收益提升也不明显。
对于个人而言,我并不关心卷或不卷的定论,我介意的是个人劳动是否得到了应有的报酬,以及是否有获得了某些成就感。
悟已往之不谏,知来者之可追
两年鹅厂打工之旅暂时告一段落,鹅厂打工仔们江湖再见!
未来,我想去成都的街头走一走,感受一下这座有人吹捧上天、也有人频繁劝退的城市。
To Be Continued…
]]>在我入职腾讯满两年的第三天,在这个动荡的时间点,我发起了离职流程……
两年前,我鬼使神差地拒绝了鹅厂 Offer,后面又莫名奇妙地不做任何 Argue 的情况下,加入了腾讯微信支付。
来腾讯的第一天,导师把一个古老的项目的需求交给我来做,当我打开平台首页时,我脑海里浮现的是「快跑.jpg」。
过去的两年里,我像一个老中医,专治开发 & 运维环节的各种疑难杂症;也像一个扫地僧,没日没夜地清理遗留的历史债务。
如果用一个词去总结,那便是「喜忧参半」,更详细的复盘还是留到真正离开时,在《从腾讯离职是一种什么体验(下)》更新好了。
也许,在这样的大环境下,像我一样勇的人,并不多了
大概是三周以前,决定了要离开,但为了那所谓的两周年仪式感,便往后拖了拖。
直到两周年的第二天上午,我才给组长发了企微,说明了我的想法和离职原因。也许是组长早已换位思考过,明白我离开的决心,在后面的面谈中,没有挽留与拉扯,一切都风平浪静。
在写交接文档的那几天里,几位朋友陆续得知消息,清一色地祝贺我「脱离苦海」,或者是为我过去两年的付出与收获打抱不平。
而我想说,其实哪里都是苦海,只有自己实现了生产资料自由,不再靠「出卖劳力」的时候,才可能尝到甜头。
提离职之前几天的惴惴不安,在提了离职流程的那一刻,全部烟消云散。
这种如释重负的快感,让我想起来毕业答辩后,我和小白坐在交大花园的长椅上,抬头欣赏蓝天白云与飞鸟,那种自由与舒畅。
如果来腾讯前已落户深圳……
如果深圳的限购政策宽松一点……
如果来腾讯前争取到了目标职级和待遇……
如果腾讯不要求入职满一年才可以落户……
如果不需要满一年才能申请晋级……
如果有更多的 PHP 开发伙伴一起分担……
如果第一次晋级申报顺利通过……
如果女朋友在深圳有还不错的工作……
如果……
我对外的离职原因是不打算继续在深圳发展,但在这个简单粗暴的原因背后,其实有很多都没成真的「如果」。
不过,这些都已不再重要,这里引用陶渊明《归去来兮辞》中的一句话,与大家共勉。
悟已往之不谏,知来者之可追
之前参加部门团建,去过一次珠海长隆,拍了很多海洋动物的视频给小白,她很喜欢。
虽然在工作日外出游玩看起来很爽,但时不时的企微和电话打扰,也十分地煞风景。恰好小白也在深圳,就计划五一再好好逛逛长隆,顺便看看珠海市的其它地标。
可惜天公不作美,五一那天全程在下雨,好心情全部被破坏了,就早早地结束珠海长隆之旅。
不知道还要攒多少个离职体验,才可以实现真正的自由
腾讯的离职体验上篇就先这样吧,更详细的体验我们下篇再见!
PS:之前有朋友打算投资搞事情的,或者是有一些需求想探讨的,可以约时间聊起来!5 月中下旬可能是我比较空的时间段了哦~
]]>这家机场已跑路,不再推荐!
本文主要是方便科研及工作上有需求的同学,请在遵守相关法律法规前提下使用网络加速服务。
之前用过一段时间「后浪云」,各方面都还可以,但一直没时间写篇文章介绍下。最近他们家改名 Waves Link,就借这个机会写一写。
总的来说,Waves Link 无论在节点/线路资源或是解锁技术方案上,均有自己的独特优势。
晚上高峰期试看了油管的 4K 视频,体验下来也还算流畅,如果是看一般的视频,问题不大。
除了一个企业套餐外,站点提供了三个套餐,每个套餐除流量不同外,无其它差异。新人用户可以试用「轻量后浪」套餐,体验后若 OK 可以根据平时流量使用情况再换套餐。
若有兴趣,可通过 我的链接 注册账号,在购买时使用本站专属优惠码 nobody
可享受年付 8 折优惠。
在上一篇博文 二零二二之新春快乐 中,我将「重拾 Go」作为 2022 的一项新年挑战。
返乡过年的路上,我在想该拿一个什么项目来试试水比较好呢?
然后想到,之前有折腾过 用 Golang 写一个 BT 服务,核心代码已经开发完毕,稍加整理应该可以出一个 MVP 版本。
于是,就有了 HunterXuan/bt 这个开源项目、公开的 BT 服务,以及这篇文章。
这个项目的核心功能分为数据面板和伺服两大块,在设计和实现上尽量简单。
本项目只使用了 torrents
、peers
两张数据表,分别用于存储种子数据、同伴(上传者/下载者)。
BT 下载的客户端会向伺服上报当前下载种子的 info_hash
值以及自身所使用的 IP/PORT,伺服在接收到请求后,查找 info_hash
对应的种子(如果不存在则新增),更新 peers
表中数据,同时将种子关联的其它同伴的连接信息返回。
数据面板主要面向伺服的维护人员,主要包含种子(总数/活跃数/不活跃数)、同伴(总数/做种者/下载者)、流量(总流量/上传量/下载量)等几个关键指标信息,同时列出当前最热门(下载数最多)的部分种子信息。
推荐使用 Docker 部署,对应的镜像为 hunterxuan/bt,需要设置以下几个环境变量:
APP_NAME
:应用名称APP_MODE
:应用模式(dev
时会自动创建表)APP_LISTEN_ADDR
:应用监听地址,如 0.0.0.0:8888
DB_HOST
:数据库地址DB_PORT
:数据库端口DB_NAME
:数据库名DB_USER
:数据库用户DB_PASS
:数据库密码TZ
:时区,默认是 Asia/Shanghai
目前服务还不稳定,谨慎使用
如果懒得部署,也可以直接使用我搭建的服务,将 http://bt.endpot.com/announce
添加到种子的 Tracker 列表中即可。
又双叕是好久没更了!
想着快过年了,赶紧在一月结束之前,唠叨下过去的一年,顺便立下未来一年的 Flag!
职场圈里比较火的 App 估计就是脉脉了,每天都有老哥在上面吐槽或者炫耀。
刚注册的时候,每每看到有人在得瑟,我就会觉得很 emo,感觉自己很菜或者是自己的选择很错误。但现在,我已经释然了,更多是作为一个吃瓜群众找找乐子。
倒不是因为我变得有多强,或者做了多么正确的选择(进入了所谓的大厂/所谓的 Top 部门),而是我对职场/工作,形成了更坚定和清晰的观点。
我见过不少「谁是最好的/最烂的语言之争」,也遇到过「熟背数据库不同引擎坑点而沾沾自喜」的老哥,给人的感觉就如「小破孩比谁尿得更远」一样幼稚:
代码写得越多,越觉得程序员很像搬砖工
如果以盖楼来类比软件开发,一般码农就好比按图施工的工人(有经验的出活快且活好),架构师则负责房屋的整体框架设计。但无论是砌墙还是设计,都需要按照规范(及业界最佳实践)进行,否则就是豆腐渣工程。
一流企业做标准
如果非要给这行业的从业者定个三六九等,最牛的应该还是那些语言设计者和规范制定者。而大多数开发者,都是在前辈们划定的圈子里玩泥巴罢了。
作为一个程序员,我追求的应当是泥巴带来了什么用户价值,而不是把这坨泥巴玩出了什么花。
城外的人想进来,城里的人想出去
在网宿的时候,总是有用户站在个人角度对产品提各种奇怪的打补丁需求;出来创业后,个人拥有最大的自由度,但是穷得叮当响,最后不欢而散;在虾皮短暂的时光里,觉得都是用一些熟悉的开源组件,担心没有成长;来了腾讯后,被历史代码和技术债务折腾得死去活来。
工作大多数时候仅是一份谋生的手段
经历过毒打之后,我深刻地意识到:没有完美的工作,只有不怕困难的打工人。不过,选择工作依然是要慎重,利益相当情况下,尽量选择坑能接受的工作,那样也有利于稳定。
忘了什么时候,「倒挂」这个词开始流行,然后每年都会有一批老员工因倒挂愤而离职。
在互联网行业,「同工不同酬」的现象太过明显:即使是做的同样的事,在一线大厂拿的薪酬通常要比二三线公司高得多,新员工可能比有经验的老员工薪资都高。
大厂盈利能力更强,自然可以投入更高的雇佣成本,所以有条件的情况下尽量往大厂冲,有了大厂经验(和所谓的光环)之后,再去小厂技术扶贫也会容易一些。
至于新员工待遇倒佳老员工,盲猜是由于需要新鲜血液来搅活老油条们,而各厂在市场抢人也比较激烈,不得不开高价拉人头。至于这里的高价是否真的像 offershow 里的一样到位,可能应届生们还是要多思考和对比,有些话不能说得太明白。
我放弃了这次职级晋升的申报,也许以后也不会再申报
接受支付的 Offer 前,鹅厂的朋友建议我慎重考虑,尤其在职级和待遇这块要争取到位,不然到时很容易后悔。
现在想想,朋友作为过来人给的建议是非常靠谱的:入职后要满一年才能申请晋级,眼看同龄人都已经晋升时,真的是焦虑,这就是所谓的「Peer Presure」吧!
好不容易满一年可以申请,又恰逢债务治理忙碌期,每天用于做答辩 PPT 的时间少之又少。在答辩前一天,才把 PPT 凑好,答辩稿都没准备就上了。
不明真相的群众,以为我债务治理整得有模有样,都开始提前恭喜我了。一个个都带着靠谱的小道消息「忽悠」我,差点给我洗了脑,结果证明都是一波毒奶了!
由于各种原因(此处省略若干字…),我做了这个「违背祖宗的决定」。
不用准备答辩的这几个月,不需要熬夜写 PPT,不需要想着怎么说服八杆子打不着的评委,只管把负责的框架开发好,下班时间简直愉快得很!
身边有朋友选择了远程工作,举家在国外过上了人上人生活:一天八小时工作、周末双休到处浪、工资待遇优厚。
我也常常在思考:如果我选择了远程工作,会是什么样子的?
远程工作,意味着通勤时间归零,不用忍受痛苦的早高峰晚高峰,或者是高价的公司周边出租屋;同时也意味着,没有公司食堂管饭,每天得自己解决口粮;以及可能的孤独感。
好在,我本身并不是很喜欢闲聊的人,甚至我会比较享受孤独感,在家办公有一只旺财或者阿喵陪伴就足矣!
嗯,也许,我更适合远程工作!
年末这些天不是特别忙,手上的活做完可以早点溜了,下班路上也会和同事吹吹牛皮。
小白放假来我这里写论文,顺便陪我过过正常人的休闲生活:闲时有人聊天、偶尔压压马路、逛超市买水果蔬菜、周末偶尔炒个菜。
二零二一年,在大厨的指导下,学会了做几种吃的:红豆栗子糖水、醪糟鸡蛋糖水、清炒时蔬、辣子鸡丁、干煸四季豆、焖饭、泡蛋青菜汤,以及怎么都做不好的粉蒸排骨。
工作是为了更好的生活,而深圳更适合打工人短暂的停留,不宜定居:令人望而却步的房价、拉垮的教育和医院资源、不低的限购条件。
有个多年的朋友(一起参与过奶油葡萄创建),会经常给我发成都购房通的一些文章,也会分享他在成都的悠哉生活。说实话,我是动心的:不算离谱的房价,还不错的医疗和教育资源,充满着诱惑力!
当然,还要纠结工作机会太少、天气不好,以及离家远带来的诸多问题…
之前在西安上学,一入冬心情就容易低落,冷而且空气质量不好是其中最重要的原因。
还记得毕业时坐火车去厦门,早上起床恰好看到窗外的大海,阳光照在海面上,海鸟慢悠悠地飞,整个人心情都很好,当时我感觉爱上了厦门。
深圳大部分的天气都是好的,空气质量优秀且阳光明媚,偶尔的阴雨天也无伤大雅。
假如是成都,可能情况就不一样了:冬天没有暖气,被雾霾支配的日子里没有阳光,人就很容易不开心。
以前觉得鲁迅先生的「我家门口有两棵树,一棵是枣树,另一棵也是枣树」完完全全是在凑字数。不过最近把这个句式一带入到「我的账户里有两只股票,一只是绿的,另一只也是绿的」,马上就懂了,不愧是大文豪,悲悯的氛围感一下就出来了。
在过去一年,除了一些稳定的理财产品,基本就不投资了。想用来炒股的银行卡还没申请下来,避免了一波投资损失,算是因祸得福。
数字货币也不再继续投了,市场风险太高,偶尔用一些外快钱补仓抄底,其它的随缘咯!
保持独立思考很重要
以前,我们接收新闻的渠道比较单一,新闻发布方也比较官方严谨,可信程度高。
现在,每个人都可以在互联网上发声,门槛低且传播速度快,有的媒体为了博眼球,罔顾事实企图制造大新闻。
在这样的背景下,如果我们不能保持独立思考,就很容易被带节奏,轻则被割韭菜,重则形成错误的三观,甚至成为操盘者的刀。
比如,以讹传讹的鹅厂人均七万月薪,或者是鹅厂实施强制到点下班的措施等传言,让我很是困扰!
未来一年,该立些什么 Flag 呢?
语言是一门工具,在不同的场景选用不同的工具,可以发挥最大的开发效率。用 Go 写庞大的单体应用可能是很痛苦的,但在微服务开发方面却有很强的优势。
所以未来一年里,我想重拾 Go,写几个开源小项目;有机会的话,以 Go 作为主要语言,从零到一建立起一套微服务架构。
之前折腾的 AI Pocket 做了点 AR/VR 尝试,从一些小数据来看,体验者的反馈还行。
下一步便是小试牛刀,做一个网页端的 AR 内容自助开发平台,投入市场试试水。
嗯,希望能在一个城市里扎根,拥有相对愉快的工作状态,舒适的生活状态。
希望身边有爱的人,周末当一个炒菜或者短途旅游的阿婆主,最好是有条件养一只萌宠…
定一个小目标:「躺赚收入」完全覆盖生活支出
嗯,就这样,努力实现这个小目标吧!
最后,祝大家新春快乐,万事胜意!
]]>最近身边陆续发生了一些新鲜事,给枯燥的生活带来了一些色彩。
晚上和女朋友聊到这些事时,我们突然发现,其实都可以追溯到在福州创业的那段时光,突然感慨万千。
福州福州,有福之州
在福州创业的那段时间里,人虽然穷,但时间却还是相当充裕,可自主支配的时间较多,创造力也充分得到了发挥。
除了在创业项目上,我们做的很多技术上、业务上的尝试,想方设法造些有趣的玩意,吸引学生、老师、校长买单之外,我自己也在尝试一些新奇玩意儿。
比如,我开始学习炒菜做饭,尝试做一个推广博主,试着开一家淘宝店,逐渐优化我的 AI Pocket 小程序,并因此接了一些外快单,小赚了一笔。
脱口秀里有一个技巧叫「Call Back」,我的生活好像也有看到「Call Back」的影子。
比如,我的开源小程序不仅让我可以赚外快,也让我结识了更多 AR/VR 圈内人,也因此了解到更多有趣的点子。比如,我最近在考虑开发一个类似弥知科技的小平台,也许它以后会深受一小部分 AR 开发者所喜爱,就像几年前我建立的「奶油葡萄」站点一样。
这种「Call Back」效应也可能不仅仅体现在技术方面,也许未来我会成为一个炒菜 Up 主,或者是一家小而美的淘宝店的店长,又或者我会成为一个老师。也有可能,我会组建一个「无聊工作室」,专门整一些好玩但没啥用的玩意。
前几天报了一个叫 IDP 的班,虽说有割韭菜嫌疑,但里面讲到的「废掉一个人的最好方式,就是让他忙到没有成长」,我表示强烈赞同。
回想一下过去的这段时光,我发现成长并不在于做了多少需求,解决了多少问题。
恰恰相反,我的成长来自于闲暇时候的脑洞大开、天马行空的想法;或者是从当前场景抽身出来,自上而下地把过往经验在其它场景实践的过程。
就像你不去买彩票,就永远不可能中大奖一样;如果一成不变,你的运气怎么会发挥作用?
夜深了,不继续展开了,明天又是继续打工人的一天,晚安!
]]>公众号上次发了一个小小的 DEMO 视频,演示了小程序如何识别目标图像,并加载 3D 模型的效果。
然后,感兴趣的朋友们就开始催更了。本篇文章算是对之前的一些尝试做一个总结,给对这个方向感兴趣的朋友提供一些思路。
不过,本文不会过多阐述具体的原理,感兴趣可以看下 AI Pocket 的开源实现。
代码移植过来比较仓促,基本没有考虑工程化的最佳实践,相信阅读起来也是比较痛苦。后续是否会优化,取决于我是否会以此做些商业化的实践,以及是否有更多的业余时间。
注:对实现过程的流水帐不感兴趣的同学,直接拉到最后看原型效果和小结即可
如果一件事情,短期和长期都看不到价值,那就没有尝试的必要
不知道大家在平时生活中,有没有一种被「无聊」氛围笼罩的感觉:一切好像都是在重复,没有啥新鲜的玩法。
玩的手游趋同化严重,换了个地图和皮肤,改了改规则就是一个新游戏;商场与商场间的差异性不大,都只不过是逛逛买买;景点设计也像是出自同一人之手,尤其是一些打着古镇旗号的商业街。
这和 AR 有什么关系?
AR 是 Augmented Reality 的缩写,中文含义为增强现实;与之紧密相关的还有 VR(Virtual Reality),意为虚拟现实。
注:除此之外,还有 MR/XR 等名词,此处不展开
Augmented reality (AR) adds digital elements to a live view often by using the camera on a smartphone.
Virtual reality (VR) implies a complete immersion experience that shuts out the physical world.
简单地说,AR 是利用手机等辅助设备,把虚拟的物体/场景融合到现实世界中;而 VR 则是完全模拟一个虚拟的世界,让人沉浸其中,比如世面上有很多 VR 眼镜。
由于 VR 往往需要专门的设备(而且通常价格不低),无法大规模应用。但 AR 就不一样了:几乎人人都有手机,而且手机性能一代更比一代强,在手机端运行 AR 应用通常不是问题。举几个例子:
你有没想过,个人名片也可以像这位老哥一样骚包?
小朋友的教材平平无奇?那如果可以来个 360 度无死角的 3D 观赏模式呢?
还记得小时候玩的卡牌游戏吗?如果每张卡牌的小宠物都可以动起来,是不是很奇妙?另外,说到 AR 游戏,不得不提任天堂出的「Pokémon GO」。
注:以上提的几个应用来自 10 inspiring examples of Augmented Images。
AR 营销相比其它几个领域,要更为常见。比如丝芙兰 AR 虚拟试妆、宜家新购物 App、可口可乐变身音乐播放器等等。
注:可以从 什么是AR营销? 了解更多有趣的应用
从以上举的这几类例子来看,它们都有一个特点:不是必须品。
也就意味着,用户没有安装 APP 的强烈意愿,更多地希望可以用完即走。
小程序恰好具备这种特性,而微信具备庞大的用户基数,且其社交属性会给裂变效应带来可能,作为 AR 轻应用的主战场,则再合适不过。
AR 一般无法脱离 AI 技术单独存在,比如上篇公众号文章的视频中的示例,我们首先要识别出目标物体,然后还要计算出目标物体的坐标变换,进而加载和变换 3D 模型。其中的目标物体识别,就少不了一些 AI 领域中的图像识别、图像追踪等手段。
图像识别、图像追踪等机器视觉技术其实早就非常成熟,近年来也涌现出很多 WEB 端的 SDK/应用,比如 tracking.js、jsfeat、ar.js 等等。
但是,它们无一例外地是面向 Chrome 等标准 WEB 浏览器,小程序的定制版运行环境根本无法直接运行。举个例子:大多数的库都依赖了 Document、XMLHttpRequest、Worker 等接口/全局变量,恰好都是小程序不支持的。
使用 WebView 内嵌网页,网页直接使用现成的 SDK 固然降低 AR 的实现成本,但与此同时带来的是更高的使用门槛、更高的互动开发成本:
一个字:改!
展开描述一下,就是找到最适合移值到小程序的 SDK,有办法简单适配最好,否则就是弄懂代码后魔改,然后移植到微信小程序环境。
方向确定了,接下来要定一个小目标:实现一个 Demo,能够识别指定的图案,在图案上加载 3D 模型,并且图案运动时,3D 模型也要跟随。
小目标包含两个关键点:
其中,3D 模型的加载和变换是比较成熟的,可以使用 Three.js 实现,官方和民间都有适配库可以选用,这点更多是体力活,还不算是技术上的难点。所以,本次小目标的实现难点主要在于目标识别和追踪。
对 AI Pocket 小程序有了解的朋友应该都知道,这是一个主打 tfjs & 神经网络模型的小程序,包含了一些常见的图像识别示例,比如 COCO-SSD。
一开始,我也是想的使用 SSD 模型实现目标识别和追踪。但很快,我就打消了这个念头:模型需要大量的训练数据来保证准确性,且模型体积(即使裁剪后)也比较大,不太适合在小程序端运行。
就在一筹莫展的时候,我研究了下 AR.js 这个库的文档,发现它提到了 marker based image tracking
这么个关键词,然后顺着线索发现了 jsartoolkit5 这个宝藏库,给困境中的我带来了希望。
jsartoolkit5 是 ARToolKit 的 JS 实现,支持以下几种图像识别和追踪:
JSARToolKit5 support these types of markers:
- Square pictorial markers
- Square barcode markers
- Multi square markers set
- NFT (natural feature tracking) markers
其中,NFT 刚好能满足我的需求:基于它,可以在不使用神经网络模型的前提下,实现任意图片的识别的追踪。
顺着 NFT 这个关键词,我找到了一些看上去还不错的实现,经过代码和文档的粗看,最终只有两个选手角逐到最后:JSARToolKitNFT、mind-ar-js。
JSARToolKitNFT 是 jsartoolkit5 的轻量版,使用 TS 开发,仅保留 NFT 能力,通过 WASM 把底层实现包装起来,外层代码其实较少,代码比较容易读懂。
mind-ar-js 则是纯 JS 实现了 NFT 的功能,基于 tfjs 加速了某些耗时的运算,但是代码的组织确实有些混乱(且注释不足),需要非常集中精神(且有一定的机器视觉背景知识)。
功能相近的情况下,代码越清晰易懂,则越易于改造。所以我首选 JSARToolKitNFT 来尝试,但不久就宣告失败了:
imports
变量在尝试 mind-ar-js 移植的过程中,因为代码实在是太绕了,我曾经想过再次挑战下 JSARToolKitNFT 的 WASM 编译,但依然没有成功,就只好暂时作罢。
在选定 mind-ar-js 之后,我就开始了漫长的魔改(国庆假期也在尝试)。一边阅读理解,一边修改,主要改动的几个地方:
这种大批量的修改,是非常难受的,因为效果出不来的时候,你不确定是库的问题,还是改出来的问题,还是小程序环境的问题。
而且刚开始改,都是可以称为魔改,一心只为实现小目标,哪管可维护性,根本谈不上什么工程化实践,所以现在的代码依然非常难懂。
经历艰辛的魔改过程,原型总算是出来了,直接看下效果吧。
AR 可以给游戏、教材、营销等等场景赋予新的玩法,微信作为一款国民级应用,如果能在小程序中加持 AR,会是一个新的爆点。
但是很可惜,小程序没有提供 AR 相关的 API,需要开发者自行实现。而目前市面上的 AR 库多数针对的标准 WEB 运行环境,微信小程序没有办法直接使用。
很幸运的是,我通过一系列的努力,在微信小程序中实现了图像识别与追踪,并在其基础上成功加载了 3D 模型,实现了一个粗糙的 AR 原型,证明了这个想法的可行性。
小字:这里有未来的财富密码,你学废了吗?
长话短说,本次代码主要更新了以下几点:
我希望把更多的 AI 模型装到 AI Pocket 上,在小程序上就可以跑机器学习/深度学习模型,打造一个「流弊」的 AI 口袋。
正如之前立的 Flag,我老早就想把 handpose、facemesh 等模型移植过来。
然而,小程序有 2M 的包大小限制,tfjs 的核心库再加上各模型库的大小已经逼近/超过了这个上限。而使用分包并不能有效解决这个问题,因为 npm 包默认安装在主包中(主要是懒得折腾)。
所以,支持更多模型和应用的想法,就暂时搁置了。
某一天,一个少年给我提了个 Issue,里面提到了他使用 tfjs 3.x 的 custom tfjs 特性进行体积裁剪的实践。当时我眼前一亮,觉得又双叒叕可以搞事情了。
总结一下这位少年的实践经验,主要在于两点:custom-tfjs 和 tree-shaking,或者强行归类一下,其实都是 tree-shaking。
tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块语法的 静态结构 特性,例如
import
和export
。这个术语和概念实际上是由 ES2015 模块打包工具 rollup 普及起来的。
tfjs 体积之所以大,是因为包含了巨多的方法;然而实际进行模型预测时,我们仅需要其中的少量 API。
若能使用 tree-shaking 技术精简代码,便可以大幅度减少小程序体积,利用有限体积的代码包,做更多有趣的事情。
以下图为例,使用小程序的构建工具得到的 miniprogram_npm 代码共 1.36MB,而使用 tree-shaking 手段得到的代码(主要是 chunks 和 app.js)共 640KB,体积减少 50%。
尽管这个想法很好,但是一直没啥时间去尝试,也就又搁置了几个月,直到最近的中秋假期。
虽然技术手段是明确的,但改造点带来的工作量也是不小的:
所以,中秋节前一周,几乎每天晚上加完班回来,我还要 Debug 搞到 12 点多才休息(其中有几次是因为接到同事咨询电话,整个人清醒了,干脆接着肝一会)。
直到今天晚上,终于把重构的第一个版本肝出来了!欢迎使用体验!
我发现,在优化体积这方面:
再聊聊 rollup 的一些其它作用:
getInputSize
优先判断输入是否为 HTMLCanvasElement
,在安卓端运行时会产生错误(迷之 Bug),同样也是使用 rollup 插件,可以非常方便地偷梁换柱,让该方法直接返回 Tensor 的大小,完美解决兼容性问题也许是我文档和教程的不完善,有不少的同学联系到我,咨询我如何快速把源代码玩起来,就还挺烦的。
计划有时间补一下文档和教程(或者出个小册子/教学视频),方便众多学生和相关从业者快速上手。
目前代码写得比较赶,有些异常/错误没处理,需要逐渐优化补齐。
目前,趣味篇除了一个「初识 AI」,一直没有新增。
现在基础的模型都算补齐了,接下来重点结合具体场景,出一些有趣的应用。比如:手机算力测试、在线试妆、舞姿测试等等。
也欢迎有想法的同学提 Issue/MR,一起共建 AI Pocket,再次附上开源代码地址:HunterXuan/wx-tfjs-demo。