跟随,学习,进步

Yisaer

Yisa's Blog

https://yisaer.github.io

上海大学14级计算机学院,酷家乐黄埔研究院 研发工程师

转到作者网站

五一游记

爽到五一假期抽了当中两天去扬州玩了一趟,整体而言玩的还是比较令我满意的。比起以前去的其他城市玩,扬州有一个特点就是他的所有景区基本上都集中在主城区这一块,这样就基本上节约了弯弯绕绕的来回奔波,可以直接沿着一条线路直接玩下来,对安排旅程而言。这次决定五一去扬州玩是一个非常仓促的决定,一来是前段时间我一直在忙工作上的事情,基本上4月份的每个礼拜都有那么几天一直加班到八九点,另外一方面我女朋友也在着急肝毕设(在这里要特别谢谢小宝宝在邮肝毕设的情况下也愿意出来旅游!!)。所以真的开始打算五一决定出去玩的时候,已经是4月26/27这几天了。所以在五一前的最后一个周末,我们打开携程上的一个简略的高铁路线图。挑了几个离上海比较近的城市后,最终定在了去扬州玩。瘦西湖我和女朋友两人11点一起坐高铁从上海出发,坐到南京站后转乘动车来到了扬州站。出了站之后,我们就直接坐着旅游专线公交到了大明寺啦。本来按照原来的计划是先在大明寺玩,然后再从大明寺出来到瘦西湖的北门进去。可惜到达大明寺时都已经下午三点了,为了能更好地体验到瘦西湖的景色,于是我们俩就决定这次先不去大明寺了,而是直接从瘦西湖的北门进园区。同时去瘦西湖玩的话推荐提前一天在网上订购园区票,这样就不用到了园区再排队买票啦,可以直接刷二维码进去。比起五一的其他景点,扬州这里有个明显的好处就是没有其他景区的人山人海,虽然瘦西湖里也有不少的游客,但是整体程度还是比较让人满意的。来到瘦西湖里面以后,给我的感觉比起湖,自己更像是来到了某个园林。从北门进入园区,会先来是来到一片松树的盆栽园区,我们俩进去逛了逛还挺好玩的,各种松树盆栽修剪的确实各有特色。这棵盆栽我要特别的说说,我当时看到它的第一眼就觉得这颗盆栽就像一艘中世纪大帆船一样,不知道有没有人和我有同样的看法。从盆栽园区出来后继续往南走,路上还有许多其他的小花园和小溪流。有各种各样我叫不上名字的五颜六色的花以及小动物。可能是因为瘦西湖园区里面人不是很多,所以还有很多野生的动物能看到。可惜当时我和女朋友两个人都在那一个劲的哇,哇,哇了。都忘记拍下来了,结果现在写游记时发现都没有那时候的素材了。一直往南走走到西门这块,我俩到了西门这块的一个景点,石壁流淙这里。这里也是另外一处让我记忆深刻的地方,这里的亭子和游道都修的非常古色古风而且有很多座位可以让我们坐下来休息会,而不远处石山上的水流以极大的声势砸在池中漫起水雾的情景也挺好看的。从石壁流淙出来后,我们就往二十四桥这个景点去。虽然瘦西湖整个园区的人并不多,但是二十四桥这里景点人是真的多,而且大家都会想去走下二十四桥,结果就导致这个桥常年人满为患。其实玩到这里我和我女朋友俩就已经比较累和比较饿了,好在二十四桥这里还有观光船可以坐。所以后面的旅程就是我直接和我女朋友坐船从二十四桥坐船到南大门了。坐船前我们俩发现湖上有个小岛后很多小鸭子和一只黑天鹅,也是非常可爱了。扬州狮子楼从南大门出来的时候已经临近饭点了。在去扬州之前,我看了很多攻略都介绍说去瘦西湖玩推荐从西门往北走,一路玩到北门再去大明寺转转。然而我发现扬州的美食和特色街道都集中在南边这块,所以我当时做出决定从北门入园,这样我们从南门出来以后就能直接去吃晚饭以及体验其他项目啦。我们晚饭去的时候在扬州大学内部的扬州狮子楼,这个饭店也算是在扬州非常有名的一家饭店了,而且离我们出来的南大门并不远,走路5分钟就走到了!我们到饭店的时候还算早,所以就等了20分钟不到就能进去吃饭啦。来到狮子楼以后,肯定要点一下他们的特色狮子头啦,这个狮子头做的非常大也非常酥软,里面肉非常多量也非常足。其实非常适合三到四人去吃,我们俩吃了非常久最后都没彻底吃完…另外到了扬州怎么能不品尝一下当地的扬州炒饭!感受这比拳头还大的狮子头…心心念念的扬州炒饭!虹桥馆温泉坊在我查攻略的时候,一直听他们说扬州素来有“白天皮包水,晚上水包皮”,即是指白天要去体验扬州的早茶,晚上要去体验扬州的洗浴。所以吃完晚饭以后,我们俩就直接走到了虹桥坊这块。不得不夸的是,当时决定从南大门出来的决定简直太对了,因为虹桥坊就在南大门旁边,所以我们从狮子楼走到虹桥坊的整个过程不到二十分钟就到了。当时在大众点评上搜扬州的洗浴类目时,虹桥坊的温泉馆就是里面置顶的第一名,所以我们吃完饭后就直奔而去了。虹桥坊内部修的其实也非常漂亮,我们沿着大路左逛右穿,那个时候要接近落日了,路上的景色也十分漂亮。在经历了一天的奔波与逛景点后,尤其是我还背了一天比较重的斜挎包,没有什么能比体验一趟温泉和按摩更加爽的事情了。温泉馆里面能体验的项目非常多,有各种冲击的温泉,也有露天的岩泉和汤壶,我也第一次接触了一下搓澡和敲背,被老大爷熟练的手法痛并快乐的玩弄着。因为温泉馆里面男汤和女汤是分开的,所以和女朋友约定好7点半出来见面,结果我一不留神把自己放纵到了8点一刻才出来,出来以后被女朋友爆K了一顿,并且立下了下次再敢迟到就写检讨贴在博客上并广而告之。说道这个我是真的错了,希望大家以此为戒千万千万千万不要让你女朋友等你,尤其是你居然在里面享受,否则你会死的很惨。总之在经过了深刻我的认错检讨后,我们俩泡完澡以后躺在榻榻米上休息了半小时拍了非常多的沙雕自拍。一个假装自己有长发的傻子总结从温泉坊出来后就已经九点多啦,本来还打算逛一逛东关街的夜市了,然而扬州夜晚结束的太快,即使才晚上九点整个城市也像快要入睡了一般,我们怕打不到车就决定放弃夜逛东关街,早早的打车回酒店啦。总的而言这一天在扬州玩的非常开心,整个路线安排也是比较满意的,虽然大明寺和东关街没去比较可惜,但是能体验到瘦西湖和温泉坊真的让我非常惊喜!


黑客攻防72小时

维京勇士北风造正文2018年的10月,某个我吃完午饭在园区里面慢悠悠散步回公司的星期二中午,企业微信里面突然收到一条来自主管的消息。大意是在我们业务的后台用户中心看到我们的CTO一连注册了好几个用户,感觉到非常奇怪。让我回来看看线上环境是不是出什么代码BUG了,他则去联系CTO了解情况。回到电脑前,我看了下之前那几个非常诡异的账号,之所以被主观认为是CTO注册的,是因为这些账号的用户名都是用我们CTO的真名来注册的。而主管他也和CTO确认完毕,那些号并不是CTO自己注册的。“也许是有人恶作剧吧”,当时我们都是这么认为的,然而就在这时,这个后台的系统显示此时注册的数量逐渐变的越来越快,而且注册用的用户名都是用的我们的CTO的名字,而邮箱则是用了一串明显随机出来的字符串。看着日志看板里快速跳出的一行行注册日志,我意识到了刚刚诡异的账号注册只是一个试探,而我们现在则是遭受到了真正的恶意的API调用攻击。此时遭受攻击的服务是我们业务中的账户服务,而我恰巧是这个服务的Owner。看着自己负责服务的注册接口被恶意刷接口调用,我心里贼气。然而这些恶意注册的账号都有一个非常明显的特点,就是不知道为什么他在填写用户名的时候都是用了我们CTO的真名,所以对于我们来说要鉴别出这些恶意请求非常容易,那就是将这些在注册请求中,所有用户名是那个名字的请求全部拒绝掉即可。想好对策后,我立即打开项目后写了相应的针对代码然后直接推送到线上。4分钟,从写代码到推送到代码仓库经历CICD到最后线上版本滚动更新的整个总共花了4分钟。线上服务版本更新后,这些恶意的请求果然都在日志中记录被拒绝了,我们暂时有了喘息的机会。1234567{ "email": "random@random.com", "username": "fixed_name", "password" : "random", "locale": "en_US", .....}此时的我们面临了一个问题与一个难题。问题在于,作为一个在当时还处于创业期的业务,本身用户与平台侧并没有任何较高的价值,攻击者为什么要对我们进行大量的刷用户注册的攻击,他的意图到底在哪里?如果我们不能搞明白攻击者的真正目的,那么我们将永远处于被动的节奏。难题在于,我们之前上的新的线上版本只是一个专门针对刚刚攻击者的一个版本,等到那个恶意攻击者察觉到以后被针对以后,他只要随便更换一下规则我们就会又立即处于被攻击的状态,我们必须尽快的找到一个彻底的防御方法。就在我们思考与寻找上述的答案时,后台系统的用户增长数又开始了诡异的增长。我们知道,攻击者发现我们开始针对他了,在他简单将注册的脚本中用户名的值改为随机生成后,他的攻击对我们又开始生效。在我们彻底搞清这两个问题前,我们首先需要为自己争取时间,在不影响用户正常使用的情况下,我们必须要将那些恶意请求的特征找出来,然后拒绝掉。 在后台的数据系统内,我们看到这些恶意的注册请求,虽然在请求内容中有着随机的数据,但是请求IP却都是来自同一个IP,而这个IP是新加坡的某个云服务商的IP。很明显,这名恶意攻击者将自己的注册脚本放在了某个云服务器中来进行攻击。就在我想要在服务端BAN了这个IP时,我的主管突然拉住我说,“等等,别让他知道我们发现了”。 这一拉瞬间让我冷静了下来,当时作为一个刚刚毕业三个月的我来说,第一次遇到自己负责的服务在线上直面的恶意攻击,让我从发现开始到现在变得焦躁不安,而焦躁与愤怒正是在对抗时最致命的弱点。 当务之急是在保证网站、数据安全的情况下搞明白攻击者的动机与寻找到长期有效的防御手段,所以我们的防御手段不能让攻击者意识到我们又一次针对他了,从而使他麻痹,为我们争取到尽可能长的时间,即使用蜜罐手段。 我猜测攻击者之所以之前能如此快的意识到自己被针对了,肯定是对恶意请求的HTTP返回值进行了记录,当发现自己的所有请求都被拒绝时,从而更改了脚本。那么此时此刻我的蜜罐手段则非常简单,我在接口对这个注册IP进行特判,对于恶意注册者的IP,我并不会让代码走到数据库层面的逻辑,而只是在接口层稍微等待了1-2秒后,将伪造好的假数据返回给他,让他成功的进行一次HTTP请求。这里的精髓在于,我故意在这里加入了停顿1-2秒的逻辑,从而让这个请求看上去真的好像作为一个正常请求一样被处理了。我快速编写好这个蜜罐后,推送上了生产环境。一分钟、十分钟、一小时过去了,攻击者果然如预想中的一样并没有察觉到这个蜜罐的悄然上线。对于敌人的第二轮攻击,我们暂且挡住了。12345678{ "email": "random@random.com", "username": "fixed_name", "password" : "random", "locale": "en_US", .....}with a fixed IP当攻击暂且消停后,我们开始琢磨上文提到的一个问题和一个难题,对于敌人的动机我们都觉得非常奇怪,为什么要对一个还没有面向公众的业务进行攻击,无法理解能得到什么价值。所以我们决定先解决上文所提到的难题,即找到并立即上线一个长期有效的防御手段。由于之前敌人的攻击都是用同一个IP来进行攻击的,所以一个非常明显的手段就是进行IP限制,即对于同一个IP在同一时刻时能进行的连接进行绝对数量上的限定。由于我们在架构上使用Kubernetes配合Nginx作为流量的负载均衡,所以通过Ingress Anootation就能非常简单的做到在NGINX层进行IP层面的限制。12345678910apiVersion: extensions/v1beta1kind: Ingressmetadata: name: ingress-rules namespace: default annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/limit-connections: '10' nginx.ingress.kubernetes.io/limit-rps: '1'然后这只是在对于同一个IP而言,我们非常清楚当攻击者在使用IP池的情况下,我们的IP限制的策略同样也会被攻破。所以防御的关键手段在于如何在服务端甄别出这个请求究竟是正常人发的请求,还是某个API脚本所发的请求。这一点我相信大家都在日常的APP和网站的注册流程中都会遇到,即注册后会先进行一波手机号验证、邮件验证,或者是输入或滑动一些验证码,来达到人机甄别的目的。这个流程其实我在早期账户服务的构建中向我的主管提出过,然后我的主管否决了这个提案。原因在于对于一个处于创业期的项目而言,我们需要尽可能的降低获客成本,提升用户的注册率,当我们在注册过程中加入任何一个验证流程时,都会成为一个漏斗,从而将部分用户从漏斗中筛出去,降低了我们的注册率。所以对于一个早期业务而言,提高用户注册率的优先级高于各种复杂的验证流程去保证安全。对于这个说法我认为确实是有道理的,但也确实造成了我们目前遭受攻击的局面。万幸的是自从蜜罐上线后,攻击者也一直未能发现,从而给了我们喘息的机会。从中午受到攻击,到思考对策上线防御版本,一转眼的时间就到下班时间了,我和主管对了一下晚上盯防生产环境日志的安排,就先撤了。敌在暗我在明,我们的服务放在公网上能被他攻击,但我并不知道他是谁、在哪。 敌在明我在暗,我能在后台看到他的每一个请求内容,从而抓出特征,但他并不能意识到我们的服务发生了哪些变化。星期三的中午,就在我思考该如何彻底解决这个问题时,我的主管从会议室里出来急冲冲的问我,“我们的用户体系是否和国内主站是不是打通的?”“对呀,账号密码共通的” “坏了”,他说道:“那个黑客盯上的不是我们,而是国内业务。” 到这里我才如梦初醒,明白了那个攻击者的真正动机。十月初的时候,研发部决定用中台化的能力来对接所有前台业务,我们作为一个扩展海外的前台业务,则是接入了国内的用户中台,而这块的接入也正是我负责的。当时我想当然的将我们自己的国外业务的用户名密码原封不动的接入国内用户中台。虽然我们作为一个早期的创业业务并没有任何价值,但是我们的国内业务则是有着丰富的数据和方案,可以说是我们的核心资产与价值,而国内的业务在接口安全这块也做了非常多的防御与检测工作,可以说是严防死守。而由于我们业务在不做任何变动的情况下接入了用户中台,等同于在我们业务中注册的用户也能无缝访问登录国内业务。而由于国内的业务在防御这一侧做了相当多的工作,所以注册一个账号并进行爬虫的代价是极大的,所以至今为止击溃了无数的爬虫攻击者。而我们这里,则相当于无意中开了一个小口子,让攻击者能以极低的成本在我们这里注册账号,然后拿着这些账号凭证对我们的国内业务进行自杀式爬虫,这么做的结果必然会在拿到一点数据以后被我们国内防御策略封号,但是对于攻击者来说他并不怜惜这些账号,因为他可以通过我们取之不竭。而事实上,我们的国内业务也确实发现遭受了爬虫的攻击,而负责国内业务的大佬们从这些爬虫账号看出是在我们这里注册的账号,从而找到了我的主管。 一方面这个漏洞是因为我的疏忽而起,另一方面攻击者的隔山打牛确实很秀,让我有那么一瞬间感觉自己被晃得眼睛疼。我快速更改了接入用户中台的方式,从而断开了国内外的用户体系,然后推送上线。知道了攻击者的意图后,我们开始联系国内主站一起进行对之前攻击者的僵尸账号进行封禁,从而让之前攻击者在我们这边注册的账号在主站那边也提前封禁掉,不让他用爬虫再继续爬我们的数据。在解决了第一个问题后,赶紧上线一个稳定的防御手段则是至关重要。这个时候主管找到了国外一个专门用来判断一个请求是真实用户还是机器行为的免费SAAS服务,简单的说就是你只要将请求中的用户数据比如邮箱啊、用户名啊这些内容以及请求的IP这些数据发送给他,他就会返回给你一个分数,分数越高则越接近人类,分数越低则越接近机器。于是我立马就调试了一波在注册流程中接入了这个SAAS服务,然后在内网中进行了测试,同时我也将这个版本推送到生产环境,去观察当这个SAAS服务面对生产者的恶意请求时所给出的分数。 令人沮丧的是,这个SAAS服务的表现并不令人满意,对于一些真正的人为操作请求,他给出了一个低分,而对于线上生产环境发生的真实恶意注册请求,他又给出了高分。很明显这个SAAS服务并不能帮助我们。雪上加霜的是,我们发现后台的数据系统上,注册数又开始出现诡异的增长了。很明显,黑客又一次发现了我们的伎俩,并更改了脚本。 “他这次是来真的了,”主管说到:“你看这些请求,用的全是真实手机号和163的邮箱,每个请求的IP都不一样了。” “也就是说他这是为了伪装成真实的用户,把自己的肉鸡邮箱和IP池给拿出来了?” “我想是的。” 情况对我们说一下子严峻了起来。12345678{ "email": "telephone@163.com", "username": "random_username", "password" : "random", "locale": "zh_CN", .....}with a RANDOM IP之前我们之所以能快速的在服务端给恶意请求下蜜罐,是因为他的请求中有一个或多个非常明显的特征将他与真实的用户请求区别开来。但是当他这次对于每个恶意请求,都进行精心的构造,伪装成一个真实的用户请求时,我们该在如何代码层面去彻底区分他与真实用户的区别呢?就在我们组在为这一点愁眉苦脸时,坐我旁边的前端开发凑了过来,看着这个恶意注册请求内容说,“这个locale属性有问题呀。” “啥问题?”,我问他,毕竟他的这个邮箱用的是网易的163邮箱,所以locale属性是zh_CN也很正常。”他这个locale属性太规范了,我的代码逻辑里面locale当时随手敲的,所以并没有遵守locale规范。” 听到这个,当时我们整个组都笑尿了,没想到黑客他把自己每个请求都精心伪装成真实用户,甚至连locale的值都遵守协议规范,结果我们自己的前端在开发的时候并没有care这个规范,导致了这个点致命的、难以察觉的特征。我迅速针对这个特征做了新版蜜罐推送到生产环境。再一次,我们为自己争取到了时间。而主管经过调研,发现Google的Recaptcha服务非常适合我们的场景。然而和我对接的前端开发明天才在公司,所以我们只能在明天才能将接入了recaptcha的版本推送到线上。不得不说,这个特征选取的真的非常刁钻。自从上线后,攻击者那边就一直处于未察觉的状态。星期三的晚上我们平安的度过去了。周四中午,我看着生产环境的日志,发现攻击者的注册脚本停止了。“他在思考”,我想道,这个攻击者一定在思考为什么自己伪装的如此像的请求会被进入蜜罐处理,而他肯定也会在我们网站上正常注册,然后他就会发现正常注册的用户就能正常使用,而他的注册脚本则会进入蜜罐。而在这里我犯了一个错误,这是一场我和攻击者的博弈,但是这场博弈并未结束,攻击者正在整理他的牌思考如何破局,而我此时则对我下的这个蜜罐过于自信,而并没有紧急和前端开发对接调试recaptcha的上线。攻击者长时间的沉默让我们之前紧张的氛围变得稍微了轻松了起来,而我也从下午开始与前端开发不紧不慢的开发、调试、对接。非常不巧的是,今天的CICD机器出现了一点问题,导致每次应用打包时拉取maven倚赖变得异常缓慢,原本2-3分钟就能结束的CICD环节,则变成了20-30分钟。晚上7点的时候,就在我还在调试对接repcptcha的请求时,用户注册数又开始了诡异的增长。“坏了,他又发现了”,我心里一惊,那个黑客最终还是发现了locale的秘密,再一次的对我们展开了攻击。而此时我们这边rechpcha的对接工作并没有做完,在我和google服务器的通信中,总是会出现一个通信失败导致验证失败,外加每次CICD上线新版本服务又在今天出现异常的网络拉取问题,使得耗时非常长,让我变得非常急躁。 我在代码里增加了大量的log信息来帮助我查看究竟是哪里出了问题,但怎么都发现不了问题。我知道目前的状态非常糟糕,而且生产环境正在遭受攻击,而越是紧急的情况下我越是需要冷静。于是深吸一口气,从10倒数到1,平静了一下。这么一做确实很有效,我很快发现自己在于google通信时在构造url阶段时居然不小心错失了一个符号,从而导致通信失败。在我迅速解决这个问题后,立即将新版本推送到生产环境。晚上9点,新版本服务recptcha功能上线,而攻击者的请求也因为recptcha的验证被抵御住。至此,前文提到的两个问题终于彻底解决,我也如释重负的下班了。周五中午,我们通过后台的数据系统将攻击者的恶意注册的僵尸账号整理起来,联合国内业务一起封禁了这些用户,让恶意攻击者之前的那些账户彻底报废。历经72小时,我们与这个攻击者的攻防拉锯也终于告一段落了。总结后来,我们在组里聊起这段往事时,感叹道这个黑客真的非常专业。首先他的攻击手段非常巧妙,他真正的攻击对象是我们国内业务的数据,但是国内业务严防死守,使得攻击的代价非常高。在国际业务对接用户中心以后,他敏锐的发现了国际业务与国内业务在账号体系上打通了,从而通过攻击国际业务的方式来攻击国内业务,实在是一招非常高明的隔山打牛。另一方面,他在伪造真实用户请求时,所用的手机号也都是真实用户的手机号,所以我相当怀疑他当时用的邮箱则是真实存在的邮箱,即他拥有了极大数量的邮箱池与IP池来躲过各种防御和限制。另一方面,在我用蜜罐与他的攻防拉锯中,他也一直步步紧逼,不断的见招拆招,逼迫我赶紧想出下一个对策。对于这次攻击,我事后总结时,对自己的检讨在于对情绪的控制。在上文中提到,我的焦躁与轻敌让我没有在第一时刻给出正确、冷静的对策,从而减少对我们的损失。不过后来我和主管One on One聊到这件事的时候,他也安慰我说这种事情碰到过一次后才能成长,以后就能成熟应对了。后来为了应对以后更多的爬虫、攻击者时,我做了不少平台层基于K8S和ISTIO的监控、报警、防御、主动逐出的机制。这块以后有空整理吧。


通过自定义Istio Mixer Adapter在JWT场景下实现用户封禁

介绍互联网服务离不开用户认证。JSON Web Token(后简称JWT)是一个轻巧,分布式的用户授权鉴权规范。和过去的session数据持久化的方案相比,JWT有着分布式鉴权的特点,避免了session用户认证时单点失败引起所有服务都无法正常使用的窘境,从而在微服务架构设计下越来越受欢迎。然而JWT单点授权,分布鉴权的特点也给我们带来了一个问题,即服务端无法主动回收或者BAN出相应的Token,使得即使某个服务主动封禁了一个用户时,这个用户同样可以使用之前的JWT来从其他服务获取资源。本文我们将阐述利用Istio Mixer Adapter的能力,来将所有请求在服务网格的入口边缘层进行JWT检查的例子,从而实现用户封禁与主动逐出JWT等功能。背景在我之前的投稿中,描绘了一个非常简单的基于K8S平台的业务场景,在这里我们将会基于这个场景来进行讨论。对于一个简单的微服务场景,我们有着三个服务在Istio服务网格中管理。同时集群外的请求将会通过nginx-ingress转发给istio-ingressgateway以后,通过Istio VirtualService的HTTPRoute的能力转发给对应的服务,这里不再赘述。从下图的架构模式中,我们可以看到所有的请求在进入网格时,都会通过istio-ingressgateway这个边缘节点,从而涌现出了一个非常显而易见的想法,即如果我们在所有的请求进入服务网格边缘时,进行特定的检查与策略,那么我们就能将某些不符合某种规则的请求拒绝的网格之外,比如那些携带被主动封禁JWT的HTTP请求。了解Istio Mixer为了达到我们上述的目的,我们首先需要了解一下Istio Mixer这个网格控制层的组件。Istio Mixer 提供了一个适配器模型,它允许我们通过为Mixer创建用于外部基础设施后端接口的处理器来开发适配器。Mixer还提供了一组模版,每个模板都为适配器提供了不同的元数据集。在我们的场景下,我们将使用Auhtorization模板来获取我们每个请求中的元数据,然后通过Mixer check的模式来将在HTTP请求通过istio-ingressgateway进入服务网格前,通过Mixer Adapter来进行检查。在Istio Mixer的描述中,我们可以发现每个请求在到达数据层时,都会向Mixer做一次check操作,而当请求结束后则会向Mixer做一次report操作。在我们的场景中,我们将会在请求到达istio-ingressgateway时检查这个请求中的JWT鉴权,通过JWT的Payload中的信息来决定是否要将请求放行进入网格内部。得益于Mixer强大的扩展能力,我们将通过经典的Handler-Instances-Rule适配模型来一步步展开,同时也意味着我们将要编写一个自定义的Istio Mixer Adapter。Mixer适配模型那么怎么通俗易懂的理解Handler-Instances-Rule这三者的关系呢?在我的理解下,当每个请求在服务网格的数据层中游走时,都会在开始与结束时带上各种元信息向Mixer组件通信。而Mixer组件则会根据Rule来将特定的请求中的特定的数据交给特定的处理器去检查或者是记录。那么对于特定的请求,则是通过Rule去决定;对于特定的数据,则是通过Instances去决定;对于特定的处理器,则是通过Handler去决定。最终Rule还把自己与Instances和Handler绑定在一起,从而让Mixer理解了将哪些请求用哪些数据做哪些处理。在这里我们可以通过Istio Policies Task中的黑白名单机制来理解一下这个模型。在这里appversion.listentry作为instances,通过将list entry作为模版,获取了每个请求中的source.labels[“version”]的值,即特定的数据。whitelist.listchecker作为handler,则是告诉了背后的处理器作为白名单模式只通过数据是v1与v2的请求,即特定的处理器。最后checkversion.rule作为rule,将appversion.listentry和whitelist.listchecker两者绑定在一起,并通过match字段指明哪些请求会经过这些处理流程,即特定的请求。123456789101112131415161718192021222324252627282930## instancesapiVersion: config.istio.io/v1alpha2kind: listentrymetadata: name: appversionspec: value: source.labels["version"]---## handlerapiVersion: config.istio.io/v1alpha2kind: listcheckermetadata: name: whitelistspec: # providerUrl: ordinarily black and white lists are maintained # externally and fetched asynchronously using the providerUrl. overrides: ["v1", "v2"] # overrides provide a static list blacklist: false---## ruleapiVersion: config.istio.io/v1alpha2kind: rulemetadata: name: checkversionspec: match: destination.labels["app"] == "ratings" actions: - handler: whitelist.listchecker instances: - appversion.listentryJWT Check 的架构设计当我们理解了以上的Mixer扩展模型以后,那么对于我们在文章开头中的JWT封禁需求的Handler-Instances-Rule的模型就非常显而易见了。在我们的场景下,我们需要将所有带有JWT并且从istio-ingressgateway准备进入网格边缘的请求作为我们特定的请求,然后从每个请求中,我们都要获取request.Header[“Authorization”]这个值来作为我们特定的数据,最后我们通过特定的处理器来解析这个数据,并在处理器中通过自定义的策略来决定这个请求是否通过。当我们搞清楚了这么一个模型以后,那么之后的问题就一下子迎刃而解了。在我们的设计中,我们将要自定义一个JWTAdapter服务来作为特定的处理器,JWTAdapter将会通过HTTP通信把数据转交给Adapter-Service来让Adapter-Service来判断这个请求是否合法,而Adapter-Service的凭证则是通过与业务服务的通信所决定。在我们的场景中,假设每个请求所携带的JWT的Payload中有一个email属性来作为用户的唯一标识,当业务领域中的账户服务决定封禁某个用户时,他将会通知Adapter-Service,后者将会把这个信息存于某个数据持久服务中,比如Redis服务。当JWT-Adapter服务向Adapter-Service服务询问这个请求是否合法时,Adapter-Service将会通过Payload中Email属性在Redis中查询,如果查询到对应的数据,则代表这个用户被封禁,即这个请求不予通过,反之亦然。如何自定义编写一个Adapter?说实话,自定义编写Adapter是一个上手门槛较为陡峭的一件事情。我在这里因为篇幅原因不能完全一步步细说自定义Adapter的步骤。在这里我推荐对自定义编写Adapter有兴趣的人可以根据官网的自定义Mixer Adapter开发指南和自定义Mixer Adapter详细步骤来进行学习和尝试。在这里我给出在我的JWT-Adapter中的关键函数来进行描述。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354func (s *JwtAdapter) HandleAuthorization(ctx context.Context, r *authorization.HandleAuthorizationRequest) (*v1beta1.CheckResult, error) { log.Infof("received request %v\n", *r) props := decodeValueMap(r.Instance.Subject.Properties)var Authorization interface{}if len(props["custom_token_auth"].(string)) 0 {Authorization = props["custom_token_auth"]} else { // 没有获取到JWT,直接将请求放行return &v1beta1.CheckResult{Status: status.OK,}, nil}cookie := props["custom_request_cookie"]host := props["custom_request_host"]if host == "www.example.com" {url := userService + "/check"request, err := http.NewRequest("GET", url, nil)if err != nil { //出现异常时,直接将请求放行return &v1beta1.CheckResult{Status: status.OK,}, nil}request.Header.Add("Content-Type", "application/json; charset=utf-8")request.Header.Add("cookie", cookie.(string)) request.Header.Add("Authorization", Authorization.(string)) // 发送请求给Adapter-Serviceresponse, _ := client.Do(request)if response != nil && response.StatusCode == http.StatusOK {body, err := ioutil.ReadAll(response.Body)if err != nil { //如果有异常 log.Infof(err) //记录异常即刻} else { log.Infof("success to get response from adapter-service")var value map[string]interface{}json.Unmarshal(body, &value)if value["pass"] == false {//当用户确实返回处于封禁状态中时,才返回封禁结果return &v1beta1.CheckResult{Status: status.WithPermissionDenied("Banned"),}, nil}}}}log.Infof("jwtadapter don't have enough reason to reject this request")return &v1beta1.CheckResult{Status: status.OK,}, nil}通过以上描述可以发现的是,在我们的场景下,我们当且仅当从Adapter-Service中确实得到了不允许通过的结果才将这个请求进行拒绝处理,而对其他情况一律进行了放行处理,即使发生了某些错误与异常。由于我们的错误处理会直接影响到这些请求能否在网格中通行,所在做Istio Mixer Check时需要时刻记住的到底是放行特定的请求,还是拒绝特定的请求,在这一点处理上需要十分谨慎与小心。Handler-Instances-Rule当我们将自己的Adapter上线以后,我们只要通过声明我们得的Mixer扩展模型让Mixer识别这个Adapter并且正确处理我们想要的请求即可。这里我们再回顾一下我们之前所提到的特定的请求,特定的数据,特定的处理器。对于特定的请求,我们需要将网格边缘的请求筛选出来,所以我们可以通过host是www.example.com并且携带了JWT作为条件将请求筛选出来。对于特定的数据,我们选用authorization作为模版,取出header中的JWT数据,最后通过特定的处理器,将这个check请求交给jwt-adapter。至此,我们通过Istio Mixer Aadapter来进行JWT封禁的需求场景算是基本完成了。12345678910111213141516171819202122232425262728293031323334353637# handler adapterapiVersion: "config.istio.io/v1alpha2"kind: handlermetadata: name: h1 namespace: istio-systemspec: adapter: jwtadapter connection: address: "[::]:44225"---## instancesapiVersion: "config.istio.io/v1alpha2"kind: instancemetadata: name: icheck namespace: istio-systemspec: template: authorization params: subject: properties: custom_token_auth: request.headers["Authorization"]---# rule to dispatch to handler h1apiVersion: "config.istio.io/v1alpha2"kind: rulemetadata: name: r1 namespace: istio-systemspec: match: ( match(request.headers["Authorization"],"Bearer*") == true ) && ( match(request.host,"*.com") == true ) actions: - handler: h1.istio-system instances: - icheck---扩展阅读网格边缘层验证JWT的可行性?既然在网格边缘层能对JWT进行检查,那么能否可以做成在网格边缘层同时也进行JWT的验证?答: 在我最初做Mixer Check时确实想到过这件事情,并且无独有偶,在PlanGrid在Istio中的用户鉴权实践这篇文章中,PlanGrid通过EnvoyFilter实现了在网格边缘层进行JWT以及其他鉴权协议的鉴权。但对此我的看法是,对于JWT鉴权的场景,我并不推荐这么做。因为微服务场景中,我们使用JWT的初衷就是为了分布式鉴权来分散某个服务的单点故障所带来的鉴权层的风险。当我们将用户鉴权再一次集中在网格边缘时,我们等于再一次将风险集中在了网格边缘这个单点。一旦istio-ingressgateway挂了,那么背后所有暴露的API服务将毫无防备,所以鉴权必须放在每个微服务内。另一方面,在我的《深入浅出istio》读后感中提到,对于生产环境使用Istio,必须拥有一套备用的不使用Istio的环境方案,这意味着当Istio出现故障时,可以立即通过切换不使用Istio的备用环境来继续提供服务。这同时意味着Istio所提供的能力与服务不应该与业务服务所强绑定在一起,这也是为什么我在上文中将Jwt-Adapter与后面的Adapter-Service成为插件服务的原因。JWT封禁用户这个能力对我们就像一个插件一样,即装即用。即使当我们切换为备用环境时无法使用Istio,暂时失去用户封禁这个能力在我们的产品层面也完全可以接受,但对于用户鉴权则不可能。所以这意味着当我们使用Istio的能力时,一定要时刻想清楚当我们失去Istio时我们该如何应对。关于作者从去年毕业以后一直对服务网格与CloudNative领域充满兴趣,在工作中使用Istio在生产环境中也将近有了半年多的时间,写作分享则是平时的业余爱好之一。如果你对服务网格或者是CloudNative领域有兴趣,或者是对我的技术文章写作有想法与建议的话,欢迎联系我交流。Github 博客RSS订阅


《深入浅出Istio》读后感

前言《深入浅出Istio》这本书这两天开始卖了,我也第一时间入手了以后到现在已经基本上全部翻完了。在这里记录一下看完这本书的读后感。总体来说,这本书是一本既适合Istio本身有一定了解程度的使用者,也适合对ServiceMesh初学者的去学习Istio的书籍。这本书比较全面的介绍并总结了Istio的各个组件及其使用方法,并给出了许多具体的场景。作为一名接触ServiceMesh领域和Istio快小半年的我来说,对于书中一些比较基础的章节和内容快速翻了一翻,同时也对部分对我帮助非常大的章节做了一些总结和心得。在这里用一篇读后感记录我读完以后的感受。关于书籍作者作者在ServiceMesher社区里面是一个非常活跃的人,对istio.io的中文化工作做了非常大的贡献。作为一个大部分时间在社区内处于一个围观群众,在这里对作者对国内ServiceMesh领域做出的贡献表示由衷的感谢。全书结构整本书分为十章,整本书我重点看了第1,2,3,5,8,10。第七章重点看了后半部分。服务网格历史服务网格的特性介绍istio安装istio详解Helm部署istioIstio插件服务Http流量管理Mixer应用Istio安全生产环境使用Istio的建议正文作者从微服务的诞生和发展谈起,简略谈了一下在微服务架构为我们解决了许多老问题时,它所给我们带来的一些新的问题。为了解决这些问题,Kubernetes为代表的容器云系统出现提供了部署、调度、伸缩等功能,,而ServiceMesh则应运而生去解决如何管理、控制、保障微服务之间的通信。随后,作者挑了几个重要的产品与事件来梳理了ServiceMesh的发展历史。从SpringCloud确定微服务治理的标准特性,到Linkerd发布后受人关注,再到Istio横空出世。ServiceMesh领域中目前发展的最好的毫无疑问是Istio。这不仅是因为Istio吸取了前面产品的经验,同样也背靠了Google,IBM和Lyft这三个公司共同组成的开发团队。由于我接触ServiceMesh领域时,Istio都已经发布到0.7版本了,所以这块的内容让我了解了整个ServiceMesh领域的发展历史。在第二章,作者着重聊了一下Istio官网首页所印着的四个特性:连接、安全、策略、观察,所分别代表的意义和场景。然后在第三章介绍了Istio整体架构和每个组件所承载的意义和功能以及一些Istio自定义的CRD。关于Istio的架构设计和功能组件Istio官网本身就有非常详细的介绍了,直接看官网介绍就行,对于部分CRD的介绍挺好的,可以帮助理解每个Istio组件对应了哪些Istio配置文件。第四章和第五章这块都是关于安装Istio的,这一块我比较熟悉就直接跳过了。同时对于个人开发者学习Istio而言,需要的是一个更快的本地搭建Istio环境的教程,这里推荐一个更简单的快速本地搭建istio教程第五章着重介绍了Istio安装文件种的Helm结构,以及每个参数所代表的意义。这一块我觉得对我的帮助非常大,由于之前我在生产环境安装istio都是通过我本地开发机helm template一个完整的安装文件,然后一并apply到生产环境上。这给我至少带来了两个大问题:我的本地开发机拥有一个具有读写权限的生产环境k8s账号修改各个部件相关参数变得十分麻烦目前在这上周我已经全部回收了我们开发组内所有人的生产环境权限,统一通过K8S DashBoard进行操作。当然,这也意味一旦需要更新istio就不能再走本地apply安装文件的方式。一个更加科学的方法则是通过CICD系统,用helm install/upgrade来管理生产环境的istio配置。所以,掌握istio的helm文件结构就显得非常重要,这块第五章给我的帮助很大。第六章作者介绍了Istio的一些官方推荐的插件服务如Prometheus,Grafana,Jaeger这些。我就直接跳了。第七第八章作者介绍了通过Istio进行网格的Http管理和Mixer应用。这两张是本书的两个大头,当然同样也是Istio应用的两个大头。对于第七章,作者介绍了VirtualService,HttpRoute,Gateway这些相关概念,以及通过这些组件进行负载,转发,灰度这些内容,基本上我就快速看了过去。我个人在istio的实践上,也已经在生产环境上通过istio进行灰度发布,所以对于这块内容我已经比较熟悉了。相关资料: coohom在生产环境上使用istio的实践与经验第七章的后半段提到了通过Istio在生产环境进行故障演练的方案,这一块挺让我耳目一新的.一方面是没想到还能这么玩,一方面是在生产环境的故障演练同样也是我今年上半年将要去做的一个目标之一,这块对于故障注入与故障演练的场景方案对我帮助很大。第八章作者着重介绍了通过Mixer来进行一些黑白名单、限流、自定义监控指标这些操作。在Istio官网上关于Mixer的大部分内容我也已经全部看完并实践过了,所以这块内容我看的比较快。一个比较遗憾的一点是没有看到关于如何自定义Adapter相关的介绍,这一块是Mixer有着非常大潜力与价值的一块内容,但同时也有着不小的门槛。这一块我前不久一直在花时间调研并通过自定义Istio Mixer Adapter完成了一个比较常见的需求,这次放假有空将会整理一下。第九章讲了Istio安全认证这块,我暂时直接跳过了,在我目前的场景中,生产环境集群内所有服务都将长期处于互相信任的状态,所以我暂且并不关心这方面内容。第十章作者给了一些在生产环境上使用Istio的建议,有大部分内容和我在生产环境上实践所的出来的结论相同,以及Istio目前的一些发展问题。关于生产环境使用Istio的建议,这里我摘录几条我深表赞同的:永远准备一套不用Istio的备用环境,( 目前我们服务的生产环境长期保留了主备服务,主服务使用Istio,备用业务则不使用Istio,每当进行istio升级或者是部分参数调整时都会提前进行主备切换,等升级调整验证完毕后才切换回来)确定使用Istio的功能范围 (在我们的场景中,只有真正的业务服务才被服务网格管理,其余不需要网格管理的服务绝对不强上网格)时刻考虑Istio功能的性价比 (不为了用功能而用功能,Istio Citadel安全功能对于我们来说目前收益接近于零,但风险极大,所以就坚决不用)同时对于Istio这个产品发展的现状,作者给出了一定的担心,即目前Istio团队,发布的产品API稳定性太不稳定,不向后兼容,很多API全部改写。另一方面在发布质量上也出现过比较大的问题,造成了版本回退,发布延期等问题。同时Istio组件目前本身也有着一些瓶颈与问题如Mixer的复杂性与高成本学习,Pilot的性能瓶颈,SideCar的性能消耗。以上这些都有待Istio团队去解决。最后整体来看,对于Istio,我个人认为Istio目前的发展状况在ServiceMesh领域中还并没有像K8S取得事实胜利,可能Istio也有可能步Linkerd的后路,为未来的产品开路和经验,但是ServiceMesh这条路无疑是正确的,我也会继续关注ServiceMesh和istio在2019年新的表现。


吉薇艾儿

这是一个考验,来自过去的考验,人的成长,就是战胜自己不成熟的过去。下落今天表弟来我家做客,和以往不同的是,家里人的脸色都沉沉的不怎么好看。原来这次表弟来我家,主要是因为他这次考试很不理想,他已经初三了,今年就要参加中考。表弟家里人非常着急,所以带着他来我家里,希望我作为一个哥哥,曾经的一个学习不错的学生,能指导指导他,让他好好读书。把表弟接进房间以后,看着他低着头坐着一言不发,我叹了一口气。“家里人希望我好好劝他,让他乖乖读书,生活回到正轨”,我心里想:”但是我又有什么资格去劝别人的生活呢,我自己的生活都一团糟。”从去年十二月份开始的时候,我的女朋友,或者说是曾经的女朋友就一直发信息向我抱怨说和我在一起一直不开心。虽然我一直劝她,也一直向她保证以后会花更多的心思在和她的相处上。但是在两个礼拜前,在我定好电影票和江边城外的座位准备和她一起享受周末时,她的一通电话告诉我,我们分手了。一段从大学时代开始一路走来的感情就这样结束了。逃避我一直以为我是一个非常坚强的人,以往碰到了大大小小的挫折都被我克服了过去。但是那个周末,我一直躺在床上想,想为什么会变成这样,一边想一边哭,一边哭一边想。最后哭累了睡了过去迎接了周一。“至少不能让同事看出来”,我用冷水尽力把眼睛消肿,平复着情绪上班去了。我尽力的让自己不去想感情生活上的事情,将自己心思一股脑的扑到工作上。比起所在业务组一开始的同事,现在的同事来了一批新同事也走了一批老同事,虽然我是这里面年龄最小的一个,但我目前成了生产环境上最细整个技术架构的人了。所以每天我除了自己需要完成的工作内容之外,还要帮助其他同事一起对接平台组,运维组,监控组,质效组这些其他支撑部门的需求。事情一件接着一件,虽然让我感到身体和内心都十分疲劳,但也是让我不去胡思乱想的好事。“你平时做作业么?”,我问他,表弟低着头轻声的说:”不怎么做。” “那你平时上课有听过么?” “也不怎么去上课。” “那你为什么不去上课呀?”,我问他,但表弟他只是在那沉默的低着头不说话。“他不想说,一定有他的原因。”,我想。 在我眼里,表弟家在学习上一直希望他能够好好读书成为一个好学生,而且经常拿我和我们家族里一位学霸姐姐给他做例子。“就知道玩,能不能用点心思在读书上?”“这么语文怎么考的这么差,能不能考考好?” “能不能像你哥哥姐姐学学?” 这些话经常从他家里人出来,成为一个个施加在他身上的要求。这些话在我还是一名初中生的时候也同样经常听到,虽然我已经不大记得当时自己的心情了,但是我非常了解当听到别人以抱怨的语气对你像机关枪一样射出一个个要求。“每次约会都是玩到晚上8点你就回去了,为什么不能玩的更晚点?”“喜欢我难道不应该风雨无阻来找我吗?”“你就不能过的更有仪式感一点吗?”当我听到电话那头的她对我说出这些话时,就像一颗颗子弹打在我心上。我想开口说点什么,但不知道说什么好。远眺“考不好其实也没关系”,我拍了拍表弟的背,“这只不过是中考前的某个模拟考。” “对了,”我问他:“你有想过读书的意义是什么。” 本来一直低垂着头的表弟,抬起头来看着我,没有说话,露出了疑惑的表情,也许他并不理解为什么我在这个时间突然抛出这个问题。“其实这个问题在我读完高中以后也一直没有明白”,我告诉他:“虽然那个时候我成绩整体上还行,但其实一直起起落落。” “我那个时候读书的,就是一开始凭借了一点小聪明,在刚开始的起步稍微比别人快了一点。让老师和家里人都觉得我是学习好的一个优等生。但其实我的心思都在打游戏上,完成作业,考个好成绩之类的都是从小学起往上带过来的惯性,没有别的想法了。”学生时代的我只是知道读书要保持一个好的成绩,如果不做作业的话会被老师骂然后通知家长,最后吃到苦头。读初中的时候,老师向我们强调中考的重要性,读高中的时候,老师向我们强调高考的重要性。“所以那段时间我,心思放在玩上面一段时间以后,成绩自然就下落了,被老师和家长一埋怨,我就又把心思放到学习上把成绩拉了上来。一直这样周而复始”,我告诉他:“一直到我读了大学以后,才慢慢理解读书的意义。” 到了大学里以后,发现了各色各样,有厉害的特长和有趣的性格的同学们以后,我逐渐想明白了自己想成为一个怎样的人,未来去做哪些事情与行业。“然而这些事情在我初中高中的时候,我都没机会,也没精力去想。每天晚上回家有很多作业要做,而且时不时还要面对我不喜欢的化学课,虽然我自己很喜欢写作,但我的写作分一直不高。”我跟他说,“后来在大学里有了自己的时间,见识了很多很多同学以后我才逐渐想明白自己要的是什么。你有想过自己未来想成为怎么样的人吗?” 表弟歪着脑袋想了一下,然后摇了摇头告诉我,“没想过,也不知道自己想成为什么。”“这很正常,”我告诉他:“其实现在读书,就是为了将来能在大学里面经历这些事情。你可以不喜欢语文或者不喜欢考试,但是我希望你一定要想清楚自己想要什么,想成为什么。而这个问题,不用那么急去想。等到以后读大学了你的身心智力都成熟了以后再慢慢去考虑也不迟,而你现在的读书的目的,都是为了你未来的做出人生选择时所慢慢做的准备。一次模拟考没考好真的没有关系,他对你的人生所造成的影响简直微乎其微。中考没考好还有高考,高考没考好还能在大学继续努力。但是这一切的前提下是你要想明白你真正想要的是什么,并为之努力。” 表弟点了点头。“但是遇到挫折,然后克服挫折才是人生常态。”,我叹了一口气,想到了自己感情处理上的失败。以前在大学里读书的时候,至少还会经常加一些班里女同学和学妹们的联系方式,还经常一起讲讲话聊聊天。自从有了女朋友以后和其他女生的联系都断了,也没再加过别的女生。现在想来自己这一年来就没有跟除了妈妈和女朋友以外的其他女性说过一句多余的话,现在分手了以后感觉自己未来离相亲不远了。“但你必须要去克服在读书的时候碰到的这些小挫折,等你毕业工作以后才能去克服工作、生活上的更大的挫折。”,我背靠在椅子上对我表弟说:“其实我觉得在你参考中考和高考前,考差几次也蛮好的。把你自己所有的问题都暴露出来,这样等你中考高考的时候就不会犯这些错误了。” “嗯。”表弟轻轻的点了点头。新生给了表弟一些学习上快速提分的建议后,和他一起玩了一会游戏吃了顿饭就送他走了,我打开了新标日继续开始学习。去年年末的时候答应前任我会开始自学日语然后争取今年年底我们俩能一起去日本旅游。虽然两个礼拜前和她分手了,但是这个每天背单词看网课的习惯却一直保持了下来。想到这里我心里又开始隐隐难受,我打开QQ,小心翼翼的翻开前女友的状态想看看她最近过的怎么样。发现她换了一个新的情侣头像的时候,我楞了一下,沉默住,内心不像是难过,也不是嫉妒或者愤怒之类的感情,倒更像是一种平静的释然,一个像过去的生活告别的撬动点。自从毕业了以后,我依旧凭着过去在学校生活的惯性,像完成考试内容一样完成工作内容,像争取让父母满意一样争取让女朋友满意。我为工作上的困难愁眉苦脸,也为未来两个人如何继续走下去而辗转反侧。这些问题都没有写在教科书上,也没有哪门课教,只留给我自己去独立面对。有失必有得,在我失去了女朋友以后同样也失去了感情上的烦恼。我开始有时间去思考自己下一步想如何发展,也可以单身一人想去哪个城市定居就去哪个城市定居。我可以有时间继续探索我的兴趣,而且目前学日语也逐渐对我来说变得有意思了起来。拥有这段感情时创造了许多美好的回忆,所以在失去这段感情时我也会感到如此难过。但我选择接受Both Good&Bad。 这个月Mentor和我One on One的时候告诉我,鉴于去年我在工作上的热情与成绩,所以绩效非常不错,他也为我争取了这次升职高级工程师的机会,让我好好准备,对我来说这算是2019年来的第一个好消息吧。我删光了前任女友所有的联系方式,然后向她妈妈发信息表示了感谢和道别。2019年,我希望自己在工作能力上能更进一步,同时继续着自己在技术专栏和个人博客的写作之路,今年能够有机会凭借着自己的力量能去日本游玩,然后多交一些朋友吧。当然同时也希望我的表弟能尽早走出这个低谷,在未来的人生路上不再迷茫吧。


那2018年就这样了

引言等风来不如追风去,追逐的过程就是人生的意义。正文距离上一次写博客已经是10月下旬了,本来想着至少保持着每个月至少一更的频率。结果这次硬是拖到了年底才开始动笔写这篇博客,好在从来没有人对我的博客催更,于是这次就打算水一篇自己的2018年的年度总结。姑且写,也姑且看。今年十月份的时候,收到了公司的一周年纪念品,感觉挺开心的。收到的时候我自己也挺诧异的,不知不觉的就在酷家乐工作了已经一年多。回顾这一年,我觉得两个字就是幸运,四个字就是比较幸运。我是一个不大喜欢闲着的人,所以在决定好校招offer以后,我过了十月份的国庆长假就直接去实习报道上班了。所以我对于我入职的第一天的日子10月9号记得一直比较深刻。那个时候上海的研发团队算上我才总共5个人,我参与了一个几何算法图形库的基础组件库的开发和维护。虽然我以前从来没怎么接触过计算机图形学并且一些高中的计算几何知识都忘得差不多了。不过好在大二的时候参与过一年ACM,脑子没算完全荒废掉。凭借着一点小聪明对于Mentor派发下来的任务都能比较顺利的完成。那段时间有一个我比较深刻的事情是我第一次提交merge request。那个时候研发组人少嘛,所以我的merge request就所有人都review了一遍,然后就指出了很多低级错误。让我最为深刻的就是我的那次merge request里居然忘记把调试时的控制台打印语句给删掉了。当时我看到Code Comment里面被人指出代码里不要加入控制条打印语句时简直都要羞愧到爆了。现在我经常给实习生做code review的时候,虽然我也经常会提很多意见,但说实话现在的实习生写的代码比我当实习生时写的代码好多了,也没有很傻的低级错误。诶,都是后话…年初的时候,Mentor找到我希望交给我一个计算几何的课题让我单独尝试。这个课题非常小众,而且我当时也没任何计算几何的基础。不过当时凭借着一股迷之自信,我就没怎么多想的接了下来。这个问题算是平面计算几何里面一个比较经典的问题,即Nest Algrithm。当时给我的需求是提供一个服务端执行的算法包。当我搞清楚这个问题以后我就意识到这似乎是一个单纯靠我自己无法解决的问题。好在天无绝人之路,万能的Github上面居然有这一个问题场景的算法Demo,而且效果居然惊人的好。但是最可惜的一点在于这个算法的Demo居然是Javascript写的,而我需要完成的则是一个服务端可跑的算法库。我看了看这个Demo仓库里的issue,不少都提到了希望作者提供一个服务端可以跑的算法库。或者是有人自己搞了一个计划准备做一个服务端版本的算法库,可惜到最后都不了了之了。于是我的难题瞬间就降低了一大部分,即只需要读懂他的算法然后用服务端语言改造即可。于是自从十二月份开始,我就一开始针对这件事情做封闭式开发。由于作者的算法相较传统的算法使用的是临界多边形结合遗传算法去计算排列顺序的可行性,而对于这两块我又都是零基础,所以只能边啃论文边调试代码。说到这个调试代码也是一脸泪,由于这个算法Demo全程运行在浏览器端并且用javascript完成,所以我只能通过一次又一次的打印控制台里的各项参数来明白整个算法逻辑。就这样一边啃一边调一边写,差不多到二月底的时候我的第一版服务端排版算法引擎就已经出来了。这当中经历了许许多多、日日夜夜的调试以及各类问题。比如如何在遗传算法中通过多线程并行计算来加快效率,和如何处理浮点数在进行几何计算时的精度误差等等。这些都是在一步步踩坑中,然后对很多地方再推倒重来的解决方案。最终这个服务端引擎输入了当时原作者使用的测试用例Demo,最终得出了如下的结果。后来我将这些工作成果整理过后开源了出来,同时作为我的本科毕业设计,顺利毕业了。后来七月份还是八月份的时候,李青老师电话找到希望我给这篇论文写一个短篇杂志期刊,为我争取了一个优秀毕业论文。虽然没啥用吧,但是作为我大学期间为数不多的奖项,也算是一个善始善终吧。当然后面在七月份的转正述职上,当我再次提到这个项目时,我将它评价为2018年上半年我个人最满意的作品,没有之一。在完成了上面的工作中,我就从三月份开始转去了国际站业务组开始后端业务开发之旅了。这个时候我的Mentor也换人了,实习期间的Mentor由我现在的主管@橙子担当,而技术上的指导则有杭州的@磊哥跟我远程联系。磊哥的代码写的非常棒,而且在技术上教了我非常多的工具、方法和思想。让我有一种过去Java后端白学了一样。感觉自己和他写的完全是两种语言。整个三月到五月基本上都是磊哥带着我写后端的代码,我也是慢慢从CRUD开始,然后一点点写复杂的业务逻辑。最终变成了几个服务的Owner。当我完全可以独立的负责这几个后端服务的时候,磊哥的历史任务也就完成了从我们组去了监控组开发了。四月份的时候,我抽空回了一趟母校,然后在开源社区做了一次分享。比起那时候我大三的时候办开源社区分享活动,今年的开源社区热闹了许多。来了不认识的学弟学妹都过来听,非常开心。五月份的时候,我开始梳理我们整个项目组的持续集成与持续交付。由于作为一个新的前台业务组,我们当时的情况是每个服务Repo自己控制部署节奏。每个服务自己在gitlab-ci上写好了各个环境的部署脚本以后,Owner负责部署到内网环境,然后在某个特定的时间点由主管将所有服务部署到生产环境。当时我正好接触到了Istio体系中的Service Graph。那个时候我们的业务调用关系还比较简单,可以看做是一个简单的拓扑图结构。当时我看到这个图的第一想法是这个服务间调用的拓扑图不也正代表了部署的部署顺序吗?如果有一个总控制台去每个服务的部署节奏,让每个服务部署时必须要所有先制服务部署完才能部署那该多好。于是凭借这个想法,我通过Gitlab-CI的API,单独完成了我们组的级联部署系统的第一版雏形。整个六月份,我都在忙于挤毕业论文和处理毕业的各种手续,终于在7月初的时候正式毕业了。同时,我们组也开始紧锣密鼓的筹备校招和实习的事项了。当时来上大做校招的宣讲会之前,由于之前在大摩实习的时候碰到了不少大一大二甚至高中生的实习生。让我意识到其实实习这件事没必要从大三才开始找,所以我特地也去了大一和大二的年级群里做了实习的宣传。最终竟然成功招到了一名大二的学妹来我们这里做前端实习生。另外一方面,因为我一直坚持每个学期都来开源社区做一次分享,我觉得促进我们组来开源社区做技术宣讲的同时吸引更多的学弟学妹们来我们这里实习是一件两全其美的事情。在七月份九月份的时候我分别带了我们后端和前端的负责人都带到开源社区做了一次分享。在工作上,从六月份开始我开始接触我们的组基础设施平台,Kubernetes与Istio。由于我们的组的所有服务都运行在Kubernetes,而我一开始对这个又毫无概念。所以光是了解Kubernetes我就看了不少的资料和文档,边看官网Demo边尝试性的在内网进行一些简单的操作,才一点一点看明白了K8S的整体架构和如何使用。在我们的文档系统里面,有两个目录专门用来记录我们在Kubernetes与istio上的最佳实践和踩坑经历。现在看下来这些文档我自己一个人这半年来总共块贡献了有三四十篇文档了。从刚开始被K8S一上来大量的概念而感到力不从心,到后面内网或者生产环境出现BUG时自己都跟在大佬后面看他们是怎么解决问题的。到后来出现问题时自己也能慢慢的独立解决。整个六七月份我的工作重心一直铺在国际站的基础设施建设上。慢慢的对K8S和Istio的使用越来越得心应手,我也顺其自然的被大家放心去管理建设整个项目的基础设施与负责生产环境的稳定性。 在那段时间我个人总结了一些管理和运维经验在博客上。从七月份开始,我成为一个正式员工时,也成为了一个带实习生的mentor。对于带实习生这件事情,我是比较乐意的。因为我个人是一个乐于分享的人,有一个实习生能天天主动或被动的接受我的叨叨我还挺开心的。和实习生合作了三个月不到,从七月份成为mentor到九月份送走实习生回学校。当中的过程总体来说比较开心,但是作为Mentor,我事后回想过后给自己打分大概就是一个及格分。从我开始带实习生起,我给我们俩定位就在于我和实习生一起去完成我工作中的指标。所以为了保证工作能够顺利完成,我会将这些工作拆分成一些比较简单机械的任务交给实习生,然后将一些比较有难度或者比较有风险的事情交给我自己做。另外一方面,为了让工作能更快完成,某些比较困难的事情让实习生做可能需要花一天,我自己做的话可能就十几分钟就搞定了。于是我最终也是选择让我自己做。最终可能因为干的事情大多数都是没有挑战性,所以实习生在干完两个月暑假结束后就选择离开了。前段时间我和我主管One on One的时候,我谈论到了这件事情反思到对自己当时的决策比较后悔。我的主管告诉我其实交给实习生干一些有难度的活或者有些事我做的比较快,实习生做比较慢,但让他去做一样没有问题。只要我们能将任务的风险控制在一个可控的范围内,完全可以不追求效率和成本让团队内的其他人去做一些有难度和深度的事情。这样对于整个团队的成长是最有利的。九月份的时候,我邀请了我们前端的负责人@影魔来到了上大开源社区来做技术分享。影魔大佬的技术分享说实话我个人认为是我2018年听到的最好的一场分享。在他的分享当中,他特意谈到了他从阿里干了9年以后出来加入我们团队有很大一部分原因就是想要去体验和整个团队共存亡的感觉。这个话给我的感触很深,让我意识到即使是为了我自己,也要开始考虑从如何更快的提升自己转变为如何更快的提升整个团队的战斗力。我觉得这句话是我目前为止从九月份以后加班越来越厉害的罪魁祸首。我会开始主动思考我们的后端服务架构是否合理,我们的部署节奏是否能够灵活支撑频繁的服务更新迭代。当我们业务的服务越来越多,临时用的小工具越来越碎,同时每个Sprint的Story Point越来越多,以及加入了很多新的同事以后。我开始意识到我们业务组从以前的通过某个临时的工具去解决问题的情况需要转变为一个完整的内部管理体系去统一协调。10月份和11月份的时候分别参与了今年的QCon和KubeCon。QCon这个会议以前还在大学里的时候就已经听说过了,但是一直是以学生的视角走马观花的一样看了几场分享的资料。但是看完听完就完了,并没有留下啥感触。今年参加的这两场分享,第一次以一个已经工作的人视角去参加,这两场会议我都是直接直奔了Kubernetes与Service Mesh相关的主题会议。QCon参加了第一天,我之前也写了自己在QCon上的参会感受。如果说在QCon上的ServiceMesh产品主角是蚂蚁的SOFA,那么毫无疑问在KubeCon上的ServiceMesh的当家主角则是G家的Istio。可以看得出来,今年整个Kubernetes话题上,大家都对新出来的ServiceMesh概念非常关注,尤其是Google主打的Istio。但同样也有阿里和华为在KubeCon上推荐自家的ServiceMesh产品,这些更多针对的可能是国内用户。值得一提的是,这一年的KubeCon上也有不少公司分享了自己在Istio使用上的探索和经验,以惠普为首。这让我感受到了单兵作战和小组作战的效率差距。不过让我感到遗憾的是,大部分公司仅仅是用了Istio,但并没有说按照Istio提供的工作来真正在生产环境上实现一些互联网的常见需求,这一点我有了一种我上我也行的感(错)觉,不过未来的目标之一确实是希望自己也能作为主讲人参加这种会议吧。这个十月份同样还参与了ServiceMesher组织,并成功给ServiceMesh投稿Coohom在生产环境中使用Istio的经验和实践。有意思的是,通过这篇文章,我有幸被一些HR和出版社编辑认识,前者经常说要我找过去聊聊,后者则是鼓励我把这些经验时间总结成一本书来出版。 关于出书这个事情我一直在考虑,一方面非常开心有出版社找我写这方面内容,而且serviceMesh这块作为一个比较初期且前景比较明朗的领域的确是越早越好。另外一方面,工作上比较忙,而且说实话我对自己在这一块的深耕也并不自信,觉得自己学到的也只不过是些皮毛,只是起步应用的比较早而已。但是纠结归纠结,明年应该会开始构思在这方面的内容与方向吧,希望自己能做到。最后是一些工作上的感悟,以前总是觉得一个牛逼的程序员/工程师应该要有自己的拿手绝活,这样才能提高自己的核心竞争力。来到国际站项目组将近10个月以来,从项目刚开始定下到现在已经在北美打出SAAS的市场,经历了一轮又一轮商业模式与目标客户的探索,数据运营与服务治理的搭建,以及敏捷流程与团队成长的建设。我感受到的是,在互联网模式,尤其是作为最前台的业务组,一定要不能将自己局限在某个领域里面,而是需要了解业务和团队从发芽到开花方方面面的思维和工具,不停的消除瓶颈与优化效率。以前的自己有一种程序员的定性思维,总是觉得各种工具或者服务要自己写和自己实现那才是最牛逼的,但其实这些工具明明在开源软件里就有成熟的工具和解决方案却不愿意去用。技术是要为产品服务的,而产品的最终目的就是利益,所以为了产品成功,工程师就应该首先要抛开技本位的思想,从ROI的角度考虑每一个决策,多用不同的思维和视角去看待产品和业务。(可以理解为互联网版的给钱什么都做)以前在知乎上看到一个关于职场成长的金句,原文我记不大清了,原意是在职场发展中,在技术上成为一个高手固然厉害,但是更厉害的则是可以带出一个都是高手的团队。现在一想,在职场上跟对leader很重要,好的leader会为了团队成长而培养每一个人,最终形成一个有战斗力的团队,那么在业务上的成功也是迟早的。很开心这一年能在国际站团队学到这些。下一次上班又是新的一年,新的工作内容。


Qcon上海Day1有感

前言2018年的QCon上海有幸去现场听了一天,在这里记录我听的几个主题的感受和一些想法.先直接讲我听了哪些内容:面向大规模研发的自动化持续交付The past, present ,and future of GoDubbo MeshSOFA Mesh携程容器云弹性构建之路阿里统一调度系统Sigma不多哔哔,我先直接讲大家最关心的阿里下午两场关于Service Mesh的分享.正文关于Service Mesh首先值得一提的是,阿里下午这两场关于Service Mesh的分享可以说是听众爆满,人非常非常多。基本上整个会场都坐满了,然后后面站了一大堆人都在那听。究其原因,我觉得可能是Service Mesh这个概念对于大多数听众来说ServiceMesh是一个非常新的概念。阿里的这两场Mesh的分享,基本上都是在了解一点Mesh的概念上进行的,所以如果有对ServceMesh / K8S 不大理解的话,可能不一定能理解。两场Mesh的分享Dubbo Mesh这场分享更多的专注于为什么要上ServiceMesh这一个问题。首先李云开篇立论就讲了,你要上Mesh,你首先得是一个微服务架构。你如果整个业务是单体超大应用的话,也没必要继续往下听了。所以以下的所有谈论内容都基于一个最重要的前提,那就是你整个业务的架构是微服务架构。那么既然是微服务架构,那么对于应用的水平扩展以及在技术团队的管理上就会方便许多,比如不同服务的技术团队可以分居两地,分开管理等好处。但是坏处是在微服务下,集群管理与容器管理的成本就会急剧上升,你不得不花很多力气去做监控,追踪,收集,流量管理等一系列工作。为了解决这个问题,微服务架构面临的挑战就是: (这里直接用Dubbo Mesh上的总结)微服务框架自身演进困难,业务与框架代码强耦合,强绑定。 ( 说白了就是你一个应用服务,除了处理你负责业务逻辑以外,你还要接很多业务无关的配置项才能融入集群/容器管理体系。 比如你必须告诉他服务注册中心,给他接Tracing组件之类的。多语言支持弱,异构服务框架难以共存演进, 在问题1的基础上,我们可以给应用开发特定的基础设施相关SDK给业务服务用。但是很明显对于阿里这种超大集群与超多业务与服务的情况下,他的技术栈语言肯定是非常多样化的。为了将所有服务都接入管理体系中而特地开发一个SDK明显是很不合理的,并且非常打击工程师积极性的。服务治理整体缺乏全局观,易用性和可控性。 ( 这里我大概能理解他的意思,设想一下对于阿里这种超多、超大集群的角度来说,如果有一个统一的总控平台肯定是巨爽的。从基础设平台建设的角度,如果每个事业群,业务线都在集群/容器管理上各玩各的,自成一套规则,估计要心累到爆了。也就是说,在微服务架构下,如果我们碰到了上述的问题,就适合去接入Service Mesh。当然,在Mesh这个领域里,活跃的Istio正式发布1.0-stable也只不过是今年的事情,但是微服务化却已经早就存在好多年了。所以在接触ServiceMesh时,我们首先理解一件事情,微服务给我们带来的好处是人力成本上管理更加分而治之,但是在集群/容器管理难度上就上了好几个数量级。但是集群/容器的管理在Mesh出来之前也是有解决方案的,所以Mesh不光光为了去解决微服务的带来的问题,而是大家发现为了解决微服务所带来的管理问题,现有的解决方法又引发了一些新的问题,即上述所说前两个问题。为了解决上述所说的问题并且还要保留原来的集群/容器管理能力,也就诞生了ServiceMesh。所以在Dubbo Mesh这场分享里,分享人着重强调了Mesh的价值在于他所提供的解决方案根本不care你服务的语言,让应用本身只专注于业务而没有任何多余的代码。也就是说你一个Web应用,就只要写传统的Controller,Service,DataAccess就行了。其他逻辑你通通不要管也不要写在业务代码里,这就是ServiceMesh的最终形态。所有业务应用,不管你是什么语言写的,Java也好Go也好C++也好。最终你的服务内只有业务代码,(几乎)没有管理相关的配置与代码。当然这块听起来很美好,但是具体咋做呢?ServiceMesh领域里目前唯一的成熟的产品只有Istio,但是Istio是紧密绑定G家的K8S平台的,他实现的概念也是通过K8S POD这一特性通过Linux Network namespace去实现的,这一块以后我会整理分享一下Docker与Namespace相关的内容,这里就不展开细讲。但是作为一个共识,目前整个Mesh领域内对于ServiceMesh的具体实现,都普遍认为Sidecar模式和数据层与控制层这两个设计概念是正确的,而落地到具体的应用中去,真正的区别无非就是Sidecar的对象是谁即可。所以对于Istio这一产品来说,他的sidecar所瞄准的对象则是单个业务服务容器,并且由于Istio天生绑定K8S的基础下,Istio所给出的解决方案就是利用K8S的POD特性,在每个POD内注入Istio proxy作为sidecar,去劫持所有进入和出去这个POD的网络流量(tcp层与http层)但是对阿里的现状,那么多的服务肯定啥情况都有,我们将物理机/虚拟机和单机应用/容器这两组来做个排列组合就有4种情况。但是想要用Istio就必须得是K8S平台,另外一个方面,由于sidecar劫持了所有流量并且istio本身是G家主导开发的,所以除了支持Http协议以外,istio很自然的也支持了grpc。换言之就是Istio本身目前不支持定制添加协议,所以基于以上两个原因,阿里想要用上ServiceMesh,一来首先需要支持多个平台环境,二来需要支持自己特有的协议,所以Dubbo Mesh应运而生。Dubbo Mesh的发展思路基本上就是照搬Istio,在控制层与Data层做支持Dubbo协议的扩充。这个是Dubbo Mesh里明确指出的,不过分享里没有明确提到DubboMesh目前在支持的平台上目前支持到哪一个级别,这个同样也很关键。所以整场Dubbo Mesh的分享上,分享人着重描述了ServiceMesh价值,形态,和所要解决的问题。然后带了一笔Dubbo Mesh。 值得一提的是,分享人还特意指出,他们认为由于sidecar会劫持所有的网络流量,所以对于性能的要求非常高,所有会进行GC的语言都是不适合作为sidecar的,所以他们决定基于Enovy去做他们Dubbo Mesh的sidecar。虽然给我听上去感觉就是目前只给envoy增加了一个Dubbo协议解析。SOFA MeshSOFA Mesh是作为蚂蚁金服内部孵化的一个ServiceMesh产品,这个分享就比较实在。分享的内容基本上就着重于目前SOFA Mesh发展的进度如何,有了哪些feature。首先SOFA Mesh也是设计模式也是完全照搬Istio,他的控制层目前完全使用Istio的控制层,但不同的是他自己重写了数据层。在Istio体系下,数据层所使用的应用是Envoy,而SOFA Mesh体系中使用的是他们自研的SOFA Mosn。Mosn有两个特点,一个是不同于Enovy,Mosn使用的是Go语言编写,另一点是Mosn目前也支持了多种协议。还有分享人关于Mson的说法是先上车后补票,也就是说对于没有上K8S平台的物理机/虚拟机容器应用,SOFA Mson同样也是支持将其纳入到SOFA Mesh的管理体系中。如果这点目前完全实现的话,那么阿里内部接入Mesh就不需要经历漫长的K8S迁移过程而是在保持原有架构不变的情况下直接享受到Mesh管理的好处。然后在全面接入Mesh的前提下,慢慢完成K8S平台的迁移。对于物理机虚拟机架构的sidecar模式,我猜测很可能是采用唯品会Mesh的解决方法,将sidecar的粗粒度调粗。在Istio体系下,sidecar利用Network namespace的特性就是直接劫持你POD的网络流量,将粗粒度控制在容器级别。而唯品会的解决方案则是将sidecar的控制对象从容器提升到了宿主机,sidecar也就从proxy变成了daemonset。另外一点来说,SOFA Mesh还特意提到了自己为何使用Go语言来作为sidecar的编写语言,从分享人的陈述中来看,SOFA Mesh是有信心至少要运行10年的产品,从开发维护的角度上来讲,C++的维护成本太大,所以最终选择了Go语言来作为开发语言。而使用Go语言所带来的好处这是,他们这个项目如果要增加一个新的协议支持只需要几个小时,一百行左右的代码即可完成。所以在听下来两场Mesh之后,我个人感觉Dubbo Mesh与SOFA Mesh在理念与努力的方向上基本是一致的,但是在完成度上Sofa Mesh比Dubbo Mesh完成度更高一点。由于我今年7月毕业以后就一直在国际站项目里负责了K8S与Istio维护,而对于物理机和虚拟机容器架构应用如何去解决相关的问题这一方面基本上不怎么了解。在目前的情况下,国际站已经完全上了K8S和Istio作为底层平台和ServiceMesh管控,所以对于ServiceMesh在理念上想要提供的请求转发即服务发现和负载均衡上,这些都交给了K8S去完成,在我的眼中Istio更倾向于去解决服务治理与服务安全这些内容。不过对于物理机/虚拟机容器的架构,我认为SOFA Mesh是非常合适这个场景的,如果SOFA MESH真的达到了他所说的效果。另外一方面,分享人也注重提到了关于Mesh这一概念在整个架构中的定位,如果我们将K8S看做平台,将每个服务实例看做应用层,那么Mesh则是夹在平台层与应用层当中的通讯层。这个通讯层,就是指将目前微服务架构下把服务治理从应用中剥离出来,单独抽象为一个层,融入到基础设施的建设当中。同时,由于sidecar会劫持流量,这也就意味着本来生产者发送到消费者的网络调用则会变成生产者,生产者的sidecar,消费者的sidecar,消费者的网络调用,即多了2个中间环节。所以对于流量劫持的性能优化方案,分享人给出了三个思路,一个是iptables的优化,一个是IPVS方案,还有一个是Cilium+eBPF的思路。以上基本上是这两场分享所讲的大概内容。一些想法站在我自身的角度来看,这两场分享我听下来我个人感觉就是阿里和蚂蚁金服向国内布道了ServiceMesh这一概念并且宣布入场。虽然我接触ServiceMesh也有将近半年的时间了,不过阿里和蚂蚁金服更像是Mesh的开发者而我则是Mesh的使用者。我个人感觉在管理国际站项目的K8S平台与Istio的过程中,我可以通过个人的精力去维护整个国际站的服务治理与流量管控这一点就是完全通过Istio来实现的,即灰度发布,ABTest,而K8S平台则天然解决了服务发现和负载均衡这两个问题。另一方面,K8S与Istio所倡导的声明式API配合GIT的使用则更进一步高效和科学的去管理集群配置与Mesh配置。以国际站目前的情况而言,在完全基于K8S平台和HTTP通信的情况下,我认为在ServceMesh这一块坚持使用Istio即可。作为整个ServiceMesh领域中来说,Istio比起SOFA Mesh和DubboMesh而言没有历史包袱。不过对于整个Istio社区内,目前也没有比较好/普及的关于Istio在服务治理上的使用实践。所以目前对于国际站而言,就是在现有Istio提供的能力下,探索出一条适合我们现有体系架构,利用Istio进行管理的服务治理方案。 对于国际站目前在Istio这一块的探索实践以及所达成的进度,可以参考Istio 实践另外有意思的一点是,SOFA Mesh和Dubbo Mesh这两个产品是否是整个阿里集团内部的赛马产品?两个分享人都故意的没有谈对方产品,也许可能蚂蚁金服作为一个独立的公司,SOFA Mesh则是只会影响到蚂蚁金服这个业务内的Mesh领域,两个产品可能井水不犯河水。 关于这点就不得而知了。暂时先写Mesh这两块的听后感,关于其他分享的听后感后续接着补。


我的日常工作

前言最近组长跟我们商量准备做个抖音小视频来做19届的招生工作,然后举了一些之前的活动作为内容。包括Hackday啊,乔迁酒会,以及休息室的VR游戏。不过我个人感觉这有点像是哄小孩子的感觉,很多东西就是看看也没啥效果的感觉。从我个人的角度来说,对于未来的工作还是比较关注真正的工作内容以及工作情况。所以今天特意写这篇博客来让大家了解一下我平日里的工作安排与工作时间是怎么样的,就以今天为例吧。正文9:00 am我每天的起床时间都是固定在早上九点,然后开始刷牙洗脸洗澡吃早饭,一般会打开电脑一边吃早饭一边看新闻看RSS订阅。折腾完了大概固定十点钟出门上班。10:45 am现在的工作地点在陆家嘴软件园,所以每天会从坐8号线转4号线到蓝村路站下站。从地铁站上下来以后还有一段将近几百米的路,这个时候通常都会选择共享单车骑过去,所以一般到岗打卡时间是十点五十多。我在酷家乐上海这边算是来的比较晚的了,不过由于公司打卡制度比较弹性。外加我是一个爱睡懒觉,然后晚上效率比较高的人。所以选择晚点来,晚点走的方案。11:00 am基本上这个时候会开始打开工作台电脑开始一天的工作,一开始会看些RSS订阅,JIRA任务日志和confluence上的文档更新以及检查邮箱这些日常工作11:15 am技术组站会,进行短暂昨天的工作汇报与今天的将要去做工作内容。11:20 am开始工作,目前我负责的工作内容包括两块,业务开发部门与基础架构建设部分。业务部分会进行国际站后端服务的需求开发,基础架构建设将会围绕Gitlab-ci的自动化工作,Kubenretes与Istio的架构建设与稳定性维护。12:45 pm一般干到这个时候去园区内的食堂吃中饭,附近吃中饭的选择还是比较多的,价格也很合理,算是比较实惠了。一般吃完中饭会回到办公室里进行午休,大部分时间是刷刷知乎,看看Github。14:00 pm一般这个时候再度开始码代码,写文档的工作。18:00 pm一般6点的时候公司订的晚饭就到了,然后会在休息室里和同事一起吃晚饭,吃完晚饭就聊聊游戏,刷刷知乎之类的。19:00 pm一般这个时候进行一天工作的收尾工作,比如进行文档的编写,或者再开始进行功能开发之类的。19:40 pm基本上到这个时候一天的工作基本上到这也就结束了,我会收拾东西跑到休息室玩VR游戏。最近公司里放了一台VIVE的顶配VR,然后配合Beat Saber这款游戏玩还挺有意思的。基本上下班以后我都会去玩这个VR游戏20:20 pmBeat Saber这个游戏还是挺累的,基本上切了6、7首歌以后就蛮累了。然后背包打卡走人。21:20 pm到家,一天结束。关于实习的思考基本上以上就是我今天以及最近这段时间的日常写照,回想起去年十月份那个时候的我。感觉如今的自己变化还是非常大的。从那个时候刚进组的实习生,到现在作为三个后端服务的owner以及基础架构建设的主要负责人,一方面感觉能有这些挑战的机会非常难得,另一方面也是组长mentor对我能力的充分信任敢于让我去尝试各种各样的挑战吧。会想刚开始进入国际站的项目开发那会,基本上大佬的谈话都是听他们讲。如果K8S平台有啥问题,就只能等着大佬来救场然后问他们是怎么处理问题的。经过这几个月的各种内网环境和生产环境的折腾与亲自尝试以后,作为基础架构建设的负责人,我基本上已经可以胜任我们K8S平台维护的救火队长了。上周五我在公司内部举行了一个K8S最佳实践的分享,分享结束完以后有一个人向我提问关于使用K8S与Istio作为国际站的基础架构建设在性能上是否有信心,我当时回复他,即使K8S与istio的组合真的出了性能问题,以我们目前的维护积累与经验也有信心能搞定修好。关于发展空间这点从我个人的例子我觉得基本上已经很有说服力了,至少从大四开始实习到目前刚毕业的这个状态而言,我对自己的进步还是比较满意的。这点也脱离不开这个集体对我的信任吧。最终目的说了以上这些,主要目的就是希望19届或者以后的学生可以投我们酷家乐上海的校招、实习岗位。同样可以找我内推,内推邮箱是 yisa@qunhemail.com


Kubernetes在酷家乐国际站的最佳实践

前言最近这段时间一直想要把工作上的一些收获和成果沉淀下来,想主要讲讲gitlab与kubernetes包括在k8s平台上更往上一层service mesh这些东西。然而这段时间一直在负责Coohom项目组基础技术架构的建设和三个后端服务的需求与维护,抽空还要带一个实习生,所以一直都找不到比较空的时间可以写一下博客。本着再忙不能忙娱乐的精神,在补完番,神界原罪1代游戏通关以后,恰逢这周五也要在公司内部开展一个kubernetes最佳实践的分享,所以干脆就以此为机会写一篇博客,正好一石二鸟。正文这篇博客讲什么这篇文章的目的并不是为了说服为什么要去使用Kubernetes,我相信对于这方面有许许多多的文章和博客或者观点讲述了为什么我们最终会选择kubernetes。对于容器编排平台来说,虽然从来没有官方钦定过Kubernetes是最终的胜利者,然而kubernetes却已经是事实胜利了,这点已经无需辩驳。所以这个博客的主要讲述的聚焦点将在于我作为一个应用开发者,是如何在Kubernetes平台上帮助我更好、更效率、更方便的进行我的工作。关于Kubernetes这个单词写起来太麻烦了,我还是喜欢用k8s作为简写。所以后面的k8s都用来指kubernetes.服务与被服务在提供服务的企业内经常会有这么一句话,叫做顾客就是上帝。对于写应用的程序员来说,使用你应用的人就是你的顾客,换言之你就是为这些人服务的。所以如果你让你的顾客不爽了,那你的好日子基本上也到头了。假设在工作中,你不可置否的需要研究k8s平台并且做出一些改动从而让使用这个平台的人的应用能跑的更爽,这种情况下多半你可能会是在一个类似基础架构组这样小组中工作。套用上面的理论,那些使用我们平台的程序员们就是我们的客户,换言之基础架构组就是要为业务开发所服务的,一个傻逼的业务开发可能会让这个应用的某个功能非常难受。然而一个傻逼的基础架构建设者就可能让整个产品变得傻逼。当我们k8s上做出一些改动或者新的方案时希望去推动业务开发程序员们一起推进时,一定要学会站在他人的角度去考虑,思考这项改动对他们侵入度会有多大。如果由于基础架构的改变使得业务开发的程序员们都疲于配合你的改动而不能进行业务开发时,这就是一个非常傻逼的事情。所以不管是否是k8s平台也好,作为基础架构建设,一个最明智的举动就是润物细无声的改动。底层的变化对上层是无感知,这是最好的。关于Docker虽然k8s对于底层的容器技术并没有指定,除了大家熟悉的Docker,我们同样其他容器技术作为k8s的底层容器。不过还是那句话,虽然Docker没有被官方钦定,但是使用Docker作为k8s的底层容器几乎已经成为了事实标准,包括我们的k8s平台也是同样基于Docker作为底层容器。我们是如何使用k8s的对于我们的国际站项目,我们的所有应用、所有环境已经完全上了K8s平台,并且对于某些环境我们还上了最新的ServiceMesh技术,即Istio。不过这个东西如果要做整理分享的话也可以再拿出来写好几篇博客了,所以打算放在以后再说。作为一个后端开发者,我们主要使用Java作为我们的主力开发语言。所以下面我提到的一些实践虽然有语言无关的,但也有一部分是语言相关的,当然说白了就是JAVA服务和K8S。作为后端开发人员,我也非常清楚目前这个时代不少程序员对JAVA的看法有着不少意见,可能会更喜欢Python Golang之类的语言。而我的看法是Java作为一个后端语言确实有他不足的地方,但是和他庞大而稳定的生态圈而言同样也带来了很多便利。我们可以不喜欢JAVA,但他值得我们去尊重与了解。不过既然这里是我的博客,所以我想写啥就写啥嘻嘻。语言无关代码与配置分离在我们的开发过程中,我们的程序会同时面临好几个环境。比如本地开发环境,内网的alpha,beta,gamma环境,外网的正式生产环境。当产品达到一定的规模时,我们希望有全球部署的能力,那么很可能对于生产环境来说也会有比如美东环境,欧洲环境,华东环境,东南亚环境等等等等。由于环境的不同,不同环境我们的代码表现是一定有可能存在不同的。一个通用并且科学的做法是对于我们的程序永远只保持一份代码,但是有多份配置,应用会根据我们的环境加载不同的配置从而有不同的表现。在我们的配置中,有些是敏感配置,有些是正常的配置。什么是敏感配置呢,比如数据库连接,数据库用户名和密码啥的。对于内网的测试数据库来说,应用开发程序员是可以接触到的,所以会写在项目本身的配置文件中。但是对于生产环境的敏感配置该怎么办?如果我们用的是虚拟机,可能这些配置文件通常会交付给运维组来处理,提前写到虚拟机内的环境变量之类。那么假设我们用容器呢?Docker容器比起虚拟机而言在管理上确实会更加方便,但是假设我们内网镜像和外网镜像用的是同一个镜像,那么这意味着镜像内部就要带上所有配置文件,考虑到镜像泄露的可能性,这同样也是非常危险的。对于K8S而言,他非常好的解决了这个问题。在K8S平台内,有专门成为配置与密钥的两种资源。K8S会根据我们的定义将镜像或者镜像们包装成为一个更为抽象的容器,即POD,这个POD的内部环境变量都可以通过配置与密钥两种资源获取。这就带来了以下几个好处:代码仓库脱敏,代码仓库中没有任何敏感信息与配置,开发程序员只要知道最终运行时配置项会被相应填上,而不用关心这个配置项是如何被注入的。镜像脱敏,对于容器而言,我们的镜像内配置文件资源永远只有非敏感的资源,而对于敏感资源只有在K8S拉取镜像后创建POD时才会被自动注入。配置即代码,由于配置与密钥对K8S而言就是一种资源文件,而K8S的资源文件是可以通过文本定义的,这也就是意味着我们可以通过Git来管理我们的配置文件。确立K8S平台权限就像上文我所讲的,我们可以通过K8S平台来进行敏感配置的管理。这就意味着能够操作K8S平台的人就能接触到敏感信息。所以确立K8S的平台操作权限是非常关键的。在我们早期使用K8S时,我们所有开发的K8S配置都是相同的一份。这意味着从刚进组的实习生到整个项目的技术负责人而言,他们对于操纵K8S平台的权限是一样的。这是非常致命的,这意味着很可能一个并不了解K8S平台或者项目业务的人很可能因为好奇或者是别的什么原因修改或者查看我们的所有在K8S平台上的资源、配置等一切敏感或者不敏感的信息,而我们对此一无所知。在某一次我们对于Mysql数据库监控的时候,我让一个开发人员只要去操作我们的内网K8s环境即可,结果在第二天的站会上他告诉我同样也给我们的生产环境接上了监控,当我问他是从哪里获得我们的敏感链接时,他告诉我就是直接在K8S平台上查到的。这让我当时就感到很无语。另外一方面,过多的人同时修改K8S平台内的资源配置非常容易造成双方都意想不到的后果。所以目前在我们项目组而言,所有对于K8S的修改都是经过一个专门的人审核过后,通过我们的CICD系统来自动部署到K8S平台上,而并非在某个人的开发机上去做这件事情。当然审核的人需要对整个业务环境的资源配置非常熟悉才行,而这个苦逼的工作目前就交给我负责了。总而言之,让很多人都有同时修改查看K8S的能力是一个非常危险的事情,确立K8S的操作权限是一件非常重要的事情,这点从技术上和流程上来说都能实现。滚动升级与探针策略在我们之前的开发流程中,每当我们的后端服务进行部署时,对于前端开发以及我们平台内的应用监控而言,都会有一小段不可用时间。从而导致我们的客户端开发人员发现后端服务出现了闪断现象,我们的监控服务发现后端服务出现了一段时间的不可用从而发出报警。 究其原因,假设我们一个服务的实例副本是2个,那么我们之前的更新其实就是起两个新的实例的同时再将两个旧的实例关闭,在旧的实例被关闭以及新的实例完全启动的这当中的时间就会造成服务的不可用。一个比较好的做法是,我们先将新的服务实例启动起来,但是并不接入流量,等到服务实例完全启动完毕后再将旧的服务实例关闭,并且将流量接入新的服务实例。那么如何判断服务实例完全启动以及新老服务实例交替该遵循如何的规则,在这方面上K8S都给出了非常棒的解决方案,即滚动升级与探针策略。使用K8S的滚动升级与探针策略以后将会将我上文提到的所有操作完全自动化进行,不会出现任何一刻的服务不可用,这对于后端服务来说尤其重要。镜像预热对于我们的业务环境来说,我们的项目在现在以及未来将会进行全球化的部署,比如美国,欧洲,中国香港等地进行部署。那么一个问题是当我们定义K8S的deployment资源进行部署时,K8S会新建POD以后再通过这个POD进行镜像的拉取。由于我们的业务部署环境的网络情况各不相同,在我们真正部署到生产环境时,很可能会遇见因为网络原因从而拉取不到镜像的情况。虽然这个风险会被我们上文的滚动升级策略所抵消,但是直到部署时才发现我们的镜像网络拉取问题这件事情同样是非常可怕的。一个我所实施的办法是进行镜像预热,所谓镜像预热,即在进行部署之前事先让K8S的每个Node进行镜像的拉取,这个拉取会直接存放在K8S每个NODE的文件系统中,而每个NODE都拉取完我们所需要的镜像这个过程即是镜像预热。等镜像预热结束完以后,再进行部署以后,我们的所有应用将会在拉取镜像环节秒起。虽然对比上面直接拉取的方案,也许在网络时间消耗上并没有减少,但是我们将网络问题的风险提前转移到了镜像预热环节中,从而避免了生产环境部署时出现网络问题拉取镜像失败的问题。 另外一方面,从工作环节的角度去考虑,部署生产环境这个过程总是让人神经紧绷,而镜像预热这个过程可以有效减少生产环境部署的这个流程。假设我们预定晚上6点开始进行生产环境的部署,那么我们在当天下午3点或者4点就能进行镜像预热,在镜像预热的过程中我们不必要像部署生产环境时一直盯着,可以说是非常节约,提高工作效率了。监控报警的垂直领域切割对于K8S平台上的监控与报警,我个人非常推荐使用Prometheus搭配Grafana进行使用。一方面是prometheus与grafana在K8S平台上集成的已经非常成熟,运用这些开源组件我们不仅可以看到集群的情况,我们更可以对K8S内每个POD以及每个Container进行监控与报警,比如CPU使用率、内存使用率和网络输入输出的流量等。一个我比较推崇的做法是,对于集群而言,他有一套自己的Prometheus来监控集群的情况以及集群内每个POD和Container的使用情况。而对于业务开发组,每个组有自己的prometheus和alertmanager来对自己的应用健康状况进行监控与报警。这是一个我认为比较科学的从垂直方向上去切割监控与报警的作用域。语言相关所谓的语言相关,说白了就是我写JAVA服务时如何更加爽的跟K8S平台集成使用。关于这一点我觉得这篇文章再写下去就太长了,准备放下次写。


其实机会都给你了

引言 今天和沈老板一起吃完晚饭去地铁的路上,沈老板跟我讲其实上大的计院学生在格局上还是有点欠缺的。你不管是自学前端、后端还是其他什么技术,你大一学也好或者大二去学,大三去学,等到你毕业了或者准备找实习了的时候其实你当初的那些技术大家也都会了。但这将会是你在技术上发展的第一个瓶颈,如何帮助学弟学妹们发现这个瓶颈或者突破这个瓶颈,是我们需要去帮助和引导你们的。正文2017年刚开始的时候,那时候还是大三的冬季学期,天蒙蒙灰,我在前往计院上课的路上。刚到1楼的时候,正好巧遇了圣*二次元 丁一凡,他一脸神秘的暗搓搓的跟我讲了一句,世界五百强实习机会了解一下?虽然我觉得他当时的样子和语气更适合偷偷卖光盘的,但是我还是知道他野路子是特别多的,所以抱着纯粹试一试的态度让他把我拉进了一个微信群。那个微信群的名字我现在已经想不起来了,但是我记得这是一个纯粹用英文写的一个群名,大致是Morgan Stanley Summer Intern之类的词汇,后来我下课了在寝室里查了查这个MS到底是个什么企业。当然查完以后我的心里就只有一句感想,就是尼玛真的假,丁一凡连这种路子也能搞到。和今年上大计院15级申请MS的Fast Track所不同的是,我记得3月开始的那两三个月,找我来问MS面经的学弟学妹特别多,有问我怎么准备简历,还有问我面试会问什么的,要穿什么衣服过去。给我的感觉就是15级同学对于这个面试特别重视,会为此准备很久。这让我想起了我当时在上大面试Fast Track的时候,也许现在的15级16级同学压根不会猜到的是,当初我们14级这届在做这次面试的时候,其实鸽了这场面试的人不少,以至于就只能让来上大的面试官一直坐在那干等,现在想起来真的非常非常尴尬。说实话,其实我当初几乎也要放弃这个Fast Track的面试,一是觉得不太可能,怎么会在上大招实习生呢?二是觉得不自信,因为觉得自己其实非常弱,所以就觉得肯定也不会通过面试,去了也白去。就在我纠结到底是去还是不去的时候,坐着7号线也坐到上海大学了。那就去试一试呗。后来就因为戏剧性的那年报名Fast Track的人不少,投简历的人不少,结果去面试的人不多,不少人都鸽了,结果我和大部分我认识的去参加Fast Track的人都过了。其实当初面完一面的时候我心里都很沮丧了,因为我当时说英语的时候也很结结巴巴,半天吐出一个词,面试官问的问题也有一些没回答上来,但最终可能因为来的人也不多,我就这么侥幸的混过了一面,直接来到了最终面。后面的事情我觉得就没什么好多说的了,无非就是我过了终面以后真的拿到了实习机会,在那实习十周以后写了一些博客给学弟学妹们读和感受,然后辅导员那也有学生去了大摩实习也有过宣传和表扬。总而言之,这条路被我走通了,验证了确实是可行的,到了15级的时候投简历的情况明显高涨了很多,大家也都精心准备了简历,问很多学长关于面经之类的经验,自然而然的,报名的人多了,被筛下来的人也就多了。路通了,走的人多了,自然就不好走了呗。想到这里,我会去想,如果当初的我再犹豫一点,再忐忑一点,再胆小一点,会不会就错过这个机会呢?或者说当初这么好一个机会放在我面前,我为什么会犹豫、忐忑和胆小呢?为什么会有那么多人的人明明报了名交了简历,最后又选择鸽了面试呢?他们是否会后悔过这个事情呢?2018年了,当初他们的想法我已无从得知,而我也在现在的公司工作快一年了。在这一年里面呢,我回到过开源社区两次,一次是我自己做分享,一次是今天以酷家乐员工的名义来参与分享。你说我做这些事情有什么好处么,其实压根没有。开源社区和公司又不会因为我做了分享而给我钱,而对于社区的负责人本身来说准备一场分享也很累,比如租场地,搭建直播环境,写推文帮助我们做宣传啥的。他们有好处么?学校也没说他们钱,我作为分享人呢,其实很大程度上也在用着他们提供的服务,而且服务的相当不错,但我也没说给他们经费啥的。可以说是最爽的就是听众了,人来了就行,听完了就走,还不用付钱啥的。但就这么一个分享人出力,承办人出力,听众听就完事儿了的分享会。为什么上海大学的开源社区却能一直办下去呢?如果你加入了上海大学开源社区的那个QQ群,你会发现里面人的组成特别有意思。首先就是有学生,这是很显然的,其次就是研究生,当然是我们上大的研究生,然后还有很多已经工作了的学长学姐,最后是还有外校的以及其他组织的计算机爱好者。就是这么一个没有官方背景,没有学校拨钱,纯粹是由学生自治的,大家毕业以后工作了都好几年了也一直在这个社区内吹水的社团,为什么会活跃度这么高,生命力这么顽强呢?我作为一个这一两年一直参与的成员来说,其实作为开源社区的内容输出者的同时,也是开源社区的内容收益这,你对社区有贡献,社区同样也会反馈给你。在我大二、大三、大四这三年,其实一开始开源社区的分享有过一段断代的历史,那个时候还是圣*二次元 丁一凡作为社长的时候,他那个时候就没有开展过开源分享的活动,以至于都快成为一个不被人知的小众社区。后来他跟我讲起的时候我说这样不行啊,我们要搞起这个事情,于是分享主题开始设计了,微信再次启动推送了,丁一凡终于有了将功补过的机会。当然到后面社长传到15级群老板这代以后,那个时候我开始忙着实习不在继续在上大读书了,从上大开源社区的推送上就能看的出来15级的学弟学妹们把这件事情搞得非常出色,这让我说实话还是有点感动的。作为我个人来说,学生时代受过学长们的指点和建议,实习阶段也被各个已经工作的学长学姐们给了内推机会,最后等我工作了我需要找实习生时,我又回到了开源社区来麻烦学弟学妹们。这些都是我在参与这个社区时所收获到的资源。从大三开始,我有点忘了我第一个在社区中所分享的主题是什么了,可能是系统编程,可能是高并发之类的主题。不过从刚开始加入社区到现在也有两年了,每次社区开展活动的时候我也会去15级16级那做宣传,在这个过程中呢也认识到了很多有意思的人,其中部分现在也成为了我的同事。不过我最开心的还是碰到了17级的一部分小朋友,他们给我的感觉是真切的喜欢着技术,喜欢去做技术的人。在今天开源社区活动分享的时候呢,我当时也跟在场的各位16级17级的同学们这么聊过,作为一个技术的爱好者,我非常羡慕你们,因为你们有着很好的资源,学校和导师也给你们非常大的支持,网络上的学习资源越来越成熟,技术发展也越来越人性化。但作为一个14级的学长,我也非常同情你们,因为你们将来所面临的竞争压力将会远远高于我当初和现在所面对的竞争压力。所以你们一定要跳出学生的这个思维限制,敢于跨出走进社会实践的一步,我个人是非常非常建议你们在大一大二的时候参与暑假实习的。当然这话我讲给大三的人的话是没有意义的,所以我在宣传的时候侧重点也不在15级。这个建议呢,有的人被我打动了,说服了,选择大二的时候就出来找实习,并且经过一段时间的磨合已经在技术上有了突飞猛进。有的人呢压根我不需要我建议,自己就已经意识到了,所以即使现在才大一的时候也已经参与到了暑假实习工作。当然更多的人对于这样的建议就是听过就算了。当初我大三刚开始找实习的时候,为了把简历写满一页脑袋都快想破了。绩点又不高,个人项目就是简单的课程大作业设计。等到真正看到网上的大佬们晒offer,晒简历,一看他们大一大二的时候就出去实习了,而且越是年龄小越是容易申请到offer时,才猛的一拍大腿说哎呀真是晚了一步。等到大家都意识到得赶紧找个实习为了以后能找个好工作,联系个好导师时,才发现这块竞争是多么激烈。这正说明了等大家都觉得这是机会时,往往已经不是机会了。所以我这个礼拜一直在思考的是,为什么当初大摩第一年来做Fast Track的时候,优惠条件这么高,性价比这么明显时,大家都选择了放弃和退缩,包括我在内也是内心一直犹犹豫豫,甚至一度觉得我压根不会有机会,愿意相信反面的观点呢?可能是我内心所事先期望的那个结果,让我做出了扭曲的判断吧。所以今天来开源社区做分享,以及前段时间的分享会,我一直强调出作为大一大二的年轻人一点要有危机感,要趁大一大二实习的这片市场还没有被人完全铺开来竞争的时候,去找实习,去体会真正生产环境上的制度和实践。而开源社区又有这么多工作了的学长学姐,他们能给你在上海各种互联网的大公司,中公司,小公司,创业公司,外企等的这些内推实习机会。这些机会本来就是给你了,希望你不要像当初放弃Fast Track的那些人一样反着来。


成为mentor之前

引言 下礼拜项目组会迎接来两个大三的后端实习生,然后不出意外的话我会成为其中一个实习生的mentor。一想到其实我自己也是实习生,现在拿的也是实习工资,从下礼拜起就还要带另外一个实习生想想简直不要不要的。正文从去年十月份加入酷家乐开始算起,到现在也已经快有差不多三个季度了。从上海分公司这里七月份才刚刚创建来说,我也算是酷家乐上海的老人了… 一年之前我在刚刚踏入实习生活的时候,就曾经写过一篇文章描述过自己在进入实习的一些状况和期许吧。现在有些时候我也时常会翻回去看看自己以前的博客。从刚开始到现在,虽然可能只有短短的不到一年的时间吧,当中也辗辗转转了好几个公司,最终落脚在了现在的公司。从技术上来说,看到了不同公司的不同的技术栈以及技术方向。从产品和业务的角度上来说,也做过一些2B,2C或者是对内支持的产品。如果说自己有什么改变的话,可能说每次看待一件事情或者看待一个产品都会有很多的思考方向吧,心态也逐渐平缓了许多,不像一开始实习的时候那样火急火燎的。因为换的实习很多,所以带过我的mentor也很多。在和他们共同工作的日子里,可能我和他们有些人公事了几个月,也有的只公事了几个礼拜。虽然他们每个人的特点都各不相同,但是其实都给我留下了很深刻的印象,我想我现在工作的心态与观念可能也受到了他们每一个人或多或少的影响吧。以前我在开源活动做过一个分享,主要面对的对象都是一些大三大二的学弟学妹们,主题则是关于在实习前我们需要准备的是什么。在分享的一开始我跟听分享的那些学弟学妹们讲,其实做一个实习生是最爽的,因为你其实不用有任何负担和压力,如果你有什么任务完不成的话,你把锅甩给你的mentor就好了. 说实话这招用起来其实挺爽的,但是会给你的mentor造成很大的压力。我基本上甩过很多问题给我的所有mentor过,带过我的那些mentor里面,有技术好的,也有技术一般的,有负责的,也有不负责的。碰到技术一般并且不负责的,他就是帮我看一看然后发现自己搞不定就又甩给我说随便我咋解决咋尝试了,然后留下我一脸懵逼然后人肉试错了将近七十多次。碰到技术一般并且比较负责的,这个项目是我到现在以来接触过的最为复杂,最为冷门的一个非常小的领域的算法项目。说实话我和mentor当时都对这个项目一脸懵逼,但是mentor当时和我吃饭的时候就说没关系不要有太大压力,你就算不会做也还是有我在的。后来他帮我收集了这个方向的很多资料以及参考论文省了我非常多的时间。当然那个时候我也意识到了他其实算法技术一般,因为那个小众领域的算法项目到后面就是我自己一个人在一直跟进和维护了。不过在我的工作过程中,我依旧一直和他讨论和分享我在解决问题时候的困境和烦恼,虽然最后我都靠自己解决了,不过还是非常感谢他对我的帮助的。我碰到过两位技术牛逼并且非常负责的mentor,有趣的是他们两位对我的帮助的表现形式是不一样的,同样是我把问题提出来问他们,其中一个人是将我的代码拿过去以后改好了告诉我我已经帮你修好问题了,而另外一个人是听完我说的问题后,给了我一个建议的方向,让我找找这方面的资料去解决我的问题,最后我通过他给我的建议竟然真的能解决问题。这两位Mentor在技术上给我的帮助都很大。有趣的是其中一位为我解决问题的时候喜欢亲力亲为,与此同时另一位则是听完我的描述以后给了我一个正确的方向。对于前者,给我更多的感觉是非常的可靠,碰到问题也不会心烦意乱了。而另外一位给我的感觉就是有点神奇了,虽然他给我的建议往往是正确的,但是过程中还是有时候会感到心烦意乱,或者不如说解决一个奇怪的问题本身/BUG本身就是一个痛苦的过程。在这一年不到的实习生活中,已经基本上完全适应了一个全职工作者的工作节奏。在我实习生活的一开始,我曾希望自己能早日成为一个能独挡一面的人,因为这听起来非常的酷。然后现在停在这一刻,我审视了一下自己。在最近的CES上海展览上,我们的产品获得非常难得创新奖,而我则是直接负责并维护了这个产品背后的三个核心后端服务,以及后端背后的一套组件。在运维上,我一直在做基于Docker和Gitlab-ci的构建发布工作以及构建目前kubernetes平台上Service mesh的各种服务组件。在Github开源社区中,我发布了自己毕业设计的第一个release版本,逐渐获得了一些Star和issue,也有一些人fork了我的项目准备给我增加功能。在这一刻,也许我能稍微骄傲的讲一下感觉自己确实成长了,能够独挡稍微一点点面了。下礼拜以后实习生就要来了,我也要成为一个mentor了。回想起刚刚来到现在的公司的时候,研发团队只有五个人,那时候还是冬天,到了晚上空调停了以后就只能一边抖腿一边写代码。而现在我们马上就要搬家去陆家嘴软件那边好容纳下我们预计马上将要到达的八十人团队。作为一个Mentor,我不知道对于那位实习生来说我会是怎么样的。而我自己又希望自己作为mentor是一个怎样的人呢,这个问题也留给了我去思考。不过不可否认的是,作为一个mentor对于实习生的影响很有可能会是巨大和深刻的。从这个角度去考虑,我希望我尽可能是一名称职的mentor吧。后记突然想起一个有趣的事情,上次Hackday大家在休息室里一起吃午饭的时候。我们的一个产品问我工作几年了,我说我还没毕业。他惊讶了一下笑着跟我说看你做事挺可靠的没想到居然还是一个实习生。


时代与观念

引言昨天酷家乐宣讲会在上海大学来了大概50个左右的同学。当我问在场14级同学有多少时,百分之30的同学举起了手。当我问15级同学有多少时,百分之60的同学举起了手。当我问16级同学有多少时,两位同学举起了手。正文当我们把时光倒流回2011年,那一年上海大学第一次开始专业分流的政策。如果这个时候你在上大图书馆前的主干道拦住那些来来往往匆匆忙忙的理工大类学生,告诉他要好好学习,争取能进入计算机学院学习编程。他一定会用一个不以为然的眼光告诉你,我对学习计算机没有兴趣,我认为上海大学的某个学院实力更加强劲,今年专业分流的政策让我有种参加第二次高考的感觉,我要好好复习去了。那个时候,上海大学开源社区已经成立一年,08级学生胡瀚森作为上大开源社区的开创者,吸引了当时热爱编程的11级学生沈杨华和他们的同学们在这个非官方社区一起交流讨论。这个时候的中国互联网,团购的概念开始兴起,越来越多的团购网站开始兴起,O2O的概念让人们开始钢架关注生活服务,为了挖掘和占领市场上的需求,移动端开发程序员一时间成为了市场上最为紧缺的资源,而那时候他们的薪资也像风起云涌的海面一般一浪更比一浪高。2013年时,已经是上海大学开启专业分流政策的第三个年头了。你去跟当时的理工大类学生说,快去报计算机学院啊,现在市场上特别缺程序员。也许你碰到他鄙夷的眼光,计算机学院排名这么低,我要是报考这个院系岂不是浪费了我这么高的年级排名? 那一年,上海大学计院在大一时招收了英才班,部分同学不用通过专业分流考试,大一时直接就能就读计院。同年,第39届ACM World Final,上海大学的ACM队勇闯世界总决赛获得成绩,kuangbin的“人十我百,人百我万”成为每一个ACMer心中的目标。那一年,上大计院的专业分流排名是百分之九十多,成为差生才去的学院。2015年,我室友问我专业分流第一志愿是什么,我说我想去计算机学院。那你稳了呀,计院很好进的,我室友说。我看了看往年计院的专业分流排名,心想确实,这次春季的微积分3和大物2只要能过了就行,没必要复习的太好。同年,校园司令公众号上透露消息这次专业分流很多人都报名去了计院。后来他的专业排名果然就如同一匹黑马一般瞬间达到了1600多名。那一年,我以微弱的差距遗憾掉档了。2016年,如果你在路上拦住一个理工大类的学生,问他打不打算报考计算机学院时,也许他会告诉现在互联网发展特别迅速,计院的排名也不低,我要好好准备这次专业分流。那一年,在焦急的打开转专业结果查询时,看到我的转专业申请终于通过了,我长长的舒了一口气,准备感到教务处将我的大三第一学期的课程全部更换,变成计院的大二大三专业课。同年,计院的专业排名一路飙升来到1100名左右,一名15级叫钟鸣宇的学弟加了我询问关于转专业的相关事宜。我将我那一年在选课和申请的所有心得告诉了他并安慰他不要紧第二年还有机会。2017年,计院的专业排名已经在招收两百多名的情况下一路高升到890多名,第一次超越了通信学院。胡瀚森学长回到上大作为摩根士丹利员工来开启实习生招聘,我作为一个无绩点无奖牌无成绩的三无学生急急忙忙的准备了一份英文简历。看着自己空空如也的简历,我在研究生办公室里问沈杨华学长究竟什么是后端开发,沈学长当时一边忙着编写他的实习项目,一边告诉我他对后端开发的理解。同年,我和14级的同届同学徐际岚一起过了摩根的初试,为了准备终面,我们日复一日的在计院701实验室内准备到晚上。 某一天,他苦笑着告诉我,这一次终面还会有很多清华北大的从北京赶过来面试,我告诉他我们尽力就好。本来以为在找实习的时候会轻松一点,没想到这一块早就已经厮杀成血海了。那一年的夏末,我和他结束了两个月的大摩实习生活,他开始了联系导师读研,我一头跳进找工作的大海里扑腾。笔试,面试,内推,一面,二面,三面,HR面。这些名词是当时的我们口中最念念有词的东西,在牛客网的交流社区内,最火爆的永远是某某某公司的面经,找工作好难啊,九月份的时候我感觉自己失眠了一个月。一个月前,我在v2上看到阿里的HR已经开始悄摸摸的发放内推渠道。这么早!?我感慨之余,把这个消息转告给了钟鸣宇学弟,虽然过程曲折,但他终于已经来到了最初想要来的计算机学院,开始了忙碌的课程。一定不要拖得太晚申请,我告诉他,现在实习申请就是一个萝卜一个坑,申请晚了就没了。一个礼拜前,酷家乐打算在上海大学开办第一次宣讲会,我特意跑到了16级群里多次宣传大二的同学一定要趁早开始准备实习,不要惯性思维的觉得实习等到大三再开始也来得及。在宣讲会上,当我问到16级的同学有多少时,看到有两位16级的同学能意识到从现在起就要敢于踏出学校进入社会实习磨炼,我非常开心觉得自己没有白白宣传。2018年,假如你拦住一位理工大类的学生,问他是否会考虑专业分流报名计算机学院时,他也许会用一个惊讶的眼神告诉你,那还用考虑么?去年计院排名这么高今年只会越来越难进,我要好好学习微积分和概率论,那是机器学习的基础。


我是如何获得那些微博大V的手机号

前记这几天公司里的事情忙的七七八八了,再加上马上要过年了实在是有点提不起劲。就在我一边刷刷知乎,一边改改代码的时候,突然看到了一个我关注很久的大V的一个分享活动,说白了就是银联送钱送红包的活动。我一看,还有这等好事儿,便点了进去。大家都知道,一直以来,银联爸爸不缺钱,不缺资源,但偏偏缺的就是广大人民群众的爱。每天我上班坐地铁的时候,都能看到便利店里面贴了大大的云闪付广告,只要用云闪付就能减免多少多少元。不得不说这一次的云闪付红包力度确实还可以,不过就在我点入她的分享链接时,出于职业敏感型的我,注意到了一件事。验证与复现为了验证我的这个猜想,我打开了新浪微博,然后搜索“云闪付,分享”这些关键字。首先随便找到一个分享链接然后点入分享链接后截取他的URL参数最后将这段参数Base64一下以后,手机号就直接出来了总结从这通操作来看,这次红包分享的内部应该是直接用手机号作为数据库的主键,但是在URL加密构造上实在是太忽视广大人民群众隐私的安全性了。虽然手机号泄露这种听上去并不可怕,但是正是这种日常的泄露经过社工库不断地汇总才会导致一个人的安全隐私被逐渐曝光。其实光是有手机号往下就能继续去做很多事情。这里就暂且不表了。附记本来是想告诉大家这次的银联红包活动既然出了这个差错,就不要把自己的红包链接分享给陌生人,自己熟悉的人内部玩玩就可以了。不过刚刚登录了一下微博看到这个BUG已经修复了,看来银联自己终于意识到了这个问题。估计这个项目的负责人今年这个年也不大好过啊。然而在2月8号之前的分享链接依然存在这个问题,想要用银联撸羊毛的同志们记得将自己8号以前的分享删除,换上新的分享链接。


2018年的第一行代码

引言本来想在2017年结束前写一篇名为《2017年的最后一行代码》作为年末总结,可惜跨年休息的那几天在家又要忙工作里的项目,又要开始准备毕业论文的开题报告,顺便还忙里偷闲和小金打了一把文明6和风暴英雄,直到2号的晚上才有空闲时间来好好写一篇博客,于是就干脆就起了这个一个题目。这些日子最近这些日子,可真是忙死我了。本来在公司里做做打杂的活儿,作为一个小研发实习生,调研调研公司里程序员的需求,接几个mentor布置下来的小任务,写一些辅助开发的工具包,发布到nexus服务器上然后给大家写封邮件告诉大家:嘿,我写了一个新工具,可以帮你们在工作中的哪些哪些方面提供一键操作,提高效率。 就是这样水润水润的生活,闲下来还能刷个知乎,吃吃公司里的免费水果零食,一下午就过去了。 结果就在某一天的早上,mentor突然给了我一个看上去挺小的算法需求,并说诶这就是个小问题,感觉跟你们以前打的ACM很像啊,你去解决一下吧。 得,研发就是要去做开发大爷们没(bu)时(yuan)间(yi)去写的工作,看了下Jira上的相关需求,然后国内外搜了下相关资料。尼玛, 这居然是个NP-hard问题啊!!意识到事情开始变得我无法掌控时,只能拿出作为实习生最为无赖的一招:甩锅给你的mentor,哭诉着说啊呀我做不到啊这好难啊。 可要不怎么说天无绝人之路呢,我的Mentor听完我调研完的报告后,跟我讲了句没事儿这事儿还有我在就先暂缓了。 我嘴上说了一句哎呀靠谱啊,心里想着我的甩锅绝技真是越来越娴熟了。周末的时候我玩着Overwatch的时候,突然发现和尚这个英雄特别有意思,就在沉浸在五连珠秒人和与源氏斗智斗勇中,突然企业微信响了。我接过一看,mentor的消息。原来他调研了一下以后找到了几篇论文,看了以后觉得论文里提供的方法可行性比较高,让我试着复现一下。试着复现一下当时读到这几个字的时候我简直一口老血吐出来。也是从这开始,传说中美滋滋的实习生活也终于离我而去。一个短会自从那个周末以后,我就开始茶不思饭不想,整天就在那琢磨那几篇论文说的到底是个什么东西,我到底该怎么办。精神也萎靡了,人也变得不爱说话了,博客也变得没时间写了,游戏也玩的不得劲了。回想起刚开始一边啃论文,一边根据伪代码复现的时候,真的是从一步步猜测的基础上,慢慢用各种假想用例和测试用例去验证自己的写法是否符合论文的结果。 那时候一天的日常就是别人在实习,我也在实习;别人下班了,我也下班了;别人开始玩游戏的时候,我在家看论文做复现;别人去睡觉了,我才开始玩游戏。 所以也经常会一两点才睡觉。到公司的时间也一点点的往后推了,本来第一天实习报道的时候是9点到了公司,后来就一天天比前一天晚到个几分钟,现在终于稳定在十点十分了。后来某一天下午,mentor跟我说待会开个短会,和广州分公司那边的人以及与杭州总部的人沟通交流一下这个项目的进度。 要不怎么说互联网公司管理扁平化呢,通过微信开个语音会议的总人数就四个人,上海是我和mentor,广州那边是个下游公司的负责人,总部来的则是供应链的总监兼副总裁。真是随随便便就见到上级了。 会议一开始还是主要听mentor和大佬在那叽里呱啦吧,一方面是我是个实习生不大好指手画脚,另一方面我也确实写了一天程序有点累了。 然而这个项目现在的主要负责人有且只有我一个,所以大部分时候还是有很多情况和问题需要从我这里了解一下,逃不掉啊。最后开会结束后杭州那边的总监希望我们能去总部当面交流一下,mentor为我一脸高兴的答应了下来。然而我心里想的却是天哪我不想出差去杭州啊,跑来跑去好麻烦呀。如果是以前大学上课的那会,以我下雨天就翘课的习惯来看,多半是超不愿意。不过想想放总监鸽子好像也有点太遭不住了。得,只能出差一趟了呗。后续后来顺利去了杭州,碰上了个不巧的下雨天,恰好杭州的交通状况真的是非常诡异,到处修地铁不说,这马路也修的高高低低简直遭不住了。 到了总部以后,没等多久就见到总监了。其实在见面之前,我一直没有好好介绍我自己,所以其他人都以为负责这个项目的是个比较有资深经验的人或者是专家啥的。不过因为上海这边实在是太缺人了,所以只好把这个任务实验性的交给我来做做调研,不过从目前来看确实效果还不错。所以当总监见到我这么年轻并且还是个实习生的时候,他被惊讶到的表情确实让我暗爽了一下。回到上海以后,我后面的所有其他实习安排,我的mentor都帮我推掉了。想到前几礼拜我们的PM还一直请我们吃饭,并且希望我能去帮忙国际化业务那边的后端工作,结果我现在抽不出时间了想想还是挺不好意思的。然而做这个复现的工作真的是太苦逼了,上周五的时候我下班前统计了一下整个项目目前的代码量,一周以内我居然写了足足两千多行的代码,考虑到还有推到重来删掉的代码,保守估计我一周估计敲了三千行代码吧,简直高产如母猪. 每次都快觉得玛德写不下去我想放弃了的时候,mentor总是会说一些等这个算法写完这个项目就是整个被你所own来威逼利诱,被他这么一说,我想了想还是继续点下debug这个按钮开始找bug. 为了star, 只能头皮上了。在今天过年后回到公司的第一天,我终于在下班前写完了整个算法的大体骨架,接下来就可以开始对各个小模块进行开发与测试。大体上的来说,虽然任务依旧艰巨,但是至少前景可期。等忙完这个项目,后面就会慢慢展开毕业设计,国际化项目等等一系列的事情。2018年,既是一个崭新的开始,也是2017年的一种延续。2018年的第一行代码,我正马不停蹄的为2017年所开的坑来不停的填坑,如果说要有一个新年愿望的话,我觉得在之前的这段时间的转变,是我成长的最快的一段时间,我希望在2018年的我,也能一直像去年一样,永远心态年轻,永远有机会成长。


生活与工作

引言最近日子过得不温不火,感觉自己的神经不经意的放松了下来。这两天闲来无事的时候就偶尔打两把守望先锋然后刷刷知乎。前两天终于把自己心里的一块大石头给解决了,算是正式决定好了校招offer的去向。虽然校招的offer是可以毕业以后再去工作的,签了两方协议后,十一长假过后我就决定去那边先实习起来了。 很久没写博客了,这次也算想到什么写什么吧。 很杂,标题之间也没什么逻辑性。 2333有时候我会想,大学的这几年对我的改变真的很多。随着这几年一点点过去,我有一种强烈的想要能靠自己的力量做到什么的欲望。而如今,我也算是一个有着手艺技能的职业人了吧大学有时候我会偶尔回想起从前大学刚开始的日子,那个时候我只不过是一个只会玩游戏,考试前看看书临时抱佛脚的学生。后来在专业分流的时候果不其然的掉档没有去成计院。也是那个时候心里突然觉醒了一种我是真的很想学习计算机的欲望,一直从大二开始马不停蹄的每天提升自己,又害怕自己野路子走错了方向,不停辗辗转转的日子从打ACM开始,到后面转入计算机学院,再到不停的上课完成学分。一直觉得自己什么都不会,那时候每天7点起床到计院的实验室开始一天的生活,一直到晚上8点离开实验室,回到寝室看书到12点的生活在大三过了一年后,拿到了投行实习offer开始步入社会。暑假实习的时候去了大摩,随后在十周的summer intern告别大摩后。我又紧接着去了daocloud做容器开发的实习。当然现在我也都不在两家公司干了,最近也是刚刚拿到并决定好了校招offer的去向。工作环境是在淮海西路的大厦里,我非常喜欢这种高档写字楼的感觉,附近也都是蛮繁华小资的商圈,算是满足了我虚荣心作祟的要求吧。坐地铁过去也只有半小时,10点弹性上班的条件也很适合我这种爱睡懒觉的懒癌。给我一个应届生开的条件每年的工资啊年终奖啊补贴啥的七七八八凑在一起也有个二十多万感觉养活自己也是绰绰有余了。回过头来想想,自己现在能在一个互联网公司里面做着自己喜欢的开发方向,也许正是以前的失败打醒了自己吧。 有时候生活就是这样,永远也预料不到以后的发展。比起当初得知自己专业掉档了那种人生完蛋的感觉,人还是坚强的过好当下更为现实。关于生活在学校里,我只是一个学生,我只用关心好我自己的学习就行了。现在融入了社会,我觉得我虽然是计算机系的出身,但是还是应该要把视野放的广阔一点。因为之前在投行实习的关系,多多少少接触了金融方面的一些知识。渐渐的开始关心起来如何理财。感觉自己在作为一个学生的时候,实在是没什么科学的用钱观念,我觉得这样也不大合适。正好实习赚了一笔钱,拿出了一小部分去做了点投资玩玩,就当试试水。另一方面,实习的时候发现中午的时候老是要去商城里去吃饭,一个月下来也是一笔不小的开销。决定趁现在这个大四的时间,继续学习烧菜做饭这些事情,希望以后自己可以成为一个能自己给自己烧饭的初级厨师(这个想法很迷)实习的日子在大摩的日子里,每天的日子里不算开心,也不算不开心吧,只是有一种隐隐的压抑。回想起之前刚来的时候,壮志踌躇的要让把实习任务尽心尽力的完成。可是在投行里的这个氛围,我一直觉得有一种对我冷冰冰的感觉。这是一种很难表述的感觉,我和另外一个实习生负责一个项目的不同部分。很多时候,我被分到的任务都是极为苦逼并且连我的组长自己也讲不清楚该怎么做,所以我只能连猜带蒙的一遍遍的试错,一遍遍的寻找正确的方法。在我的实习过程中,有很多公司内部所必须用到的工具链是没有具体的文档的,靠的都是口口相传的方法一个个教。偏偏我的实习mentor在为给我制定了所需要的工具以后,他竟自己也讲不清到底该怎么正确使用。我记得我最痛苦的一段时间,是要将我所写好的程序通过Jenkins打包构建,但是因为这次使用的集成工具用的是gradle,所以我经理在自己也不怎么会的情况下,让我去使用这个工具。而一次打包构建的过程大概需要花30分钟,每一次出错,我都要通过一个Log信息高度抽象高度集成的Console中去查找,大部分时间是猜测,出错的理由。 最后在70多次尝试后我终于成功了。当然鉴于公司内部是没有使用文档和例子的,所以我的项目也作为了他们未来使用gradle构建项目的base structure.至于配置为什么这样设置,以及未来如何扩展,可能以后就要交给下一个实习生吧。笑如果只是技术上的劳心,我觉得倒也可以接受。毕竟作为这个专业的人,排查bug的心理素质还是需要过硬的。真正让我感到伤心的,是我感受到的冰冷的相处对待。我们一直说企业的人文关怀,是希望能让工作的人在公司里能顺心的工作。但我并不是想说公司缺少这种人文关怀,如果说你和公司的圈子一致的话,你还是会感受到浓浓的人文关怀。但是说实话我作为母校这几年第一个到大摩里面实习的学生,一方面我在来之前感到很自豪,一方面在我来了以后我才知道这又有多么无助。我觉得校友圈子是在工作中融入环境的一个很快的方式。事实上我也觉得和我合作的另外一位实习生因为和组里的大多数人都是一个校友圈子的,收到的关怀也是无微不至。反观我每次有问题发给我的mentor和组长,他们也都是大部分时间视而不见,邮件不回,skype不回,微信不回。这种感觉真的让我很无奈,也很自卑。我的程序部署需要权限,我每天都在内网系统提出申请,也一直都是无人问津,就看着他一天天过期,然后我一天天提,周而复始。一件小事硬是拖了两个多礼拜。在实习期间有很多次hr调查,希望能得到每位实习生的反馈。那个时候我不希望把自己和组里的关系搞僵,所以一直在反馈里多次提到wonderful corporation,还让组长签字。 现在想想真是有点好笑,难道他们是怎么区别对待的,我们会心里没有点B数么?不过现实中当然还是,表面wonderful的,愉快的提交了上去。因为我的权限迟迟没批下来,最后为了把项目上线部署时才终于想起我了。而这个拖了我几个礼拜的破事儿也在我拿到权限后两三天内解决了。在这漫长的被冷处理过程中,组里一次次的给另外一个实习生各种机会去做presentation宣传我们这次项目用了angular以后,performance是多么的好,学到的是多么的丰富。我看着展示上的成果,心里想着这个实习项目里面,我独自一个人在一片未知领域里一遍遍的去试错,猜测,才终于让他可以被展示出来。但是功劳却都到了另外一个实习生的身上,仅仅是因为她写的是angular,是一种很fashion可以show出来给人看的东西。我不仅苦笑感觉自己学错了领域。十周的实习结束后,我离开了大摩。在这十周里面,我觉得我学到的,感受到的,很多并非是一种技术上的,而是一种心态上的。后来陆续知道了关于大摩的转正offer和待遇,我觉得大摩不适合我,也不需要我。去上汽做开发我受同学推荐来到了daocloud做容器平台的开发。而我这次负责的项目,则是去了上汽内部驻场做私有云平台的开发。这次做的开发方向比起以前的Web,我觉得还是非常有意思的,我也觉得容器云是以后的一个非常火热的方向,为数不多暂时还未被完全普及的应用。国企的氛围与外企还是有很多不同的,但是让我印象最为深刻的还是这么一件事。就是上汽集团请我们吃饭。结果我一个多年来滴酒不沾的学生,在这种国企的饭局文化里实在是为了给公司面子,只能在那一直给领导啊啥的敬红酒,搞得虽然我酒量还行吧,但是国企里面这种只有喝的人脸潮红了才能算给足面子了,“到位”了的这种文化我是真的不喜欢,所以因为这件事我也决定不想在这种环境下工作,就选择了离开。


在大摩实习的日子

引言这段时间的实习生活非常有意思,也见到了很多非常有趣的人。走出校园的这段时间内让我见识到了过去的我眼界还是非常的狭隘,也见识了真正的生产环境内是如何运作并盈利的。非常开心能来到大摩,特此记录一下这段时间的感受。当我拿起那本记录了所有实习生资料的手册后,我竟发现八十个实习内,我是唯二来自211学校的,而这当中的四分之三的学生,全部来自清北交复。 一些说在之前的话这礼拜四在家跑程序的时候,突然整个电脑卡住,重启以后发现主磁盘都找不到了。。。 送到iapm的苹果店查了一下发现是硬盘的总线坏了。 一天时间以内维修人员就帮我修好了非常开心。。。前几天回到家因为都是处于没笔记本用的状态不能写程序,就看看书顺便把之前挖下的redis大坑填了一部分。 仔细想想这段时间,感觉自己渐渐从一个宅在实验室里的大学生逐渐变成了一个早九晚六的社会人… 身为计算机院的学生,可能各位对于在BAT等互联网大厂的实习或者工作经历都看过不少了,但是在摩根斯坦利这样的投行的体验应该很少看到.. 今天拿回笔记本后想特地谈谈这段时间实习的感受,为后面的学弟学妹做个参考。初到大摩第一次去大摩的时候,还是最终面试那块。那个时候还是比较冷的冬天,我和同学都收到了参与最终面试的资格,我们定在了同一天面试以后,在那天一起去了大摩面试。大摩的上海开发部处于浦东嘉里城这块,坐地铁去非常方面。对于上大的学生来说坐七号线直接坐到终点站花木路即可。我直接面试的几家公司要么就是一整栋楼都是办公楼的那种,或者是小公司在浦东的别墅区里落位。嘉里城属于下层建筑为各种商铺区,上层留给做办公区,我觉得这还挺方便的。当时第一次进到大摩内部时我感叹的最多的便是大摩内部整体的安全性。和互联网公司不同,大摩的安全性可以说是做的很厉害,如果没有工作证的话可以说在大摩内部是寸步难行。在经过面试和等待以后,最终我还是被通知了拿到了暑期实习的offer,从七月份开始了实习生活。实习的第一天,基本上是个和公司内部各路人见面,熟悉熟悉team的过程。我们每个人在进公司后都拿到了关于整个实习的计划书,后面还有每个实习生的资料。结果不看不知道,一看吓一跳。大摩的暑期实习的招收对象应该是全中国的范围,也就是说在全中国大学招的实习生最后都来了上海地区的总部。我看了下号码编排实习生数量大概是八十个左右,也就是说每年会从全国招八十个左右的实习生。在实习生资料一览内,会有每个实习生的简短的资料,这上面最有意思的资料便是说明了你来自哪所大学。当时我看完所有实习生资料以后,我的内心大概有如下这么几个结果八十个实习生内,将近差不多一半来自上海交大剩下的四十个实习生内,将近三十个来自清华北大复旦同济最后十个不到的学生差不多是北邮,南大等学校我的学校应该毫无疑问是这当中最不起眼的来到大摩之前,听到身边人对我的恭喜我还有点小开心,然而在这第一天上班的大热天,我的心拔凉拔凉的低落。。。开始工作好在第一天的见面工作结束以后,第二天就直接开始到办公区域内开始进行项目的立项与设计了。在短暂的与组长和组员谈论完短期内的目标后。我就坐到位子上火急火燎的开始进行准备工作了。大摩的环境搭建还是非常方便的,任何需要的软件或功能都可以直接在内网里直接下载并安装,非常简单方便。不过在这点上还是我觉得还是微软做的最后,听在微软实习的同学说,他们的环境搭建就是双击一个执行文件就可以全自动执行了,对此我只想说老铁双击666…好在坐在电脑前的这种熟悉的感觉,让我暂时忘记了第一天看实习生资料的低落感。但就在简单的安装好了环境以后,我就碰到了我在大摩里碰到的第一个困难。神TM内网里我申请安装一个基本框架却装不上,和同学与HR反映与谈论这个问题以后,解决办法就是要拨打内网的技术支持电话,让运维人员帮我安装。于是我拨通了内网电话的那刻,我突然意识到大摩是一家美国公司,第一天老总给我们演讲时用的也是英语,那么我在电话里沟通的时候到底是用中文还是英文?简短的思考了下我觉得肯定是使用英语。虽然我英语成绩一般,但是对自身的口语还是稍有自信,可能平时有用到英语交流的机会时其实都是跟国人交流,当我听到电话的那端传来了一声咖喱风味的Hello时,我愣住了。 因为我意识到了一个事实,大摩不亏是一家全球都有分部的公司,很明显的我这通技术支持电话并不是打给上海分部的运维人员,而是直接转线到了印度分布的技术支持,也就是说我现在打的是一通国际电话。。稍微平复了一下紧张的心情以后,我简短的将我遇到的问题以及我需要的帮助表达给了技术人员。。幸运的是他理解了我的话,然后很自然快速的在电话里回复了我一堆噼里啪啦噼里啪啦,并很明显的需要我在电脑上做某些操作。。 而我就像一个缓冲区极小的套接字,非常尴尬的表达了自己没有听清,能否重复一遍。电话那端的技术人员当时就理解的笑了,并缓慢的将关键操作告诉了我,万幸的是在这次以后我跟他终于可以开始互相交流,整个技术支持的过程就愉快的结束了。和国内的互联网企业相比,在大摩这样的外企,并且是在全球都有分布的企业里,掌握口语交流能力真的是十分重要,否则在交流上会很有困难。实习项目我的办公位比较靠近通往我们这一楼的玻璃门,在那玻璃门的对面就是我们这一层的会议室。所以,每次我们组需要讨论东西,组长就直接在内部系统上预约一个会议议程,然后我们可以很方便的拥有使用会议室的权限,在里面开组会讨论项目。我在实习项目内负责的模块对我来说似乎是刚刚好,既不是完全陌生,也不是特别熟悉,属于需要花点时间去掌握然后就可以正式进入生产环节,我个人对此还是比较满意。毕竟做一个完全陌生的东西属于对我之前所有的经验与知识而言都作废重来,而做一个完全熟悉的东西则是将有限的时间浪费在了无意义的重复劳动上。但是在实习的第一个礼拜,我感觉我每天都过得非常难受,这种难受并不是指心里上的难受,而知是一种体验上的难受,因为过去我作为一个学生,在开发上有着完全的自由,我想用什么技术用什么技术,我想怎么解决就怎么解决,需求,模型都可以自己决定。但是当来到了大摩以后,首先需求这种东西就不可能是我自己来定,而是我要先摸清我所负责项目的业务部分,之后才能开始展开工作,而这块对我来说可以说是完全陌生的。其次,就是技术,框架,环境的选择,也是并非我可以自己决定。一方面出于对安全的考虑,一方面有着组内的各种原因,首先我能用的技术只能从大摩内部的库中申请,其次用什么技术也要考虑到业务,性能,以及可维护性等多方面因素,所以在一开始选择框架技术的时期内,在我这块可以说是变动非常大,可以说在主框架上我经历了两次转换,在数据库技术上也经历了两次转换,而每次转换时都需要我花费个人时间来掌握这些技术,这对我来说当时节奏还是非常紧凑的。开发项目如果说在学校里做个人开发的那段日子就像无拘无束的飞在天空,那么在企业里做开发工作则像带着铐链在那跳舞。在刚进入开发的那段时间内,我大部分时间都花在了调整配置,理清业务关系,确定需求,审视程序全局的连通性并为其做单元测试。在这段时间里,很多我在平日只要1秒钟就能解决的事情,放在公司里可能换一天也没有什么进展。这里我就要吐槽一下大摩的技术栈了,作为投行而言,系统最重要的是稳定性与安全性,新技术的引进是非常严格的,并且是迫切需要才会引进。而面向内部开发的组也是更不用太去关心新技术的演变,只需要保证现有的系统安全稳定即可。所以技术的革新演变与外面的互联网企业相比是比较落后的。可以说我现在负责的项目所有的框架技术是较为老的,所以在填坑的路上也是困难重重。第二周的前两三天我可以说是每天愁眉苦脸上班,然后每天愁眉苦脸下班,想了想今天做了很多,但又好像什么都没做。不过终于在我这么几天放弃午休并且加班到七点多的努力下,我对整项目的code struture的改造总算符合了我作为处女座对自己的代码洁癖,这让我感到很开心。后来的几天就过得比较开心了,理清了业务关系后的几次组会,前端提的每个需求我都在当天接需求然后都当天完美交付了。我觉得这高效率的开发过程和我之前痛苦的去适应去了解的前提是密不可分的。一点感想在最初的实习中,每次碰到困难以后我都十分烦躁,甚至有了打退堂鼓的想法。但是正如大家所见,像大摩这样的外企对给予offer是有target school的倾向的,而毫无疑问交大是大摩内最为吃香的学校。而我作为上大的学生,似乎是最近这好几年来第一个拿到这次summer intern的学生,况且我能有这次实习的机会,也离不开上大开源社区初代目学长给予的机会和帮助。直白的说,我的表现会直观的映射在大摩对上大的看法,如果我中途放弃或者消极对待,这明显会对后面的学弟学妹对进入大摩这样的公司造成负面的影响。于是我每次遇到很多匪夷所思的Bug时,我内心都会反复平静自己,不要生气,要知道自己现在代表着学校。现在这段艰难的适应期度过,我对自己目前的表现还算满意,希望自己可以顺利度过这段实习期,给后面的学弟学妹们做个榜样。


Redis中利用sizeof从sds转为sdshdr

概述今天在阅读 Redis 源码时看到 sds.h 中的 sdslen 和 sdsavail 中看到一行语句不是很理解struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));最后查了资料终于明白了,特此记录一下。原因这里 s 是 const sds 类型,相关类型定义如下typedef char *sds;struct sdshdr { unsigned int len; unsigned int free; char buf[];};当使用 sdsnewlen(const void *init, size_t initlen) 创建一个新的动态字符串时,Redis 会创建一个 struct sdshdr *sh 结构体,并将这个结构体的最后一个字符数组,也就是 sh-buf 返回,这样返回值是个普通的 C 字符串,因此便可以使用一些 string.h 和 stdlib.h 中的 C 字符串函数,而当想访问 sds 的 len 和 free 属性时,就需要先转成 struct sdshdr 结构。但是直接用 s-(sizeof(struct sdshdr)) 却是不知道怎么回事。后来直接使用那一行代码进行 Google,然后找到了结果。C99 中的标准规定:As a special case, the last element of a structure with more than one named member may have an incomplete array type; this is called a flexible array member. In most situations, the flexible array member is ignored. In particular, the size of the structure is as if the flexible array member were omitted except that it may have more trailing padding than the omission would imply.However, when a . (or -) operator has a left operand that is (a pointer to) a structure with a flexible array member and the right operand names that member, it behaves as if that member were replaced with the longest array (with the same element type) that would not make the structure larger than the object being accessed; the offset of the array shall remain that of the flexible array member, even if this would differ from that of the replacement array. If this array would have no elements, it behaves as if it had one element but the behavior is undefined if any attempt is made to access that element or to generate a pointer one past it.struct sdshdr 结构体中的最后一个 char buf[] 被称为 flexible array member ,在计算结构体大小的时候是不记入在内的,因此 sizeof(struct sdshdr) 实际上就是 sizeof(unsigned int) + sizeof(unsigned int) 这样就能理解了。-----------|5|0|redis|-----------^ ^sh sh-buf所谓的 sizeof(struct sdshdr) 实际上是就是 len 和 free 所占的大小,因此用 sh-buf 的位置减去 sizeof(struct sdshdr) 就是 sh 的位置了,再经过 struct sdshdr * 转换,就可以得到 sds 对应的 struct sdshdr 结构体了。


do{...} while(0) 的意义和用法

概述linux内核和其他一些开源的代码中,经常会遇到这样的代码:do{ ...}while(0)这样的代码一看就不是一个循环,do..while表面上在这里一点意义都没有,那么为什么要这么用呢?实际上,do{…}while(0)的作用远大于美化你的代码。辅助定义复杂的宏,避免引用的时候出错:举例来说,假设你需要定义这样一个宏:#define DOSOMETHING()\ foo1();\ foo2();这个宏的本意是,当调用DOSOMETHING()时,函数foo1()和foo2()都会被调用。但是如果你在调用的时候这么写:if(a0) foo1();foo2();这就出现了问题,因为无论a是否大于0,foo2()都会被执行,导致程序出错。那么仅仅使用{}将foo1()和foo2()包起来行么?我们在写代码的时候都习惯在语句右面加上分号,如果在宏中使用{},代码里就相当于这样写了:“{…};”,展开后就是这个样子:if(a0){ foo1(); foo2();};这样甚至不会编译通过。所以,很多人才采用了do{…}while(0);#define DOSOMETHING() \ do{ \ foo1();\ foo2();\ }while(0)\...if(a0) DOSOMETHING();...这样,宏被展开后,才会保留初始的语义。GCC提供了Statement-Expressions用以替代do{…}while(0); 所以你也可以这样定义宏:#define DOSOMETHING() ({\ foo1(); \ foo2(); \})避免使用goto对程序流进行统一的控制:有些函数中,在函数return之前我们经常会进行一些收尾的工作,比如free掉一块函数开始malloc的内存,goto一直都是一个比较简便的方法:int foo(){ somestruct* ptr = malloc(...); dosomething...; if(error) { goto END; } dosomething...; if(error) { goto END; } dosomething...;END: free(ptr); return 0;}由于goto不符合软件工程的结构化,而且有可能使得代码难懂,所以很多人都不倡导使用,那这个时候就可以用do{}while(0)来进行统一的管理:int foo(){ somestruct* ptr = malloc(...); do{ dosomething...; if(error) { break; } dosomething...; if(error) { break; } dosomething...; }while(0); free(ptr); return 0;}这里将函数主体使用do()while(0)包含起来,使用break来代替goto,后续的处理工作在while之后,就能够达到同样的效果。避免空宏引起的warning内核中由于不同架构的限制,很多时候会用到空宏,在编译的时候,空宏会给出warning,为了避免这样的warning,就可以使用do{}while(0)来定义空宏:#define EMPTYMICRO do{}while(0)定义一个单独的函数块来实现复杂的操作:当你的功能很复杂,变量很多你又不愿意增加一个函数的时候,使用do{}while(0);,将你的代码写在里面,里面可以定义变量而不用考虑变量名会同函数之前或者之后的重复。


浅析unix文件系统

前言先这篇文章的原因在于前几天操作系统考试之前复习到文件系统那一块,发现书上关于目录,目录文件,索引结点这些说的十分模糊。光是看书完全没明白他在说什么,还好这块内容我在学习unix编程时接触过,这里特此做一个总结。概述文件包含数据,目录是文件的列表。不同的目录相互连接构成树状结构,目录还可以包含其他目录。那么如何理解文件在”一个目录中”? 硬盘实际上是一个金属圆盘,每个盘面上都有磁性物质,这些圆盘又是如何显示为一个包含文件,目录的树状结构呢。从用户的角度看文件系统从用户的角度来看,系统中硬盘里的文件组成了一颗目录树,每个目录包含了文件或者其他的目录。 我们可以通过常用的命令如: cd ,ls ,pwd 来查看目录或者文件的信息。很显然,文件的查看或者是更新都是任何一个操作系统都会提供的功能。 但是命令ln却并不常见,但却是unix里的一个基本操作。假设我们输入以下命令touch xln ./x ./xlinkls ./xlinkcat ./x我们创建了一个文件叫做x,然后创建了一个链接指向了x文件,然后将ls的输出重定向给xlink的内容,那么当我们执行cat ./x的时候会发现输出了当前所在位置的文件信息。从这些就可以大致了解了Unix的文件系统,硬盘上呈现了一个深度和广度都广泛延伸的目录树,Unix也提供了许多命令来和这种结构的对象一起工作。但他们是如何工作的?目录是什么?如何知道文件所处的目录?从一个目录转换到另一个目录意味着什么? pwd又是如何得知你当前的位置? 这些问题我们将继续探讨下去。Unix文件系统内部结构硬盘的实质其实是由磁性盘片组成的计算机系统的一个设备。所谓的文件系统,其实就是对这个设备的多层抽象第一层抽象 从磁盘到分区一个磁盘能存储大量的数据,一个磁盘可被分成区,每个分区都可以看做是一个独立的磁盘第二层抽象 从磁盘到块序列一个磁盘由磁性盘片组成,每个盘片的表面被分为很多同心圆,这些同心圆称为磁道,每个磁道又进一步被分成扇区,每个扇区可以存储一定字节数的数据。例如常见的每个扇区是512字节。扇区是磁盘的基本存储单元。第三层抽象 从块序列到三个区域的划分文件系统可以用来存储文件内容,文件属性,和目录,这些不同类型的数据是如何存储在被编号的磁盘块上呢?Unix使用了一个简单的方法,将磁盘分成了三部分。超级块文件系统中的第一个块被称为超级快,这个块存放文件系统本身的结构信息i节点表每个文件都有一些属性,如大小,文件所有者和最近修改时间等。这些性质都被记录在一个称为i节点的结构中,所有的i节点有相同的大小,文件系统中的每个文件都有一个i节点。数据区文件的内容保存在这个区域,磁盘上所有的块的大小是相同的。如果文件包含了超过一个块的内容,则文件内容会存放在多个磁盘块中。 那么系统是如何跟踪这些独立的磁盘块?文件系统的实现,创建文件文件的内容和属性分区存放看起来很简单,但实际上是如何工作的额呢? 创建一个文件时又会发生什么?如果我输入命令 who user当这个命令完成时,文件系统增加了一个存放命令who输出内容的新文件。 创建一个新文件的主要操作如下。存储属性,内核找到一个空的i结点,比如第47块,内核将文件的信息记录其中存储数据,文件内容存储在数据区的块中,如627 200 992记录分配情况,内核在i结点的磁盘分布区记录了上述块序列。添加文件名到目录,新文件的文件名是user,unix内核将入口(47,user)添加到目录文件。cat命令的工作原理现在我们再回头看之前所打的cat命令。cat ./x内核首先在目录中寻找文件名,得到x记录所包含的i节点号定位所得到的I节点并读取数据块编号访问存储文件内容的数据块,通过上面两步,内核已经知道文件内容在哪些数据块以及他们的顺序。理解目录用户看到的文件系统是目录与子目录的集合,每个目录能够包含文件和子目录,每个子目录有一个父目录,在文件系统内部,目录是一个包含文件名与i节点对的列表的文件。从用户的角度看到的是一个文件名的列表,而从Unix的角度看到的是一个被命名的指针的列表。文件在目录中的真正含义一般都说某个文件在某个目录中,但是现在已经知道目录中存放的只是文件在i节点表的入口,而文件的内容则存储在数据区内,文件在某个目录中,从用户的角度看文件y在目录demodir中,而从系统角度来看,看到的则是目录中有一个包含文件名y和i节点为491的入口。简单的说,目录包含的是文件的引用,每个引用被称为连接,文件的内容存储在数据块,文件的属性则被记录在i节点的结构中,i节点的编号和文件名存储在目录中。目录包含子目录也同样如此。目录包含子目录的真正含义从用户的角度看a目录是demodir目录的一个子目录。实际上demodir包含一个指向那个子目录i节点的连接。从系统角度看,最上面一个表包含名为a指向277节点的连接。如何知道277是左边那个目录的i结点号呢? 内核在每个目录中都设置了目录本身的一个i节点号的入口,称为”.” 。 子目录的父目录同理。文件名在Unix的文件系统中,文件没有文件名,但是链接具有名字。文件仅仅拥有i节点号。


编写命令解释器 上篇

前言这周操作系统的最后一个实验室写一个Shell,然而实验手册的pdf上只指明了你必须要完成什么函数,对于怎么写则是一点提示都没有。所以本篇文章则立意于如何写出一个简单可用的shell,个人认为这个主题应该会分为上中下三篇文章来阐明从零开始的Shell编写过程Shell做了什么简单的说,shell是一个管理进程和运行程序的程序,所有常用的shell有三个基本功能运行程序管理输入输出可编程Shell是如何运行程序的shell打印提示符,输入命令,然后就运行这个命令,随手再次打印提示符,如此反复。那么在这个过程中,背后到底有什么?一个shell的主循环执行下面四步:用户输入shell建立进程shell将程序从磁盘载入程序在他的进程中运行直到结束所以一个Shell的主循环可以写成while(!end_of_input) get command execute command wait for command to finish所以要写一个shell ,就需要学会运行程序建立进程等待exit一个程序如何运行另一个程序使用函数 int execvp(const char* file,char* const argv[]) ,这个函数会从PATH变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,然后将第二个参数argv传递给执行的文件。使用demomain(){ char * arglist[3]; arglist[0] = "ls"; arglist[1] = "-l"; arglist[2] = 0; execvp("ls",arglist);}第一个Shell Demo12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667#include#include#include #include #include /** **prompting shell version 1 ** **Prompts for the command and its arguments. **Builds the argument vector for the call to execvp. **Uses execvp(), and never returns. **/#defineMAXARGS20#defineARGLEN100#defineCMDPROMPT"Command please: "#defineARGPROMPT"next argument please: "#defineprompt(n)printf("%s", (n)==0 ? CMDPROMPT : ARGPROMPT );main(){char*arglist[MAXARGS+1];intnumargs;charargbuf[ARGLEN];char*makestring();numargs = 0;while ( numargs #include#include #include #include /** **prompting shell version 2 ** **Solves the `one-shot' problem of version 1 **Uses execvp(), but fork()s first so that the **shell waits around to perform another command **New problem: shell catches signals. Run vi, press ^c. **/#defineMAXARGS20#defineARGLEN100#defineCMDPROMPT"Command please: "#defineARGPROMPT"next argument please: "#defineprompt(n)printf("%s", (n)==0 ? CMDPROMPT : ARGPROMPT );main(){char*arglist[MAXARGS+1];intnumargs;charargbuf[ARGLEN];char*makestring();numargs = 0;while ( numargs 8, exitstatus&0377);}}char *makestring( char *buf ){char*cp;if ( cp = malloc( strlen(buf) + 1 ) ){strcpy(cp, buf);return cp;}fprintf(stderr,"out of memory\n");exit(1);}总结到了这里,我们的shell demo已经可以基本的运行命令并且正常运行了,但是我们会发现一个新的问题,那么就是现在退出demo的唯一方法就是按ctrl-c键,那么如果在等待子进程结束时输入ctrl-c键会如何呢?我们会发现子进程结束,但是shell也技术了,ctrl-c生成的SIGINT信号不但杀死了运行的子进程,而且也杀死了运行shell的进程,这是为什么?键盘信号发给所有连接的进程程序shell和tr都连接到终端,当按下中断键以后,ttr驱动会告诉内核向所有由这个终端控制的进程发送SIGINT信号,子进程死了,shell也死了,即使他还在等待子进程的结束。那么如何才能让shell不被用户按下的中断或退出键杀死呢? 我将留到这一主题的中篇