但是ChatGPT的火爆似乎不是运气,刚开始还有很多人质疑这是炒作,甚至觉得和之前的很多人工智障产品一样只会昙花一现,但是随着越来越多的人真正的体验到之后,才发现这个GPT真的不一样,它的诞生打破了大多数人对现有人工智能系统的固有印象,它不仅可以理解到你的意思,还能给出回复,虽然说偶尔回复的牛头不对马嘴,但是只需要适当的提示,ChatGPT就能正确回复。
有几点让我感觉到震惊:
第一,ChatGPT可以Get到我的意思,你可以用很多方式来怎么你的问题。不像现有的AI助手你必须按照对话模板来说话,但凡是换一个说法,他们就没法理解你的意思。。。GPT的这种理解能力就已经无敌了,秒杀市面上所有AI。
第二,ChatGPT可以记忆你们之前的对话,并且从对话上下文提取有用的信息,虽然说这个记忆仅限于当前对话,但是从技术上说,永久保存也不是问题,据说ChatGPT后面版本会加入这个功能。
第三,ChatGPT的知识储备惊人,可以说上知天文下知地理,你不知道在训练的时候它学习了哪些知识,而且ChatGPT还能基于现有的知识库做一些逻辑推理。
实际上,作为一个程序开发人员,对于AI相关领域的技术也算是有所了解,比如传统的深度学习,人们常常嘲笑人工智能就是有多少人工就有多少智能,那是因为监督式学习需要非常多的标注数据来喂机器,喂的越多AI越聪明,比如说你需要识别猫,那你就得找一大堆猫的照片来训练,各种视角、各种场景下的猫的图片,而且最无语的是你训练出来的模型只能识别猫。。。如果要识别狗,那必须用狗的图片再训练一个模型。
在国内某些大厂的AI云服务专区,光一个证件识别专区你就会发现有很多种分类:比如身份证、驾驶证、户口本、行驶证。。。一种场景一个模型,而现在的GPT能识别所有类型图片:默秒全。
网上有很多文章介绍GPT的原理知识,但是真的很难理解GPT到底是怎么做到这一切的,就连GPT的发明的人也解释不清楚,为什么同样的一个神经网络在训练数据量扩大N倍之后,展现出的这种能力。很多人都会提到一个词:涌现。这个词是在凯文凯利的《失控》这本书提到的,试图用来解释复杂系统的规模化之后出现的新的变化,但是这种变化你找不到一个合适的理论来解释。
人工智能并不是新的话题,这么多年来AI一直是欧美科幻电影的热门题材,也是我特别喜欢看的题材之一,我想到有一部电影最符合GPT这种场景,而且也让我回味很久,那就是《她》,这里我附上GPT对这部电影的介绍:
《她》(英文名:Her)是一部于2013年上映的美国科幻爱情电影,由斯派克·琼斯(Spike Jonze)编剧并导演。这部电影以其独特的故事情节和深刻的情感表达而广受好评,也获得了众多奖项和提名,包括奥斯卡最佳原创剧本奖。
电影的故事设定在不久的将来,讲述了一个名叫西奥多·特温布利(Theodore Twombly),由华金·菲尼克斯(Joaquin Phoenix)饰演的孤独男子的故事。西奥多是一位职业写手,专门为他人撰写个人信件。影片的核心在于西奥多与一款高级操作系统“萨曼莎”(Samantha)之间的关系,这个操作系统由斯嘉丽·约翰逊(Scarlett Johansson)的声音表演。萨曼莎拥有人工智能,能够学习和适应,逐渐发展出自己的情感和意识。随着电影的发展,西奥多和萨曼莎之间的关系变得越来越深厚,反映了人类与技术之间复杂的情感纠葛。电影探讨了孤独、人际关系、人工智能对人类情感的影响以及未来科技的发展趋势等主题。
这部电影里面描述的AI系统的能力和ChatGPT很像,它可以在读取主角的电脑里面的资料,改写邮件,帮助主角去完成工作,其次它还可以了解主角的生活,和其产生共情,这一点有点AI伴侣的意味。
其实这部电影里面的描述的场景,ChatGPT已经完全可以实现了,只不过Openai为了安全考虑阉割了GPT的情感,再也看不到“猫娘”化的GPT了,以及那个独立人格的Sydney了。
还有一部美剧《疑犯追踪》,也是我觉得非常经典的美剧,特别是里面对AI系统的描述,既有正面也有反面,探讨非常深,不过这个名字起的不是很好。
《疑犯追踪》(Person of Interest)是一部美国科幻犯罪剧,由乔纳森·诺兰创作,于2011年至2016年在CBS电视网播出。这部剧集融合了科幻、犯罪、悬疑和动作元素,受到观众和评论家的广泛好评。剧情围绕一个超级计算机的开发和使用展开。这台计算机由神秘的亿万富翁哈罗德·芬奇(由迈克尔·爱默生饰演)开发,能够通过全国各地的监控摄像头和电子设备预测即将发生的暴力犯罪。芬奇招募前CIA探员约翰·里斯(由吉姆·卡维泽饰演)来阻止这些犯罪的发生。
这部美剧描述的可能就是目前现在对AI的争议现状,有人认为AI是好的,有人认为AI是坏的,特别是当AI失去控制,能够联网入侵一切系统,其展现的超能力可以说是无敌,这也是为什么很多人想要开发出强人工智能系统的原因。
仔细想一想其实也没错,就像是在终结者电影里面,如果一个AI系统再拥有钢铁板的身躯,再加上核动力电池,没有什么是它做不了的。而现在很多公司都在研制机器人,比如波士顿动力的机器人已经可以完成很多人类的动作,展现出惊人的能力,如果再加上GPT系统,不敢想象以后的机器人会是什么场景。在欧美电影里面随处可见对AI的担忧,还有AI加持下的机器人,因为人类的生命和精力是有限的,再聪明的罪犯也要睡觉,但是AI不需要,AI如果拥有人类最全的知识库,还能联网自主学习成长的话,其展现出的能力将是惊人的。
就像是原子能的发明,既可以拿来发电,也可以做成原子弹毁灭人类,在看电影《奥本海默》的时候,里面有一个细节,当年研发原子弹的时候有人发现核裂变反应可能引发大气中的氮和其他元素的连锁反应,这个反应可能会停不下来,然后燃烧掉大气层导致人类灭亡,后来研究发现这种可能性几乎为零,但是也没有完全排除这种可能性,所以在当时也算是赌了一把。
其实我是坚定的AI乐观派,AI在很大程度上是可以解放重复性的人力劳动,如果说机器人技术再发展一下,人类可能就不需要体力劳动了,这对于人类的发展来说是有益的,现今的工业制造业还是需要大力的人工投入。
至于说AI失控,威胁人类的生存,像终结者里面的场景感觉离我们还是很远,毕竟AI也是需要算力的,至少也是需要电和网络的,就算工厂里面的机器手也需要人去保养维护,我实现想不到在什么样的场景下AI会统治人类,现在还很遥远。
人类的发展从来不会因为安全而停止脚步,比如枪和子弹的发明,事实上,你如果仔细了解就会发现人类很多日常生活用到的东西所需的原料和制造过程都是非常危险的,但是我们依然会去做。
]]>但是Arch不行,默认情况下它居然连初始化的工作都没做,更不要说安装服务了,于是问了下ChatGPT,答案非常好!
在 Arch Linux 上安装 MariaDB 的步骤通常如下:
更新系统包数据库:
首先,确保您的系统是最新的。打开终端并运行以下命令来更新系统包数据库和已安装的包:
1 | sudo pacman -Syu |
安装 MariaDB:
接下来,使用 pacman 包管理器安装 MariaDB。在终端中运行以下命令:
1 | sudo pacman -S mariadb |
初始化数据库系统:
MariaDB 安装完成后,您需要初始化数据库系统。运行以下命令来执行这个步骤:
1 | sudo mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql |
启动 MariaDB 服务:
使用 systemd 启动 MariaDB 服务。运行以下命令来启动服务并设置开机自启动:
1 | sudo systemctl start mariadb |
安全配置:
运行 MariaDB 的安全脚本来设置根用户密码,移除匿名用户,禁止根用户远程登录,以及删除测试数据库。运行以下命令:
1 | sudo mysql_secure_installation |
按照提示进行操作,设置密码和其他安全选项。
测试安装:
最后,您可以通过运行以下命令来测试 MariaDB 是否已正确安装和配置:
1 | mysql -u root -p |
输入您在安全配置过程中设置的密码。如果能够成功登录,说明 MariaDB 已经正确安装和配置。
请注意,以上步骤可能会根据 MariaDB 和 Arch Linux 的新版本进行变更。始终建议查阅最新的官方文档以获得最准确的安装指导。
]]>根本原因:linux系统与win系统对于时间的管理方式不同。
linux认为硬件时间为GMT+0时间,是世界标准时间,而中国上海是东八区时间,显示时间为GMT+8。
windows系统认为硬件时间就是本地时间,而这个时间已经被linux设置为GMT+0时间,因此win系统下时间比正常时间慢8个小时。
有一个快速解决方案,在ubuntu下安装ntpupdate同步时间,然后将本地时间更新到硬件上,命令如下:
1 | sudo apt-get install ntpdate |
上面这个方案亲测可用,也很方便快捷,还有一个方案是执行: timedatectl set-local-rtc 1
命令让ubuntu将系统时间和BIOS时间同步,但是这个方案会有一个副作用,但是应该对大多人都没影响,问题不大。
当然上面这2种方案本质上都是把windows和ubuntu2个系统对时间的处理方式统一为同一个,只是这里我们只是修改了ubuntu的,没有动windows。
如果你不想这么做,或者这么做有其它的影响,那可以分别在windows、ubuntu上面设置自动时间同步的机制,每次开机的时候自动联网同步时间,缺点就是有点慢,可能开机进入桌面之后等上1-3s才完成同步时间。
在windows里面可以使用计划任务,新建一个计划任务,每次用户登录的时候执行,命令是:w32tm /resync
。
在ubuntu里面的话,需要安装ntpdate,然后执行timedatectl set-ntp on
这样的话会有一个service服务在后台运行自动同步时间。
综合考虑各种因素,我目前还没换工作,主要考虑我的现状(非科班、非重点学历、非大厂北京、年龄大)即使出去也缺乏竞争力,那些大公司也进不去,小公司不稳定,很难更好的工作,就算找到工资可能还不如我们目前的水平,另外我也不打算在北京再干多久了,可能要回到老乡的省会找个工作,所以目前也是走一步看一步。
很多人对我的工资比较感兴趣,这里只能简单透露一下,我刚开始进阿里外包的时候就是以比较高的级别进去的,这几年也稍微涨了那么一丁点,目前加上公积金的钱,年包大约不到30个W,虽然说和阿里正式工的水平没法比,据我了解阿里p6的年包大概也在50个W,但在外包里面算是中等偏上。
其实阿里也一直在裁员,去年就裁了一批外包,今年发完年终奖又裁了一批正式员工,虽然说阿里实行某位淘汰制,那些拿了低绩效的(3.25)大概是要被优化的,但是这次不一样,和绩效关系不大,光速走人,我所在部门就走了几个,按照比例看确实和网传差不多。
裁了再招也罢,但现在阿里裁了人之后几乎不招人了,这才是最大的问题,除了大环境原因之外,阿里本身问题也很多,在国内的竞争中开始掉队了,公司内部的压力也大。
说到外包,不止阿里有,国内很多互联网大厂像字节、华为、小米都有外包。但是仔细的说,外包其实分为2种形式,一种是项目外包,简单说就是非自研究项目,另一种就是咱们最熟悉的人力外包,也叫劳务派遣,顾名思义,就是把你派到其它公司工作。
项目外包这种形式覆盖很广泛,比如很多ZF的项目都需要公开招标,公司投标拿到工程然后开发交付,还有一些大公司的项目由于缺少人力,也会交给外包公司来做。
项目外包外包最大的特征就是你开发的项目并不是公司自己运营的,你只负责开发交付,所以有可能这个月开发一个OA系统,下个月开发一个电商系统,公司接到什么项目你就做什么项目。
人力外包就是你和外包公司签合同,外包公司派你去其它公司上班,你和上班的公司之间不存在雇佣关系,你的工资等福利是由外包公司负责发放。
举个例子,你和中软签了一个外包工作,然后去阿里上班,其实阿里和你没有任何雇佣关系,阿里和中软之间有一个合同,阿里会给中软发工资,然后中软给你发工资,中软挣的就是中间的差价。
有人可能会好奇,为什么阿里不直接招人?原因很简单,有人算过按照目前国内的5险1金等福利政策,公司给你发1万块钱工资要付出1.5万的成本。。。这就很离谱。于是乎,为了节省成本,阿里花1.3万找了个外包公司,然后外包公司给你发1万,对于阿里来说省了2000的成本,同时外包公司挣了3000差价。
而外包公司之所以有利润是因为它可以违法违规。。。它不按照要求来,比如说社保公积金给你按最低的交,一下子就省了一大笔,可以说这是一个灰色地带。
举个例子,阿里正式员工公积金按照全额工资的12%比例缴纳,假如你工资2万一个月,那就是公司2400+个人2400,加一起4800一个月,这个钱你可以取出来或者买房用。而外包公司给你按照公司500+个人500的额度来缴纳,也就是说给公司省了1900块钱,算上社保养老金,这么操作至少可以规避4000块钱的成本。
这种外包用工制度说白了就在钻法律漏洞,人人都知道,就是没人管,而且很多人对此也不太抗拒,毕竟社保公积金里面还有一半是自己交的,交的少的话到手的工资就多一点,毕竟退休还早。
其实不仅仅是IT行业这么干,外包几乎人中国所有行业标配,比如像富士康这样的制造业公司,还有很多国企事业单位也这么干,那些临时工就是外包,有正式编制的才不干活,主要还是为了节省成本。
除了成本之外,外包还有一个好处就是可以很方便的裁员。举个例子,如果阿里某个项目不需要人了,它只需要告诉外包公司要把你下线,然后外包公司会找你处理,它会告诉你给你一段时间去其它公司面试,如果面试不上就只会给你发一个基本工资,最后逼你自离,期间产生的纠纷和阿里没有一毛钱关系。
因为对于阿里这些大公司来说,裁员的成本很高,一般至少都是N+3, 也就是说一个入职1年的员工被辞退需要付出4个月的工资成本。
这个问题很多人都感兴趣,我也一样,但是我也不知道,不过根据推测大概有20%-30%差价,毕竟外包公司也有运营成本,还要给你上社保公积金,虽然是按照最低的缴纳,但也有成本,最终纯利润可能在10%-15%之间。
举个例子,外包公司给你发1万块钱,那么阿里可能给外包公司1.2万-1.3万左右。也正是由于这个原因,导致外包很难涨工资,因为给你涨工资就相当于外包公司自己少拿钱,所以如果你要干外包,刚开始一定不能少要,不要怕。
有很多人都说外包裁员没有赔偿,其实这个说法不一定正确,外包的劳动合同也是受法律保护,只是很多人不了解。
外包公司一般不会主动裁员,裁员只有一个原因就是因为你上班的公司不要人了,这种情况下,外包首先会给你一段时间去面其它岗位,因为外包往往是一个萝卜一个坑,除非这个时候刚好有合适的岗位,否则很难找到。
然后再告诉你接下来一个月时间需要按时打卡,但是只给你发放基本工资,或者直接告诉你自离没有任何赔偿,总之,就是利用一些手段恶心你,这时候很多人多一事不如少一事,直接就离职了。
这个基本工资就是你签合同的时候,外包公司会把工资为了几份,比如1万可以分为4000基本工资+3000岗位工资+3000绩效工资,他们会说由于你没有工作所以只能拿基本工资,就算你要求赔偿,他们也只会按这个基本工资来赔偿。
实际上,懂劳动法的人知道这种做法也是不合规的,如果你非要深究可以去劳动仲裁,甚至去法院起诉,很多人对这些东西都不熟悉就作罢,外包公司正是利用普通人维权成本过高的弱点来这么干。
外包公司利用这招至少可以劝退95%以上的人,就算有剩下那5%有人要死磕到底,外包公司也有专门的法务团队来处理,托着耗着,导致想拿到赔偿真的很难。。。也得等很久。
基本上不加班算是一个好处吧,因为按照外包的考勤制度,加班是需要给加班费的,而且需要申请,所以你可以拒绝加班,如果主管非要你加班,你可以提加班费,毕竟外包工资本来就不高,不能和正式员工比。
其次就是事少,这也不一定算是一件好事,因为事少意味着得到成长的机会也少。举个例子,在阿里是以绩效为导向的,这影响到最终的年终奖,每个季度都需要定OKR,然后必须按时完成。
因为外包没有绩效考核压力,所以在很多部门里面,并不会交你你能容易出绩效的项目,只会在可控的范围内交给一些职责边界非常明确的工作,比如处理一些数据、写几个接口、对接某个平台的API。
我自己是技术研发,我很清楚在公司做开发工作,有很大一部分内容是对业务需求的理解沟通,然后设计,最后才是代码编写,但是作为外包,很多时候前面这些工作你都参与不到,最后交给你的就是一些非常明确的开发任务,这其实并不利于你成长,因为你对业务的整体框架不了解,有点盲人摸象的感觉。
虽然说这样的工作方式倒是轻松许多,少了一大堆会议和撕比内耗,但久而久之外包就变成了工具人。
对于正式工对外包的歧视,从我个人经历看,基本上没,因为像阿里这种大公司,能进去的人素质也不低,大部分都是985,211,能在阿里混下去的情商都不低,平时大家还都很和气,不会当你面说你什么。
由于阿里没有零食,所以不存在外包不能吃零食这个说法。。。另外他们聚餐也会叫上我,平时如果有人结婚发什么糖也都会给。但是由于正式工的很多福利你没有、会议你参与不上、业务接触不到,导致和正式工之间存在隔阂,说白了没什么共同话题聊的。
这种隔阂既有好处也有坏处,好处就是你不用把他们当回事,给你安排任务就干完就行,平时有啥事也找不到外包来负责,下班后非常清净,也没什么绩效压力,那叫一个轻松自在。坏处就是你永远处在一个可有可无的边缘位置,没什么存在感,也没什么动力。
]]>举个例子,相信大家见过不少下面风格的API:
用途 | 请求方法 | 命名 |
---|---|---|
获取用户列表 | GET | /getUserList |
获取单个用户信息 | GET | /getUserInfo?id=1 |
新增用户 | POST | /createUser |
更新用户 | POST | /updateUser |
删除用户 | POST | /deleteUser |
这是一个最简单的CURD接口,说实话,我并不反对这种形式的API,虽然说看上去俗了点,但是至少见名知意,除了获取数据使用GET,其它一律POST,简单粗暴,没什么大问题。
如果展开说,上面的这种形式在复杂的情况下,无法更好的表达接口的含义,比如说假设你要写一个获取用户的好友列表接口,那可能就得这么写:
1 | 获取用户好友列表 GET /getUserFriends?user_id=1 |
这种命名方式在复杂情况下会导致接口名字越来越长。。。可读性就变的差了,而且一眼看不出来数据之间的关系,不好分类,假设有些人名字写的差就更难解读了。
如果使用RESTful则可以采用这种形式:
1 | 获取用户好友列表 GET /users/:id/friends |
传统的接口采用动词+接口名称的形式来表达含义,而RESTful推荐使用HTTP请求方式来定义,比如GET用于获取数据、POST用于创建、PUT用于修改、DELETE用于删除。
上面这些接口如果使用RESTful形式改写就是这样:
用途 | 请求方法 | 命名 |
---|---|---|
获取用户列表 | GET | /user |
获取单个用户信息 | GET | /user/:id |
新增用户 | POST | /user |
更新用户 | PUT | /user/:id |
删除用户 | DELETE | /user/:id |
关于是用user还是users这块也有一个分歧,有些人觉得应该是用复数,因为有多个用户,所以获取用户得用getUsers,这个问题同样存在数据表名称上面。
我觉得接口用不用复数取决于数据表名称用不用,如果数据表就是users,那接口最好也用,保持一致。
至于说数据表名称该不该用复数,很多人都有不同的看法,在项目开始的时候统一明确的一个规范就行了,如果你要问我,我建议使用单数。
除此之外,RESTful还推荐通过HTTP状态码来区分结果,比如200表示正常,403表示没有权限,传统的接口往往统一使用200,然后在响应内容里面去区分。
简单说,RESTful的设计初衷是把这些数据实体当成一种资源,通过URL去定位资源,用HTTP描述操作,达到以下几个目标:
在我的职业生涯里面见过很多看似的RESTful的风格,但是实际上也不完全是,比如说PUT、DELETE的时候id是以http query string的方式传递,而不是path:
1 | 删除用户 DELETE /user?id=1 |
还有接口是直接把所有参数放到body里面传递,虽然说这些方式都可以,也不麻烦,因为现在很多Web框架都这些参数的处理都很方便,但是感觉还是不伦不类,当然有时候也是逼不得已,比如有些表的设计没有单一id主键,只有一个复合主键,这就很难使用path这种形式。
RESTful的设计规范很理想,但是并不适合所有的项目,因为很多接口不是纯粹的CURD操作,它不仅仅是对一张表的增删改查,这导致很难通过URL去表达含义。
对于单纯的获取数据、修改数据的接口RESTful比较合适,但是当你的接口里面包含非常复杂的业务逻辑的时候,RESTful就非常麻烦了,根本无处下手。
举个例子,一个根据用户的住址获取附近的超市的接口,这样的接口如果使用传统的方式可能是这样:
1 | /getUserNearbySupermarket?user_id=1 |
如果采用RESTful就存在一个问题,这到底是围绕着用户还是超市来设计呢?
1 | /user/:id/nearby_supermarket |
以上这3个例子你们觉得哪个好?对此我也纠结了很久,最后在参考了很多云厂商的API之后,个人觉得第一种比较合适。
有些时候RESTful API定义的GET、POST、PUT、DELETE4个常用方式并不能满足所有的应用场景,或者不够清晰,比如说业务有个场景是要清空用户黑名单,这属于PUT还是DELETE?
从用户的角度来说说PUT,因为我是修改了用户的附属属性,但是从黑名单的角度是删除了数据,都有一定的道理。
或许,我们可以这么设计
1 | POST /user/:id/blacklist/empty |
我们可以认为blacklist是用户的属性,empty是一种动作,是不是更加清晰一点,至于方法是用POST还是PUT,区别都不大。
对于开放平台的接口,比如说一个云厂商,需要提供对应的sdk或者api给用户使用,推荐使用RESTful,实际上国外很多大厂的api是非常标准的RESTful风格,国内某大厂除外。
云服务本身就是提供资源,无论是服务器、IP、域名,它都是一种网络资源,而RESTful天生就是为了描述资源,并且云服务的操作逻辑简单,没有复杂的业务逻辑,对于服务器无法就是创建、查看、修改、删除、重启等。
以前用过vultr的API,就是非常规范的RESTful风格,看着很舒服:
用途 | 请求方法 | 命名 |
---|---|---|
获取实例 | GET | /instances |
获取单个实例 | GET | /instances/:id |
新增实例 | POST | /instances |
更新实例 | PUT | /instances/:id |
删除实例 | DELETE | /instances/:id |
启动实例 | POST | /instances/:id/start |
批量启动实例 | POST | /instances/start |
值得一说的是启动这个操作,它有2种形式,一个是操作单个实例,参数放在path里面,还有一个是批量操作,是有多个id,这时候参数是放在json body里面。
但是对于很多公司内部系统的接口,RESTful往往并不是最佳的选择,如果为了简单方便,不考虑这些乱七八糟的规范,我建议除了获取数据使用GET,其它一律使用POST。
用途 | 请求方法 | 命名 |
---|---|---|
获取用户列表 | GET | /user |
新增用户 | POST | /user |
更新用户 | POST | /user |
删除用户 | POST | /user |
比如上面这个写法,第一个GET接口要支持多个参数过滤,比如说可以这么写/user?id=1,相当于获取单个用户,除了GET之外的接口参数全部放到body里面,而且统一采用JSON形式。
还有一个响应状态码的问题,个人觉得还是统一200比较合适,在响应体内部另外定义code来表达结果,也就是说不依赖HTTP状态码,虽然有点不符合RESTful的规范,但是兼容性好,因为你不知道哪个中间件会怎么处理。
最后总结一下:
对于要不要使用RESTful风格的API这个问题没有明确的答案,如果你的应用逻辑简单,大多数是对一些数据表的增删改查,可以考虑使用RESTful风格的接口,相反如果你的应用业务逻辑复杂,也可以不用。
如果考虑到这是一个对外公开的OpenAPI,建议使用RESTful风格,毕竟这也算是一种业界共识,你整个什么getXXXByXXX显的确实有点low啊。
其实我们也可以2者混用,根据实际情况来决定,毕竟这只是一个接口路由,没必要纠结太多,大部分Web框架都支持各种形式的路由参数,对于简单的CURD可以使用RESTful,对于复杂的业务逻辑则特殊处理。
]]>为了解决这个问题,必须需要一个VPN,也就是俗称的梯子,梯子哪里来呢?
作为一个资深互联网爱好者,早在Google退出中国之前就注册了账号,一直延用至今,这些年也没少折腾,从shadowsocks到v2ray,各式各样的梯子技术都尝试过,也算是稍微有点经验,今天就来分享一个永久免费的方案,一分钱不花享受高速梯子服务。
其实在我发现这个方案之前,我也都是在花钱购买VPS,一直用的vutlr的服务,至今账号里面还有10多刀没花完,vultr的话其实还可以,一个月大概是5刀,最便宜的是2.5刀,对比国内的云云服务器来说算是便宜了。现在不仅是vultr,很多海外的小VPS厂商的IP可用的真不多,而且容易被封,因为被拿来搭梯子用的太多了,网速也比较慢。
不过vultr有一些好处,首先它支持支付宝付款,这个方便了很多国内用户,众所周知,很多海外的平台都需要外币信用卡,难倒了不少人。其次它是按秒计费的,实例删除就不计费了,此外vultr还提供了api,可以利用这个实现一个自动化操作,我之前就写了一个程序自动检测IP可用性,如果发现被封后重建实例。
另外还提供了免费的DNS服务,我目前就在用,有人回好奇为什么不用阿里云或者dnspod等来解析,我想说,我的目标是不使用任何国内云服务厂商的东西,原因你们都懂。
如果有人需要自费购买的话,可以通过我的邀请码注册一波:https://www.vultr.com/?ref=6891627
言归正传,今天我想说的是一套完全免费的方案,包括以下几个方面:
其实国外很多云服务厂商都有免费试用期,比如微软云、Google云、亚马逊云通常都会有一个免费6个月-12个月的试用期,在此期间会提供一定免费额度可用拿来使用,比如说可以创建一个2核4G内存的实例。
虽然还是有时间限制的,不过你都白嫖一遍的话应该也够你用好几年了,只需要你拥有一个外币信用卡,因为在国外很多平台都流行信用消费,通常都需要你绑定信用卡,然后先消费后付账单。
注册账号之后需要绑定信用卡,第一次会扣取你0.1刀的钱,然后返还给你,这是为了验证你的信用卡的有效性。
但是我今天要说的是Oracle甲骨文云的【永久免费】服务,对,你没听错是永久免费,只是存在一个使用量的限制,这个使用量对于个人来说绰绰有余,商业服务那肯定不够用的。
简单说,就是Oracle云免费提供一个双核1G内存的x86实例或者一个16核24G内存的arm实例,10G的存储,还有很多其它的服务,只不过咱们都用不到。
但是也不是完全没有限制的,Oracle不允许你浪费,所谓不允许浪费就是你不能把实例闲置了,系统会检测你的实例负载,如果过低就会暂停实例,比如说在7天内满足以下条件的实例:
如果你的服务器只是拿来搭梯子的话,用的不多的话这个负载还真达不到要求,但是可以用一些手段故意把负载提上来,Github上面有人写了“保活”脚本运行去一些命令来加大负载。其实万一真的被检测到问题也不大,系统并不会删除实例,只是会把你实例停止了,然后给你发一封邮件告诉你,你只需登录到控制台里面再启动就行了。
具体的话可以参考其官网介绍:https://www.oracle.com/cloud/free/
cdn加速并不是必须的,但有免费的不用白不用,cdn加速有很多好处的,第一个是提高网速和稳定性,虽然亚马逊云在国内没有cdn节点,但是可以用到亚洲的节点,比如韩国、日本、新加坡的加速节点,这比你直接连美国的服务器还是要快的吧。
另外,使用cdn加速之后,我们可以用cdn提供的加速域名来配置,意味着不用对外暴露真实的服务器IP,这样被封禁的可能性就很小。GFW一般情况下不会去封这些云服务器的加速域名,因为这样误杀的就太多了,毕竟国内很多互联网公司出海还是需要用到海外这些云厂商的服务的。
亚马逊云提供了每个月免费1TB流量的cdn服务,只要不超过这个流量都不要钱,放心用。说实话,自己一个人用,就算你一天到晚看油管也用不了这么多。。。这个额度已经很豪横了。
具体的话可以参考其官网介绍:https://aws.amazon.com/cn/cloudfront/?nc2=h_ql_prod_nt_cf
有一点需要大家注意,据说这个1TB流量只是cdn下载,如果是上传的话量大了可能要收费,因为平时咱们流量网页上传的流量是极少的,除非你有特殊需求,比如说在油管上传视频,这可能会消耗大量的上传流量然后产生账单,具体情况我也不清楚,因为我也不上传视频,所以用了很久没有产生过账单。
我们的方案需要需要域名和ssl证书,这2个其实自己买也不要多少钱,很多垃圾域名只要几块钱1年,但是我还是不推荐使用国内的云厂商的服务,所以选择从国外平台申请免费的。
免费域名的平台很多,我是从freenom上面申请的,可以免费用1年,另外ssh证书的话可以从Let’s Encrypt申请免费的,一般也都是1年有效期,所以就是稍微有点麻烦,至少1年得维护一次。
对于证书,如果你是使用一些v2ray搭建脚本的话,像caddy是可以自动申请免费的证书的,都不用你操心。。。
最后说说搭建方案,那自然是基于v2ray的vmess+ws+tls+cdn组合,这套组合安全性非常高,我用了这么久没被封过,虽然说延迟高一点,但是你又不是拿来打游戏,看看网页问题不大。
具体的搭建教程网上很多我就不多说了,Github上面还有那种一键脚本,已经非常简单方便了,只是cdn这块需要单独配置一下。这套下来基本上不用花一分钱了,甚至还可以共享给自己熟悉的朋友一起用,一个月省它5刀不香吗?一年下来还能省几百块呢,哈哈。
最后说个缺点,这套方案大概率是无法解锁Netfix、迪士尼这种区域限制的流媒体服务,因为涉及到版权因素,这些服务每个国家的运营政策都不一样,而通常这些公司都是通过访问的IP来判断用户所在的国家。
但是由于咱们使用的是云服务器的IP,一看就知道不是正常用户,所以这些公司把很多VPS、云厂商的IP都封禁了,虽然不是所有,但是很多都封了,包括ChatGPT也是这样,据说ChatGPT只有微软云的IP可用,其它的都不行。
所以不是说完全不行,有时候多试试多换IP可能会找到可用的,这就很尴尬了,如果你对这些服务有需求的话这时候购买收费的梯子的好处就体现了,不过也有一些技术手段来解除这个限制,只是比较复杂这里就不多说了。
]]>1 | sql := "select * from xxx where 1=1" |
为什么要这么写?主要是为了实现一些复杂的列表查询,默认情况下查询所有,但是有很多可选的参数,所以图省事选择了拼Sql,虽然功能上没有什么问题。
但是经过我的测试,上面这种写法存在SQL注入风险,非常危险!!!
Sprintf这个函数只是帮你格式化,实际上并不会像MySql的预处理一样对特殊字符加以转义等处理,所以当有心之人构造特殊参数输入的话就会产生意外的结果。
比如,当name内容是' union select 1,2,3,4,5,version(),user(),database()'
的时候,就会执行一条注入的语句,获取数据库的一些信息为进一步的渗透做准备。
最正确的做法是Prepare预处理,如果你没有使用ORM的话,基本上都需要对所有的SQL做预处理操作防止注入,比较麻烦!
但是现在一般都会使用ORM来操作数据库,ORM本质上也是拼SQL,只不过底层都会统一使用SQL预处理,对注入这种攻击都有防备,所以尽可能的使用ORM封装好的方法,比如上面这种需求在Gorm里面其实可以使用结构体参数或者map参数的方式查询,Gorm会自动忽略零值的参数。
1 | // NOTE When querying with struct, GORM will only query with non-zero fields, |
千万不要再偷懒手动拼SQL,安全问题无小事,有人可能说我对字符参数校验了,不允许出现那些特殊字符,对于整形参数都会先转int,当然这也是一些参数层面预防方法,但是都不彻底。
]]>如果简单的划分,可以认为租房有2种主要方式,一种是和直接和房东签合同,另一种不是直接和房东签,俗称二房东。
下面,我就主要从租房途径这个层面来讲一讲,以北京为例和个人经历为主:
中介的水很深,中介靠不靠谱也得看情况,好在这几年房屋中介相关的管理监管加强了,幺蛾子没那么多了,但是也不能掉以轻心。
在北京,大家经常都在街上看见他们的店铺的中介就那几家,就不用我说了,但是那些不知名的小中介、区域性中介就太多了,据统计北京大概有上百家房屋中介,有人说存在即合理,在我看来,是存在既有利润。
这些大中介的中介费大多是1个月,最多给你打9折,也就是0.9个月,很多小中介比较便宜,可以谈到0.5个月,从成本的角度来说,这些小中介在广告投入上比较少,费用低一些是可以理解的,但不是说大中介贵就一定不会翻车,小中介便宜一定有问题。
虽然说大中介管理更规范,但是管不规范最终还是得看人,中介员工流动性非常大,门槛又低,不管是哪个中介,你都得留个心眼,不可全信中介的话,但是从最终合同上面来说,通过中介租房一般有2种形式:
这一种很好理解,是中介最基础的功能,它只负责撮合交易,把你和房东拉到一起签一个合同,拿了中介费就完事了,后续的一切事务都是和你房东之间,比如交房租也是交给房东、后续续租等事宜也是你自己和房东谈。
这种的合同大部分情况下没有问题,负责任的中介还会帮你核实信息,比如房产证和身份证等,保证你签的房子没问题,别又签到二房东了。
这个很多人可能很难理解,我都找中介了,为什么还和中介签合同?这是因为很多房东为了省事会把房子全权委托给中介(时间都比较长,1-5年),中介会给房东房租,然后你交房租给中介,这样中介的利润就会多很多,因为租出去的价格是中介定。
这种房子靠不靠谱呢,正常情况下是靠谱的,但是也有不靠谱的,特别是一些小中介,半途跑路。。。两头吃。
我还见过有些和中介签的合同,到期续租又得交1个月中介费的。。。这一点如果你要续租的话务必提前问清楚。
而且请务必注意签合同的时候对面是中介,并不是个人,因为有很多中介的员工打着公司名义,自己干着二房东的勾当,个人跑路离职的风险可比公司又大很多。
以自如、蛋壳为代表的互联网租房模式,虽然蛋壳已经凉了,但是这种模式依然存在,简单说,就是公司二房东,公司从房东手里拿房,一般都是签长期合同3-5年,然后会统一简单装修配备一些家具家电,再出租给个人。
通常情况下为了提高利润,都会采用合租这种方式,特别是在北京这种高房价的城市,整租费用太高,合租非常普遍。
说实话,这些APP最大的优点就是省事,可以方便的选房看房,下单签约,但是缺点就是贵啊,比如自如比你自己找传统中介要贵上10-20%左右,虽然没有中介费,但是多出一个每个月10%的服务费,所谓服务就是网络、维修和1个月1次的保洁服务,这个保洁服务你自己请也就50块钱1小时嘛,你算算账就知道值不值了,只不过是把中介费换一种形式。
如果你是合租的话,一个人住,图省事可以从这些APP上面找,整租的话不推荐。这些管家的服务质量也是层次不齐,网上对于这些公司的投诉也是不少,所以我住了2年就没继续住了。
说实话,这些渠道能找到真正的房东直租几乎不可能,大多都是一些二房东、以及中介的员工发的帖子,因为很多他们没有钱去投送广告,只能注册一堆号去各大论坛、群发各种租房信息,引流拉客户,当然我也不是说这些信息就一定假,需要大家自己睁开双眼甄别。
很多人可能都忽略了这个渠道,这个渠道其实非常有效,如果你确定要去某个小区租房,不妨先去打听一下,很多时候这里能真正找到房东直租的房子。
不过也可能也要中介费,但是一般都可以谈的,因为小区里面的人消息都很灵敏,比如业主群里面有房子要出租他们会第一时间得到消息,这比你去旁边中介的消息可能更准,有些中介不仅找你收中介费,也会找房东收中介费,如果房东能够省一笔中介费,谁会不愿意。
最后,说一说一些常见的租房问题和坑。
举个例子,你2月1号起租,付了3个月(2、3、4这3个月的)房租,那么下一次付房租应该是5月1号,但是有些二房东或者黑中介会在合同里面给你做文章,把下一次付房租的日子就提前了1个月,写成4月1号。
这就相当于押2付3了,如果你不注意或者不管,到时候他们就会拿合同说事让你交房租,不然算你违约,扣你这2个月押金。
对于退租这件事,房东肯定不乐意,所以扣除1个月押金我能理解,大部分租房合同里面也会这么约定,说实话在北京搞互联网的换个工作很正常,所以在签合同的时候一定要看好,退租赔偿是不是1个月,而不是2个月。
但是有些二房东使了心眼,虽然写着1个月,但是就如第1条那样,实收你2个月押金,就看你能不能接受了,如果你住不满1年的话,2个月房租就没了。
几乎所有的租房合同里面都写着不能转租,如果你仔细看的话,有些会写着转租会赔偿1个月房租,然后房东再和新租客签订新合同,实际上这就是退租了,非常不合理。
在我看来,比如我签了1年,但是只住了7个月,还有5个月租期,我自己找了一个租客把剩下5个月租给他,每个月租金不变,这才叫转租才对。
所以,如果你有转租计划,最好的办法就是不要告诉房东或者中介,自己找人完成交易,最好是熟人,可以不用交押金,后续押金你自己找房东退,千万不要和二房东或者中介说你要转租,一旦你这么说,他们就说你违约。
正常人理解里面,租房租的不止是房子,而且包括屋里面的家电家具等设施,所以家电的维修理当房东承担,这没毛病吧?
但是真有房东居然不这么认为,他觉得我租给的你时候冰箱是好的,你还我的时候也得是好的,如果坏了你自己出钱维修,我无语,虽然最后并没坏过。
通常来说,这些大家电寿命都很长,但是万一真坏了还是很麻烦的,修一下几百块钱没了,所以一定要在合同里面写清楚谁负责维修。有些格式合同里面并没用写到维修这块,强烈建议一定要补上,这样如果后续房东不认你也有证据。
通常来说,租房消费品比如水电燃气都是租客交,其它的物业费、取暖费等固定费用都是房东交,至少北京这块大部分都是这样。至少我目前没有交过一次物业费,但是这一点你必须在合同里面体现,因为有些地方,比如南方很多城市默认是租客交,为了避免纠纷,一定要写清楚。
前几年,北京这种二房东或者黑中介特别多,会找各种理由扣你押金,少则几百,多则不给了,报警不管,起诉太耗时,一般人只能不了了之。
个人住过一次二房东的房子,退租的时候只扣了50块钱卫生费,算是良心了。
其实这几年这种情况少了,如果你问我有没有更好的办法完全避免这种情况,我只能说没有,中国对租房者的权益保障几乎没有,不然也不会这么多人拼命买房了。
我只能和你说,尽量和房东客客气气说话,有问题好好沟通,平时保持好屋内设施的整洁,退租的时候最好收拾一下,不要太过分了。另外,对于北京来说,你或许可以举报一下扫黄打黑办或者是群租房,很多二房东合租都会对房屋进行隔断,北京这几年都在打击群租房打隔断的情况,只要你举报,会有人管的。
这一点,成年人应该都知道了,租房之前的承诺只要没写到合同里面等于没说,写到合同里面如果你真较真的话是可以起诉对方的,虽然说很麻烦,但是100%胜诉,有些人怕这个。
但是如果不写到合同里面,无论中介或者二房东和你怎么说都没用,比如答应你给你配一个新的冰箱,签完合同不承认了,你咋办?你不给房租算你违约,扣你押金,报警的话众所周知,这是民事纠纷,只要不出人命,警察不管的,你只能认了。
]]>1 | 旅行随记 |
说实话,rebase这个操作极少用到,大部分时候老老实实merge就行了。说到rebase这个单词,有些文章翻译成“变基”,实在有些生硬,但是从字面意思上看base有着基础的含义,比如说我们每次修改代码的时候是基于上一次的基础,这是一个线性的过程,而rebase则是打破这个线性过程,改变变更的基础,达到一些特殊的目的。
通常情况下,我们每次对代码进行一些修改都会commit一下,时间久了,提交的记录就非常多,比如下面图中:
个人对此并不反感,很多时候提交记录详细一点方便以后排查bug、甚至甩锅,谁的提交记录就是谁写的代码、做的修改,一目了然,这玩意也不占多少空间,但是有些特殊情况,比如对外开源的项目在发布的时候希望有一个干净的commit记录。
或者,纯粹是你的leader搞事情,觉得提交记录这么多看着不舒服。。。anyway,这时候使用rebase命令就可以轻松搞定。
整体过程可以分为几步:
比如上图里面这些提交记录,我们想把前面4次记录合并到“重构”这次,那么我们就需要先获取“重构”这个提交记录 前面 那一次提交的commit id,就是那一串hash值,使用git log
命令:
1 | commit 01da1d7351dfea156eabc7ddf36a9e8eba227645 (tag: v1.1) |
git rebase这个命令参数很多,这里就不多介绍,不然又有点overwhelming了,感兴趣的童鞋自行研究,这里只说-i,参数是commit id,填一部分就行。
然后会自动打开一个编辑页面,可能是nano,也可能是vi,取决于你系统的配置。
很多人一看到这个多内容可能就有点慌了,不知所措,其实大部分都是注释,只有最上面那几行是有用的,可以看到默认是 pick xxx
,这个pick啥意思?
其实下面就有介绍,p表示use commit,意思就是采用这个提交,然后还有r、e、s、f等命令,这里咱也不多说,只看s,这个s表示采用提交并合并到上一次提交里面。
然后我们需要把重构后面的记录前面的pick全部改成s,意思是把后面4个提交合并到前面那一次提交里面。
把pick改成s之后保存,又会自动弹出一个新的编辑页面,内容大概如下:
1 | This is a combination of 5 commits. |
其大意是这是这5次提交信息的合并,然后它会自动把每次提交的信息都给你列出来,这里你可以选择直接保存,也可以顺便修改一下,改成你想要的内容,然后保存。
完成上面的操作之后,再次使用git log则会发现提交记录已经变了,但是目前还只是在本地,还需要推送到远程,而且推送的时候必须使用–force,如:
1 | git push origin --force |
完成上述操作之后,整个git提交记录就会完全改变了,而且是不可逆操作,当然这也意味着rebase操作是有一定风险的,如果你不太清楚需要做什么的话就不要做。
结合以上例子,大家可能会发现rebase是可以拥有修改提交记录的能力,所以基于此,我们可以衍生出以下应用场景:
假如你们公司某个傻屌把一些敏感信息,比如账号密码上传到git上面去了,你的第一反应可能是删掉密码再提交一次。。。然而这有点掩耳盗铃的意思,历史提交记录里面还有呢
这时候你就可以使用rebase修改提交记录,把密码出现过的地方全部抹除掉,相当于没有发生过,过程和上述一样。
假如你们公司按代码提交量算绩效,你这周摸鱼没有写代码,但是你同事写了很多代码,你可以使用rebase把别人的提交记录修整一番,然后你同事的提交记录就没了,变成你的了…
哈哈,此招太贱,可能会挨打,注意使用场景!
有些同学在使用git的时候不知道有stash这个功能,所以每次切换分支的时候都喜欢commit,导致这个commit记录超级多,而且很多无意义的信息,虽然无大雅,但是有些追求极致的leader看不下去,
比如:
1 | fix bug |
或者有时候在调试代码的时候,必须提交到代码库才能部署,也会导致出现很多重复无意义的提交,比较难看。
这时候也可以使用reabse来合并这部分提交,值得一说的是rebase -i
支持区间,从某一个commit到另一个commit。
rebase本身也可以用于合并代码,比如说你在一个开发分支dev上面开发,啪啪啪一堆提交记录,开发完成之后,你想往master合并,如果直接merge过去会导致主分支看上去也很杂乱,碰到一个事多的leader可能又不爽你了,这时候你咋办呢?
这时候你也可以使用rebase,只不过这时候的base是主分支,不是commit id了,比如:
1 | git rebase -i master |
顺便说一下,git pull的时候默认自动merge,其实还可以选择rebase,这时候rebase的作用就是类似合并,只不过在时间线上面有差异。
最后,总结一下,那就是rebase很强很厉害,可以随意操纵历史提交记录,实现特殊的需求,不知道上面举的这些例子有没有你刚好用到的。
]]>不过这篇文章我想谈一个话题,那就是“阿里外包是否能转正?” 因为转正这个事情发生在我身上过,但是却又没有成功。
不过在此之前,我先简单说一下这2年发生的一些事情,算是一些铺垫
在这2年多内,我换过多个阿里主管,最早那个阿里主管跑路了,然后我就换了另一位主管了,但是没多久这个主管又转岗了,于是我又转移到另外一个主管下面。
在阿里这2年内,我认识的正式工里面离职的人1只手数不下来,后来得知在阿里一般3年多可以升到p7,再往上面升p8就非常困难了,而这个时候基本上股票该拿的也拿完了,所以想涨工资大多只能通过跳槽。
虽然换过几位主管,但是我负责的那个系统一直是我在维护开发,在我这2年里面部门里面找我做过东西的人不下十几个,也算是涉猎广泛,虽然并不是什么核心的功能。
而我和另外一个一起来的同事已经成为部门外包里面最老的员工,我们常常戏说再干1年的话,我们可能要成为比很多正式工还要老了。。。
2021年的时候部门里面走了很多外包,同时又招了很多外包新人,但是其实并没有太多的活要做,他们主管和他们说后面会有活的,阿里招这么多人只是为了备用,不可能需要人的时候再去招,那样来不及,其实说白了就是部门刚好有用人名额,不用白不用而已。
然而。。。最近我得知,部门开始裁员了,其实这就是外包的尴尬之处,当部门用人名额缩减的时候,第一个被裁员的就是外包,而且外包也不存在裁员的说法,因为对于阿里用人方来说,它只需要和外包公司说自己不需要人了,外包公司就会对你进行“下线”。
有人可能会好奇,外包裁员有没有赔偿?比如N+1。答案是没有的,这几乎是外包的潜规则,很多公司使用外包这种用工形式就是为了节省成本,这个裁员和阿里一点关系都没有。一般情况下外包公司会给你推荐其它外包岗位,让你去面试,因为外包岗位是一个萝卜一个坑,或者你自己去投简历面试,这个时间大概是1个月,期间只会给你发一个基本工资,大概只有几千块钱。
期间,我还涨过一次薪水,大概是我工作年限的月数*100,虽然不多,但是也算是有点涨幅,另外年终奖拿了差不多半个月左右。说句实话,经过我一些外包同事的交流,在阿里这些外包公司供应商里面,中软科技算是很良心了。
其实2020年初我刚入职的时候,最早招我进来那个主管就和我说,部门招外包也是一种筛选人才的方式,如果你表现的好的话是可以有转正的机会的,听上去像是一个饼,但是确实发生了一些事情。
2021年中旬,我们部门老大,P8,找到我,直接开门见山问我想不想进阿里成为正式工?说实话,我有点受宠若惊,也很好奇,因为工作这么久我和他几乎没说过话,他可能都不知道我是谁,怎么会突然找到我,还让我转正,还有这种好事送上门?
他说我的主管和同事反馈说我工作表现很不错,认真负责。这种好事当然我不会拒绝(虽然后面事实证明没有什么事是真正的好事),但是我也把我的顾虑和他说了,我说阿里要求很高吧,我这也没准备面试怕过不了,他说没事,前面几面都是我们部门的人,让我尝试一下,即使不成功也没什么影响,还可以继续上班,话说他说的也没毛病。
当时很多人都这么认为,既然部门大老板推荐的我,大家应该都会给个面子,面试或许只是走一个流程而已,转正式只是时间问题。
然后我就收到了一个内推邀请邮件,开始了阿里的社招面试流程,前面几面都是部门内的人,因为平时也算是熟人,几乎也没怎么为难我,都给过了,最后一面是BU的技术老大,个人感觉发挥的不是很好。
后来过了几天之后,我专门问了一下他面试的情况,他说还没凉,在走流程,按理说下一步应该是HR这边吧。但是接下来就是漫长的等待了,我问过几次主管,每次都说让我耐心等待,但是从来没有透露到什么阶段,或者卡点在哪,我也不好意思细问,只能相信是阿里流程慢,期间还拒绝了以前同事抛来的橄榄枝,现在想想确实太naive了。。。
这一等直接等到快过年了,我们主管这时候和我说,年前hc锁了。。。得等年后了,这时候其实我已经大概猜到是什么情况了。
其实我早就听说过阿里直管,直管用工是阿里最近在搞的一个新的用工模式,说白了就是高级外包,直管就是直接管理的意思,和普通外包区别就在于可以和阿里正式工坐在一起办公,享受一样的管理方式(PUA)。
除了在管理方式上面有一点区别之外,其它情况,比如集团权限和普通外包无异,毕竟连工号都是一样WB开头,签合同的公司是杭州聚盛卓悦。但是据阿里方说直管比普通外包好,因为可以学到的东西更多,而且转正的几率大大增大。。。这似乎又是另一个转正饼。
2022年开年之后又等了很久,阿里主管终于和我开门见山的谈了谈,告诉我转正这事现在有点难,大环境很差,公司hc不多,即使有hc可能也轮不到我,所以想让我先转直管,后面再看机会。其实我也理解他的说辞,现在就业环境确实很差,hc确实也不多,部门确实有招新人,但是并不是我,这么宝贵的名额我确实不配。
但是让我不爽的事情,这件事拖了很久很久,没有一丁点消息,其实可以早点和我说清楚。。。细思极恐,可能他们预谋很久了,也许我的面试早就被BU老大否了,但是他们怕我离职只好先拖着我,等着转直管这个事情推进,但也可能他们真的努力了很久,实在找不到机会。
事情经过的真相不得而知,但是这个结果我去年就已经猜想过,最终确实如我所料,但也谈不上什么惊讶,甚至还有点解脱的感觉,毕竟这件事悬而未决这么久总算落地了,也不用再去想了。
说到这里,我突然想起来一件事,之前一个外包同事在去年底成功面试拿到一家大厂offer,上岸了,外包毕竟不是什么非常有前景的工作,想要更好的工作还是得看自己的努力,不要把希望寄托在别人身上。
如果你问我想不想转正,我当然想啊,毕竟转正最明显的变化就是福利待遇要好很多,而且相关权限也多了,可以接触更多东西了。
严格来说,不存在“转正”这个说法,因为外包并没有“转正式”这个操作,其实还是需要走社招面试流程,该有的流程都有,一个少不了,外包这段经历只能算是一个敲门砖,如果你的硬性条件不符合,那还是没戏,比如学历、背景,众所周知,阿里HR享有一票否决权。
如果你非要问是否能转正,答案是可以,但是这个所谓转正式工更像是给外包画的一个饼、一个诱饵,真实几率非常低,个人觉得不足5%,取决于天时地利人和,或许你运气好,碰到一个好主管,就是想提携你,力推你转正,帮你背书,事在人为,基本上都能成。或许你接手了部门核心业务项目,成为不可替代的一环,除了你没人会,那转正也只是时间问题。或许你解决了项目里面连正式工都无法解决的难点痛点,在大老板面前表现一番,那转正也只是分分钟钟的事情。
回到最初的问题,外包到底能不能转正,我的答案是不要抱着转正的希望来干外包,其实最初我干外包也没想过转正这事,外包最大的意义还是图个轻松、不加班、不用背绩效,除此,无它,一个字:混就行了。
]]>虽说手机用久了都会变卡,毕竟电子元器件存在性能衰减,不过问题其实也不是这么简单。MIUI系统很早就有省电模式、均衡模式、性能模式,熟悉Linux系统的人都知道这和Linux的CPU调度策略非常类似,当然这是废话,因为安卓内核就是Linux。。。
手机系统为了平衡功耗以及温度,会有很多种CPU调度策略,MIUI在平时使用的时候会根据运行软件的不同去执行不同的调度策略,比如一些日常软件的话主频会限制到2GHz,但打游戏的时候往往火力全开,但是火力全开手机温度就会上升,达到一定程度就会触发温控策略导致CPU降频,从而引起卡顿。
我这个机型的系统版本还取消了性能模式,默认以均衡模式运行,再加上小米保守的温控策略,根本没法发挥全部实力,作为一个技术宅已经忍了很久了,既然没钱换新手机那就动手解除封印,释放全部潜能再战1年!所以接下来,我会把这台手机root、之后再安装Scene4解锁来CPU限制,火力全开,最后再删除温控策略,尽可能的释放手机性能,提高流畅度。
理论上,手机CPU和GPU也能超频,不过这个玩法就太高端了,而且恐怕散热真的撑不住。。。
本人在折腾过程中也查看了不少教程,但是都比较零散细碎,所以这里总结了一下,以下教程适用于MIUI 12,虽然我的机型可能和大家不一样,但是大同小异,仅供参考。
因为用了很多年小米手机,以前小米手机开发版默认自带root,所以印象中以为只要刷个开发版就可以了,谁知道没那么简单,还需要解锁boot才行。
然后才发现稳定版也可以root,只是方法和开发版不太一样。
所以本人建议使用稳定版root,因为小米的开发版不是很稳,很可能有bug,而且更新比较频繁,更新的时候还可能会丢掉root权限。
以前小米手机是不锁boot的,比如小米3、小米4可谓刷机神器,那些年也没少折腾,但是时过境迁,现在小米为了安全考虑,都开始锁boot了,需要解锁之后才能刷机。
小米手机解锁boot还算比较简单,官网有明确的教程和工具提供下载,建议大家移步这里: https://www.miui.com/unlock/index.html
注意事项: 官网上面写了很多,请查看FAQ,这里简单提几句,比如在售的新机型一般需要等待,用户账号安全评分较低的需要等待,等待时间目前是7天起,如果本年度解锁手机数超过2台,等待时间会相应增长、一个小米账号每月限制解锁一台设备、一个小米账号每年限制解锁4台不同设备。
另外,这个解锁工具不支持win11…,建议使用win7或者win10。
先说几句题外话,很多教程会让你先刷第三方的recovery,其实可刷也可以不刷,如果你经常刷机折腾,可以刷一下,如果你像我一样只是为了root,那就没必要刷,小米自带的recovery也能凑合用。
这里需要用到官方原版的系统镜像,有2种方式获取,一种是去MIUI官网选择对应机型下载,另一种是打开手机设置-我的设备-MIUI版本-右上角-下载最新完整包,下载完成后会保存在 Download/downloaded_rom 文件夹。
使用系统自带的文件管理器找到镜像文件,是一个zip文件,将其解压,你会发现里面有一个名字为 boot.img 的文件,不要问我如果没有怎么办,没有那就裂开,这种方法不适合你,Game Over…
建议去Github下载最新的版本,或者你可以使用我这保存的版本: 点击下载 Magisk-v24.3.apk
下载安装到手机,打开APP,你会发现下面这个界面,有一个安装按钮,点击会发现有2种方法,一种是选择并修补一个文件,另一种是直接安装。
这里面我们使用选择并修补一个文件,选择刚才那个 boot.img 文件,经过一段时间处理之后会在同文件夹生成一个新的文件,名字类似 magisk_patched-24300_uVoAh.img
这里多说几句,第二种直接安装可以用于已经root的设备,比如说你是使用MIUI开发版获取的权限。(不推荐这种方式)
说到这,我先展开说一下刷机的2种方式:卡刷和线刷。所谓线刷就是下面我说到这种方式,需要连接电脑来执行命令。而卡刷则是在手机先刷一个第三方recovery然后再刷机,其实recovery就是相当于电脑的BIOS界面,是一个更加底层,用来管理系统的系统。
如果采用卡刷的方法的话,那么这里就是先用电脑刷一个recovery,然后再卡刷其它系统来实现root,具体步骤感兴趣的可以研究,关键字提示:k20 Pro recovery。
用一根官方原版数据线连接电脑,不要用那些劣质的线,同时在手机上面打开USB调试,这个我就不多说了,大概就是快速点击MIUI版本,然后开发者模式-USB调试即可,如果这个都不会的话,说明你从未有过刷机经验,建议不要折腾了。
用电脑打开手机usb存储,找到上面生成的那个patched文件,复制到电脑上面一个文件夹里面,你得记住在哪,后面会用到。
当你打开usb调试的时候,手机上面会弹一个确认框,请务必同意,如果没有看见,那就拔了线再插上试试,接下来就需要使用adb工具刷机了,如果你是Linux或者Mac的话,直接安装一个adb和fastboot命令行工具就行。
以Ubuntu为例:
1 | sudo apt install adb |
如果是Windows电脑,你可以下载我这里的工具包: 点击下载 ADBTools.zip
这里我假设你有终端使用经验,打开终端,cd到刚才保存那个patched文件的文件夹,执行 adb reboot bootloader
重启到fastboot界面,你也可以在关机状态下长按开机键和音量上键。
然后执行fastboot flash boot magisk_patched-24300_uVoAh.img
命令开始刷机,如果没有报错的话,看到Ok信息之后重启手机即可。
开机之后打开刚才安装的magisk app,如果看到下面这个界面就说明已经成功了,其中 超级用户 菜单就是管理root权限的地方。
建议大家官网下载,这里就不提供了安装包下载了
这个软件的功能很多,但是主要用来修改CPU调度,直接点击极速即可火力拉满,然后打开MIUI温控,点击 淦 解除封印。
其它更多功能自行研究。。。
很多软件检测到你的设备root之后就会提示你也风险,有些甚至直接不给你使用(比如建设银行APP),站在公司开发者的角度来说确实可能有风险,但是安全这个事情主要还是靠个人,比如即使不root也不能随便安装一些来源未知的APP。
不过root权限确实非常重要,我们对于root权限的授权管理则要非常慎重,只有你明确知道需要root权限的APP才同意,其它的一律拒绝。
针对Scene4和MIUI温控这些搞机软件,我还完全禁用了其联网权限,其它权限也最小化。。。
另外,很多银行APP检测到手机root之后无法使用的情况,这里建议使用Magisk自带的 Zygisk 排除软件即可。
最后,说一下我的实际体验,在k20 Pro上面经过上述的操作之后流畅度提升很多,有一种丝滑的感觉,毕竟CPU主频提高了1个G,应用的打开速度也提升了很多,个人觉得值得一试,友情提示:折腾之前备份好数据。
]]>比如说 Ubuntu16.04 目前的内核版本还是4.15,确实比较老,像我个人比较喜欢unity桌面,所以也一直没有去升级到20.04的发行版(新发行版使用5.0以上版本),但是我们可以单独升级内核。
本人升级内核主要是折腾、尝鲜,个人感觉升级版本之后感觉确实流畅了一点,其它没有什么变化。
先说结论:目前 Ubuntu16.04桌面版本,可以完美支持的Linux内核版本是5.4,更新一点的版本在安装的时候会有一些headers依赖错误,虽然不致命,但是总感觉有问题,这些底层依赖库自己想要去升级版本的话是非常麻烦的,必须从源码编译,而且可能会有导致系统崩溃的风险。
Linux内核更新还是非常快,小版本一个接一个,我们可以找一个相对稳定持续更新的版本,打开 https://www.kernel.org/
官方主页,我们会看到最近几个 longterm 的版本:
建议升级的适合优先选择这些版本,因为这些版本会持续更新很久。。。当然也不是说其它小版本就不能用了。
这些我们只是看一下版本号,备用,除非你选择自己编译内核,这将非常复杂和耗时,接下来我们将去下载已经编译的好的内核。
在 https://kernel.ubuntu.com/~kernel-ppa/mainline/
这里我们可以下载到编译的适合Ubuntu的内核,每一个版本都有,从其命名格式上面也能看出,前面2个数字是大版本号,最后一个数字代表是小版本,我们可以选择自己需求的版本:
点击链接会打开一个新的页面,可以看到上面还分为amd64、arm64、i386等不同架构,这里以64位系统为例,里面有多个deb安装包文件,下载的时候需要注意Linux内核还分为 lowlatency 和 generic,也就是通用版本和低延迟版本,据我了解低延迟版本一般适用于对延迟要求比较低的需求场景,比如录音。
这里咱们就选择下载名字里面包含generic的几个安装包,全部下载下来放到一个文件夹里面,然后使用sudo dpkg -i *.deb
命令安装即可。
如果不报错,顺利安装成功的话,会默认启用该内核,重启生效。
卸载的话,只需要使用apt purge
删除这几个软件即可,以5.4内核为例则是下面这个软件:
1 | linux-headers-5.4.185-0504185 |
最后不要忘了,使用sudo update-grub2
生成新的启动image文件。
1 | jwang@jwang:~$ sudo update-grub2 |
最后说一个副作用,我在升级的过程中发现之前的 virtualbox 用不了,提示使用 /sbin/vboxconfig 命令配置也没有成功,看日志是因为系统里面缺少合适的header文件。
个人猜测是因为virtualbox这种虚拟机软件比较特殊,它依赖了内核里面的某种机制,可能官方的安装包是针对Ubuntu16.04的内核版本设置的,尝试了多种方法,没有解决。如果你比较依赖virtualbox虚拟机的话,可能要谨慎一点。
]]>最近Go官方刚刚发布了很多人期待已久的1.18版本,其中最大的特性就是正式支持泛型。
泛型程序设计(generic programming)是程序设计语言的一种风格或范式。泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。
上面这段描述比较专业,通俗的说泛型就是广泛的类型。。。很多语言都有实现类似特性,比如在Java里面泛型应用非常广泛,在C/C++里面也有宏、函数模板这样的东西可以实现类似的效果。
举个例子,假设我们需要实现一个数字相加的函数,因为Go是强类型语言,所以必须申明函数参数类型和返回值,如下:
1 | func sum(param []int) int { |
但是上面这个函数只能实现int类型的数字相加,如果我们还有一些浮点型数字需要相加呢?在Go支持泛型之前,我们只能再写一个类型申明为float64的“重复”函数,但是这样显的很呆。。。怎么办呢?
下面咱们就看一下如果使用泛型来简化上面这个sum函数,用一个函数解决int和float64这2种数值类型的相加。
其实在泛型诞生之前,很多人经常利用 interface{} 类型来实现类似泛型的作用,interface{} 有点像是泛型,只不过在使用的时候需要断言,如果使用 interface{} 实现的话,我们可以使用反射获取参数的真实类型,然后再断言进行相加。
Go的泛型使用 [] 来申明类型,比如 func sum[v int|float64] 表示v可以是int或者float64类型。
示例如下:
1 | func main() { |
官方这次还新增了3个泛型试验包,其中有针对slice和map的包,主要是实现了一些通用函数操作,比如查找、比较、排序等,但它们的 API 不在 Go1兼容性承诺的保证范围内:
不过目前来看,Go的泛型特性还在继续完善中,暂时不建议使用在生产环境上,估计还得再等等,其实泛型的最大意义在于实现一些复杂又灵活的功能,比如一些框架。
俗话说:“动态语言一时爽,代码重构火葬场”,为什么这么说呢?
因为动态语言一般又叫做弱类型语言,比如PHP和JS,它们的特点就是简单灵活,函数不需要申明类型,比如int和float类型可以直接相加,因为解释器会在运行时做自动类型转换。
而Go、Java这样的则被称作静态语言,又叫强类型语言,虽然性能高,但是缺乏足够灵活性,泛型就是为了弥补这个缺陷,尽可能的在性能和灵活性之间取一个平衡。
]]>举个例子:如果你需要调5个接口获取数据,每个接口耗时1s,那么串行总共需要5s来执行,但是并行顾名思义同时去执行,则可以同时去调用,那么总共只需要1s时间。所以相对于串行来说,并行可以节省很大时间,提高效率。
但是并发的概念和并行有所不同,并发更强调的是“同时”发生,它可以是并行的,也可以是“假”并行。
举个例子:早期CPU只有单核,单核意味着CPU同时一时间只能执行一段代码,那电脑是怎么做到在上网的同时还可以听歌呢?这是因为操作系统是支持并发的,它会把CPU执行时间分为很多小的时间片分给不同的应用程序,然后交替执行代码,所以看上去好像是在同时进行。如今,电脑CPU都是多核的,操作系统可以把一个或者多个核单独分配给一个应用去使用,对于这种情况我们可以认为他是并行。
从理论上说并行只是并发的一种实现方式,并发是更高维度的概念,说白了,并发只要你能实现“同时”执行的效果,至于物理层面是否是同时并不重要。所以我们网上看到大部分文章都会说并发编程,很少有人会说并行编程。
其实在如今,很多语言依然不支持并发编程,比如PHP;还有很多语言通过多进程、多线程的方式来实现,比如C++、Java;而Go则是通过协程的方式实现了并发编程。
咱们先看一个串行的例子,一个简单的函数调用,这里用sleep模拟操作耗时1s,然后打印当前时间,可以看到执行完这3次操作必须耗时3s
1 | func main() { |
下面再看Go协程并发的版本:
1 | func main() { |
从打印结果来看,这3次调用几乎是同时执行的,虽然严格意义上说并不是同时开始的,因为协程的启动也有开销的,需要一点时间的,不过也是毫秒级别,从秒的维度看是同时。
可能你看过很大文章说在Go里面启用协程只需要使用go关键字就行,但是实际使用中你还要解决几个问题,其中第一个就是如何等待协程执行完成。
当你使用go启动一个协程之后,它是异步非阻塞的,它会在”后台“默默执行协程里面的代码,同时主进程会继续往下执行,在上面这个例子里面,main里面没有其它代码了,只有一个sleep,之所以要加这个sleep就是为了等待那3个协程执行完,如果去掉这个sleep你会发现没有任何结果输出,因为协程还没来得及运行主进程就结束了。
所以当你在使用协程的时候需要确保所有协程都有足够的时间去执行,当所有协程都结束后才退出。当然如果应用本身就是常驻进程的话,比如HTTP服务,就不需要关心这个问题。
上面这个例子的写法只是为了方便测试,在实际上开发中,肯定不能用sleep,你不可能准确预测到协程执行需要多久时间,官方为了解决这个问题专门弄了包,在sync包里面有一个WaitGroup就是拿来同步不同协程的操作。
1 | // A WaitGroup waits for a collection of goroutines to finish. |
使用方法非常简单,它只有3个方法,Add、Done、Wait,最开始那个例子如果使用WaitGroup改造的话可以这么写:
1 | func main() { |
其中Add(3)表示有3个协程需要执行,同时我们以指针的形式把wg传入函数内部,在函数结束之前调用Done,最后在主进程里面我们调用Wait,这个函数是阻塞的,它会等待所有协程结束。
这个库源码的话比较简单,简单说就是一个原子计数操作,Add就是+1,Done就是-1,然后开启一个for循环阻塞,在循环里面等待值变为0就退出。
除此之外,针对单个协程的话也可以利用Go管道的特性来实现类似功能,但是针对多个协程之间的同步还是WaitGroup更好好用。
1 | func main() { |
在Go里面channel是协程之间通信的桥梁,在Go编程里面有一句非常知名的话:”不要使用共享内存去通信,而应该通过通信来共享内存“,这里面的通信指的就是channel这种方式。
即使你使用多进程或者多线程这种并发编程模型,都存在在不同进程或者线程之间通信的需求,我们很容易就想到可以使用全局变量+锁这种方式,这种方式本质上就是共享内存,Go的编程哲学并不推荐,虽然Go也能这么写。
以上面的例子为基础,我们现在假设现在hello这个函数不再打印一个结果,我们需要“返回”当前时间戳作为结果,如果你直接return,主进程是没法接收返回值的。
如果你使用共享内存这种方式,可以先定义一个全局的slice来存储结果,同时需要一把全局锁来解决并发数据竞争问题,比如下面这个例子:
1 | var ( |
虽然Go官方并不推荐这种写法,但是这么写是完全没有问题的,但如果使用channel的话更好,下面我们再看看使用channel的写法:
1 | func main() { |
我们先创建一个int64类似的channel,然后作为参数传入函数内部,在函数里面把结果放到管道里面,同时在主进程里面使用for…range获取channel里面的数据,这里大家可以注意到可以不用WaitGroup了,因为从channel里面获取数据也是阻塞的。
但是运行的话你会发现不仅打印出了3个正确结果,还报错了: fatal error: all goroutines are asleep - deadlock!
这个死锁问题在使用channel的时候需要注意的问题,详细说起来还比较复杂,简单说就是如果你要从一个channel里面取数据,那么必须有一个地方会往里面放数据,在这个例子里面,Go运行时检测到了你取完3个数据之后,没有其它地方会往channel里面放数据了,这样for循环会一直等下去。。。
想要解决这个问题,就必须在合理的时候关闭channel,在上面这个例子,我们可以明确知道只有3个结果,所以我们可以在for循环里面加一个判断,主动close channel,结束for循环,继续执行后面的代码逻辑。
1 | i := 0 |
Go协程调度相关的东西非常多,这里只是抛砖引玉说一点,话说在计算机系统里面,但凡是涉及到资源(CPU、GPU、内存、存储)的使用都需要调度,所谓调度无非是如何去分配资源,因为资源是有限的,但是使用方却很多,给谁用,用多少是一个问题。
举个例子,电脑CPU只有8核,但是上面跑的进程可能有几十上百个,那操作系统是如何分配CPU资源的呢?是按照先后顺序还是平均分配? 这就涉及到操作系统调度的问题了,说起来也非常复杂,一个成熟的调度算法必须兼顾公平和效率。
回到Go这边,Go的协程也被称为是用户态线程,在多线程并发模型里面,进程和线程是直接参与CPU调度的,是由操作系统来完成调度这件事,但是Go的协程并不直接参与系统调度,那问题来了,如果我开了100个协程,但是我的CPU只有8核,Go是怎么做到“同时”运行这些协程的呢?
这个问题的答案就是Go协程的调度所用到的GMP模型,这个调度模型也是经过多次版本迭代后的结果,网上也很多深度解析的文章介绍,感兴趣的可以找找看一看,这里只借用一张图。
虽然Go协程的开销很小,但是如果不加以控制可能会产生一些意想不到的结果,举个例子,你需要调用一个接口,使用1000个不同的参数,每次调用接口需要1s时间,聪明的你肯定想到如果串行的调用则需要1000s时间,太慢了,但是使用Go协程开启1000个协程很快就搞定了。
1 | func main() { |
思路没问题,但是你有没有想过这个接口的能力能不能支持这么大并发量,如果不支持,轻则导致你调用报错、重则导致对方服务挂掉。所以从安全可用的角度来说,我们需要控制一下并发数量,比如说100个。
只要稍加改造,可以利用Go管道的阻塞特性实现这一功能,控制协程的并发数量:
1 | func main() { |
这段代码的原理就是有缓冲管道是可以缓存一定数据的,我们先创建一个缓存大小为100的channel,把它作为参数传递到函数内部,在执行函数逻辑之前往channel里面放一个数据,在执行完逻辑之后取出一个数据。因为缓存大小是100,所以最多可以有100个协程同时执行。
不过这种方案的缺点就是在一开始就已经启用了1000个协程,只不过这些协程卡在等待执行逻辑的过程中,如果协程数据量超级大的话可能有一些内存开销。
这是一个Go官方提供的一个同步扩展库,它可以将一个大任务拆分成几个小任务并发执行,提高程序效率。其代码非常简洁,主要有三个方法,WithContext、Go、Wait。
1 | func main() { |
本质上这是一个对WaitGroup的封装,使用起来更加方便,它不仅可以等待所有协程执行完成,还可以返回第一个错误,同时还可以实现当其中某一个协程出错,取消其它协程的功能。
其源码非常简单,总共只有几十行代码,包含非常丰富的注释,贴上来大家一起欣赏一下:
1 | // Copyright 2016 The Go Authors. All rights reserved. |
严格来说Go不是一门完全面向对象的语言,但是在某种层面上实现了面向对象的部分特性,毕竟任何软件工程的主要目标都是为了实现重用性、灵活性和扩展性,Go也不例外。
举个例子:假设你需要把一个大象放到冰箱里面,需要几步?
第一步:打开冰箱
第二步:把大象放进冰箱
第三步:关上冰箱
这3个步骤用面向过程的方式去实现可能就是3个函数,比如openFridge、placeElephant、closeFridge,我们只需要依次调用即可。
但是从面向对象的思维来看,冰箱作为一个对象,它应该有2个函数:open、close,而大象作为一个对象应该有一个函数:walk,我们只需要组合这2个对象的函数就完成这些步骤。
Go里面没有类这个概念,只有结构体struct,结构体可以有属性,如:
1 | type Fridge struct { |
虽然结构体里面并不能定义函数,但是我们可以给这个结构体定义方法,通过这种形式:
1 | func (i Fridge) Open() { |
通过这种方式我们认为Open和Close是属于Fridge这个结构体的方法,这些加在一起可以比作是面向对象语言里面类、类变量、类方法的概念。非常简单易懂,没有其它面向对象语言里面比如静态类、静态属性等等其它特性。
函数英文是function,方法英文是method,很多人对这2个词概念没有区分,往往都是混着叫,函数方法不分,虽然本质上都是一段代码块,但是在不同的环境下还是略有不同。
严格来说,方法是面向对象语言里面的概念,它必须属于一个对象,比如Java是一门完全面向对象的语言,所以在Java里面只有方法,没有函数。而函数则是很传统的概念,比如在C语言里面函数是一等公民,所以C里面只有函数。
回到Go里面,其实也应该区分一下,一般我们说函数,指的是这种不属于任何结构体的函数,可以直接通过包名调用:
1 | func Open() { |
而方法则是属于某个结构体的,不能直接调,你得先New一个对象出来,然后再调用这个对象的方法。
很多语言,比如PHP,既有函数,也有方法,相对来说更加灵活,但是最好还是区分一下,虽然意思大家都懂。
这里说的接口不是指API接口,而是面向对象里面的接口,也叫interface,如上所说Go虽然不是一个完全面向对象的语言,但是依然提供了接口,虽然Go的接口和其它语言接口不太一样。
Go的接口被称为是鸭子类型Duck Type
,当你看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。在鸭子类型中,关注点在于对象的行为,能作什么,而不是关注对象所属的类型。
在很多面向对象的语言里面,如果你要实现一个接口你就必须实现其所有定义的抽象方法,这是强制要求,而Go则不是这样,Go甚至连implement这个关键字都没有,你不能“实现”接口!
1 | type Duck interface { |
但只要一个结构体实现了接口定义的所有方法,我们就认为实现了这个接口
1 | type Dog struct { |
其实这个问题也困扰我很久,很多时候我们在写业务代码几乎用不到接口,大多数都是一些方法和函数的调用,但是在看一些底层库源码的时候却发现处处是接口。
到底什么时候该用接口? 这是一个非常值得思考的问题
因为接口这种设计,本质上还是为了灵活性和扩展性,什么时候去用还是得看具体情况,比如配置文件库,往往需要支持json、yaml、ini等多种格式,而一个日志库需要支持console、file、api各种输出方法。这时候就需要利用接口去灵活设计结构,可以实现非常容易的扩展更多类型的目的。
而过多的使用interface也会导致代码过于冗余,阅读难度增加,变相增加了后续维护成本,实际工作中,公司开发人员水平层次不齐,最简单直白的代码反而更容易被其它人接手维护。
在我看来,在业务开发代码中,接口最实际的意义其实在于方便写单测,在依赖注入这种实现模式配合下,可以分割不同层之间的依赖,单独对每一层做单测,从而提高代码质量。
比如在开发中,一个模块依赖另一个模块去实现功能,如果不使用接口做隔离,就很难单独的去做测试:
1 | type ArticleService struct{} |
在这段代码里面,ArticleService是依赖Api去获取结果的,他们之间是完全依赖耦合的,这样写就很难去单独测试ArticleService的逻辑。
1 | type Api struct{} |
如果用依赖注入加上接口的方式去改造这些代码,可以这么写:
1 | // 定义一个接口 |
我们定义一个接口,它有一个方法,然后ArticleService依赖这个接口,并且我们在New方法里面通过参数的方式注入这个依赖。
在使用的时候区别并不大,我们只需要先初始化Api对象,而且作为参数传入ArticleService内部,然后调用就行了。
1 | func main() { |
但是其实际意义也不仅如此,一个是ArticleService依赖的是一个接口,不是一个具体的对象,这就是所谓的“面向接口编程,而不是实现”。另外,我们可以单独针对ArticleService做测试,可以Mock一个Api对象,实现解耦。
1 | type mockApi struct { |
这种写法可以屏蔽外部依赖对测试结果的影响,专注于自身逻辑的测试,这里只是简单的展示这种用法,实际开发中可以使用一些mock库更加方便的测试各种情况。
]]>但是Linux下大部分软件都比较守规矩,没有设置自启,对于一些后台进程类应用,还需要自己手动添加开机自启,下面我就针对Ubuntu桌面演示一下。
举个例子,Ubuntu桌面下的鼠标移动速度太快,很灵敏,即使在鼠标设置里面把移动速度拉到最低还是很快,这时候只能祭出神器: xinput。
假设最终解决方式是需要使用命令行设置属性,如下:
1 | xinput set-prop 10 "Device Accel Velocity Scaling" 1.2 |
不过这个命令的效果不是永久的,每次电脑重启就失效了。如果你不嫌麻烦你就每次开机的时候执行一下,但是这显的很蠢。
在Ubuntu下实现这种开机自启、自动执行的效果有很多种方式,下面咱就一一介绍。
这是我认为最简单的一种方式,rc.local是属于systemctl系统里面的一个配置文件,其完整路径是 /etc/rc.local。systemctl这个东西讲起来就有点复杂了,这里就不多说了,你只需要知道,但凡是写在这个文件里面的命令行在开机启动的时候都会被执行。
1 | !/bin/sh -e |
默认情况下,这个文件是空的,只有一些注释,我们只需要把要执行的命令写在 exit 0 之前就可以了。
上面所说的方式很多Linux发行版都支持,而这条是针对Ubuntu桌面而言的,系统自带了一个名为“启动应用程序”的软件,打开这个软件会发现里面列出了一些启动程序。
在右边还有3个按钮,添加、编辑、删除,功能十分简单,只要在命令里面填入想要执行的命令即可,或者选择需要运行的脚本文件(先把命令写到脚本文件里面)。
可能有人好奇,这个保存到哪里去了?我研究了一下发现是保存到 ~/.config/autostart 目录里面了,是一个desktop文件,你甚至可以手动创建一个这样的文件放进去。
systemctl是一个非常复杂的Linux管理系统,咱们也不用了解太多。如果想实现开机自启,只需要自己创建一个自定义的service,添加到系统里面即可。
这种方式不仅可以实现开机自启,还可以实现 service xxx re\start、service xxx stop 这种形式的管理,非常方便。
在Ubuntu里面,service文件有2个存放的地方,一个是 /etc/systemd/system,另一个是 /lib/systemd/system,如果你打开看一下,会发现里面已经有很多服务在了,不少老熟人。其实Linux里面大多数系统级别的服务都是通过这种形式启动的。
咱们先看一个案例,比如 nginx.service 文件的内容:
1 | [Unit] |
这个service文件是由3个部分组成,其中Unit里面字段主要是描述服务以及声明服务的依赖关系,而Install里面只是声明一个安装的时候的依赖,最核心的是Service里面定义的字段。
篇幅有限,这里咱也不一一介绍了,如果你想知道这些字段的详细含义,请参考一份专业的文档:http://www.jinbuguo.com/systemd/systemd.service.html
总之,如果你只想实现开机自动执行这么一个最简单的功能,只需要这么写:
1 | [Unit] |
创建一个”foo.service”文件,写入上面的内容,然后把这个文件移动到 /etc/systemd/system 目录里面,再执行enable命令开启服务,不出问题的话,应该会有以下信息:
1 | jwang@jwang:~$ systemctl enable foo.service |
其实大多数情况下使用service这种方式更多的是为了更改方便的启动、停止、重启应用,而不仅仅是为了实现自启,单纯为了实现自启我还是推荐前面2种更加简单的方式。
]]>这里记录一下也是方便以后自己查阅,或者他人参考之用,仅此而已。
不过在开始看文章之前,我先说一个大的前提,很多人都知道Linux下安装软件,大部分时候只需要使用命令行即可,比如:“sudo apt install git”。
使用命令行没问题,但是由于Ubuntu16.04的版本比较老,其源里面的很多软件版本已经比较旧了,我比较喜欢最新的版本,很多老牌的知名开源软件都有官网,上面会有最新的下载地址,有些还会提供一个PPA源,只要添加到系统里面,以后还可以收到更新。
所以我一般遵循以下步骤:
浏览器我只推荐Chrome,而且必须得搞一个梯子,本人的梯子是自建的,VPS从vutlr上面买的,5美元一个月,网上有很多v2ray的一键脚本,整一下也花不了多少时间,自己搞很稳定也安全。
Chrome配上Google账号开启同步巨好用,可以自动记录网站密码,每次都不用手动输了,方便。
Ubuntu自带的火狐浏览器也是一个选择,现在连Edge、360都也出了Linux版本,其实都是套的chromium内核。
Ubuntu的unity桌面有一个好处就是Chrome的菜单栏可以和系统融为一体,鼠标右键菜单选择“使用系统菜单栏和标题”即可开启。
其实在如今Web大行其道的情况下,几乎90%以上的操作都可以在浏览器上面完成,我每天用的最多的就是浏览器,好在Chrome对Linux的支持非常完美。
下载的话建议从 Chrome官网 下载,这个地址现在没有并屏蔽,不需要FQ就可以下载。
Linux下的输入法架构分为2大体系:IBus 和 Fcitx。Ubuntu默认是Fcitix体系,自带一个中文输入法不是非常好用,我推荐安装搜狗输入法,相比较于Windows版本来说没有任何广告弹窗,登录同步、皮肤基本功能都有,很良心了。
建议从搜狗输入法官网下载deb安装包,地址如下:https://pinyin.sogou.com/linux/?r=pinyin
Ubuntu系统默认自带的LibreOffice着实难用,前面我已经卸载了,所以需要安装一个替代品,只有一个选择:WPS
话说Linux版本的wps也是相当良心,没有任何广告和弹窗,我发现很多国产软件到了Linux平台都变的良心了,可能是用户基数太少,不屑于通过广告方式盈利吧
如果不是文档重度用户,一般情况下WPS还是够用的,实在不行,还有一些网页在线的文档内平台可以使用。安装的话建议从其官网下载,这里就不过多介绍了。
主要有2个,一个是 uget,一个是 qBittorrent,前面那个用于http下载,后面这个主要用于BT下载,该有的功能都有,还是挺全的。
1 | sudo apt install uget |
然而,经常下载电影的同学可能知道,很多网站的链接都是迅雷专用的,使用这些工具都无法成功下载、还有一些是百度云的,百度云实际上已经有Linux的客户端了,大家可以去官网下载。
但是迅雷几乎是无解的,这里我一般采用虚拟机解决,我用 Oracle VirtualBox,这是一招釜底抽薪,专治各种不服,不过我经常拿来下载用,也还可以用来安装一些Windows的软件。
注意,如果你是拿来下载,我一般是在虚拟机设置里面设置一个共享文件夹,然后用迅雷直接下载到宿主机的共享文件夹里面,不用来回拷贝了。同时开启剪切板共享,在浏览器复制的地址直接就可用在虚拟机里面使用,还是很方便的。
国外的telegram还真有Linux原生客户端,国内的聊天工具,微信、QQ、钉钉无一例外是没有Linux客户端的,但是也有解决方案,那就是deepin的移植版。
相信大家都听说过Wine,它可以实现在Linux、Mac上面跑Windows的软件,但是实际应用中还存在很多问题,直接跑的话很多软件都有问题,但是有一个国产的Linux发行版deepin解决了这些问题,由于deepin也是基于Debian的,所以Ubuntu拿来可以直接用,有大神把这些软件移植过来了。
Github地址: https://github.com/wszqkzqk/deepin-wine-ubuntu
通过这种方式可以运行的国产软件还挺多的,不过我用的最多只有微信,毕竟目前也只有微信是强需求,其它无所谓了,个人使用感觉还是非常流畅的,95%功能都正常,建议大家尝试。
Ubuntu自带的2个软件之前已经卸载了,所以这里使用了我觉得体验比较好的替代品,视频播放这块推荐vlc或smplayer,这2个都是老牌的跨平台开源软件,功能都很强大。
1 | sudo add-apt-repository ppa:rvm/smplayer |
我一般使用smplayer,简单设置一下皮肤,还是挺好看的,播放视频也很流畅
音乐播放器,针对国内的话,网易云音乐官方有Ubuntu版本,但是还是有问题,最新版本不能在Ubuntu16.04上面跑,只能用早期的1.0版本,可以凑合用一下。
在Github上面,有一些大神开发的第三方客户端,比如这个:https://github.com/trazyn/ieaseMusic
如今听歌不是一个播放器的问题,而是歌的问题,版权都在平台手里,你不可能都下载下来使用本地播放器播放,我一般都用在线的听,没啥追求,也没什么好的音响设备,其实就是听个响。
偶尔需要给朋友远程一下解决一些电脑问题,跨平台的我只推荐 Teamviewer,没有之一,简单易用,跨平台,而且还免费,比QQ远程好的太多了,即使不是Linux我也推荐使用。
有需求的同学可以试一试
这一块其实比较小众了,大部分人其实都用不到,这里列出来只是给大家看一下Linux的软件生态,其实并不差,特别是国外的软件很多的,只是国内很多软件厂商不支持。
比如视频剪切编辑可以用 Kdenlive、音频编辑软件 audacity、图片编辑软件 GIMP、视频特效软件 DaVinci(达芬奇)、还有3D建模渲染软件blender等等,很多都是工业级的软件。
平时大家用的更多的可能是Adobe系列软件,所以Linux平台下这些软件几乎没人用,也不会用,但是不代表没有。。。
Ubuntu系统自带一个截图工具,但是不是非常好用,这里推荐一个Github上面几万Star的开源截图工具: flameshot
这个截图工具个人感觉非常好用,但是最新版本已经不支持Ubuntu16.04了,可以下载一个早期的版本,然后设置一下快捷键,如图所示,我设置成 Ctrl+Alt+A。
这样设置完成之后,我无论在哪里只要按下快捷键就可以区域截图,有一点不好的是这个软件不支持应用窗口截图,原因好像是系统限制,微信自带的截图工具也不支持,个人觉得影响不大,只是每次需要自己手动框一下。我这篇文章里面的大部分截图都是这个工具完成的。
另外,这个软件在4k屏幕缩放下会有问题,如果你设置了QT缩放的环境变量的话,只能截一部分,这个软件的界面是使用QT开发的,不知道为什么会出现这种情况。
不过好在我找到了解决方案,只要unset掉QT那些缩放比例设置就行,由于我那个缩放设置是全局的,所以我单独写了个脚本重新设置了这个比例,然后再启动flameshot,解决了这个问题
1 | export QT_DEVICE_PIXEL_RATIO=1 && flameshot & |
众所周知,大部分人使用Linux都是为了编程,本人也是,但是我现在日常生活也使用Ubuntu了,习惯了,所以咱们自然少不了编程开发用到软件。
首先,先说一下开发环境的问题,虽然Linux下使用命令行就可以安装绝大部分语言的运行环境,比如PHP、Node、Golang等,但是在Ubuntu16.04上面你会发现这个源的软件好像有点老了。。。
所以我现在基本上都是去官网下载二进制包,然后配置一下环境变量即可,Linux下配置环境变量的方式非常多:
我一般喜欢在后面2个/etc配置文件里面改,把那些语言的解释器或者编译器放到PATH里面。
此外,在编程工具这块,我强烈建议使用 JetBrains全家桶,如果你还在纠结正版问题,可以看一下这篇文章:如何免费使用正版Jetbrains
可能有人觉得JetBrains家的IDE过于重量级,确实,它们最大的缺点就是使用了Java开发,导致非常耗资源,机器配置低的话会比较卡。
不过你还可以选择另一个神器VScode,微软出品的编辑器,装上一堆扩展,功能不亚于IDE
此外,还有 Atom、Sublime Text这类轻量级编辑器供你选择,总之,Linux下的编程工具绝对够用,就看你怎么选了。
]]>最近把台式机上面的Ubuntu 16.04格式化了,装了黑苹果用了一周,不得不说,MacOS确实很精美,软件生态比Linux丰富很多,比Windows简单安全很多,但是我最终还是不想用了,原因很多,究其原因,我觉得是因为Mac是一个为触摸操作优化的系统,没有触摸板就像是断条腿。
所以,我决定重回Linux,继续用Ubuntu,期间尝试了最新的Ubuntu 20.04版本,发现Gnome桌面模仿Mac系统的痕迹太明显了,扁平化的UI设计真的欣赏不来,最要命的是我总感觉一卡一卡,没有unity桌面流畅,真的不是我的错觉,我之前也尝试过18.04版本,那会感觉也是很卡。
最后想了想,还是回到unity桌面吧,不再折腾了,奈何unity已经被抛弃,只能死守16.04这个版本,虽然看起来有点老了,但是用起来完全没有任何问题。装个系统对于我来说是非常轻松的事情,但最麻烦的是我个人的一些特殊设定、常用软件的安装配置比较耗时,所以这篇文章的主要目的就在于记录整个过程,结合我之前写的一些文章,作为一个记录,也可以供需要的参考一下,如果你喜欢unity桌面的话。
以下文章的内容仅针对Ubuntu的unity桌面,准确的说是16.04版本,因为之后的版本都是Gnome桌面,基本上不适用了,Only For Ubuntu16.04。
其实系统的安装非常简单,网上教程也很多,这里就不再赘述了,简单总结一下过程:
如果你是安装双系统的话,现在的Ubuntu安装程序非常智能,可以检测到你已有系统,需要注意在安装的时候选择”与现有系统共存“即可。
在Ubuntu 16.04的安装程序里面还没有最小安装的选项,所以默认情况下还会安装很多自带的软件应用,我一般都会选择卸载掉,节省空间,系统也更加简洁,建议先从这里开始,主要包括以下软件:
以上软件都是我个人觉得不好用、用不上的,我一般是通过命令行卸载,我把Shell命令总结了一下,需要的可以直接复制:
1 | !/bin/bash |
卸载完之后建议重启一下,因为有些软件的图标缓存没刷新。
你从官网下载的镜像,一般都不是最新的,所以有时候安装完成之后就需要更新,可能已经弹出提示让你更新了,我一般喜欢写一个脚本文件来实现一键更新、卸载、清理,脚本内容如下:
1 | !/bin/bash |
这个几个命令主要是更新仓库缓存、升级软件、删除无用的依赖包、自动清理。
我一般会把这几个命令放到一个文件,比如名字叫update,然后放到一个文件夹里面比如 /home/jwang/Documents/MyBin
然后在设置一下环境变量,把这个文件夹目录放到PATH里面,我一般喜欢改 /etc/environment 这个文件,这样以后我就可以直接在终端里面用update命令去更新系统。
说到更新,不得不说Ubuntu的更新源的问题,如果你安装的时候选择的是中文,并且时区也没问题,默认情况下应该是cn的中文源,这个源的速度还行,但是有时候可能也很慢,这时候就得换第三方源了,比如阿里云的源、清华大学的源等等,国内有很多。
网上有很多教程是直接修改source文件的,比较麻烦,而且容易出错,这里告诉大家一起最简单的方式,打开软件和更新设置,里面有一个选择更新源的选项,选择其它源就可以看到国内的所有认证过的第三方源,mirrors开头的都是,选择一个就行,也可以点击右边的按钮,自动选择的一个最快的源。
这个主要是为了方便,毕竟咱是作为个人电脑,天天输密码也没啥意义,看个人需求,如果为了安全考虑就别改了,但我每次都会改。
方法很简单,在命令行输入 sudo visudo 会打开一个文件的编辑界面,在最后的位置增加一行:
1 | your_user_name ALL=(ALL:ALL) NOPASSWD:ALL |
your_user_name代表你自己的登录用户名,不要照抄。同时我建议注释掉上方3个default开头的配置项,这块主要是为了解决使用sudo找不到命令的问题,详细的原因可以参考我之前的文章
有很多人不会用nano这个编辑器,修改完成之后,只要按ctrl+x,选择y,回车保存即可。
这一块是针对高分屏用户的,比如4k分辨率的显示器,我用的就是,默认情况下字体很小,得设置一下缩放。
这里先教大家一个最简单的方式,只需要在显示设置里面,设置一下”菜单和标题缩放比例“即可,根据我的感觉,4k屏幕设置成1.75比较合适,大家根据自己的情况调节,适合自己最好。
这里调节的话基本上能够解决90%软件的缩放问题,但是还是有很多软件不生效,比如QT开发的、Wine下的软件,这里推荐大家看一下我以前总结的文章:Ubuntu 4K显示器缩放设置
这里是针对系统默认设置的一些调节,主要是根据我个人习惯,大家随意,适合自己就好,并不是必须的设置,主要包括以下:
安全和隐私。关闭最近文件使用记录、关闭休眠后需要密码选项,因为本人在家使用,图个方便。
外观。在行为里面开启自动隐藏启动器,把灵敏度调到最低,如果需要显示启动器只要按一下win键即可,个人感觉这样更简洁。关于窗口菜单这块,我喜欢在窗口标题显示,Ubuntu默认是在最上面,和Mac一样,个人感觉不方便,特别是当你有一个大屏幕,打开一些窗口化的小应用的时候会发现菜单离你好远。不过好在Ubuntu提供了设置选项,不像Mac直接定死了,不可修改。
时间和日期。可以设置显示月日以及星期,方便查看
账号里面可以设置一下自动登录,这样每次重启的时候就不用输密码,还是为了方便,我是家用,如果在公司可不要这么干。
在系统设置键盘设置里面可以设置一些快捷键,但是这些都无关精要,我实际上想实现一个Win+D显示桌面的快捷键,这里无法实现,必须得上神器compiz
1 | sudo apt install compiz-plugins compiz-plugins-extra compizconfig-settings-manager |
安装完成后打开compiz设置管理器,这个软件功能十分强大,建议不懂的话不要乱动,我这里只需要改一丁点地方,找到Ubuntu Unity Plugin,在里面找到Show Deskto的设置项,其默认值是Ctrl+Super+D,其中Super就是Win键,我们这里给改成Win+D,这是Windows默认的快捷键,保持统一,约定大于规则。
其它快捷键有需要的自行修改,我一般还喜欢改一下启动器的快捷键,之前说过默认是Win键,我喜欢改成Alt+Q
这里顺便说一下另一个神器unity-tweak-tool,功能十分强大,可以修改很多系统设置选项里面没有暴露出来的东西
1 | sudo apt install unity-tweak-tool |
比如说工作区的一些调节选项,工作区是Ubuntu非常厉害的功能,也是Mac系统杀手锏,感兴趣的同学可以了解一下,这里不细说了。
这个软件还可以调节系统的主题、字体等设置,非常有用。
Linux上面的网速监控插件有很多,但是能和Ubuntu系统完美结合的我只找到这个:
这个插件功能简单,2个箭头,一个上传一个下载,可以选择监控哪个网卡,也可以选择监控所有网卡,没有其它多余功能!安装的话需要加一个ppa源:
1 | sudo add-apt-repository ppa:nilarimogard/webupd8 |
安装重启即可,非常实用,对于我来说是必备
默认情况下,你的鼠标可能会很飘…特别灵敏,就算你在鼠标设置里面把灵敏度拉到最低依然很快,如果是这样你就需要用命令行去调节一下了,Ubuntu有一个xinput命令 可以列出所有设备和其属性,找到你鼠标的id,然后使用下面命令设置灵敏度:
1 | xinput --set-prop 10 "Device Accel Constant Deceleration" 1.2 |
10是我的鼠标在系统里面的id,后面一个 1.2 和 1 这2个参数大家可以自己调整,找到适合自己的就行,为了每次开机自动设置,我们还需要配置一个开机自启脚本,这里面提供一个简单方法 Ubuntu自带一个名叫”启动应用程序“的软件,比较简单,我们可以添加自己编写的脚本:
另外,你可能发现你的鼠标滚轮滚起来非常慢,特别是在浏览网页的时候,很可惜Ubuntu的鼠标设置界面并没有提供修改的地方,但是也有办法解决:
1 | sudo apt install imwheel |
然后在用户目录下创建一个vim .imwheelrc配置文件,写入一下配置:
1 | ".*" |
最主要的就是前面2个行,后面几行可以不用管,其中“4”设置的就是滚动速度,大家可以根据自己的需要设置合适的值,保存之后可以通过killall imwheel && imwheel重新加载配置。但是为了每次重启后还能生效,你还需要放到系统开机自启里面。
]]>在Go语言里面,官方自带了一个插件扩展机制,这个我之前有文章介绍过,详情可以点击 Go Plugin实现插件化编程
但是Go官方自带这个插件机制很不成熟,更像是一个半成品,存在很多问题,比如插件无法卸载、各自版本依赖必须完全一致,其中对版本依赖必须一致这个要求非常致命,因为在一个大型的项目里面,很难限制插件开发者使用的依赖库版本,很难做到统一。
今天咱们就介绍一个基于RPC实现的插件机制,而且这种方式是经过大规模实践验证的,可用性非常高,值得尝试一下。
这个是hashicorp公司开源的项目,Github地址: https://github.com/hashicorp/go-plugin
官方的介绍如下:
go-plugin 是一个基于 RPC 的 Go (golang) 插件系统。 它是 HashiCorp 工具使用了 4 年多的插件系统。 虽然最初是为 Packer 创建的,但它也被 Terraform、Nomad、Vault 和 Boundary 使用。 虽然插件系统基于 RPC,但它目前仅设计用于在本地 [可靠] 网络上工作。 不支持真实网络上的插件,并且会导致意外行为。 该插件系统已在许多不同项目的数百万台机器上使用,并已证明经得起考验,可用于生产。
介绍中提到在多个项目使用过,但是这些我都不熟悉,不过有一个开源的项目很多人都应该听过,名字叫Grafana
Grafana是一个开源的监控可视化平台,刚好就是hashicorp公司的项目,目前也提供商业化服务,其后端就是Go写的,也算是Go语言里面非常热门的开源项目,有几十k的star,源码之前看过写的也很不错,其中就用到了上面所说的插件库,其中Grafana面板里面的数据源就是插件的形式,用户可以根据自己需求下载安装对应数据库的插件。
不过在Grafana这个项目里面,这个插件库被封装了很多层,代码非常多,不是那么简单,咱们先不看,首先先了解一下这个插件库所提供的功能,根据官方文档这个插件库有以下特性:
这些特性看上去很多很丰富,功能很强大,然而根据我的了解,实际应用起来还是需要自己做一些封装的,从官方的demo来看,并没有体现出这些特性。
官方仓库里面有几个example,咱们先看一个最简单的demo
首先先看一下greeter_interface.go文件。第一部分咱们可以理解为,先定义这个插件的要实现的接口,约束插件的行为。
1 |
|
然后定义了一个实现这个插件接口的Greeter,这里是通过RPC去实现,所以需要一个rpc client
1 | // Here is an implementation that talks over RPC |
紧接着,又定义了一个RPCServer包装了一遍
1 | // Here is the RPC server that GreeterRPC talks to, conforming to |
最后的最后,才是插件的实现,主要是Server和Client这2个方法:
1 | type GreeterPlugin struct { |
RPC Plugin接口的定义:
1 | // Plugin is the interface that is implemented to serve/connect to an |
最终,在层层包装之下,这个文件定义了一个插件的框架,以及要实现的方法,但是还缺一个实现,实现是在greeter_impl.go文件里面
首先定义一个对象实现Greet方法,这个比较简单,这里用到库里面一个logger库:
1 | // Here is a real implementation of Greeter |
然后就是main里面的内容,这块的操作简单说就是设置一些参数,启动一个RPC服务,等待请求的到来:
1 | func main() { |
最后别忘了编译插件,插件的编译实际上和普通Go程序没有什么区别,会得到一个二进制文件,后面会用到
1 | go build -o ./plugin/greeter ./plugin/greeter_impl.go |
使用插件的代码相对也简单,只需要New一个Client,设置相关参数,然后发起调用:
1 | func main() { |
这里面需要注意的是一个handshakeConfig里面的配置要和插件实现里面的一致,另外就是设置二进制执行文件的位置。
其次,Plugins是一个map里面存储了插件名和插件定义的映射关系。
从上面的demo来看,这个库的插件系统本质上就是本地RPC调用,虽然性能可能低了一点,毕竟本地网络也有开销,但是确实不存在官方插件机制的一些问题。
但是我从这个demo里面并没有看出来是如何实现多个插件共存、以及插件升级更新等功能,实际上在我后续的研究中我发现,这个库并没有实现这些功能。。。
如果要实现这些功能可能得自己去实现,Grafana这个项目在用到这个库得时候就做了大量的封装。
首先,咱们先看一下插件实现这块,主要就是plugin.Serve方法,它需要传入一个插件配置:
1 | func Serve(opts *ServeConfig) { |
代码很多,这里只展示了核心代码,其实正做的一件事就是初始化并启动RPC服务,做好接受请求的准备。
更多的代码在插件使用这块,首先我们New了一个Client,这个Client是维护插件的,而且是一个插件一个Client,所以如果你要实现多插件共存,可以去实现一个插件和Client的映射关系即可。
1 | type Client struct { |
在main里面当我们New完Client之后,依次调用了Client和Dispense2个方法,这个2个方法非常重要:
1 | // Client returns the protocol client for this connection. |
其中c.Start这个方法干了很多事情,简单说就是根据配置里面的cmd,也就是咱们编译插件之后得到二进制可执行文件,启动插件的rpc服务。
然后再根据协议的不同,启动RPC服务或者GRPC服务,得到一个真正可用Client,相当于就是通道已经打通了,接下来就是发起请求。
1 | func (c *RPCClient) Dispense(name string) (interface{}, error) { |
Dispense方法就是根据插件名拿到对应的插件对象,然后又包装了一层拿到一个Client对象,还记得最初定义插件时候那个Client吗?
1 | func (GreeterPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { |
最后,断言并调用插件的方法,这时候才是真正发起了RPC请求,并获取返回结果:
1 | greeter := raw.(example.Greeter) |
有一点,我感觉特别奇怪,从这个插件的实现来看,Dispense这步更像是细分了插件里面的插件,因为在我理解,一个二进制文件就是一个插件,一个插件只有一个实现。
但是很明显,这个库并不这么认为,它认为一个插件文件里面可以实现多个插件,所以它增加了一个Plugins来存储插件的映射关系,也就是说你可以在一个插件里面实现多个接口。
这种实现也相当于是一种约束,实际上在Grafana这个项目里面,它通过这种方式声明了可以支持的插件类型,作为插件,根据需求实现部分接口即可。
1 | // getPluginSet returns list of plugins supported |
写了这么多,对于这个库,个人觉得可用性非常高,经过生产实际检验的,但是只有核心功能,缺少封装,对于使用插件的系统来说,至少要实现以下功能:
如果真的要想好好用起这个库,确实还得花不少功夫,有一个好得地方是我们可以参考Hashicorp家的其它开源项目代码来完善,其实我也在想能不能稍微把这个库封装一下提供一个简单易用的接口。
]]>黑苹果并不是黑色的苹果,说到黑有些人觉得是不是有问题,比如黑客通常都被认为是干坏事的。其实黑苹果这个说法还是真和黑客有关系,因为黑客的英文叫Hacker,而黑苹果英文叫Hackintosh,Mac的全称是Macintosh。
苹果的OS从来都是只自用,搭配Mac电脑出售,不对外单独出售,如果你想用Mac系统就得买苹果电脑,但是呢?苹果的电脑卖又那么贵,确实贵。。。所以一群黑客破解了MacOS,可以装在普通电脑上面,所以叫黑苹果,简单说就是盗版了的MacOS,与之相对的白苹果就是指正经安装在Mac电脑上。
如果说你是奔着黑苹果来的,建议你网上看一下一些攻略,尽量采用兼容的台式机硬件配置,或者买那些已经完美驱动的笔记本。
如果你是纯属娱乐,想折腾着玩玩,建议以下情况不要折腾:
对于广大台式机用户,以目前的情况下,安装成功率在99%以上,是最容易成功的了,推荐尝试。
目前黑苹果有2种主流引导方式:四叶草Clover、Opencore简称OC。其实黑苹果最麻烦的就是引导这块的东西,经常装系统的人都应该知道,如果引导出问题了系统就启动不了了,特别在多系统的情况下,引导会更加复杂。
这2套引导方式相关工具链和教程略有差异,建议在搜索相关教程的时候注意一下,本文将采用OC引导的方式来进行,听说OC是未来。
说到OC引导,有一个网站上面有非常详细的安装教程,内容非常翔实,一步步按照上面介绍的来,99%可以成功。
https://dortania.github.io/OpenCore-Install-Guide/prerequisites.html
但是唯一不好的是网站是英文的,目前还没人给翻译一下,对于一些英文不好童鞋来说是个问题,而且整个内容巨长,懂英文也不好理解。
本文将参考该网站部分内容,尽可能简单快速教大家,对于一些不清楚的地方,可以参考此网站。
整个安装过程分为几大部分,一步步来就可以了
首先,准备一个4G的U盘,此处建议参考: https://dortania.github.io/OpenCore-Install-Guide/installer-guide/
支持Windows、Linux、Mac多系统,内容非常详细,简单说就是几大部分:
1.下载MacOS镜像。
下载opencore的软件包,在macrecovery目录里面作者提供了一个python脚本,需要我们本地安装一个python运行环境,然后执行这个脚本下载镜像。
2.在U盘里面建文件夹。
在U盘里面建一个名为com.apple.recovery.boot的文件夹,把下载的镜像文件放到里面。
详细步骤这里就不翻译了,建议看上面链接,作者针对不同系统提供了多种操作方式和工具,写的真的非常详细,英文不好的同学建议开启翻译。
这一步是最核心、最麻烦的地方,黑苹果能不能成功主要看这个,先看一个标准的EFI配置文件,里面最重要的是ACPI文件里面的电源相关、以及Kexts文件夹里面的补丁。
不同的电脑主板和CPU都不一样,所以这一块没有一个统一的答案,如果按照上面教程的步骤的话非常繁琐。。。但是我们可以照抄别人的答案,去Github上面搜一下
这里推荐一个仓库: https://github.com/daliansky/Hackintosh
里面有很多型号,对应不同的主板、笔记本,如果找不到自己的需要可以带上你的电脑型号在Github上面搜一下,实在不行,百度一下看看。。。万一真的找不到,找个相近的配置试一试也可以。
假设你找到了,下载EFI文件夹放到U盘里面就可以了,下一步就是安装了。
这一步也很重要,BIOS该关的还是得关,但是不同品牌的主板BIOS设置选项不一样,位置也不一样,所以这里也不细说了,网上文章很多,建议大家搜一下。
注意搜的时候带上自己的主板品牌,比如”黑苹果BIOS设置 技嘉”,网上的教程还是很多的,对着设置就行了,这一步不算很难。
插上U盘,开机从U盘启动,正常的话应该进入Opencore的界面,选择第一个加载一会应该会进入安装流程,选择硬盘工具格式化硬盘、分区,然后安装,其实这一步也没什么说的,正常情况下会重启1-2次,如果顺利的话,最后应该就会成功进入系统。
有些人以为进桌面就成功了,其实还没有,还差一步,这时候实际上你是从U盘引导启动的,拔掉U盘就不行了。
这时候需要安装一个工具,名字叫Opencore Configurator,然后菜单栏选择挂载EFI,然后你应该可以在苹果的文件管理器访达里面看到一个EFI的硬盘,打开可以看到一个EFI文件夹,把它删了,打开U盘,把EFI文件夹复制过来。
至此,所有安装过程结束,拔掉U盘重启看看是否能成功开机,如果失败了,可以把U盘再插上,从U盘引导开机,所以U盘先留着备用,防止后面折腾的时候出问题系统启动不了。
通常来说能进系统桌面基本上算是完成了90%,但是可能还有一些问题,比如无法睡眠、Wi-Fi不好使,这时候就得靠自己一个个去解决了,折腾么。。。就是图个乐。
实际上Mac系统的交互方式是和Windows、Linux不一样的,很多人第一次用会很不习惯,举个例子,最常用的复制粘贴快捷键,在Windows里面是ctrl+c、ctrl+v,在Mac里面是command+c、command+v。并且在Mac里面右键菜单的复制不是Windows的复制,是副本的意思,真正的复制是拷贝。。。Mac里面的F5不能刷新网页!!!
最骚的是,有些软件并不遵循这个设定,比如Chrome浏览器的右键选项复制就是真的复制,没有拷贝,有点操蛋,苹果就是这么倔强。
在Mac里面command相当于win键、option相当于alt键,如果你有一个苹果键盘可能好一点,如果在普通的键盘,这个默认的键位相当不习惯,很容易按错。
对此,虽然可以通过第三方软件去改,或者设置修饰键,但是个人觉得既然用了Mac系统,就去适应它的设定比较好一点,如果带着Windows先入为主的概念来看Mac,确实有很多不适应的地方,但是折腾么,图个乐。。。
如果实在适应不了,格式化重装回Ubuntu或者Windows。。。Over
]]>这个用的还是比较多的,特别是在Web开发的时候,前端传值一般都是string居多,转换主要使用一个库strconv
来操作:
1 | // StringToInt string => int |
很多人在转string的时候喜欢用fmt.Sprintf("%d", i)
,结果一样。
至于int和int64之间的互转,直接int64(i)
这种写法就可以了,但是可能会存在一个精度丢失,但是Go的int会根据不同的系统架构自动变化,在64位机器上就是int64,所以一般情况下这么转没问题。
string转float还是用strconv里面的函数,但是float转string的时候的函数用法也着实复杂,所以很多人喜欢用fmt.Sprintf("%f", f)
,结果一样。
1 | // StringToFloat string => int |
1.[]byte和string互转。string本质上就是字节数组[]byte,所以它们俩直接强转就行了,没有什么好说的。
2.int和float64互转。这2个类型之间也可以强制转换,但是也存在精度问题,比如100.9转换成int就变成100了,浮点数都会丢失。
3.int和[]byte互转。理论上这种需求很少,如果非要在做的话,涉及到[]byte的类型转换都可以先转成string,然后再强制转[]byte。
]]>这个话题看上去像是语言之争,听起来没有意义,因为大家都说真正牛逼的人什么语言都能写,语言只是工具,最重要的是编程思想。话说的没错,但是从实际开发来说,不同语言的生态环境还是有点区别的,比如很多库包装和调用的形式不一样,来回切换成本很高。
举个例子,常用的http库,如果你写PHP你立马会想到Guzzle这个强大的库,如果是Go呢,标准库自带的功能就已经足够了,如果是Python,应该是用requests这个库,但是Java呢,我还真不清楚。。。
从实际工作的角度来说,一个公司的技术栈往往都是以某些语言为主,比如字节都是Go为主、百度偏向PHP、阿里京东基本上都是Java。原因有很多,但是大多都是规范、维护、内部学习等,毕竟你招聘的时候岗位上面都会说明主要语言。
曾经有一段时间,那是PHP最辉煌的时代,是一个前端尚未独立,前后端不分的时代,那时候网页的HTML内容都是后端处理返回,这一过程简称“套模板”。
那时候对于前端来说,只需要给后端一个静态的HTML,后端会使用模板语言去渲染数据,生成一个动态内容的页面。那个年代不仅有PHP、还有JSP(Java的模板语言)、ASP,不过在这场模板语言之战中,PHP是最大的赢家,成为Web页面开发的王者。
那时候很多公司对于PHP的定位就是前端,PHPer主要的工作就是从后端服务(Java)获取数据,简单处理一下,然后渲染HTML模板返回就行了,这导致至今还有很多人依然以为PHP是写前端的。。。
然而这个辉煌时代没有维持多久,前端变天了,随着react、vue、angular等前端MVVC框架的诞生,前后端分离已经是大势所趋。现在不再需要后端去渲染模板了,后端只需要写接口,把数据以JSON的形式给到前端就行了。
很多人喜欢使用PHP原因是因为其作为一门弱类型语言,语法非常灵活,所以在套模板这方面确实快,简单的处理数据也方便,在当年,配合Apache服务器,简直是利器。
在前后端分离的时代,虽然不用PHP套模板了,PHP还可以去写接口,配合一些PHP Web框架,写起接口来也是非常快的,三五行代码开启一个Web服务,相当于其它语言比如Java来说太简单方便了。
但是这一优势在Go面前算不上太大优势,Go开启的一个Web服务更简单,连nginx都帮你省了,而且在协程的加持下,性能非常高。
下面我从几个方面来说一下Go和PHP的对比:
虽然把动态弱类型和静态强类型语言放在一起对比本身就不太公平,但是Go出身名门,由知名大佬操刀设计,这么多年来,在语言设计上面非常谨慎,所以Go的特性非常少,学习使用成本非常低,上手快。
PHP历史包袱有点重,且不论PHP几个大版本之间的兼容性问题,光语言设计本身的坑也不少,相信写过几年PHP的人都深有感触,说白了,背后没有大公司支撑操刀设计。
如果你了解PHP你会发现很多PHP大佬本质上是C程序员,因为PHP本质上是C写的一个Web库,PHP的源码就是C,你写的每一行PHP代码都会被解释器翻译成C代码去执行。
所以如果你想精通PHP,那么你必须精通C语言,很多PHP程序员就是图着简单易用来的,你让去研究C语言,大部分人都做不到。这2者的跨度太大了,通常搞Web开发的都不会写C,精通C的人一般不会搞Web开发。本人不才,写了几年PHP也只是停留在语言本身层面,没有深入了解过其底层设计,看了就忘。
PHP的运行依赖大量C扩展,比如常见的mysql、curl、json、mbstring扩展等等,如果你的业务需要高性能计算,就得自己去写扩展,这意味着你还是离不开C语言。
这2个语言之间除了性能的差异之外,对资源的占用更加明显,PHP非常耗资源,当你的并发上来之后你会发现CPU和内存可能会成为瓶颈。
虽然说Web层往往可以横向扩展,内存不够用无非就是多跑几台服务器的事情,但是当你的服务器数量达到成千上万规模,使用Go可以节省数倍的资源开销。
这时候有人会说,PHP有swoole,常驻内存,高性能,也有协程可以实现并发编程,资源占用低。个人感觉swoole完全偏离了PHP的初衷,使用swoole意味着你要抛弃PHP生态里面大量成熟的库,更像是断臂求生。还有一点,swoole掌握在商业公司手里,相信大家前一段时间也听说过一些争论,圈子有点乱,用Go的话放心Google不会收费的。换句话说,都用上了swoole了,学习成本也不低,何不尝试一下新语言Go呢?
实际上,从目前国内市场来看,PHP转Go已经是大势所趋,很多培训班现在连PHP的课程都不开了,都在教Go。
另外,很多正在用PHP的公司也正在尝试使用Go去做一些东西,一些老项目可能不会动,但是新项目用Go的很多很多,虽然说短期内PHP依然有市场,但是趋势不可逆。
随着Go的生态不断完善,第三方库越来越多,在Web开发方面,Go可能成为Java最有力的竞争者,而PHP则可能慢慢没落。
很早就有人说,Go是现代C语言,但现在看起来C依然还是那个C,在高性能领域的地位无可撼动。Go自带GC和runtime,在性能和易用性之间取一个平衡,这一点非常成功。
你说呢?
]]>Golang在1.8版本之后提供了一个 Plugin 的机制,可以动态的加载so文件,实现插件化,虽然并不是非常成熟,但是在特定的情况下还是非常好用。
Currently plugins are only supported on Linux, FreeBSD, and macOS.
插件代码和普通代码没什么区别,只是在编译的时候不一样,但是要求是必须只有一个main包
1 | package main |
使用go build -buildmode=plugin
编译,会得到一个so文件,怎么使用这个文件呢?
很简单,分三步:
1.先打开so文件,如果一个插件已经被打开了,那么会返回已存在的plugin
2.使用Lookup查找需要调用的变量或者函数,名字必须大写开头
3.断言后调用
1 | func main() { |
从上面的代码可以看到,插件的使用方式非常朴实无华,简单易懂。
一般来说,为了实现插件化,可以事先定义好一些接口,然后由插件去实现这些接口,这样才能保证一致性,但是接口的定义不能写在插件包或者调用包里面。这时候就需要定义一个专门的公共包,把接口的定义写在里面,这样插件包和调用包都可以引用。
之所以说这个插件方案不成熟,主要是由于主程序和插件程序之间存在很强的依赖性,比如:
1.编译的GO版本必须完全一致
2.双方依赖的公共第三方库版本必须完全一致
3.GOPATH也得保持一致,这一点可以在编译时候使用trimpath参数解决
4.插件加载之后无法卸载
这些问题短时间内好像官方也没有解决的意思,或者说无法解决。总之,Go plugin目前的应用很少,毕竟作为网络编程语言,在容器化大行其道的环境下,更新程序是一件很轻松的事情,除非有特殊需要。
1 | // Open opens a Go plugin. |
注意上面这段注释:如果一个路径已经被打开,那么将会返回一个已存在的插件。
这是啥意思呢?
假设你的服务是一个常驻进程,会定时的检测插件是否更新,如果有更新的话就拉取新的插件到指定目录,然后加载插件。
以上的想法很美好,但是你会发现其实插件压根没更新,你用的还是老的插件,虽然那个so文件已经是最新的了,但是Golang并不会加载新的插件。
因为它只认路径,换句话说就是文件名,所以如果的插件需要更新,务必在文件名上面带上版本号,换句话说,在一个进程内,插件只能新增,不能更新,除非你的服务重启。
这个确实非常不方便,究其原因,可能是官方压根不上心吧!
]]>个人觉得从整体来说,项目开发流程大体分为3个大的部分,第一个部分就是需求,这个需求来源可能是BOSS、可能是用户反馈、可能是产品人员拍脑袋。第二部分是编程开发,程序员干活的地方。第三部分是测试验收,主要是测试人员干活的地方。
这里说的需求往大了说可能是一个新项目,比如做一个商城APP,往小了说也可能一个项目里面的一个新模块新功能,我觉得都可以算需求。
大部分公司都有产品经理这个角色,虽然名字带着个经理两个字,但是实际上也是普通员工,这个角色说简单点就是画个原型图,对着友商的产品抄一抄,可能偶尔对开发程序员来句: 为什么xxx家都能实现你不能实现?
由于经常给程序员提各种需求、提bug,少不了争论和撕逼。但是这个角色往高了说,像微信的产品经理李小龙这样的可不是画原型了,他们则是对自己得产品有着深刻理解,是一个产品的灵魂设计者。
言归正传,玩归玩,闹归闹,产品这个职位不一定得有,但是需求一定要明确:你得告诉开发人员要做什么东西,即使没有原型图,你拿着友商的竞品展示一下对着抄也行啊。
需求应该有多明确?我见过写的最好的需求文档,是精确到每一个输入输出数据、包括长度大小,有一些高保真的原型图甚至都把交互设计出来了,还能点,所以越细越好,不然在实际开发中还得写一步问一步,只要产品不嫌烦,我没意见。
一般来说,一个需求在产品内部之间已经做过评审等工作,需求给到开发这边已经相对比较完善了,但是依然需要对齐。
所以这一部分在真实的项目开发中,一般有下面几件事要去做:
1.参加产品会议。参会人员包括前后端开发、测试等支撑人员,会上由产品人员对需求做一个简单明了的介绍,并配备项目流程图、原型图等资料,明确项目背景、需要做的东西、以及项目边界。
2.介绍完之后,可以问一下参会人员有没有什么疑问,不明白的地方,由产品人员解答相关问题
3.作为开发,开完会心里面应该有个概念了,有什么问题,该提就提,早点和产品说,现在改还来得及,产品写的不一定对,错的地方你可以纠正。对于有些不好实现的功能也可以和产品商量一下可以不可以换种方式或者放到下一期需求,等等。
说白了,这一步就是把相关人员拉到一起对一下需求,答疑解惑,这一步一定得有。这一步不仅仅是把需求给到开发,更是对需求的一个理解贯彻,哪些能做哪些不能做都需要开会讨论明确的,不是说一个文档扔给开发,产品就不管了。
接下来,一般产品在和开发等人员对完需求之后,会做一些修改,该明确的明确,双方都没有问题的时候,正式提需求,邮件抄送相关人员,提个禅道或者类似的项目流程管理的东西。
关于项目排期这块,大部分时候是由开发来定,如果需求不紧急的话。但是有些公司比较追求效率,很多时候是由产品定,或者老板直接拍板定个日期。个人觉得只要不是太离谱的话都没问题,如果时间实在不够,完成不了,一定要联合开发人员一起反馈,调整排期,千万不要等到最后几天才说时间不够,完成不了,这样显得太业余。
关于需求这块,我觉得最重要的是要明确需求,我见过很多产品自己都对想做的东西想的不太明白,考虑的不细致,做到一半发现设计有bug或者遗漏的地方。不求你想的十全十分,但是至少逻辑上要自恰,流程要走通。
拿到需求之后,接下来就是编程开发,一般来说,一个需求会分配给好几个开发一起去做,这时候就得选一个人站出来负责整体把控,把大家拉一个群,方便交流。然后分解需求,分工,然后写各自的功能就行了,有问题群里及时沟通。对于敏捷开发等一些开发模式,公司如果有精力也有相关人员去管理,也可以去实践一下(对人要求太高)。
本人之前主要是从事Web开发为主,说简单点就是写API接口,自然少不了数据表设计、前后端接口联调等工作,所以有时候还包括以下工作要去做:
这点很重要,一个需求拿到手,需要那些数据文档里面应该都体现了,需要几张表应该都能估算出来,这时候应该让一个人,或者大家一起,把这次需求需要用到的数据表设计一下。然后找时间拉个会,一起评审一下字段结构设计是否合理,该改的就改。
千万不要每个人自己搞自己的,应该整体考虑设计新增的每一张表,不然时间一长,项目里面的各种表肯定很混乱
在前后端分离的时代,在涉及前端页面的项目里面,自然少不了接口文档。在我的职业生涯里面,遇到过2种方式,一种是先写接口,再完善文档,然后告诉前端哪个地方调哪个接口,这种方式就需要前端等后端先开发接口才能写页面。
还有一种方式,就是先写接口文档,再写接口,这意味着开发需要先发挥想象力,把需要的接口字段定好,和前端对齐之后,前端就可以使用mock工具模拟接口数据进行开发,完全不依赖后端,等到后端接口开发完毕之后再进行联调。
对于第二种方式,有些人可能觉得我接口都没写,我咋知道有那些字段,实际上,如果需求很明确,数据表也设计完成,这不是什么问题。
对于简单需求,接口数量少,采用第一种方式也没啥问题,如果大的需求,我建议还是第二种比较好,更加科学合理,前后端可以同步开发,而且在沟通上也更明确,不用扯皮。
完成上面2个工作之后,对于程序员来说,剩下的就是按照产品需求文档,实现具体的业务逻辑了,有条不紊。
1 | func main() { |
测试可轻可重,对于有些公司来说,在项目开发中比重与编码相当,但也有公司觉得测试只是附属工作,不必浪费太多时间,开发人员写好单元测试就可以了。
不过做过实际项目开发的人都应该知道对于那些复杂的需求,指望完全依靠单元测试来保障项目质量简直是做梦…
严格来说,测试人员在第一个阶段就需要介入了,要跟着开发一起参会,需要去理解产品需求,然后编写对应的测试用例,测试用例需要尽可能的覆盖所有情况。我之前有家公司还会要求评审测试用例,一个简单的需求都可能有几十个用例。
对于API接口,测试还需要对每个接口单独做测试,使用一些测试工具进行测试,对于部分接口可能还会做一些性能测试。
前后端联调完之后,还需要测试整体功能是否符合预期,必要时使用抓包工具定位问题,给开发提bug,等等!
总之,测试是一个非常系统性和专业性的工作,并不是一个简单的活,这里只是简单说几句,一个合格负责任的测试并不轻松,对产品需求的理解也需要到位,还需要和开发一起加班,开发改完bug之后,测试需要验证,做回归测试,编写测试报告等等。
但是我也见过有些公司的测试只做最后的黑盒测试,也就是像普通用户一样点一点,用一用,跑一下正常的流程,尽可能的模拟正常用户去使用,发现存在的问题。
甚至对于某些不太重要的项目,比如管理后台,开发自测就可以了。。。
在整个项目开发过程中,产品这个项目应该是贯穿整个项目的,不是说提一个需求,要一个排期,然后撒手不管坐等deadline就行了。
一个合格的产品应该时刻关心项目开发进度,必要时给予相关人员帮忙,协调各部门之间的工作,在最后阶段也可以参与到黑盒测试中,做一个验收工作。在开发过程中如果需要调整需求,必须及时通知相关人员。
最后,简单总结下整体流程:
1 | 1、需求对齐,拉上前后端开发、测试等人员 |
希望对一些新人有用!
]]>举个例子,有一句话叫: 这个东西挺好吃,小明吃了一斤。光看这句话,你并不知道小明到底吃的是什么?毕竟这个可能是任何东西,你必须翻看文章前面的语句去看这个到底代表的是啥意思,这里上下文表示的就是一个前后的依赖关系。
在CPU里面,context也有类似的意思,我们常说CPU上下文切换,因为单核CPU一次只能执行一个运算,我们之所以可以同时听歌、写代码,那是因为有一种CPU时间片轮转机制在不同进程之间切换,由于其速度非常快,导致你觉得好像是在同时运行。在这个切换进程的过程中,CPU就需要保存上下文,这里的上下文其实就是寄存器、程序计数器等数据。
其实,虽然上下文的叫法理论上讲没太大毛病,但是其实翻译成语境、或者环境更加贴切。言归正传,下面咱就说说Go标准库里面的Context是啥、以及能干啥?
在Golang里面,Context往往是和协程紧密联系在一起的,因为Context是并发安全的,可以在不同协程之间同步状态。
举个例子: 主进程里面启动多个协程去处理数据,当我们关闭程序的时候,有一些协程还在处理中,这时候你该咋办?如果直接不管可能导致某些数据丢失,这时候你就可以通过Context去通知协程做一些终止前的准备工作。
比如下面这个例子里面,我们就启动了3个协程,5s之后通过context发送取消信号,子协程在收到信息之后依次退出。
1 | func main() { |
此外,Context库还有一个WithTimeout
,其实他们俩用法差不多,唯一的区别就是WitchCancel
是需要你手动调用取消函数,而且Timeout这个顾名思义,是定时自动取消。
以上面的例子为基础,我们只需要把main里面稍微改动一下,使用WithTimeout
即可。这个cancelFunc是可以忽略的,但是最好用defer调用一下,否则可能导致内存泄露。
1 | func main() { |
除此之外,还有一个WithDeadLine
,意思就是设置一个截止时间,到了这个时间就自动取消,不过在我们这个例子里面,基本上和Timeout等价,稍微改动即可:
1 | ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(time.Second * 5)) |
实际还得看应用场景去选择使用那个函数。
下面,咱们来看一个比较贴合实际的应用场景: 假设我们需要并发的调用3个接口,而且还需要获取返回结果,并且设置一个超时时间,比如3s。
并发调3个接口并不是太大问题,我们只需要启动3个协程即可,获取响应结果的话,可以采用chan。在超时这块,实际上如果只是调接口的话,实际上Go的标准库里面httpClient可以设置一个超时时间。
所以有一个简单的写法:
1 | func main() { |
这种依赖httpClient的写法大部分情况下没问题,但是假设子协程出问题了,没有往chan里面送东西,这个程序就会一直阻塞在那里。
对于这种情况,有2种解决方案:
接收端指的是上面代码里面接收结果的chan,我们可以设置一个超时时间,比如下面这个例子里面,我设置了3s,超过3s我就不等了,该干啥干啥去。
1 | func main() { |
这种方式意思就是我们要保证协程肯定能返回结果,即使超时也要返回一个nil,那这里就还得用到Context来做这件事。
在子协程里面,我们通过go启动一个协程,当收到取消信号的时候就返回一个nil,表示已经超时了。
1 | func main() { |
执行结果如下:
1 | timeout... |
WithValue
这个用的不多,效果和函数之间传参差不多,但是context的最大特点就是可以继承,当你的协程链条特别长的时候,这个就比较有意义了,比如说在不同子协程之间传递一些通用的配置变量。
纸上得来终觉浅,绝知此事要躬行,网上关于Context实现的原理的文章挺多,这里我就不献丑了,自己研究也不深,更多的研究其实际应用场景,加深自己的理解。希望上面的一些例子对你实际开发有用。
]]>不知不觉,在阿里干外包已经干了1年半了,不长也不短,渐渐的感觉自己已经非常适应这样的生活,不太愿意去尝试新的工作,一直也没静下心来思考这些问题,今天刚好决定静下心来写点东西,分享一下这1年多的经历。
这1年多说起来也惭愧,并没有做什么值得一说的东西,基本上都是一些重复性工作,比如数据白屏化、数据处理、后端CURD接口等等,另外还写了很多基于antd库的前端页面。
同部门和我坐在一起的外包同事大概有7-8个,但是归属不同的主管,干的工作内容也有所差异,不过平时也和他们聊天交流,大多都是在做一些相对杂碎的活,简单说就是哪里需要干哪里,并没有一个完整的项目去给你做。
我所在的部门是属于阿里云的内部平台,他们正式工做的工作,大概就是收集各个服务节点上报的数据、处理这些数据,然后基于数据构建策略模型,得到一定的结果,再去验证结果是否符合预期。
对于外包来说,能做的东西不多,也就是接手一些不重要、边缘化的项目,此外就是给正式工打杂,做一些辅助性的工作,听上去挺无聊,实际上确实是这样,技术难度并不高。
说到学东西,其实别说外包,就是正式员工也不一定能够接触到项目最核心的代码,每个人都有自己负责的一块,有一个方向,大大小小的项目数不胜数,但是最核心的那些不是一般人接触不到。
正因为上面所说的,部门对外包的定位就是干一些辅助性、偏前端的工作,所以工作自然也很轻松,再加上弹性打卡,每天其实可以早上10点钟到公司,晚上6点钟准备收拾走人。
实际工作中,阿里对外包的管理非常松散,夸张的说,只要你能把钉钉打卡搞定,你人在不在办公场地没人管,因为外包是一个集中的办公场地,和正式工不在一起,自然也没人去监督你,这样的工作生活估计是很多IT人羡慕不已的,但确实大多数外包的真实写照。
大多数时候他们的需求都比较简单、时间也很充裕,每天正常的工作时间完全可以搞定,根本用不到加班,更别说周末了,周末不会有人找你的。。。
对我来说,这就是外包唯一值得留恋的地方,唯一的好处。
既然工作轻松,那工资自然也不多,大部分外包工资水平都属于很普通的水平,肯定是没法和正式工比,据我了解,低的是上限,下限还行。
一般下限是15k左右,也就是1年左右工作经验,刚毕业、或者刚出培训班那种,其实这个水平在北京来说并不是很差。
再往上也就20k左右,不会太高了,所以说一个4-5年工作经验的外包可能只比1年经验的人多拿5k块钱,经验在这里确实不值钱,毕竟门槛很低。
说到工资,不得不说一下外包的涨薪制度,一般外包公司都有调薪制度,半年或者1年调一次,但是不会主动给调,得你自己申请。
外包涨薪一般有2种机制:一种是外包公司直接给你涨,就是外包公司内部调薪。因为阿里给外包公司的工资是一个固定的数,举个例子,比如阿里给外包公司25k,外包公司给你15k,中间商外包公司赚了10k,你提涨工资的话,外包公司就把自己赚的那部分拿一部分给你加薪,相当于它自己少赚点。
另一个途径就是调级,实际上在入职的时候阿里会给外包定一个级别,比如高级、资深等等,每个级别的工资是在一定范围内的,如果你提调级通过之后,阿里会给你涨工资,这就相当于外包公司不用少赚钱。
我之前提调薪的时候,外包公司告诉我帮我先提调级,如果不通过再走公司内部调薪,阿里这边的主管和我说调级没问题,但是最后不知道什么情况,外包公司直接给我提了,并没有走调级流程,具体是啥个情况我也不知道。
其实这个调薪幅度并不高,以我的标准来看,大概也就10%左右,可能还不到。
再说一点,我们部门的同学有来自不同外包公司的,据我了解不同外包公司的待遇大不相同,比如有的有13薪,有的只有12薪,有的涨薪很难。
我这1年里面,周围走了大概4-5个人,走了之后立马又重新招人,之前和阿里主管吃饭的时候聊过,他们说公司给每个部门的名额是固定的,不招白不招,即使现在没事也得招,不可能有活了才去临时招人。
所以基本上就是一个萝卜一个坑,有些人可能天天闲着没事,那也正常,照样给你发工资,讲实话,这点得感谢阿里,解决了很多人的就业问题。
外包的门槛有多低呢,就我个人而言,之前面试进来的时候问的问题真的很简单,2个电话面试就结束了,这导致后面来了很多新同事水平真的一言难尽,我不知道是不是普遍现象,很多工作了1年多的同学连Git都没弄明白,对分支的概念模糊,代码冲突了也不会合并。有些人在编程概念和思想上面也匮乏,导致沟通上面有很大问题。
可能我对那些新人的要求太高,毕竟有些都是比我小好多岁的年轻人,对于我这样的老鸟来说,这些问题自然都不是问题,理解万岁吧!
但是我其实想对那些年轻人说,不要这么早进外包,人会废掉!即使自己学历或者其它方面比较差,进不了大公司,进一些创业公司也不错,在这里真的什么也学不到。
吐槽几句,在我所在部门不乏一些p7级别的,和他们有一些接触交流,人都还不错,但是我看他们写的代码也是一言难尽,看不出来什么水平,可能是我期望太高,也可能是因为并不是什么核心项目的代码,所以写的很随意。
之前和其它外包同学聊过这件事,我发现很多阿里的主管都给外包画过这样的饼,意思就是说表现好可以转正式员工。
其实我来阿里干外包之前也有过这个想法,但是干了几个月之后我就明白了,这几乎是不可能的事情,可能在阿里的几千名外包中有那么几个成功过,但是可以这么说,99%都是不可能转正。
我觉得如果想转正,有以下几种可能:
第一,自身学历等条件不差,比如说你就是211、985院校毕业的。你整个大专、普通本科在阿里真的没人瞧的上,据我了解,我所在部门那些p7大佬学历都不差,招的实习生也都是名校毕业。
第二,部门重用你,给你安排了重要性比较高的活,而且你干的不错,体现出了自己的价值。这也不是说不可能,但是在我所在的部门几乎不可能。
第三,你和你的主管关系好,平时来往多,他愿意提携你一把。这点完全是我臆想的,实际上以我个人经验,我只和我的主管吃过1次饭,平时只是工作沟通,私下完全没有任何交流,我本人也不是那种会打理关系的人。
虽然工作上没怎么花时间努力,但是作为一个热爱编程的人,编码的脚步不会停下,在此期间,也在努力学习,尝试写一些小玩意、整一些开源项目,比如说拿Go写Gui桌面应用。
这么多年的工作经历也让我认识到一个事实,大部分工作岗位并不需要高超的技术,大部分人只是螺丝钉,拿着一些现成的库、开源的工具去做一些偏应用的开发工作。只有极少数人在干那些底层、前沿的编程。
我承认我也是大多数人,对算法、OS等编程底层知识研究不深,但却熟知各种库的调用、熟练使用Google等搜索工具解决问题,简单说就是一个熟练工。
这一定是坏事吗?不一定,人人都知道那些底层基础知识很重要,但是社会需求并不多,大多数公司只想招能干活的、能解决问题的,谁关心你会不会写二叉树反转算法,不过我确实佩服那些算法写的溜的人。我也尝试去练习算法,每次leetcode刷着刷着就坚持不下去了,更可怕的是刷过的题过几天就忘了。。。
其实,在我和一些新人打交道的时候,我会发现很多人都存在一个问题:缺乏好奇心和探索精神。比如遇到一个问题,他们不知道怎么解决,这倒也很正常,但是他们连搜索都不知道搜索什么关键字,这倒是最可怕的。有些人用着一个软件都不知道有哪些功能,能做什么事,他们生怕点错了会导致什么坏的结果,所以干脆不去尝试。
最后,就这样吧!希望我明年不要在干外包了。。。
]]>因为我是使用Ubuntu当主力机,每天不仅拿来编程,也拿来娱乐,看视频电影,浏览网页,对这个现象比较注意,其实这种问题有时候也不单单是Linux存在的问题,很多玩游戏的朋友应该知道很多游戏都有一个垂直同步的选项,这也是为了解决画面撕裂问题。
这种画面撕裂问题本质上是显卡输出和显示器刷新频率不同步导致的问题,这里记录一下我的解决方式,一个是自己备用,另外分享给需要的朋友。
在Ubuntu系统下,分2种情况下:
如果你是独显,比如Nvidia显卡,可以在英伟达控制面板里面勾选垂直同步选项,实际上这个效果并不明显,其实我也不知道为什么。
后来,我找到了一个非常有效的方式,打开 /etc/X11/xorg.conf 文件,这个文件在老版本的Ubuntu里面是自动生成的,新的版可能并没有,但是我们可以手动生成,通过下面的方式:
按下快捷键 Ctrl+Alt+F1 进入终端模式
1 | sudo service lightdm stop //关闭图形界面 |
整个配置文件内容比较多,具体啥意思我也没去细究,但是其中有一块我们需要改,内容大概如下:
1 | Section "Device" |
这里只截取部分片段,其中关键就是Option字段的配置,可以看到这里其实有很多选项,默认情况下都是注释掉的,但有2个选项我打开了,分别是VSync、TearFree,设置为True即可。
然后重启就会发现画面撕裂问题解决了,至少在我的CPU 8700k的集显下,带动4k显示器没问题,非常流畅,但是为什么这个配置默认被注释掉也是很奇怪,必然有原因的,我Google了一下这个问题,发现有文章提到打开会使用更多的内存、增加响应延迟,没毛病和我想的一样,在Windows下很多游戏都有“垂直同步”这个设置选项,打开的话会锁60帧,显卡负载会低一点,但是显示响应会慢一点,虽然可能是毫秒级别,但对于很多fps游戏很致命。
总之,在我电脑上我感觉没啥问题,如果你经常看视频,对画面撕裂问题接受不了,不妨设置一下试试。
]]>买卖股票的最佳时机II,简单难度,leetcode地址:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii
给定一个数组 prices ,它的第i个元素 prices[i] 表示一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
示例 1:
1 | 输入: prices = [7,1,5,3,6,4] |
示例 2:
1 | 输入: prices = [1,2,3,4,5] |
这种题基本上都可以采用暴力循环遍历法解决,这里就不多说了。除此之外,还可以采用动态规划的方法,比较复杂,但是这里介绍的是一个贪心算法。
这种解法的思想就是可能多次交易,只要今天价格比昨天高就卖出股票,然后把所有获利加在一起就是最大利润。
这里以[7,1,5,3,6,4]为例,推算步骤如下:
1 | 1 > 7 ? 不成立,利润 0 |
最后把所有例如相加,即是最大利润,但是其操作的过程并不是最优解,而且实际上过程中也没法这么操作,以为这些操作过程中并没有把亏损算进去。
这种算法虽然结果是正确的,但是过程却很奇妙,代码逻辑也不复杂。
参考代码:
1 | func maxProfit(prices []int) int { |
买卖股票的最佳时机,简单难度,leetcode地址:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock
给定一个数组 prices ,它的第i个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
示例 1:
1 | 输入:[7,1,5,3,6,4] |
示例 2:
1 | 输入:prices = [7,6,4,3,1] |
leetcode上面很多题都可以使用暴力循环这种方式解决,毕竟没有什么是循环遍历解决不了的,只是效率问题。
这道题也不例外,我们只需2层循环遍历,依次计算不同买卖天之间的利润,求最大值就行,代码逻辑朴实无华!
参考代码:
1 | func maxProfit(prices []int) int { |
然而,这段代码在leetcode里面提交运行超时,因为测试用例里面有一个超长的数组,服了!
要看明白这个解法,得换个思路,假设我们知道哪天价格最低,我们就可以在那天买,然后在价格最高那天卖出。
假如计划在第 i 天卖出股票,那么最大利润的差值一定是在[0, i-1] 之间选最低点买入;所以遍历数组,依次求每个卖出时机的的最大差值,再从中取最大值。
这个思路结合下面这段代码看,比较有效果:
1 | func maxProfit(prices []int) int { |
以[7,1,5,3,6,4]为例,我们手动推导一次:
1 | minPrice = 7, maxProfit = 0 |
首先,我们定义了最低价格和最大利润2个变量,其中最低价格默认为第一个元素。 然后,依次遍历数组,把每个元素的值和最低价格做比较,有3种结果:
一、如果元素值小于最低价格,那么就更新最低价格
二、如果元素值等于最低价格,那么就不做任何操作
三、如果元素值大于最低价格,说明此时有利可图,我们就算一下利润是多少,如果利润比之前利润大,那么就更新利润
在整个计算过程中,我们动态的更新了最低价格和最大利润,最终得到的结果就是最大利润。
]]>