其实目前我对上海的印象已经开始渐渐淡忘,不过以前的那些朋友依然会有联系,因此对上海的记忆大多是跟他们有关。
我刚来深圳那会儿,对深圳最大的感受就是,这里不仅不会排斥你,还会给你各种方便。就说一个小事,我在上海五年只成功办理了一次居住证,这个办理特别麻烦,因为他需要你有居住证明,居住证明又需要你跟房东协商租赁登记,就意味着要交税,这个税谁出呢?当然只有我这个冤大头了。而到了深圳之后,居住证非常轻松地就办下来了,居住登记非常简单,也不用帮房东交税,而且之后港澳通行证之类的都可以凭居住证来办理,非常轻松。
这种与人方便的欢迎姿态让我挺想落户到这里的,因此等我安定下来后,找了个机会,非常轻松就办下来了。
如果你觉得我在贬低上海,那也不至于,相反,我对于上海的认同感更强,上海离我的家乡挺近,文化也接近,连语言都同是「吴语」,刚毕业就到上海工作了,整整五年,有朋友有同事,我一度认为自己会在上海渡过余下的职业生涯。
我刚来深圳那会儿,依然非常怀念上海的生活,这边的历史文化环境比较薄弱,所以,一旦我想念上海了,我就会去相对有那么些历史的蛇口,连认识女朋友的第一天,我也是带着她去蛇口逛街。
后来了解了,原来深圳的医院、学校以及文化娱乐都在广州或者香港,而深圳只留下钱跟加班。
我觉得真正适应深圳,有几件标志性的事件:
不过,我还是挺想回到五年前,找到那个决定离开上海的我,用我这几年都没穿过的厚实靴子,狠狠往屁股上踢一脚,然后回来继续加班。
不知道是因为人到中年,还是我一直投身的智能硬件行业让我觉得有趣,又或是接触到硬件产品经理这个角色,我最近一两年开始折腾各种电子产品,比如群晖,再比如一些开发板。
最近我觉得自己开始慢慢克服心理的障碍,开始重新写文章了,目前我对自己的要求降低了太多,比如我不必绞尽脑汁去写「精深」的内容,也不必在意写错,更不必为了其他人而写,因为我觉得简单的要求能让我继续写下去。回顾我早期的文章,正如2016年总结 里面说到的,我重新写(是的,也是重新写)的理由也是很简单,「安安静静写点东西」就行了。只不过当时随口说的话,到今天有了不同的理解,正是因为那时候没人关注,也没有任何写作的压力才能让我坚持,现在随便翻看,一堆的水文也能让我乐此不疲地写下去。
这几年来,最大的收获除了中途事业的短暂成功之外,就是成家这回事了,来深圳不久就认识了我现在的夫人,恋爱,结婚,然后就有了女儿。
我是去年年底的时候,跟女友走入婚姻殿堂,而今年,我的女儿降生了,这是来深圳着 5 年,也是疫情三年中,我最幸福的时刻。当女儿的小手堪堪握着我的小手指时,我心里充满无限的疼爱与憧憬,同时伴之而来的,就是责任了,接下来几年,我再也不能陪着夫人随便到处去玩了,能承担的风险对我来说也不知不觉降低了,我再也不能像之前那样随意更换生活的城市,毕竟我又多了一个父亲的角色,我需要考虑的东西也多了,当公司再次濒临倒闭的时候,我再也不能像之前那样「一人吃饱,全家不饿」了。总之,女儿的降生让我产生了非常大的保护欲,也给了我非常大的信心与希望。
好了,既然写到了这个敏感的话题,先用一句鸡汤开场:
不是有希望了才坚持,而是坚持了才有希望
这句鸡汤话送给所有人,毕竟 A 股在前不久又打响了 「3000 点保卫战」,不过我也没有更多的资金用来投入了,现在只是在硬撑而已,账户也不去看,没法看,看了也没意义,徒增烦恼。只是,我没有任何理由地去相信,A 股终会迎来恢复,我们面临的极端情况也会迎来春天。
格雷厄姆在《聪明的投资者》中说:「在证券领域,一旦获得了足够的知识并得出经过验证的判断之后,勇气就成为最重要的品德。」其实说的就是当前这个时候,大多数人要不就缺钱,要不就是勇气。只是「悲观者往往正确,但只有乐观才能挣钱」,继续坚持,不要倒在黎明之前。
这篇其实就是当做跟朋友简单聊几句而已,感谢看到最后。如果对我的文章或者经历感兴趣,也欢迎微信交流。
]]>我们先从工作说起,其实上次换工作的时候,我也说了一些内容,可以作为参考。
先简单介绍下我所在的公司,叫算子(确实,我也觉着看起来挺高深的),这个名字的由来,是因为创业初期就是以 AI 方向为主,深度学习离不开各种各样的算子,所以这个名字跟 AI 还是关联很紧密的。
我是 2018 年末加入的,加入的原因上次也说过了,不再赘述。距今刚好 5 年有余,因此在深圳的 5 年基本上也可以说是在算子的 5 年。
这 5 年,我把历程分为三个阶段:
我们在这个阶段属于拿了融资不知道干什么的阶段,打算凭借人脸算法的优势去做一些项目。
我刚加入那会儿,处于刚好完成了煤矿识别相关的项目,但是接下来的方向还是找了挺久,打算尝试用人脸算法去做商场人流量识别,并以此给商场跟店铺做一些会员的管理内容。这个涉及到做硬件,同时也是我们第一次开始做硬件相关的项目,在深圳找了商场进行测试,效果挺炫酷的,但客户不愿买单,他们认为这个系统的价值不是很高。(后来发现有其它公司的把类似的项目做起来了,看来还是我们自身的原因。)
后来我们凭借在之前积累的人脉,开始尝试做了校园课堂学生状态管理这个项目,也是差不多的套路,还是做硬件项目,内容简单来说根据课堂上学生的人脸识别情况进行统计管理,不过由于忽视客户真实需求以及疫情开始导致项目失败。
这两个涉及到硬件的项目,我第一次听到了海思以及瑞芯微,也是第一次接触到 SoC 。
再后来,就是疫情开始了。我们开始做人脸 SDK 封装,我们学着商汤旷视,做了趣视视觉这个产品,卖 License 为主,那时候给我们这个产品设计了一个自认为很完美的 License 激活方案,也是第一次在嵌入式系统里面做东西,还是非常新奇的。前期效果还是不错的,我们甚至还运营了一个微信群,卖出去了一些,不过后来被疫情打乱了节奏,这个方向也没有坚持下去,因为我们发现了更有前景的东西。
尝试过我们 SDK 的客户提了需求,要求我们把现有人脸算法封装进一款 Android 人脸门禁设备,我们完成之后,觉得这是个不错的落地应用,于是,我们也开始做人脸门禁产品,但是做的是 C++ 版本的,因为觉得 Android 版本的硬件成本高,C++ 的版本虽然开发成本高,只要出货量大就可以把成本摊薄。
期间我觉得 C++ 开发网络部分太慢,因此在试验了 Golang 版本之后,觉得开发效率提升不少,于是干脆就把网络部分全用 Golang 来实现了,但由于我们是个小团队,我不仅需要做 C++ 嵌入式的部分,也全揽下了用 Golang 实现的网络部分,另外服务器的活也压在我身上,毕竟创业公司需要人成为多面手。
做了半年左右有了成品,比预想快了三月有余,不过这次也同样不会那么轻松,没有收入叠加疫情影响,中间经历了快要倒闭的情况,所有人降薪,而老板借钱给我们发工资(不得不感慨,没有赌性,难做老板)。不过,在做出了 Demo 之后也遇到了一个不错的客户,我们凭借自己的研发能力给对方留下了不错的印象,因此也做成了第一笔大订单,这就算是绝境逢生了。
有了前面攒下来的经验基础,以及第一桶金,接下来,便是结合当时的防疫要求,我们开始做防疫产品。其实一开始做的是给人脸门禁加上了测温功能,后来老板找到了资源,才给我们做的人脸门禁加上了健康码核验功能,可以用来核验国家健康码(即国康码)是否是真实的,这是个可谓非常有价值的功能,市面上同类产品还比较少,因为那时候出现了有人凭借假的健康码混过了门岗检查这样类似的事件,于是我们对外宣传做了健康码智能核验终端。
虽然没有一炮走红,但是逐渐的,我们的客户开始变多了,并且也有客户开始陆续下单。
我们真正开始大卖,是随着疫情防控政策的收紧,以及各种疫情事件层出不穷之后。政府部门也意识到,健康码是个非常不错的工具,于是各地政府也开始大力推广自己健康码,并且健康码也确实在各地的防疫过程中,扮演了非常重要的角色。我们提供的产品,非常符合市场的需求,为防疫核验健康码提供了非常便利的方式,毕竟也不需要门岗来肉眼查看颜色了,同时也能够在很大程度上,防止健康码作假。
我们的优势还是挺明显的,因为大力投入资源在软件方面,尤其是在健康码这样的偏软件场景中。另外就是,对接资源这块,我们的效率非常高,能够及时解决客户的问题与需求。这也是小公司比较核心的竞争力,尤其是在对于一些中小客户来说,我们这样的小公司,能够积极配合,并且效率很高地去帮他们解决问题,这是大公司很多时候无法做到的。
我们的劣势也很明显,这个过程中,我们出现的最大问题,莫过于硬件质量了。我们都是互联网以及软件出身,对做硬件的了解不深,质量把控的问题非常大。这个问题在订单多的时候不是很明显,随着后期订单下降了,有客户会因为质量问题给我们退货,我们的返修率一度非常高。
供应链也是个大问题,它不仅关系到硬件质量,还有产能的问题,我们跟不上,就会丢订单。我们经客户介绍,开始跟行业的同行合作,我们提供软件,移植到他们提供硬件上,这就是个互利互惠的方式,我们凭借他们的硬件能力能够更快占领市场。事实也证明了这一点,在后来的统计中,几乎一半的设备都是经他们卖出去的。(不过后来他们借此抓住了机会,稳住了原本的客户之外,也抓住了一些中大型客户,并且在我们的软件先发优势逐渐失去的情况下,凭借硬件优势抢占了我们的市场,我除了暗自叹息之外,只剩佩服了。)
我处于中心地带,可谓收获颇多。
在这个过程中,我们没有专职的产品经理,因此很多需求都是我直接经手,我根据客户的需求,设计了几个有意思并且实用的功能:
扫码配置与升级:这是个非常有趣且实用的功能,背景是我们的设备没有加触屏,需要客户进一个网络的 Web 后台配置,导致客户配置机器非常麻烦,而且终端部署的地方不一定有人会操作,客户有时候需要开车几百公里去操作。我思考过好多解决方案,突然某天看着扫码器发呆了好久,灵感就来了(可能这就是所谓的「念念不忘,必有回想」),二维码不就是个信息输入载体么?既然可以把健康码作为二维码信息,当然也可以把配置信息作为二维码信息,其中当然还可以包含升级链接,于是花了两天做出了 Demo ,大家试用了之后,一致觉得非常有用,于是推给客户用,没想到过了两周,他们几乎完全放弃了进入后台配置的方式,毕竟扫个码那么简单的事情方便太多了。这样做了之后,可以让我们针对不同的客户需求,制作不同的升级二维码与配置即可,能够让我们非常快速地迭代软件,对接各地平台的时候最快可以在半个小时的时间内给到客户,并且升级部署成功。
设备监控:这个背景是我觉着如果把设备卖出去之后,如果无法及时收到反馈的话,我们会非常被动,于是我想到既然服务器可以监控,我们卖出去的设备也可以监控,我在我们服务器监控平台中,加入基于 MQTT 协议的设备监控实现,能够让 Prometheus 收集设备中的运行指标,接下来只要配置监控图标以及告警即可。这套简单的方案可以让我们了解所有设备的各种运行状态,能够比客户更快发现问题,有时候客户找上门的那刻,我们就已经他找我们的原因了。同时,我们也能非常快速的统计所有设备的各种运行状态,给我们的日常运营提供了非常大的指引作用。
远程管理:这个功能与设备监控是配合使用的,可以让我们能够远程快速定位问题,查看实时日志,调整系统参数,有些问题甚至可以远程直接处理掉,这个也简单,基于 MQTT 做个远程穿透就行。
前期对接主要靠我支撑,那时候工作强度非常高,很多时候都是当天客户提交,第二天甚至当晚就要结果,一句「明天领导要看」,就能压倒所有人。不过这个过程也挺充实的,不光是因为我知道越早部署好,想过防疫工作就能越快开展,也因为我们也能拿到订单。其中有次非常深刻,那天晚上已经非常累了,但还是硬撑着帮客户把对接搞定了。事后老板告诉我,那次搞定,客户直接下单了几百台机器,这就是成就感的来源了。
不过,揽下太多活也不好,那段时间身心非常疲惫,效率下降不可避免(给诸位交个底,博客断更也是那时候开始的),最遗憾的一件事情也发生在这个时候,我没控制住自己的脾气,怼了一个客户,间接导致他们选了其它同行,丢了挺大的订单。痛定思痛,我们继续扩张,招新人,我也慢慢把任务分派给其他新来的同事,并且教他们一步步入手,到最后完全脱手,开始做其它一些更重要的事情,尤其是产品方向问题。一段时间下来,我慢慢体会到领导者的意义,相对于上一份工作的领导岗位,我觉得自己开始真正入门管理。
这里也顺便说说几件管理上的收获:
三级等保:在主导这件事之前,听说过二级等保,不过等看完三级等保的资料后,还是头大了好几圈,幸好那时候我们的服务器安全措施还是有一些基础的,并没有花多少精力就搞定了,不过后来发现他们连服务以及设备上的应用安全也要审核的时候,还是头疼了挺久,所幸最终通过了。这个过程,是我第一次体会到「治大国若烹小鲜」,也就是指定菜单「计划」,调动公司所有的资源把所有的食材「各种材料、人力、物力」准备好,控制好火候「事情推进的节奏」,及时洒调料「控制关键节点」,然后等所有材料审核通过「煮熟后一锅出」。
敏捷纠偏:这是我整个管理过程中,最有收获的一段时间,因为那时候明明采取的是敏捷开发流程,但是服务开发团队效率非常低下。我静下心来研究了挺久,才意识到我们采取的一直是小瀑布开发模式,也就是在一个短周期的迭代中,进行所有需求的统一「规划-开发-测试-上线」流程,一旦有任务阻塞,就意味着当前迭代所有需求都无法上线,而正常的迭代是所有需求应该是独立的,且必须要控制并行的需求数量。定位到问题就好办了,给大家进行敏捷的培训,好好回顾了下一个该有的迭代是怎么样的,讨论并实施了几项措施,陪着大家进行一个完整的迭代过程,没想到效果非常显著,进行到第二次迭代的时候,需求上线速度快了非常多,几乎每天都能看到成果。
管理监督:职业生涯中遇到了第一个品德较差的同事,他利用我们的信任,在给他负责的服务器上部署自己的服务赚钱,这勉强忍了。但是他离职之前就开始准备盗用客户资源,最后几周的代码带走不提交,逼着我只能反编译,发现他还给特么给应用挂了马。等我发现后,死不承认,给我气了好久,如今回想起来,这也算是给我好好上了一堂管理监督课了。
吹了一波自己,说回我们公司的业务,我们算是转型成功了,累计卖出了不小的数字,团队快速扩张,并且以此为基础陆续做了防疫扫码盒子以及手持设备,一度成为健康码核验防疫产品中的行业龙头。期间我也过了把做 Android 客户端的瘾,因为中间了解过 Flutter ,想着怎么用到我们的产品中,这不,机会就来了,用了一个星期,Demo 就出来了,我做了几轮迭代之后,就交给了后来新招的专职 Android 研发同事。
后来我们对我们的生意模式做了一些总结,其实我们就是以做 ToG/ToB 项目的方式卖防疫硬件产品,靠着对定制需求的「不挑食」,对接了大大小小几百个健康码平台。当然了,这些平台的背后都是各地大数据局在支持,即使我们的业务直接是 ToB,但是本质上还是 ToG。
我认为产品的成功,很大程度上归结于我们建立的快速迭代机制,即在基于标准产品功能的基础上,快速为客户开发定制的需求,然后给客户部署完毕,获取反馈,进行调整后尽快满足客户需求,同时在这个过程中,积极收集终端客户的反馈以及运行数据,一旦我们发现了这个功能可能是普遍性的需求的话,我们会将这个功能合并进主分支,并推广给其他客户。
成也萧何,败也萧何。我们最初成功的原因,也是后来失败的原因,随着疫情政策的放开,我们的状况可谓情转直下,需求端的客户几乎消失不见。
其实,我们预想过这件事情,甚至考虑过提前转型,只不过没想到它是以这样的方式来临,并且来的那么快。我们仓促应对,业务收缩,开始第一次裁员。接下来,我们调整部署,考虑到我们已经积累的销售渠道,开始回归人脸门禁本身,做考勤方向的人脸门禁。
在做考勤人脸门禁之后,我们凭借之前的经验教训,将之前的产品大刀阔斧做了不少修改,同时更注重产品质量,因此产品出来之后,质量比防疫产品提升非常多。不过,我们也意识到了之前防疫产品的质量是多么的不足,基本功不够扎实,等待防疫需求退潮,发现我们才是那个没穿内裤的,如今回想起来,那些客户对于产品质量的抱怨不绝于耳。
现实还是狠狠打了我们的脸,我们高估了市场的温度,经济下行的程度让我们始料未及,那疫情刚放开的那几周,也是我遇到的人生中最冷的时光,平时热闹的大街上几乎都没吃饭的地方,只能用荒凉来形容。而今年 2023 年疫情放开之后的开春,让我们感受到市场无以伦比的寒冷,于是我们又经历了几次裁员,直到最后,我也不得不离开。有时候,我会回想,如果半年之前,我们能够快速止损,清退所有业务,积极寻找新方向的话,现在可能没有那么狼狈。只是,既然是创业,不轻易放弃是基本素养,如果我们那么容易放弃的话,在疫情初期我们就已经解散了。至于我自己,还是获得了不少经验,不仅因为了解了完全陌生的硬件行业,还因为最后产品跟项目最终都是我来做了,毕竟初期的产品跟项目要么离职,要么被裁,除了职位的上升能够让人思维升级,换工作内容也不例外,正所谓「屁股决定脑袋」。
有句话说的好,「凭运气赚的钱,总会凭实力亏掉」,虽然说的是股市,但是用来形容我们的创业历程也有一些对的地方。我们凭借着疫情防控的浪潮活了下来,确实有运气成分存在,但是没有实力的话,其实也抓不住运气,因为运气是给有准备的人的,所以我们常说「运气也是实力的一部分」。最终难以活下来,的确是我们自己的实力有问题,但面对这个寒冷的市场,也有比我们实力更强的公司更早倒下了。
我一直认为自己运气挺好的,在互联网行业快要进入下行期的前两年,我开始进入智能硬件行业。
其实过程非常痛苦,毕竟不是那么容易转行的,跳出自己舒适区也非常难,我大多数时间还是喜欢做自己擅长的软件相关的工作,管理是一知半解,也不会有人来手把手教我怎么做,连我最陌生的硬件设计,我也得知道是怎么回事,最后还得把自己当成项目经理跟产品经理。当团队里面的所有人都不会或者不想做某件事情时,我需要跳出来去承担,比如 ISP 调试、RTSP 实现,或者人脸算法移植(现在回想,真有种我不入地狱,谁入地狱的感觉)。
可能这就是创业的魅力,也是我从老板身上学到的最重要的素质:我们要一直去挑战不可能,当其它所有人做不到,或者说不可能的时候,我就要跳出来说:「我不信,让我来试试」。毕竟如果事情都那么简单,那我们为什么还要去创业,简单的事情大家都做,凭什么给你机会?乖乖去其它公司打工,按照别人给你的既定路线走不就得了,创业就是要去做不可能的事情。另外,我认为即使去其它公司打工,也会面临类似的情况,毕竟简单的事情好做,但是只有难的事情才能体现你的价值。
如果你问遗憾吗?那肯定有,而且不少,一方面遗憾我在工作中没有做到更好,另一方面遗憾我没有早做准备,这两点本身是矛盾的,因为我一直是处于坚持的状态,无法也没有去想如果公司倒了怎么办。
而如果你问后悔吗?不后悔,人生没有后悔药,更不会所有事情都一帆风顺,以后的路还很长,收拾收拾,可以准备下一段旅程了。
]]>重要提示
本文将涉及编写并执行自定义脚本,存在一定安全风险。请不要轻易信任和运行不明来源的脚本,以免造成数据损坏或信息泄漏。请在自己完全理解的前提下谨慎操作。
夏天到了,游泳的最佳时节也到了。每次去游泳,游泳耳机是必不可少的,它让我在泳池里面不会那么枯燥了,只要戴上我心爱的 Walkman 扑通入水后,整个游泳馆就会只剩下我这最靓的仔。
不过美中不足的是,除了偶尔会被蛙泳的人踹脸,就剩下我的 Walkman 里面的音乐却是几年前的这件事了。在这过去的几年里,我的个人音乐库里面的红心歌曲也已经更新了好多,现在有必要把 Walkman 里面的歌单更新一下了。
上周末从游泳馆回家的路上,一直想着要更新歌单,想着想着,结果一进门,心里记着的事儿也就跟我扔在一边的游泳装备一样,啪嗒掉在了地上。
直到隔天晚上,偶然瞥到游泳装备,这件需要解决的小麻烦这才又浮上心头。于是,那天晚上我大概花了一个小时才把这件事情搞定:
实在不能忍受着这个效率,我得想办法节约自己的时间。回头看到了自己家那台一直在吃灰的群晖,我突然有了灵感:用群晖的 USB Copy 功能直接导出歌曲不就行了。
群晖的 USB Copy 功能还是挺简单易用的,通过简单的设置,你就能让它在你把 Walkman 插入群晖前面的 USB 口时,自动复制歌曲到 Walkman,并且在复制完成之后自动弹出设备,然后用「滴」一声告诉你完成了,你只需要直接拔出来就能直接使用了。
下面是简单的教程:
第一步,打开 USB Copy:
第二步,选择数据导出:
第三步,设置任务,来源选一个你挑好的音乐文件夹列表,注意:
第四步,选择触发时间:
最后一步,选择文件过滤,我只为听歌,因此这里只选音频:
创建完成后,可以将你的 Walkman 插上去试试效果。
不过我想把空间利用率拉满,折腾一次可以顶好久,因此我在测试的时候,等了好久也没听见「哔」声,再去群晖上瞧一眼,果然是挂了。群晖提示我空间不够,原因也很简单,因为我的 Walkman 可用的空间也就 3.5GB 不到,而之前电脑上拷贝的歌曲已经把 Walkman 的空间占满了,虽然我设置的复制策略是镜像,但是实际执行的时候,群晖是不会先把空间删掉腾出来再进行复制的,这样就会导致没有足够的交换空间导致复制失败。
解决的方案也是有的,那就是留足足够的空间,因为大多数情况下,我一次游泳也不会听那么多的歌曲,也没那么多时间来听,那么,实际上我只向里面拷贝几十首歌就行了,算一首歌 10M 的话,顶多拷贝 500M 左右的歌曲就行了,问题也就迎刃而解了。
不过,如果你跟我一样有些强迫症,非要把空间利用率尽量拉满,可以像我一样尝试更高阶一些的方法。
我重新规划了歌曲更新流程,因为本来 Walkman 的 USB 连接线这几年只是用来充电了,这次顺便就能把歌单同步跟充电,这两件事一起做了。
所以我设想了如下的流程:
是的,就那么简单,但是,为了能做到这种流程,我需要准备以下几个事情:
那么脚本的话,我就贴在下面了,自取即可。
1 |
|
接下来说说怎么用。
首先,我们需要确定参数:
MUSIC
,必须要确定存在,否则会被跳过;default-capability.xml
,你的设备如果不存在这个文件,则可以创建一个跟我一样同名的(内容可空),用来表示你只会使用这个设备来同步音乐,而不是其他同时插在群晖上的设备,不存在这个文件的设备会被跳过;/volume1/music/Music
;/volume1/music/walkman
,用来存放随机筛选的音乐(放心,完成之后会删的);查看音乐文件夹的目录:
将你的参数替换掉我在脚本中的数值即可,你的定制脚本就完成了。
然后,我们来设置你的定制脚本:
打开群晖的系统面板,任务计划。
第一步,新增,计划的任务,用户定义的脚本。
第二步,常规 Tab,任务名称随便填了,比如我填的就是 Walkman,用户账户需要选择 root,因为最后弹出 Walkman 需要 root 权限(admin 实测权限不够,截图的账号不正确,实际应为 root)。
这里需要提醒一句,对于不理解脚本的小白来说,轻易不要相信他人给的脚本,尤其是需要高权限运行的情况下。如果是恶意脚本,轻则被投毒挂马挖矿,重则被加密勒索或者数据全毁,慎重,慎重,慎重。
第三步,点击计划 Tab,可以先留空,我就是留着它默认时间的,每天凌晨执行就行。
第四步,任务设置,通知设置这边,如果想收邮件通知的话就勾选,不需要也无所谓,最后将上面的脚本粘贴到用户定义的脚本里面就行。(如果你不是跟我一样的 Walkman,理论上也可以用,但是需要你能修改脚本。)
最后一步,点击确定即可。
设置完成后将 Walkman 连接到群晖,然后选中任务,点击「运行」,手动执行一遍,测试一下。另外如果你设置了邮件通知,那么在任务结束的时候,你应该会收到任务完成的邮件提示。
P.S. 我买的这个 Walkman 当时搜了挺多的资料,看了各种推荐才下单的,如今已经用了差不多 4 年了(刚刚去瞟了一眼电商平台,价格依然坚挺)。其实当时也考虑过骨传导耳机,对比了下价格后,我立马觉得还是入耳的更好,因为它不需要额外戴耳塞了,它本身就是耳塞 🌝。
P.P.S. 这个脚本已经改了几个版本,目前能支持自动检测插入 U 口的设备,并且可以支持多个,功能还是比较简陋的,目前在我的使用习惯下勉强凑合了,有能力的同学修改下这个脚本,就能支持把追更的最新播客,有声书,相声(游泳时听郭德纲,想想就挺带感的。。。)之类的音频也放进去,显然在遇到这种经常需要更新音频的情况下,这个流程就显得更契合了。
另外,如果有其它不会修改脚本的同学也有类似的需求,或者遇到了使用问题,可以在评论区留言,我会尝试继续改进这个脚本。
P.P.P.S. 最后的最后,这个流程唯二的问题就是:
不过相信我,忘个几次之后,记性还是会长的。
今年最大的体会,就是体力与精力的双重下降:身体上颈椎与腰椎都有问题,下半年经常心悸跟腰背疼,去推拿了好几次,年底了又多了个干眼症。
另外,我的精力已经从纯技术路线转移了。
今年的疫情依然没有结束,但是我所在的公司开始做防疫相关的产品了,于是我们这一年来成长了不少,业务也真正有起色了。
不过,成功是有代价的,尽管目前只是小成功。
感觉自己的体力与精力最明显的下降是在 8 月份时候,因为那时候遇到了一波比较大的本土疫情,而我们公司的订单量也开始上升,但是我们却遇到了产品方面的问题,于是几乎每天都在加班加点(腰疼跟心悸也是那时候发生的)。你们看我的博客更新频率也能看出些,因为真是忙,忙到让我怀疑人生,非常累。这期间让我不想看书,不想游泳,也不想去更新博客(似乎找到了拖更的正当理由),只想周末的时候窝在沙发里。
现在回想,其实自己就是在逃避思考,光顾着战术勤奋,却不肯花时间,或者也可以说不懂去思考战略。
什么是战略呢?就是一个团队里面,不能所有人都埋头顾着眼前的事情,一定要有人看着前方的道路,花时间解决重要而不紧急的问题,紧急不重要的事情少做,或者交给别人去做。就如打游戏冲关一般,你不能光顾着打虾兵蟹将,不然只会被无穷无尽的它们给淹死,你的最终目标是打败 Boss 后通关,因此你需要在路上不断积累能够打 Boss 的资源与能力。因此如果你是一个团队在冲关时,一定有人要负责制定打 Boss 的计划,管好分工协作。
所以,我在 2021 最大的收获就是真正入门管理。
曾经觉得自己可以以一当十,所有事情不放心交给别人,都要自己干,累死自己(我要打十个变成被十个打),不懂何为「和光同尘」,终于在被提醒在为别人打工的时候,我才真正有所醒悟。
我学着从管理者的角度来思考:
经过了这小半年的实践,我在管理上的算是真正有所得,我不再小觑了管理人员在团队中能发挥的作用,也不再像从前那样总是认为会议总是无关紧要。所以我学着定义自己的角色与职责,也学着把手上的活分给别人并且教别人把事情做好,学着在没有招到人的时候当经理项目以及产品经理。毕竟我亲身经历了我们公司这样的小团队,因为长时间忽略管理而导致了严重损失,人员流失,甚至一度濒临散伙倒闭。
回首看去,自己一度觉得很轻松,竟是有人替自己负重前行。而当自己把担子拾起,竟发现这种重量是何其沉重。
记得曾经有人给我劝告,过早经手管理的活,会让自己的职业道路受影响,现在看来,有一定的道理。因为至少对于我来说,管理上的事情并不像做技术那么简单,管人可比管代码管机器难多了。一直沉浸在具体业务执行中的我,很多管理上的事情我是无法去理解的,也做不好,不如先把自己的技术能力提高了,再去尝试。
事实也确实如此,软件架构的目的是在于实现功能的复用、质量与维护,而组织架构的本质是为了实现企业战略目标而进行的分工与协作的安排,他们是相通的:软件架构中的模块与分层思想与组织架构是完全一致的。
今年的博客,刚开始还能勉强跟上去年的节奏,但是下半年受工作内容影响,几乎放弃了更新,不过最终还是咬牙更新了几篇。
但是,我陷入了为了创作而创作的陷阱,因为心境受到了影响:我开始介意读者对我的想法,我的文章写的内容会不会太浅薄了?这个技术知识现在才知道,会不会被认为没有实力?有时候好不容易有了灵感,但是写了一版后觉得深度不够,发出去会好丢脸,就放在草稿箱里面,想着之后去完善,但是每次看草稿箱就会觉得头大,所以最终的结果就是一直吃灰了。
当我回顾这个博客最开始的那些「幼稚文章」,那时候的心境与如今完全不能相比,当时我可以对那时所在的公司流程指指点点,对领导也敢当面顶撞,对手上接手的历史代码嗤之以鼻,随便一个小知识我就敢写文章来总结。但随着经验与知识的增加,我知道了我是那么的渺小与无知,天外有天,人外也总是有人,如今已经没了那种锐气,因为我成了当初喷的对象了。
说实话,我一度想把当初那些「幼稚文章」删掉,只留下那些看起来还行的文章。不过我一直没有这么做,因为那些都是我成长的例证,删除便是否定曾经的自己,没有必要。
今年的投资真是一滩烂泥,不过我觉着在今年的大环境下,目前保持不亏的状态就已经是不错了。话说回来,这也许是个可以贪婪的时候,毕竟市场总是会时高时低。
相信不少人跟我一样,会下决心在市场上涨的时候说等市场下跌到什么样的时候,自己就会立马入手,但是等市场真正跌的时候,反而不敢入手了,因为不知道它还会跌到什么程度,对不确定的恐惧会让自己畏首畏尾。然而,定投这件事情可以帮助自己克服这种情绪,并且,在下半年的时候我努力克服恐惧,又入手了一部分的代表国运的指数基金(我依然相信我们会有光明的未来)。
感谢你的关注,新年快乐。
首发于 Github issues: https://github.com/xizhibei/blog/issues/182 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA)进行许可接着上次的简介(这个博主真会拖更 :P),我们来说说 MQTT 的一些基本概念。
在上次非常简单的 MQTT Hello World 中,我们其实就已经涉及到了一个非常重要的概念:发布与订阅。
想象大家很容易想起的,便是设计模式里面的发布订阅模式,确实,本质上 MQTT 实现的,就是架构上的发布订阅模式。
让我们回想下, 发布订阅模式的好处在哪里?解耦。如果说观察者模式是发送方与接收方的低耦合,那发布订阅模式是两方的完全解耦了。
而随后想起的便是各种分布式应用里面的各种消息队列中间件了(比如 ActiveMQ、RabbitMQ、RocketMQ、Kafka 等),我们很容易理解错误的地方在于,认为他们两个是一类,但是它们应用的场景与范围完全不一样。
首先,需要明白的是 MQTT 只是一个应用层的协议,与之可以对比的是消息队列中的 AMQP 协议,MQTT Broker 则对应各种消息队列。
说到这里,其实他们可以配合起来使用,比如设备通过 MQTT 协议将数据传送至服务器后,放到消息队列进行缓存,防止服务器无法及时处理而丢失数据。
话说回来,其实 ActiveMQ 支持 MQTT, RabbitMQ 也支持 MQTT,详细情况请看 MQTT Adapter。
MQTT 里面的 Topic 很容易理解,可以把它与 HTTP 协议或者 Linux 中的路径来对待,但是需要把第一个 “根目录” 给去掉,因为这在 MQTT 中代表一个空的根目录。
你可以在有权限的情况下,发送任意数据到任何 topic,也可以订阅但是需要注意三个符号:
$SYS 主题
;另外需要提一句,除了测试,尽量不要订阅 ‘#’ 的主题,当客户端发送数据量太大时,大概率会出问题。
在继续之前,你最好是搭建一个自己的本地测试 Broker,这样的话,可以尽量避免被公共服务器上面,其他人的消息干扰。
下面我们以 Go 为例来说明消息发布与接收。
目前最常用的库是 paho.mqtt.golang,我们可以直接使用 go get github.com/eclipse/paho.mqtt.golang
来获取。
作为 MQTT 客户端,第一件要做的时间,便是建立连接。
1 | opts := mqtt.NewClientOptions(). |
在上面的例子中,我们用最简单的选项建立了连接,并且在一秒后断开了连接。如果对这里的选项感兴趣,可以看下代码 MQTT Client options,里面的默认选项也能一目了然。
然后,便是发布与订阅,下面便是一个非常简单的例子:
1 | { |
或者,你也可以按照第一篇文章里面的内容,尝试联动下发布与订阅,比如程序上发送数据,在桌面客户端中接收,反之亦然。
在这次的入门篇里面,我们忽略了连接时的参数,也忽略了发布订阅时的 QoS
以及 Retained
两个参数,这些都是非常重要的细节,它们将会在之后的文章中出现(放心,会让你们的孙辈们通知你们更新的 🙈 )。
首发于 Github issues: https://github.com/xizhibei/blog/issues/181 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA)进行许可又停更了,两个月。 🙈
我在上次的 简介 里简单提到过,如何用公共的 Broker 来做测试,显然,你不能用测试服务器当做生产环境的服务器,我们还是需要一个属于自己的服务器。
Mosquitto 可谓是开源届最有名气的 MQTT Broker 了,只是功能上勉强够用,有些如权限管理之类的高级功能需要自己安装插件,或者干脆自己的实现插件来拓展。
它还提供了一个公共的 Broker 可以用来测试: https://test.mosquitto.org/
它的安装非常简单,直接安装相应的程序即可,比如在 Mac 中 brew install mosquitto
,而在 Linux 中 sudo api install mosquitto
。如果想用 docker 也是类似,目前官方的镜像是 eclipse-mosquitto
,运行细节,这里掠过,主要说说它的配置1:
听默认的 1883 非加密端口:
listener 1883 0.0.0.0
如果不想配置用户密码登录,这里就可以配置允许匿名连接,也就是没有用户密码:
allow_anonymous true
但如果配置了不允许匿名,那么需要配置用户名密码。这个文件里面的用户密码可以用 mosquitto 提供的工具来配置:mosquitto_passwd mosquitto/config/pwfile username
,然后按照提示输入密码即可。
然后,我们还需要在配置文件中加入这么一行:
password_file /mosquitto/config/pwfile
另外,如果需要对每个用户进行权限限制,则需要配置 acl:
acl_file /mosquitto/config/aclfile
这个配置也很简单,它支持三种语法:
topic [read|write|readwrite|deny] <topic>
,这个可以针对匿名客户的 topic 权限;user <username>
,这个是与 topic 权限联合使用;pattern [read|write|readwrite] <topic>
,这个可以针对单个用户来做权限划分了,其中的 <topic>
可以包含 %c
代表登录的 Client ID 以及 %u
代表登录的用户名;如下便是例子:2
允许匿名用户读取所有用户级别的 topic:
topic read #topic read $SYS/broker/messages/#
允许用户 web 读取所有 topic
user webtopic read #topic read $SYS/#
显然,这种权限只能满足最低级别的要求,如果需要跟你们的平台整合起来,实现动态登录认证,则需要用到 auth_plugin,目前看到官方推荐的一个插件是 mosquitto-go-auth 。
mosquitto 本身并不支持集群部署,但是可以通过后端来实现,详情请见 MQTT server support。
另外,由于我目前也没有搭建过集群,这里就不多说了。
随着国家对隐私权的保护等级要求越来越高,加密传输是其中越来越重要的一个环节,也就是所有的个人信息传输必须加密。
其实对于 MQTT 来说,我们可以用 HTTPS 证书,因为本质上他们是一样的,都是 TLS 证书,因此也可以用在 MQTT 协议上。
如果你用经过权威 CA 签名颁发的证书,那就简单配置如下即可:
listener 8883 0.0.0.0certfile /path/to/certs/example.com.cerkeyfile /path/to/certs/example.com.key
但如果要用自己签发的证书,客户端连接的时候就会稍稍复杂些,需要配置好 ca 才能连接。
另外,跟 HTTPS 双向认证 一样,MQTT 也可以采用双向认证,这种情况下,当客户端连接的时候,服务器便会要求客户端提供证书,并且用你配置的 ca 证书来验证客户端证书的签名。
cafile /path/to/certs/ca.pemrequire_certificate true
好了,当我们搭建完毕,就可以使用客户端进行简单的测试了。不过,可能大多数人简单测试后,都会认为已经可以正常投入使用了,不过,其实你可以做的更完善一些。
比如,你可以先预估下你需要连接的客户端数量,然后是消息的数量、并发数以及消息大小,得出个大概范围,再进行基准测试。
这里我用的是 MQTT benchmarking tool,它可以很方便测试出,你现在搭建完毕的 Broker 能承受多大的压力。
它本身还是比较容易使用的,如果你使用过 HTTP 接口的 Apache bench 之类的压测工具,就能很快上手。比如,它的主页上的一个例子便是,以 10 个客户的,每个客户端连续发送 100 条消息:
1 | mqtt-benchmark --broker tcp://broker.local:1883 --count 100 --clients 10 --qos 1 --topic house/bedroom/temperature --payload {\"temperature\":20,\"timestamp\":1597314150} |
最后,在输出中,你能看到测试后的统计结果,也能提前发现一些搭建过程中隐藏的问题。虽然这多出来的一两个小时可能在你看来比较浪费,但是这些问题如果在使用了一段时间后才被发现的话,你付出的成本就会远远高于你多花的一两个小时了。而且我相信,工程师之间的差距,不仅仅在于做事快慢,更会在这些专业素养上体现出来。
其实你也可以考虑使用付费的服务,免去自己出维护的人力以及服务器成本。比如可以选择一些商业性质的 Broker,比如国内的 EMQX 以及 国外的 HiveMQ,它们既支持商业版本,也有开源版本,既可以自己在服务器上搭建,也可以直接用他们提供的服务器。毕竟是商业支持,功能更完善,体验也会比较好。
首发于 Github issues: https://github.com/xizhibei/blog/issues/180 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA)进行许可最近两个月,我几乎是停更了,虽然之前提到过不会及时更新了,但是拖了那么久还是第一次。我当然可以用忙来解释,毕竟我周末用来刷 B 站的时间也少了好多。所以就陷入了另一个窘迫的地步:现在有素材可以写,但是这些可以拿来写的素材却是我忙起来之后才能得到,导致没有足够的精力时间去整理这些素材。
好了,话虽如此,博客还是要继续写的,不然积累的经验与知识又会不成体系。
是的,我又准备开个系列了,一方面,系列的文章会显得成体系一些,更能帮助一些刚刚入门的同学,另一方面,这样的话接下来不至于冥思苦想该写什么(好像这才是真实目的)。
MQTT 是非常简单的协议,最初由 IBM 的两位工程师 Andy Stanford-Clark 以及 Arlen Nipper 在 1999 年为监控输油管道设计的。它被设计的场景就是有限的带宽、轻量级以及很小的耗电量,在那个时候,卫星宽带就是那么小,且贵得让人肉疼。1
到了现代社会,虽然带宽的成本大大降低,但是仍有大量的场景需要用到这种协议,比如,智能家居(其实还是物联网)。许多的小型物联网设备靠着一块纽扣电池需要工作几年的时间,因此 MQTT 非常适合用来当作应用层的传输协议。
总结来说,MQTT 就是一个服务端、客户端架构的发布订阅消息传输协议。它非常轻量、开放、简单,设计上就非常容易实现。这些特性让它非常适合在如机器与机器 (M2M) 以及物联网(IoT) 这样受限于小内存以及窄带宽的领域发挥作用。2
IBM 在 2013 年将 3.1 版本提交到了 OASIS,而在之后的 2014 年,OASIS 加了很小的改动,发布了 3.1.1 版本。
2019 年,OASIS 给 MQTT 增加了很多特性,比如更好的错误处理、共享订阅、消息内容类型等等,版本也升级到了 5,之后也会用专门的章节来说说这些特性。
首先,你需要有一个 MQTT Broker。首先需要安装 mosquitto …… 嗯?你不知道这是什么?好吧,那就换个更简单的做法。
首先,我们可以用一些公共的,比如国内的 EMQ(杭州映云科技有限公司)提供的 broker.emqx.io(这里必须给国产软件打广告,他们提供的 MQTTX 客户端是我目前用的最顺手的,Broker 的功能也非常强大,我计划专门出一篇来介绍服务端的搭建)。
然后,也是他们家提供的 MQTTX 客户端 ,用 Electron 实现的,各平台都支持,直接下载即可。
打开 MQTTX 客户端,我们开始一次简单的测试。
test/907839342134
,毕竟这是公共的 Broker,避免跟其它人冲突,然后点击确定即可。test/907839342134
,在它下面的内容框里面,输入 {"hellow": "world"}
,点击它更下面的纸飞机,发送后,你就能看到,接收到了自己给自己发送的消息。好了,这次就先到这,非常简单的介绍,接下来会尽可能详细介绍 MQTT 的一些概念、原理以及实践应用。
首发于 Github issues: https://github.com/xizhibei/blog/issues/179 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA)进行许可今天来跟大家分享一个有趣的事情,在开始之前,想问问大家如何在 Git 中,如何在 Git 项目中,让一个文件消失?或者说,对 Git 来说不可见?
似乎很简单对不对?用 .gitignore 文件不就可以了。对,你说的没错,只是除了这个方法呢?
接下来如果你如果想尝试这个方法,请别在你的真实项目中操作,搞坏了我可没法负责。
1 | mkdir test_git_repo |
到这里没什么奇怪的对么,好,在 test_git_repo 这个目录里面继续。
1 | mkdir hidden_path |
然后在 hidden_path 中创建一个提交。
1 | echo 1 > 1.txt |
回到上层目录,同样创建一个提交。
1 | cd .. # test_git_repo |
好了,关键的一步来了,把 hidden_path 中的 .git 文件夹删掉。
1 | rm -rf hidden_path/.git |
最终,我们的魔法操作来了:
1 | echo test > hidden_path/test.txt |
当你用 git status
,你会发现这样的输出:
On branch masterUntracked files: (use "git add <file>..." to include in what will be committed) test.txtnothing added to commit but untracked files present (use "git add" to track)
即使进到 hidden_path 去查看 git status
也是一样。奇怪了,hidden_path/test.txt 这个文件哪里去了?
而当你用 ls 查看的时候,却发现那个文件还是存在的,但是它却在当前的 git 项目中「消失」了。你也可以测试看看,无论你往这个文件夹里面写任何文件,它都会「消失」。
好了,接下来,让我们把消失的文件找回来。
1 | git rm --cached hiddle_path |
这时候再来看 git stauts
,你就会发现消失的文件回来了:
On branch masterChanges to be committed: (use "git reset HEAD <file>..." to unstage) deleted: hiddle_path new file: hiddle_path/1.txt new file: hiddle_path/test.txt
相信熟悉 git 的同学已经看出来了,就是因为 git submodule 。
其实这个「小技巧」是我在处理一个不那么熟悉 git 的同事问题时候发现的,当时也是很奇怪,检查了很多次有没有把那个文件夹加入到 .gitignore 里面去,在反复查看好多遍,并且确认整个项目中只有这一个 .gitignore 文件之后,才考虑到是 git submodule 的问题。因为我发现了同事提交了一个空文件夹,大家应该知道,git 是不支持提交空文件夹的,而且查看 git 历史也会发现这个问题。
问了同事才明白,同事不小心把外部依赖拷贝进项目并且提交了,他这样做相当于在主项目 git 中添加了一个 submodule 文件,这个文件在文件系统中会被替换成一个文件夹。本来如果他继续提交的话,我们也会很容易发现这是个 submodule,但是同事接下来的的骚操作就是他直接把 submodule 的 .git 删掉,然后又提交一次,于是结果就是这个 submodule 文件会被当前的主项目 git 给忽略了,因为它的 file mode 依然是 160000,还是会被 git 当作 submodule 处理。1
这里提一下:
file mode 160000 在 git 中的意义是:git 会认为你在记录一个提交,作为另一个项目的目录入口,而不是一个文件夹或者文件。2
因此,删掉 submodule 的 .git 目录后,对应的 commit 永远不会改变,也就相当于这个目录下的所有文件都会被 git 忽略。
首发于 Github issues: https://github.com/xizhibei/blog/issues/177 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA)进行许可通常,我们只会在两种情况下,会去分析一个程序的表现:
好了,开个玩笑,其实研究程序的性能对于每一个工程师来说,都很重要,我甚至可以这么说:这是一个工程师的必备技能。
下面来说说,我们如何去研究 Golang 程序的性能问题。
之前我也在 穷人的程序性能分析器 介绍过 C++ 的性能分析,以及很久之前也介绍过 Node.js 性能分析之火焰图 ,那么今天就轮到 Golang 了。
相比之下,Golang 的性能分析工具 pprof
可谓是豪华了,它内建支持以下几种分析:
heap
相对的,是 CPU 的采样,可以用来分析程序的耗时瓶颈;goroutine
的栈追踪;mutex
栈追踪;凭借良好的工具带来的调试体验也是非常棒的,整个过程只需几个简单的命令,你就能进行分析个大概了。不过受限于篇幅,以及之前也多次提到过 CPU 的分析,因此今天只说说如何分析内存,也就是 Heap。
Heap 的使用一般是内存泄露,或者是你想优化内存的使用。
对于内存泄露,这类问题往往难以发现与分析,因为需要监控 Go 程序本身,或者看 Linux 的 dmesg 里面的 OOM 记录才能发现。
1 | dmesg | grep oom-killer |
当你发现一次 OOM 记录时,你就要考虑给本身忽略的监控加上了,因为这种问题会复现的(但是往往难以在自己的机器以及预发布环境中复现)。如果不知道是是什么监控参数,你可以看监控数据,简单定一个比例,比如当你的程序初始化的时候占用 10% 的内存,那么一旦 Go 程序的内存使用达到一定比例比如机器内存 50% 时,就要马上进行告警了,你也可以进场分析了。
不过,也不用大费周章,因为你只需用几行简单的代码,就能给你的 Go 程序增加 pprof 支持,不会影响程序的运行,并且是支持 Web 访问的:
1 | import ( |
然后,使用 go 提供的 pprof
工具就能进行分析了,比如对于内存泄露问题:
1 | go tool pprof http://localhost:8080/debug/pprof/heap |
就会进入 pprof 的 REPL,在这里用一些简单的命令你就能定位问题所在。不过为了更好的分析体验,有两个地方需要注意:
-trimpath
以及 -ldflag "-s -w"
,最好去掉,不然会影响到你定位问题;接下来的我用的实际例子是属于内存使用分析优化,由于还没遇到 OOM,先用我遇到的一个小例子来代替,因为两个问题的分析方法是一致的。
第一步,先看 top10
:
(pprof) top10Showing nodes accounting for 3759.91kB, 100% of 3759.91kB totalShowing top 5 nodes out of 24 flat flat% sum% cum cum% 2345.25kB 62.38% 62.38% 2345.25kB 62.38% io.ReadAll 902.59kB 24.01% 86.38% 902.59kB 24.01% compress/flate.NewWriter 0 0% 100% 902.59kB 24.01% bufio.(*Writer).Flush 0 0% 100% 902.59kB 24.01% compress/gzip.(*Writer).Write(以下省略)...
这里需要提示下,flat
表示目前最右边的调用仍旧没有被释放的空间,而 cum
表示累计 (cumulative) 申请的空间。top 的默认排序是按照 flat 排序,你可以通过参数来切换排序方式:top10 -cum
。
如果在这里看不到什么异常的地方,那么还有别的地方可以看,因为 Golang heap 的采样统计会区分成四个部分:
你可以通过类似于 sample_index=inuse_objects
的命令来切换。
在我的这个例子中,由于我这里确定第一项 io.ReadAll
为什么会在我的程序中,但是第二项的 compress/flate.NewWriter
让我觉得有异常,但是不知到是哪里调用的。因此,在确定异常项后,第二步可以通过 tree
来进一步确认调用链条:
(pprof) tree 10 compressActive filters: focus=compressShowing nodes accounting for 2354.01kB, 29.36% of 8018.09kB totalShowing top 10 nodes out of 11----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- 2354.01kB 100% | compress/gzip.(*Writer).Write 1805.17kB 22.51% 22.51% 2354.01kB 29.36% | compress/flate.NewWriter 548.84kB 23.32% | compress/flate.(*compressor).init----------------------------------------------------------+------------- 548.84kB 100% | compress/flate.(*compressor).init (inline) 548.84kB 6.85% 29.36% 548.84kB 6.85% | compress/flate.(*compressor).initDeflate----------------------------------------------------------+------------- 2354.01kB 100% | github.com/prometheus/common/expfmt.MetricFamilyToText.func1 0 0% 29.36% 2354.01kB 29.36% | bufio.(*Writer).Flush 2354.01kB 100% | compress/gzip.(*Writer).Write----------------------------------------------------------+------------- 548.84kB 100% | compress/flate.NewWriter 0 0% 29.36% 548.84kB 6.85% | compress/flate.(*compressor).init 548.84kB 100% | compress/flate.(*compressor).initDeflate (inline)----------------------------------------------------------+------------- 2354.01kB 100% | bufio.(*Writer).Flush 0 0% 29.36% 2354.01kB 29.36% | compress/gzip.(*Writer).Write 2354.01kB 100% | compress/flate.NewWriter----------------------------------------------------------+------------- 2354.01kB 100% | github.com/prometheus/common/expfmt.NewEncoder.func7 0 0% 29.36% 2354.01kB 29.36% | github.com/prometheus/common/expfmt.MetricFamilyToText 2354.01kB 100% | github.com/prometheus/common/expfmt.MetricFamilyToText.func1----------------------------------------------------------+------------- 2354.01kB 100% | github.com/prometheus/common/expfmt.MetricFamilyToText 0 0% 29.36% 2354.01kB 29.36% | github.com/prometheus/common/expfmt.MetricFamilyToText.func1 2354.01kB 100% | bufio.(*Writer).Flush----------------------------------------------------------+------------- 2354.01kB 100% | github.com/prometheus/common/expfmt.encoderCloser.Encode 0 0% 29.36% 2354.01kB 29.36% | github.com/prometheus/common/expfmt.NewEncoder.func7 2354.01kB 100% | github.com/prometheus/common/expfmt.MetricFamilyToText----------------------------------------------------------+------------- 2354.01kB 100% | xizhibei-app/controllers/internal_rpc.(*SystemCtrl).GetMetrics 0 0% 29.36% 2354.01kB 29.36% | github.com/prometheus/common/expfmt.encoderCloser.Encode 2354.01kB 100% | github.com/prometheus/common/expfmt.NewEncoder.func7----------------------------------------------------------+------------- 0 0% 29.36% 2354.01kB 29.36% | xizhibei-app/controllers/internal_rpc.(*SystemCtrl).GetMetrics 2354.01kB 100% | github.com/prometheus/common/expfmt.encoderCloser.Encode----------------------------------------------------------+-------------
现在,我们基本可以确认是在我实现的 GetMetrics
中,处理 prometheus 客户端的序列化压缩时候出了点小问题(但是还没有到内存泄露的地步)。另外,这里你也可以加个第三步:用 list
加上关键词的命令来查看精确到每一行代码级别的分析。
定位到问题后,就是最后一步解决,我的解决方案是用 sync.Pool
。在之前,我是直接使用 gzip.NewWriter
来压缩每次从 prometheus 中取出的指标文本,但是这样会造成 gzip
多次重复的内存申请以及初始化,所以当改用 sync.Pool
后,我的代码从:
1 | buf := new(bytes.Buffer) |
变为:
1 | var ( |
我们可以写个 benchmark 来测试下:
goos: linuxgoarch: amd64cpu: Intel(R) Core(TM) i9-9820X CPU @ 3.30GHzBenchmarkEncode-20 2422 504022 ns/op 851822 B/op 129 allocs/opBenchmarkEncodeWithSyncPool-20 7654 150188 ns/op 48799 B/op 108 allocs/op
可以看到,内存的 allocs
从 129 降到了 108。
好了,分析就暂时到这。
对于大多数人来说,在网页上用鼠标点击分析问题更简单,因为目前 Go pprof 这个工具做到了一条龙服务,你可以直接在网页上看到调用图表以及火焰图(这里需要着重艾特下 C/C++,咱还能不能把调试体验做好点了)。
1 | go tool pprof -http=:6000 http://localhost:8080/debug/pprof/heap |
Go 会打开一个本地 6000 端口的网页,但如果你在云服务器上,你有两种选择:
wget http://localhost:8080/debug/pprof/heap
,然后拷贝到本地进行分析;ssh -L 8080:127.0.0.1:8080 user@server
;首发于 Github issues: https://github.com/xizhibei/blog/issues/175 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA)进行许可这本书在我看来,就是在教你如何创业。因为,创业成功的难度不亚于一次火箭发射,虽然还会有更难的探月、探火星、载人,只是,这个过程中的方法论都是一致的,都是由普通的人类在用科学的方法,将不可能变为可能。
事实上,在技术行业,似乎做出一件伟大的产品似乎也是不可能的,比如你能在现在想象我们国家能在十年后造出可以跟 ASML 高端光刻机吗?
同样,可能你也无法在多年前想象我们国家有了自己的高铁、盾构机、003 航母、歼 20 战斗机,等等。
这本书的作者是是奥赞 · 瓦罗尔(Ozan Varol),一个前火箭科学家,美国俄勒冈州路易拉克大学法学院最年轻的终身教授。
整本书谈到的事情,其实就是如何将不可能变为可能。我们在工作中,肯定都会遇到当时认为不可能完成的事情,没有人会告诉你如何一步步去解决,如果这时候你能有书中告诉你的思维方式,你就完全有可能将不可能变为可能。首先就是绝不在一开始就承认不可能,起码需要去尝试,打破自己的固有认知,激发自己的创意,然后从一堆的创意材料中不断尝试、失败与总结,最终找出那个能点亮灯泡的钨丝。
对于创业的人来说,这种思维难能可贵,因为在他人看起来困难重重的事情,在你眼里却是充满机遇,你可以用火箭科学家的思维方式来打破不可能,重新定义现状,开辟新道路,打出一片江山来。
科学 “不仅仅是知识,更是一种思维方式”。
其实在小时候,老师问我们想成为什么的时候,好多的同学说要成为科学家。我小时候也是这么想的,只不过我认为的科学家可能就是火箭科学家了,因为那时候刚从书本上知道了我们国家在 1970 年发射过东方红一号卫星。等我长大了,没有成为火箭科学家,更没有成为科学家,成为了一名计算机工程师(好吧,就是程序员)。
不过你看,其实这两者有相同之处:我们都在用科学的手段解决问题、实现梦想。
接下来谈谈我从书中学到后,自己的几点感悟,涉及大量剧透。
我们的本能让我们对确定性非常迷恋,因为这是自然选择的结果:进化心理学也告诉我们那些追求不确定的人在远古时间往往难以生存,所以他们的基因无法传递给下一代。
但是当我们能克服这种倾向,敢于冒险,机遇才会向你招手。
只有当我们敢于牺牲确定性答案,敢于冒险,敢于远离路灯的时候,才能真正实现突破。
美国的阿波罗登月计划,是在没有完全的准备下才开始,事实上:
肯尼迪发表演讲之时,与登月相关的许多技术标准甚至还没有制定出来,美国宇航员从未在宇宙飞船外工作过,宇宙飞船也从未在太空中进行过对接。
这幅场景,简直就跟老板说下周要看到某个巨复杂的功能一样,大家刚刚听到后,估计也是一脸懵,同样会跟 NASA 那帮科学家一样束手无策。但话说回来,如果你能担当起这个责任,拿出方案来告诉老板为什么不可以做,或者如何去实现、并且做成做好了,那么你解决问题的能力就会越来越高,当然,同样越来越高的还会有你的位置与薪资待遇。
只有当我们注意到一些微妙的线索时——数据有些问题,结论下得太快或流于表面,观察结果并不完全符合理论——旧模式才能给新模式让路。
前面说了那么多,但我们没法绕开的就是风险了,因为冒险就意味着有着很大的风险。这时候,我们就需要采取冗余和安全边际着两个工具了。
其实这就是所谓的高可用了,当你把程序部署到机器上去,它所面临的真实情况很可能就是你的测试用例所无法覆盖的,这时候就要用 Plan B 之类的冗余方式来保证高可用,不至于让程序再也无法启动,同时,安全边际就意味着你的服务能够承受较大的破坏,一个例子就是即使你 80% 的服务器都坏了,你依然能够用剩下 20% 的服务器来保证核心功能的正常使用。
另外,巴菲特与查理恐怕是最懂这个道理的人了,因为在他们价值投资的理念中,最重要的一条就是保证自己的投资标的安全边际足够大,或者说足够便宜,来保证他们的投资即使遇到股灾也不会损害到太多本金。
这句话被硅谷钢铁侠说出来后,一夜火遍大江南北,全球的科技圈都知道这句话了,其实它说的内容很简单,也就是回归本源。
每次革命性创新背后的要素原创性在于回归本源。
敢于质疑现在的每一个不合理之处,回归本源去思考来龙去脉。
作为一个软件工程师,当你遇到不合理的需求的时候,你会反驳吗?其实这种批判与质疑一切的思维方式,就是第一性原理:你完全可以在接到需求的那一刻,开始思考这种需求背后的需求,尽一切可能去了解真正的用户需求,从而能给出真正合理的解决方案,而不是在用户三天两变的口头需求下疲于应付。
用户不会知道他们想要 iPhone 这样的手机,只会在乔布斯发布后才会真正知道想要的是这样的手机。与此同时,诺基亚这样的传统手机厂商只会一次又一次将他们的功能机一次又一次优化,从来不会去质疑需求的合理性。
突破常规思维才能创新,努力跳出路径依赖:「别人就是这么做的」,这句话怕是当你提出问题时,太多的产品经理以及老板都会说的一句敷衍的话语。长久以来互联网领域的复制是如此简单而有效,导致大家互相抄袭,而且但凡有个创新的地方,没过几天就会被竞争对手抄袭,于是大家都逐渐忘记了当初为何那么做。
我们总以为同行和竞争对手知道的比我们多,我们往往喜欢复制粘贴他们的做法,尤其是在形势不明朗的情况下。
知识确实是个好东西,但知识的作用应该是给人们提供信息,而不是起约束作用;知识应该启发智慧,而不是蒙蔽心智。只有让现有的知识不断进化,我们的未来才能变得越发清晰。
不知不觉中,知识可能会让我们成为惯性的奴隶,而惯性思维只会产生常规结果。
其实,第一性原理背后,是变化的环境,当初看起来合理的方案时过境迁,我们需要回溯到过去,找到那个最初的问题,重新去思考,不要被过往的知识束缚。
这点其实告诉我们的是,在我们给出解决方案后,要及时在仿真环境中去测试。这个原则放到我们的软件工程里面,其实对应的就是各种各样的测试:单元测试、集成测试、e2e 测试、压力测试等等。但是,真正重头戏的还是放到线上预发布环境后的测试,因为那里是一个仿真环境,那里有与生产环境中非常类似甚至一致的用户测试数据,是我们放到生产环境前的最后一道关卡。
在正确的测试中,你的目标不是发现所有可以顺利进行下去的东西,而是发现一切有可能出错的东西,并找到极限点。
是否重视测试,是作为一个区分一个软件工程师是不是真正靠谱的标志,因为没有人能保证自己的东西一定不会出错,但是我们却可以通过各种测试方法来减少出错的可能。
倘若不进行系统测试,就可能产生无法预测的后果。产品出厂前的最后一刻,如果你要对产品做修改,却不重新测试整个产品,那你就要冒灾难性的风险
对于失败,我们有太多太多的讨论,无论说失败是成功之母,还是失败是失败之父等等,都会有他们各自的理由。但是这本书作者认为的,要认为成功与失败这两者的界限很模糊,或者说同等重要。
每次失败都是一次宝贵的学习机会,每次失败都会暴露一个需要修正的缺陷,每次失败之后,我们都会朝着最终目标迈进一步。
类似的观点我在 如何理性地失败:黑匣子思维 讨论过。只是在最后我说过:
建立容忍失败的文化,以及事后分析的制度。
但是却无法提供可以落地的实践,但是本书也介绍了两个有意思的实践:
一个是由领导带头,将失败经历公之于众:
一项研究还表明,人们仰仗领导者开启变革。如果领导者不承认自己的过失,如果有人认为领导者从不犯错,那么,我们就无法指望员工会冒险质疑领导者或者揭露他们自身的错误。
另一个是学会体面地失败,即所谓的「暴露疗法」,经常让自己暴露在失败面前,学会习惯性的失败,这就是所谓的平时多流血,战时不流血。
相信大家看出来了,在书中提到的很多方法,不就是软件工程领域的一些方法吗?是的,简单的说法是科学的方法都是是相通的。
不过,我还是忍不住多说一些。肯尼迪在宣布登月计划后的解释说:「因为我们现在不知道前方有什么好处等着我们」。这项计划最后的结果就是,登月成功,花费巨大,但是却极大提升了国家整体工业水平,催生出一系列先进技术,其中当然就包含给计算机工程启发的各种思维方式。
我们能从航空航天里面获得太多东西,也能学到太多东西,这也正是我们国家在发射东方红卫星时,即使还处于贫困阶段,也要这样做的原因,因为这是一项功在当代,利在千秋的事情。
首发于 Github issues: https://github.com/xizhibei/blog/issues/172 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA)进行许可Linux 中,有好些个工具是跟时间相关的,最近工作遇到了它们,于是打算写几篇与 Linux 时间相关的文章。
今天先说说 hwclock
这个工具,估计也就玩物联网的朋友会用到了,因为这个工具往往只是用来保持硬件设备的时间的,但是前大多数设备往往都是联网的,也就是用的 NTP。
另外,Ubuntu 15.04 之后就用 systemd
来管理时间了,它里面自带的 timedatectl
工具取代了 hwclock
,不过本质上是差不多的内容,这里就不多说了。
当设备无法联网的时候,RTC 就会变得非常重要,系统的时间将会依靠纽扣电池的能量来维持。如果设备需要经常开关机,那么就会更加依赖 RTC 来保持设备时间的同步。
它的原理很简单,就是用纽扣电池驱动 RTC(Real-Time Clock)芯片来保持设备断电时候的时间,这样当设备重启的时候,就能直接从 RTC 恢复时间了。
首先让我们来看看 hwclock
的帮助信息:
Usage: hwclock [function] [option...]Time clocks utility.Functions: -r, --show display the RTC time --get display drift corrected RTC time --set set the RTC according to --date -s, --hctosys set the system time from the RTC -w, --systohc set the RTC from the system time --systz send timescale configurations to the kernel -a, --adjust adjust the RTC to account for systematic drift --predict predict the drifted RTC time according to --dateOptions: -u, --utc the RTC timescale is UTC -l, --localtime the RTC timescale is Local -f, --rtc <file> use an alternate file to /dev/rtc0 --directisa use the ISA bus instead of /dev/rtc0 access --date <time> date/time input for --set and --predict --delay <sec> delay used when set new RTC time --update-drift update the RTC drift factor --noadjfile do not use /etc/adjtime --adjfile <file> use an alternate file to /etc/adjtime --test dry run; implies --verbose -v, --verbose display more details -h, --help display this help -V, --version display version
下面来说说,如何使用这个命令来解决我们常见的两个问题。
首先要分清两个时间,一个是硬件时间,也就是在 RTC 等硬件芯片中的时间,另一个是系统时间,也就是系统内核中的时间。
为了同步时间,用到它的两个参数就够了:
hwclock --systohc
hwclock --hctosys
其实这步做完就可以完成离线状态下的时间同步了。设备能够在大多数情况下,达到设备时间保持与真实时间同步。
但,如果设备的时间精确性很重要,那么你就需要用到它的矫正功能了。
其实 RTC 的工作依赖于一块 32.768kHz
的晶振,也就是一块石英晶体,然而,石英晶体是不稳定的,尤其在温度变化的时候,就会变得有误差,这个误差每天可以达到一秒或更多。
上图来自1,可以从图中看到,温度过低或者过高都会导致偏差增大,而我们的设备一般是无法放在一个恒温环境下的,于是每天必然造成误差。
如何矫正这个误差呢?有硬件方案,也有软件方案。
硬件方案,德州仪器公司给了一个方案1,可以直接用温度传感器来补偿 RTC 的精度,由于对硬件这块儿不熟悉,也说不出个所以然,只是明显的,硬件成本会增加一些。
软件方案就会朴实很多,因为我们可以假设这个设备所处的环境不变,硬件时间与系统时间的偏差是系统性的,简单点说,就是每隔一段固定的,它们之间时间的偏差其实是一致的2。于是,我们用软件工程的角度来低成本地校准,也就是 hwclock
的校准功能。
它会用到一个文件 adjfile
,用来记录校准的状态,不过先需要解释下 adjfile
的格式,它默认是 /etc/adjtime
,它的内容包含 3 行文本3:
校准的用法也非常简单:
不过在开始之前,首先你需要确认 Linux 内核没有激活自动同步系统时间到硬件时间,不然会被 NTP 的 11 分钟模式 自动同步2。具体就是运行 adjtimex --print
或者 adjtimex
,看它的 status 值,看看有没有 UNSYNC
,有就是不同步,或者需要自己计算下 status & 0x40
,为 1
表示不同步2、4。
hwclock --systohc
,这时候,/etc/adjtime
里面的时间戳将会更新,但是偏移量为 0;hwclock --systohc --update-drift
;/etc/adjtime
里面的偏移量;首发于 Github issues: https://github.com/xizhibei/blog/issues/169 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA)进行许可对于网飞的文化,我向来推崇备至,在之前的多篇文章里面,也提到过很多次:
这次见到这本网飞 CEO 里德 · 哈斯廷斯写的书出来后,也是第一时间排上了阅读计划。这几天看完之后,有种难以名状的感受:就像自己被自己打了脸。
以下涉及剧透,慎读。
整本书的逻辑很简单:给市场最高的薪水,雇佣最牛的人才,提高人才密度,然后给他们最自由的环境、坦诚的职场关系、透明的信息以及充分的授权,然后让他们以公司的利益为上来自由地做最好的决定。同时,在公司内部实行与薪酬福利无关的 360 度反馈,以 4A 原则(下面会提到)来不断复盘反省提升,从而变得更加优秀,并且公司持续按市场最高薪水来调整他们的薪酬,但是一旦发现不合适就会让他们走人。通过这种方式不断提升人才密度,做到行业最佳。
接下来,我来说说让我感受比较深的几个点,虽然有些观点在《奈飞文化手册》也是看到过的,如今再看一遍仍然觉得有些内容反直觉、反传统。
虽然刚听起来,有点耸人听闻,只是细想起来,这也会比较容易理解,在一个人人都是透明坦诚的公司里面,但凡自己做点什么有害公司利益的事情,就会被大家瞧不起,因为在这个人才密度非常高的公司里面,大家都在以公司的利益而努力奋斗。
或许你会担心,一旦给了那么高的自由度,员工滥用了怎么办,网飞是这样做的:严厉惩罚,但是依然保持这个制度。因为他们清楚,保持这种情况,会比束缚员工的自由的代价小很多。
不过这个制度有一个最大的前提,那就是足够高的人才密度,另外还有老板的胸襟以及开放透明的公司文化。
企业拥有一支高绩效的团队,员工才会认真负责地工作;企业拥有坦诚的文化氛围,员工才会互相监督,共同维护公司利益。在此前提下,企业可以放松对员工的管控,给予他们更多的自由。
这也让我想起了《大江东去》中,我们国家经济发展过程中的对待民营企业的态度。杨巡可以说就是我们国家民营经济的代表人物了,他是拼尽全力才能在这个激烈异常的环境中存活下来,一开始是不被国家承认的资本主义小商小贩,非得挂靠在雷东宝的集体企业中才有资格卖货,之后遇到了改革开放才成为有资格注册的个体户,期间也是多次游走于灰色地带「不守规矩」才没有被淘汰,如今太多的民营企业家多少都会有杨巡的影子,都是靠自己的双手拼命打拼才取得如今的成果,如果你让杨巡在他的企业中学习网飞的自由环境,恐怕他只会把你当成骗子轰出大门。
其实我想说的是,我们国家的企业与国外的企业,整体上还是有发展的差距,这种优秀的企业是只有国家发展到一定程度才会出现的产物,我们学习优秀的思想也不能只看表面,学个皮毛,也就是说可以借鉴却不能照抄。我不相信能带给企业发展前景的先进制度,企业家们不会去考虑。正如目前一直在讨论的 996 问题,迟迟没有个结果的原因何在?很简单,我们想要的薪酬福利,只有 996 的公司能给,我们没得选。其实更多是我们国家自身的发展问题,或者说,只是时机未到。
另外,据说国内企业中,字节跳动目前的文化是最像网飞的,我相信这种企业会越来越多的。
或许支付行业最高薪酬能够理解,但是取消绩效奖金确实很难让现阶段的我们来理解了,只是,网飞的理由却很充分。
他们不想靠奖金来激励员工,奖金会让员工只专注于目标,而不是考虑什么才是对公司真正有利的。
相比于专注目标,他们更在意的是员工的创造性,他们不想让员工因为奖金的多少来妨碍创新,因为真正有积极性的员工不会因为奖金多了就更加努力,奖金少了就松懈下来。
创造性工作要求在一定程度上解放你的大脑。如果你总想着要怎么做才能表现好,才能得到高额的奖金,那么你就缺少开放的认知空间,产生最好的想法和最好创意的可能性也微乎其微。结果,你反倒做得更差。
在我们用足够高的工资帮助员工减轻家庭负担之后,他们最具创造力。但是如果他们并不确定自己能否得到额外的报酬,创造力就会下降。由此可见,有利于激发创造力的,是足够高的工资,而非绩效奖金。
不实行绩效奖金,提供更高的基本工资,留住工作积极性高的员工,这些做法都可以增加人才密度。但增加人才密度最有效的办法,是一开始就支付给员工高薪,并且随着时间的推移不断上涨,以此保证他们始终获得市场上最高的工资。
对于取消奖金的做法,我也是比较认同的,相比于完成挑战自己的任务以及做出优秀的成绩所带来的成就感,奖金带来的激励很大程度上就是饮鸩止渴。
这一点怕是有老板看了就深信不疑的一个观点,于是号召大家学习这一点,并扬言 “混日子的人不是我的兄弟”,于是实行末位淘汰都会显得顺理成章。
或许他们做的没问题,关键的地方在于,他们只想像网飞一样提高人才密度,却不想为之付出努力与成本,说人话就是不想给补偿。
网飞的这个做法,即即使员工足够努力,也做出了共享,但是如果没做到跟进公司的创新步伐也要拿钱走人。这点即使是在硅谷也是非常引争议的,事实却证明,结果没有那么坏,因为他们的离职率没有想象中的高,结果也比想象中好太多,他们做出的成果是大家有目共睹的,网飞的电视剧质量越来越高也是一个我们能感受到的侧面例子。
至于网飞如何防止这种淘汰方法妨碍员工的创新能力以及互相协作,很简单,他们做的事情不是末位淘汰。他们在公司内部,营造足够坦诚的文化。所有员工都知道为什么自己会被淘汰,以及通过「员工留任提示」来与领导沟通,获取反馈。并且,员工也能通过被解雇员工的「离职后问答」,知道被解雇的原因,从而消除自己心中的不安。
员工留任提示,也就是鼓励员工与上级进行一对一沟通:
“如果我想要辞职,你会花多大力气劝我改变主意?”
人才密度提高后,要使人才真正发挥作用,就需坦诚以及正面有效反馈。
很遗憾的是,在我们目前的社会中,互相信任的关系还是很少的,我还没有遇到过非常坦诚的公司,因为他们从谈薪酬开始就不会对你坦诚,更不用说进入公司后,公司就会用劳动合同以及种种的条条框框来限制你的行动,从这点来说,我更喜欢小公司:限制少,创新能力强。
所谓正面有效反馈,就是上面说的 4A 原则:
- 目的在于帮助:反馈者应清晰阐述这样做对他人和公司有什么样的好处。
- 反馈应具有可行性:你的反馈必须说明接收人可以做一些什么样的改变。
- 感激与赞赏:我们在收到批评时都会为自己辩护或找借口,这是人类的本能。在接收反馈时,你需要有意识地反抗这种本能。
- 接受或拒绝:不是每条反馈都要求你照搬,但有必要向反馈者真诚地致谢。你和反馈者必须清楚:对反馈意见的处理完全取决于反馈的接收者。
360 度反馈是个非常有用的工具,奈何大部分使用这种工具的公司都把 360 度的结果与绩效考核挂钩,生生的把它变成了 KPI 工具。网飞如何使用它就是个非常有用的例子:它是用来获取身边人的正面有效反馈的。
360 度公开反馈引发了一系列有价值的讨论。我与直系下属系统地分享我收到的意见,我的下属又与他们的团队分享他们得到的反馈,层层分享,依此类推。这不仅可以增强公司内部的透明度,也形成了一种 “反向负责制”,即员工从中受到鼓励,能够对上级多次出现的问题进行大胆的反馈。
这种互相坦诚的做法值得每个人学习,虽然暂时会对自己不利,只是心底无私天地宽,路自然会越走越宽。
我对这个观点印象非常深刻,因为书中举了个很形象的例子:
当你的孩子要去参加一个聚会的时候,你作为家长会怎么做呢?
如果你告诉孩子什么不可做,什么可以做,并且要孩子把所有相关的信息告诉你,之后你还要偷偷跟着孩子去监视他,这就是控制管理。
而如果你是告诉孩子,什么不可以做,并且还通过一些辅助手段(一起看书、看教育片等)教育孩子为什么不可以做,事后也只是让孩子事后有问题马上联系自己,这就是情景管理。
想必大家看了也会明白,也就是所谓的少限制,多放权,少微观管理,多宏观管理。相比于事无巨细地告诉他们目标在哪、如何达到目标,激起他们对目标的渴望更重要。
现实中,大部分公司都是金字塔形的结构,即所有的重大决策都需要层层上报,但是能做决定的领导往往不是那个适合做决定的,因为这不是所谓的 “让听得见炮声的人来做决策 “(没想到吧,这句话是华为老总任正非说的)。网飞的这种所谓的树形结构,让处于树根的领导层专注于真正的战略层面,而把具体事务的决策权下放到处于各个树枝的负责人手里。
如果想要公司在松散耦合的体制中高效运转,让员工个人也能做出重大决策,那么老板和员工必须就他们的目标达成一致。只有领导和员工认识清晰,目标一致,松散耦合的体制才能发挥作用。这种一致性能够驱动员工做出决策,以完成整个组织的使命和战略任务。
这种方法论,其实国内的企业家都明白,而且很多优秀的企业也在内部实行了,一如华为。而这其中的原理,明白一点软件架构的技术人也能看出来:高内聚低耦合的架构能应付多变的业务场景,方便调整,非常高效以及灵活。放到企业组织架构中也一样,对于任何一个处于创新引导的产业中的公司,这种架构能够更有效的应对未知的市场,从而取得成功。
至于另外提到的目标是创新而不是防范错误,我已经在文章开头的几篇中多次提到,不再赘述。
其实整本书说的都是围绕这自由与责任这两个词来说的,什么是自由与责任以及如何做到自由与责任。
随着职场经验的累积,我发现自己不像从前那么强调网飞多么优秀了,因为我发现从前的自己强调的更多是自由,而不是与之相生相伴的责任,或者说,我也更理解老板们的苦衷了。虽然他们看了这本书之后,还是会认为不可能做到网飞的程度,甚至是跟以前只学华为的狼性却不学华为给员工大块肉,现在可能只想学网飞开除不称职的员工,却不想给员工遣散费。
回过头来,我才认识到自己一直是那个只见树木不见树林的人。自己一直认为是众人皆醉我独醒,可结果是小丑竟是我自己。网飞的文化终究是属于网飞自己的,但是企业优秀并不代表文化就一定优秀,可以借鉴,但不可照抄。
至于那么好的文化,有没有可能被国内公司或者自己所在的公司学到精髓我不清楚,对于我自己而言,做到配得上网飞文化,还是可以努力的:让自己得到一件东西的最好方式,就是让自己配得上它。
首发于 Github issues: https://github.com/xizhibei/blog/issues/166 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA)进行许可最近的几场外部对接,由于我们的业务涉及到对外合作,因此避免不了跟外部工程师的对接,然而对接的时候,就是出现各种幺蛾子的时候了。但是,这个过程中,让我发现了一个很有意思的事情:在群里吵起来的两个工程师,私下聊天的时候却是和和气气的。
我们从典型的外部对接开始说起。
大致的流程是这样的:销售跟商务接触合作方,双方互相了解需求,如果合适的话,就开始商讨各自去哭,确定如何进行商务上的对接,同时,这时候看双方的合作意愿情况,来决定谁是大爷,谁是孙子。至于判断谁是大爷,从我一个工程师的角度来说,那就是国企 / 央企 > 有政府关系的私企 > 大企业 > 小企业。相信你快看就能看出来,其实就是按资源的掌控程度来决定的。
所以,基本上大爷就只需要提供 API 文档,技术文档就可以了,这些文档会被商务转发到工程师手里,进行初步的技术判断,看看对方的文档能不能提供己方的需求。
当大家基本确定需求了,一起进一个群,同时把双方的工程师拉进群里,这时候,问题就出现了,有些商务比较没概念,直接把没有事先通知详细情况的工程师拉进群,就会导致奇葩情况出现。
首先大家互相介绍,之后对方在群里扔了一个文档,然后己方的商务直接艾特你,于是你很懵地看了看文档,大致明白了对方让你对接。然后问题就来了,如果你这时候说对方的 API 文档不满足需求,甚至提出了修改意见,那对方就会非常傲慢地跟你说:
“其他家都是这样对接的,他们没有问题,怎么就你有问题?“” 你这方案不行,我们不可能为你们专门修改系统的,修改了其他客户怎么办?“
“你们的实施成本高,那是你们需要解决的问题,这跟我们没有关系。“
这时候,你只能憋着一肚子火,简单回一个:” 好的,明白了 “。
以为到这个时候,事情就完事了吗?显然不会。因为后续的流程都必须按照他们的流程走,而这个流程会让你非常痛苦,这不是接口对接了就完成的事情,因为你的公司可能才是卖产品给用户的人,后续一旦有问题,客户肯定第一个找的就是你,如对接服务总是出问题,不稳定,接口出错,于是你必须抽时间来解决这些问题,再次跟他们确认问题所在,帮对接方查找问题。
一旦查到了问题所在,你会发现这时候火药味很可能会很浓,因为出错的一方注定会被「鄙视」。其实不用偷笑,就算作为当事人的你,在心里多少会对对方有想法的。
看到这里,或许你会认为对方真傲慢,是个不讲理的人。
然而如果你加了对方好友,你就会发现聊天的时候,对方完全变了个样。在群里的时候,他不会那么好说话,甚至会跟你针锋相对。但是一旦到了私下,你会发现对方可能比较好说话,有什么问题也会第一时间给你解答,帮你解决问题所在。
为什么呢?
显然,群是在一个公开的地方,商务销售甚至顶头上司也在,那他所有的话都会变的非常正式,态度强硬,不会让你觉得非常好讲话。但是私下聊天,没有了群里的压力,同为技术人员,但凡有点胸襟,大家都会互相学习,有一种同为打工人,都不容易,互相帮忙的意思。
或许你会认为我在与外部对接中是个比较好说话的人,那你又错了。
因为我们公司也有当大爷的时候,而事后才发现,我居然也会非常傲慢。比如一旦对方说看不懂我写的文档,或者不会搭建一些基础服务的时候,我就会认为对方水平不行,懒得理睬。当同事在私下提醒我,说话的时候不要那么冲的时候,才明白自己给对方留下了不好的印象。
关键,我们还不是国企央企之类的,是个小微企业,那就意味着我还不得不给他们解答,为他们修改方案。
这个问题不仅仅发生在我身上,我的同事也会如此。在群里吵架的我也见到过不少,然而那个吵得很凶的同事,现实中却是一个与我们相处很愉快的人。
有对接经历的朋友,相信会有类似的体会,明明是挺好相处的一个人,却要在外部对接中,显得那么强硬,我把这种现象叫做「工程师的傲娇」。至于背后的原因,其实很大程度上是自尊心,无法放低自己的姿态。
一旦明白了这其中的缘由,其实就可以进一步反思自己了,有必要跟同为工程师的对方较劲?放低姿态,理解对方,我们才会离成功更进一步。
我也是慢慢体会到:不要把对接的另一方同行当作同行,而因该当作你的用户,你应该服务好的用户。于是,你会认为用户他不会使用你的产品吗?或者你会认为用户水平不行,不够格使用你的产品?或许是的确不会用,但更重要的是你没有让他们觉得很好用,假如真这样的话,你就会失去用户,而一旦把对方当作你的用户,心里平和的你,也会更愿意接受对方的意见,更加理解你的用户,从而改进自己产品。
我们也不妨把这个思想推广一下:假如你要做公司内部的产品,比如监控,也需要考虑一点:你的监控不仅仅是为你自己服务的,把你的同事当作用户,不要用无穷的监控报警去轰炸他们,考虑下用更好的方案,比如 Prometheus 以及 Alert Manager 。
首发于 Github issues: https://github.com/xizhibei/blog/issues/165 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA)进行许可今天这篇文章算是对 【CMake 系列】(五)安装、打包与导出 的一个补充。其实我本打算跟上篇文章放在一起,毕竟都属于动态链接库相关的知识,但是这样一来就不容易被出现问题的同学们检索到了(才不是为了再水一篇文章 doge)。
是因为这个问题困扰了我不少时间,在好几个项目里面都遇到了这个问题。
那就是链接动态库的时候,编译出来的可执行文件会带有编译时的绝对路径,于是你将程序拷贝到其它地方运行的时候,必须把动态库放到绝对路径里面去,而不是放在系统里面相关的 lib 路径下面。
举一个例子,假如我们要实现一个 FooConfig.cmake
,这个库中既有静态库也有动态库,那么如果我们要在项目中使用,大概的实现方式是:
1 | find_path(FOO_INCLUDE_DIRS NAMES foo.h) |
将它命名为 FooConfig.cmake
然后放在位于项目根目录的 cmake 文件夹下,并且在项目中这样使用:
1 | find_package(Foo REQUIRED HINTS ${PROJECT_SOURCE_DIR}/cmake) |
最后,假如我们查找的库在 /path/to/foo/home
下面,那么我们用在项目中得到的结果会是这样的:
1 | $ readelf -d a.out | grep NEEDED |
这里就出现了绝对路径,当初这个问题折磨了我很久,一直以为是 RPATH 的问题,最后发现是 CMake 本身的问题。
出现这个问题的原因就是库的 Package Find Config 不对,我研究了挺长时间,最后在官方的讨论中找到了原因以及答案:
IMPORTED_NO_SONAM
E 的属性;UNKNWON
类型的库;于是,将上面的代码改下即可:
1 | # ... |
首发于 Github issues: https://github.com/xizhibei/blog/issues/162 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA)进行许可所谓的 RPATH,就是硬编码在可执行文件或者动态库中的一个或多个路径,被动态链接加载器用来搜索依赖库。1
这个值是存在可执行文件或者动态库的 ELF 结构中的 .dynamic
小节中,它可以用 readelf 或者 objdump 查看。
具体就是 readelf -d a.out | grep RPATH
或者 objdump -x a.out | grep RPATH
。
至于搜索路径,除了 RPATH,链接加载器在 Linux 中,还会有另外几个关键的路径,他们的搜索顺序如下:2
至于为何需要那么多的可配置方式,就在于不同的程序会有不同的需求,比如对于不同版本库的需求,就需要单独设置 RPATH,用来指定依赖库的位置,而不是使用系统相关的 LD_LIBRARY_PATH,因为这个环境变量可能会破坏其它程序的运行。
下面来说说 CMake 中的 RPATH。
跟 RPATH 相关的设置有如下几个(MacOS 相关的今天按下不表):
$ORIGIN
,相对路径;这些设置,可以使用 set_target_properties
单独设置在 target 上,也可以在加上 CMAKE_
前缀后,设置成全局配置。至于如何使用,完全取决于你想要如何运行你的程序,比如从编译目录中或者安装目录中运行,可能就需要完全不同的配置。
其实在大多数场景下,我们都不需要设置这些东西,因为一旦设置了 RPATH,很可能会不方便移植,但是如果你需要单独的依赖库路径的时候,这些东西就需要了。
而如果你真的需要 RPATH,建议的做法是 使用相对路径,这样就会更容易移植到不同的机器上,比如 Linux 系统中,可以使用 $origin
。3
1 | set(CMAKE_INSTALL_RPATH "$origin/../lib") |
首发于 Github issues: https://github.com/xizhibei/blog/issues/161 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA)进行许可在很久之前,我在 划一划 HTTPS 以及 SSL/TLS 的重要知识点 提到过客户端 HTTPS 证书,之后就没后续了,不过目前还是遇到了接口问题,不得不用上了。
接下来会给大家说清几个概念:HTTPS 单向认证、HTTPS 双向认证、中间人攻击。
在这两种认证场景中,主要的区别在于服务器是不是需要客户端提供证书验证,我们,在进行 TCP 三次握手成功后,就开始进行 SSL 阶段的握手了了。
SSL 握手阶段可以参考如下,此图盗自 Ssl handshake with two way authentication with certificates 。这张图里面的大致阶段是对的,只是并不是很详细,更详细的请参考 Transport Layer Security 。
正如如上图所示,这个过程中,主要区别就在于第三阶段1:
如果把第三阶段省略掉,那就变成单向认证了,所以担心加密过程开销的同学们可以稍稍省点心了,因为整个过程中,就是 SSL 握手阶段的开销会略微加大,而实际通信过程中的开销还是与单向认证一样。
所谓中间人攻击,就是在双方握手阶段,由于没有用 CA 对证书进行验证造成的问题:比如你如果不在意浏览器的警告,或者被骗安装了攻击者提供的 CA 证书。攻击者可以在你与服务器建立 SSL 通信的时候,先用假的证书与你建立 SSL 通信,然后再与服务器建立 SSL 通信,这个过程中,攻击者就能拿到这个过程中所有的明文消息。
用一个非常简单的例子来说明,就是 HTTPS 抓包,比如现在你需要抓手机上的 HTTPS 请求,那么你就需要在手机上安装抓包工具提供的 CA 证书,然后配置好代理后,就可以抓到 HTTPS 的明文请求了。
到目前,我们知道了 HTTPS 双向认证是怎么一回事,那么它能解决我们的什么问题呢?防止有人冒充我们的客户端来请求服务器的私有资源,那么我们可以直接去验证客户端的身份,就能在很大程度上解决问题。
下面自问自答,来帮大家理清楚思路:
相信这样的场景很常见,为了保护接口,直接跟客户端约定一个密钥,然后大家根据这个密钥来进行加密通信,这种方案在我看来很容易破解,而且一旦破解拿到密钥,你很难短时间里面通过更新客户端来更换密钥。
这种方式,做的好的能够使用公开经得起验证的加密算法,而做的差的会用私有加密算法(多个公开算法叠加套用不算私有)。这在一定程度上能够阻挡破解者,只是私有加密算法出问题的概率在遇到高价值资源的时候,就会变得非常大,简而言之,就如闭源与开源一般。
另外,还有个很尴尬的地方在于,在开发人员进行接口调试的时候,一大段密文信息将会让调试非常困难。于是,所谓的的防止破解,更多的时候都在恶心开发者自己了。
用 HTTPS 证书就不会有这个问题了,在自己电脑安装完成证书后,就能在浏览器中明文调试了。
理论上,跟 HTTPS 单向的认证区别就在于多了一步验证客户端证书的过程,因此会多出 SSL 握手时的开销,具体我还没有压力测试过,但是我相信玩过 kubernetes 的应该会对此有个概念。
如果你预计会有比较大的流量,不放心,那就建议先找几台机器进行测试再决定也不迟。
确实如此,如果客户端会流落在他人手里,那么在不加一层保护的前提下,确实有可能会泄露,只是你有两种方案可以一起使用来保护证书:
strings
命令, 或者解压你的安装包就能获取到明文证书。另外,你也可以直接用 pkcs12 格式的证书,记得更换一个更强的密码;我的理由主要有两点:
大多数没有选择客户端证书的,我认为是因为对它不够了解,或者说它的强大显得过于简单,导致很多人都没有什么安全感,尤其是领导没有什么安全感,但是请大家不妨想想现在有谁能够简单破解 HTTPS 请求内容呢?基于 RSA 加密的通信,是我们现代互联网的安全基础,没有谁能够轻易破解。
目前大部分方案都是拿 OpenSSL + Nginx 来举例的(随便搜索都是一大堆),那我就把 OpenSSL 换成更简单好用的 CFSSL 。
多种方式可选,比如最简单的就是直接去下载已经编译好的工具:cfssl/releases 。
我目前使用的是 v1.5.0 版本的,大版本前提下,命令应该不会相差太多。
生成 CA 默认配置:
1 | cfssl print-defaults config > ca-config.json |
这里面的内容需要略加修改,下面给出一个我修改的例子,因为今天只需要客户端证书,因此 profiles 里面只留下了 client,signing.default.expiry
表示签发证书的默认过期时间,时间比较短,而另一个 signing.profiles.client.expiry
则比较长,8760h 就表示一年了。
1 | { |
然后是生成 CA 证书申请:
1 | cfssl print-defaults csr > ca-csr.json |
也需要根据自己的情况修改,这里需要注意下 key,v1.5.0 下面默认是 ecdsa-256 ,而我改成了 rsa-2048,只是为了说明方便,以及兼容性。
1 | { |
然后就可以生成 CA 证书了:
1 | cfssl gencert -initca ca-csr.json | cfssljson -bare ca - |
在当前目录下,会生成三个文件:
接下来就开始生产客户端证书,与上面是类似的:
1 | cfssl print-defaults csr > client.json |
照例给出 client.json 的修改样例:
1 | { |
1 | cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client client.json | cfssljson -bare client |
命令行中会出现如下的日志:
2021/02/03 15:21:09 [INFO] generate received request2021/02/03 15:21:09 [INFO] received CSR2021/02/03 15:21:09 [INFO] generating key: rsa-20482021/02/03 15:21:10 [INFO] encoded CSR2021/02/03 15:21:10 [INFO] signed certificate with serial number 657128885698804019594922156238712961504332210277
然后生成了三个文件:
当证书发布出去后,在有效期之前,它是一直有效的,但是如果我们想让它提前失效(比如泄露的情况下),那么就需要用到撤销证书。
比如,我们要撤销上面刚刚生成的证书,需要先将序列号写入撤销证书序列号列表,然后来生成撤销证书:
1 | echo 657128885698804019594922156238712961504332210277 >> crl-serials.txt |
然后替换已有的撤销证书即可。
好了,到目前为止,证书部分基本上就搞定了,相对 OpenSSL 来说是不是很简单?
将上面生成的 ca.pem
以及 ca-crl.pem
放在 /etc/nginx/certs/client_ca
中,然后在 server 中如下配置(这里假设你已经开启了 HTTPS,并且配置好了服务端的证书):
1 | ssl_client_certificate /etc/nginx/certs/client_ca/ca.pem; |
更新 Nginx:
1 | nginx -s reload |
这里留给你一个思考题,为什么这里只需要配置一个 CA 证书就可以了?
如果你在配置好 nginx 后,直接用浏览器请求网站,那么你就会看到一个 HTTP 400 错误,告诉你需要提供证书。
本地安装证书很简单,直接鼠标双击证书就能够安装,如果是 Mac,记得在 Keychain 中配置始终信任。
另外,为了方便发送证书给他人使用,建议打包成 pkcs12
格式的证书,还能设置密码。
1 | openssl pkcs12 -export -clcerts \ |
首发于 Github issues: https://github.com/xizhibei/blog/issues/159 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA)进行许可标题是直接翻译自 Poor man’s profiler,也可以叫做最简陋的程序性能分析方法。
这种程序性能分析分析方式还是非常有意思的,第一次见到它时在 Stack Overflow 的一个问答里面,本以为最高票答案的方式会介绍一个非常牛逼的工具,哪知道他介绍的却是一种朴实无华的 Profiler 方式。
我本来也直接跳过了这个回答,觉得太不靠谱了,但是在一一尝试了 gperf、vargrind、perf 等未果之后(主要是因为程序在开发板上,这些工具都没法很好地支持),于是我尝试了下这个方案,不出所料的,居然真解决了我的问题,很简单的几个步骤就找出程序的性能瓶颈所在。
这里就简单挑重点,翻译下作者 Mike Dunlavey 的回答:
在调试的时候,停住几次程序,每次停止后,检查下调用堆栈。如果有代码占用了整个程序一定比例的 CPU 时间,那么你就有较大的可能性会在这几次查看堆栈的时候发现它们。
当然,你也许有多个不同大小的性能问题,如果你解决了其中一个,那么剩下的占用的 CPU 时间就会变大,也就会变得更容易发现。这种放大效应,当遇到多个问题交织在一起的时候,可以成为真正的加速因子。
显然,这种方式过于简陋了,导致被许多人质疑,作者就花费了不少时间来跟人辩论。
首先是说,其他的那些高级工具,给出的调用关系图有两个很大的缺点,一个是无法给出指令级别,另外还会在遇到递归时给出让人费解的统计。
另外的一些针对大家提出的问题,统一用贝叶斯统计来进行了理论上的解释,总的来说还是非常令人信服的。
最后作者把用 perf 等工具测量所有的程序调用过程叫做测量,而用 Poor man’s profiler 叫做程序调用栈采样。这两种分析方式的区别在于:前者是水平的,它会告诉你那些步骤所花费的时间,后者是垂直的,它会告诉你程序在此刻在做什么,如果你第二次发现它还在做这件事,那就意味着这就是瓶颈了。
这其实就跟领导为了了解属下是不是在认真工作(找出公司运作瓶颈),领导不可能在每个人背后装个摄像头来监控(perf 工具测量),进行视察工作(采样)一样,第一次抓到你在摸鱼,领导可能不会说什么,人之常情,可以原谅,但是第二次再抓到你摸鱼,那他就会认为你这个人肯定是在一直偷懒了,你就是性能瓶颈了(嗯,这么说来领导们才是精通程序调试的大牛 :P)。
上面只是说了原理,这里就说说,如何在实践中使用,我们就用最简单的 gdb 来做:
gdb --attach $(pidof your-app)
set pagination 0
,这样就可以让 gdb 输出所有的分页了,当然你也可以不管,手动查看;continue
;CTRL-C
暂停程序;bt
即 backtrace
,就能看到当前程序停住的调用栈了,当然,如果是多线程的程序,你可能需要看所有线程的调用栈:thread apply all bt
;熟悉这个流程后,你也可以写个脚本来进行自动化。但是其中你需要了解两个关键的地方:
一个关键地方是在上面的步骤中,我们采取的是手动 CTRL-C
暂停程序,但是在脚本里面,你很难这么去干,于是你可以用 TRAP
信号来达到同样的效果,具体就是 kill -TRAP $(pidof your-app)
。
另一个关键地方就是,kill
需要自动执行,为达到这个目的,我们还需要 bash 脚本的另外一个技巧,即多进程,在任何一条命令最后加上 &
就可以将这个命令放到后台去执行,于是你可以配合 sleep
命令来达到这个目的,即最终拼凑出类似于这样的命令:sh -c "sleep 5 && kill -TRAP $(pidof your-app)" &
,这条命令不会阻塞下面其他的命令,并且在 5 秒之后,执行 kill 命令。
最终,我们可以写出一个类似于下面的脚本(抄袭的原脚本在 Poor man’s profiler)
1 |
|
果然是一种低成本且效率高的方法,几乎可以说是朴实无华了。
首发于 Github issues: https://github.com/xizhibei/blog/issues/158 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA)进行许可今年总的来说,是比较颓的一年,首先是疫情带来的影响,无论是成长还是机遇;其次是自己今年放弃了几个好习惯,比如坚持每两周一篇的博客,每周两次的锻炼以及坚持阅读。
对于新冠疫情,到目前还是没有结束,虽然已经有疫苗上市了,看情况还是得持续一段时间。不过也有好事,各种公共场合大家都很自觉地戴着口罩,于是除了新冠病毒,今年各种呼吸道传染病减少了很多。虽说没有染上新冠,但是自己身体上的退步还是很明显的:今年又确诊了两种慢性病,腰肌劳损以及慢性胃炎。
去年给今年定的计划中,有提高厨艺这个选项,然而拜疫情所赐,这个计划竟然是第一个完成的,并且提前完成了大半年。
目前最喜欢做,也是自己最喜欢吃的菜:
今年明显感觉到了写博客的吃力起来了,写了一个 CMake 系列之后,就很难有比较好的内容来进行写作了,我觉得,这也是自己积累不够多的表现。
尽管也想去写其它的系列,比如密码学、数字图像处理,奈何自己投入的精力不够,也可以说是积累不够,没法一下子就把这几块骨头给啃下来。
不管怎样,这个系列是自己写的最长的,也算小有成就感:
这方面的总结可以看 CMake 系列完结以及一些碎碎念 ,就不在这里赘述了。
现在主业又开始了新的技能树分支,逐渐拓展自己在 C++ 以及嵌入式系统方面的知识,今年最大的体会就是,原来硬件带来的性能提升是那么明显(当然了,可能是因为自己不怎么玩游戏,没体会过更换显卡带来的性能体验)。
就拿加密算法来说,在普通 PC 上实现的 AES 256 ECB 算法,可能会达到 80M/s,而到了嵌入式设备上,纯用 CPU 就会变成 5 M/s,而这时候换成硬件加密,则能够达到 100M/s 。
另外就是 C/C++ 了,虽然是之前学过,但是如今再次上起手来还是会觉得不适应,毕竟接触过更现代的 Go,Rust,Node.js 后,就会有天然的不适应。缺少了那些随手可得的工具、依赖以及文档,实现功能时需要考虑的东西更多了,尤其当涉及到操作系统、内存管理的时候,非常让人头大。
所以,目前的开发过程中,会尽量不去考虑引入新的依赖,如果要引入依赖,首先是查找一番,对比几个项目的优劣,然后,必须要读源码,尤其是那些文档不佳的项目,还必须深入阅读。如此我才明白有经验的 C++ 工程师是多么宝贵的存在了,凭借熟悉的那些依赖,他就可以在更短的时间里面把事情做成,并且做好。
读书有点遗憾,今年把过多时间用来刷 B 站了,虽然不会跟抖音一样令人沉迷,但是一旦看了之后还是会在不知不觉中消耗了时间,好吧,其实也可以算是沉迷(比如非常喜欢观察者的一些节目,比如王骁的骁话一下、马前卒的睡前消息、董佳宁的懂点儿啥以及沈逸老师的逸语道破,当然还有罗翔老师的刑法学等等),只是在这个过程中,也会学到一些知识,甚至我觉得对比与书来说,吸收知识的效率更高。
话说回来,这些知识,也是所谓的碎片化知识,无法带给我系统的知识,所以理性点说,就是娱乐。
首先报告下今年的理财成绩:25%,只是粗略计算,实际应该会比这个数字高 1%-2%。
之所以达到这个程度,我认为一个是自己看准时机下重注了:疫情初期自己也几乎是拿出每月的工资去抄底基金了,然而最重要的是国运发展好,最终控制住了疫情,让我敢于抄底。
在证券市场中,我们把贝塔收益指市场平收益,而把超出市场平均回报的收益叫做阿尔法收益,所以大家明白了,今年我的主要受益来自于贝塔收益。
今年记账了一整年,几乎手动记录了自己所有的支出,也几乎养成了每笔消费之后的习惯性记账。
有人可能会说,现在有那么多自动记账的工具了,为什么还要自己费劲手动记账呢?确实现在几乎每一笔消费,都会被微信、支付宝甚至银行自动记录下来,并且还能够提供一些简单的分析、总结。只是,它们共同的缺点就是很难去兼容彼此,一旦你用不同的银行或者客户端,都有可能将你的消费打散开来。
还有人会说,写个脚本统一导入记账软件即可,这个我不否认,有些记账软件可以直接对接支付宝微信的导出账单,甚至银行。非常便利,而我不选择的理由就是隐私,作为互联网从业人员,我深感个人隐私泄露的可能性之高,因为数据库被拖,甚至主动卖出数据也是见怪不怪了,因此,我对于国内的这帮互联网大厂,有深深的不信任感。不过,我相信在国家力量的介入下,法制会越来越完善,行业也会越来越注重用户的隐私,真正把用户的隐私当回事,而不是永远以为用户会用隐私换取便利。
其实,之前尝试过不同的记账方式,到了最后都逐渐放弃了,因为那些记账方式,都没有让自己深度参与到其中,记账的工作被机器自动化了,以至于对于最后的账单统计也最多只会轻叹一声:「天,这个月花怎么花那么多?」。
说了半天,其实自己手动记账是一个能让自己对于花出去的每一分钱更有意识的行为,这样做,多少都会找回那种数字简单减少而产生的无动于衷的感觉。
今年记了一整年的帐后,从月账单的统计中,真正意识到了自己在过怎么样的一种生活。然而,自己还是缺乏制作并实施预算的习惯,所以,也没法去掌控自己过什么样的生活。
新年快乐,博客我还会写下去的,毕竟还有那么多的朋友关注我,只是目前频率不能保持恒定了。
首发于 Github issues: https://github.com/xizhibei/blog/issues/157 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA)进行许可在上次的 RTSP 协议详解 中,把 RTSP 协议本身简单介绍了,这次就来说说如何实现一个简单的 RTSP 服务器。
Live555 是我们经常用的 C++ 媒体库,它支持非常多的媒体服务协议,实现了对多种音视频编码格式的音视频数据的数据流化、接收和处理等支持。
它也是现在为数不多的可用库之一,它的代码比较繁琐,但是胜在简单,多数人拿到之后就能简单上手了,因此也非常适合拿来练手,以及改写。
我们拿 testProgs/testOnDemandRTSPServer.cpp
作为例子。
首先创建 RTSP 服务本身:
1 | TaskScheduler* scheduler = BasicTaskScheduler::createNew(); |
然后添加 H264 文件作为视频源,如果不太理解,可以联想上面创建了 HTTP 服务器,而下面则在服务里面添加了 HTTP 路由资源的实现。
1 | char const* streamName = "h264ESVideoTest"; |
在这里,你需要注意到,Live555 的 RSTP 服务器是基于文件去做的,如果你的视频源不是一个静态的文件,那你就需要自己去实现 ServerMediaSubsession
了。
目前,我搜索到的方案主要有两种,一种方案是利用 Linux 的命名管道 fifo
来进行数据的传输,这样就可以在不实现 ServerMediaSubsession
的情况下直接使用。
具体就是用 Linux 的 mkfifo
命令,或者系统 API 调用创建一个管道,然后就可以使用文件的的 API 来进行读写(不得不赞叹 Linux 的精巧之处,一切皆文件)。
1 | mkfifo [OPTION]... NAME... |
然而,我没有进行测试,不过看各种论坛上的提问以及经验总结来看,这种方案虽然简洁,但是性能堪忧,并且会造成很大的延迟。
那么,另一种方案就呼之欲出了,其实新版本中,Live555 已经给出了解决方案的例子,就在 liveMedia/DeviceSource.cpp
中。
1 | DeviceSource* |
目前我用这种方式,采用芯片硬编码,能获取在内网 1080P 30fps h.264 1000ms 以下的延迟。当然了,实验代码写得比较粗糙,需要进一步优化了,这里就不放出来了,相信总体思路还是对的。
https://blog.csdn.net/xwu122930/article/details/78962234
https://blog.csdn.net/u012459903/article/details/103099705
https://blog.csdn.net/weixin_33700350/article/details/86010562
首发于 Github issues: https://github.com/xizhibei/blog/issues/156 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA)进行许可如果你遇到了这样的问题:
刚往你的 Linux 机器中,写入了个文件,然后直接断电重启,发现文件只剩下了空壳,即产生了一个没有内容,大小为 0 的文件。
那么,你遇到了磁盘缓存问题,或者更确切的说法是掉电导致的磁盘缓存丢失问题。
我们在小学二年级学过(是的,我也是天天给毕导视频点赞的),在 Linux 系统(实际上是任何操作系统)中,磁盘读写都是有缓存的,因为这种缓存往往有利于系统的读写加速,毕竟我们大部分场景下遇到的都是多读少写,因此,用暂时用不到的内存来当缓存,空间换时间是非常值得的。
缓存的作用原理是,当你读了一个文件,Linux 会先检查内存中的缓存有没有对应的内容,没有才会去读磁盘上的内容,然后会先将磁盘上的内存读到内存中,再返回给用户。这样,下一次读的时候,就不用再次从磁盘中读了,这样就会大大减少文件读的时间。
如果你这时候往这个文件中写了新的内容,Linux 会往缓存中写,而不是直接往磁盘里写,这样,你写文件的时间就会大大减少。只是,写过的缓存会被 Linux 标记为脏了,也就是所谓的内存脏页,Linux 会周期性地收集所有内存脏页,排序整理,然后往磁盘中真正写入,这就是所谓的回写(writeback),也可以叫做刷盘。
所以,我们的问题的原因就找到了:因为突然掉电时,造成了内存中的脏页来不及刷入磁盘,导致数据丢失。
这里说个题外话:这个缓存有多大呢?我们来随便看个 Linux 系统的内存。
1 | $ free -m |
我们可以看到,第五列的名称是 buff/cache
,表示这个系统缓存占用了 5563 MB 的内存,几乎是大部分内存(其实 buffer 与 cache 还是有一定区别的1,但是在我们今天讨论的问题中,可以一起对待),但最后一列 available 告诉我们,缓存也是可用的,当你的应用申请内存时,Linux 就会清理缓存,让出内存给你的应用2。
至于这些缓存具体用来了干什么,不是今天的主题,这里就略过了,你可以自己做实验3。
1 | To free pagecache: |
让我们回到开头提到的问题,看看如何解决。
目前的解决方案有两种,可以单独使用,也可以一起使用。
第一种是 open
文件的时候,加上 O_SYNC
标志,表示这个文件写的操作需要直接刷盘,也就是说每次调用 write
之后,文件数据和元数据都会写入磁盘,或者调用 fsync/fdatasync/sync
这几个系统调用,效果是一样的。
不过这种方案的缺点是很明显的,即所有写操作的延迟都会大大增加,不建议在频繁写的地方使用。( O_DIRECT
不在考虑范围之内,如果你做的是数据库才可以考虑,不然造成的后果是你无法承受的。)
第二种就是内核参数大法,通过调整内核缓存相关的参数来进行调优,这种方法相对于第一种会温和一些,可以做到尽量不影响系统性能。
下面就具体说说跟当前这个问题涉及到的几个内核参数。
大家回想下小学三年级学过的虚拟内存相关的内核参数,对的,就在 /proc/sys/vm/
这个目录下,有我们需要的参数。
当然了,三年级的同学都知道还有 sysctl 这个工具,我们可以直接获取所需要的名称以及对应的值,具体涉及到的有如下几个:
1 | $ sysctl -a | grep dirty |
vm.dirty_background_bytes
以及 vm.dirty_background_ratio
是用来表示脏内存「软」阈值,一旦超过这个阈值,系统后台刷盘进程就会开始运行,将脏数据刷到磁盘上,增大这个值就会增加缓存大小,反之亦然。bytes
与 ratio
的区别就在于前者是绝对值,后者是相对值(内存的百分比),下面同理;
vm.dirty_bytes
以及 vm.dirty_ratio
是用来表示脏内存「硬」阈值,一旦脏数据到达了这个阈值,系统就会阻塞所有 I/O 操作,然后进行刷盘。这个值受磁盘写的速度影响非常大,假如磁盘写很慢,就不能设置太低,否则就会让整个系统卡住比较长的时间了;
vm.dirty_expire_centisecs
是用来表示脏数据的时间阈值,一旦超过这个阈值,就表示对应的脏数据应该刷盘了,减少这个值会减少遇到掉电丢数据问题的概率;
vm.dirty_writeback_centisecs
是用来表示内核检查脏数据的运行间隔,单位是厘秒(秒的百分之一),与 vm.dirty_expire_centisecs
配合起来使用,减少这个值也会进一步减少遇到掉电丢数据问题的概率,但是多少都会影响系统的性能;
vm.dirtytime_expire_seconds
这个主要是给 lazytime inode 设置的过期时间,比如 inode 只是更新了 atime,这种更新非常频繁的数据就没必要短时间就更新,而且负责刷盘的是另外一个专门的 dirtytime writeback 进程,因此这个的默认时间比较长:12 小时,这个就不建议调整了,作用不大;
具体如何调整,就可以按照机器的磁盘读写速度、内存大小以及具体应用场景来平衡系统 IO 性能、数据安全与成本这三者之间的关系(毕竟有钱的情况下,磁盘换 SSD、加大内存以及买个 UPS 就能搞定这些问题了,嗯,有钱真好)。
首发于 Github issues: https://github.com/xizhibei/blog/issues/153 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA)进行许可