程序員應該如何自我驅動,迅速獲得成長?
初入公司,從CRUD到運維支持
一年之前,我還是一個只會CRUD的普通程序員,常年與業務打交道,一套花式SSM框架三板斧從頭玩到底。
我入職了一個初創型的互聯網項目團隊,在迅速融入工作環境以后,我就開始上手寫起了CRUD代碼。雖然不知道底層原理, 但是SSM模版代碼已經爛熟于心,再加上有一些在以前工作時學習到的基礎和避坑的經驗,比如空值驗證,防止重復提交等, 讓我能夠比較快地完成業務代碼。
領導看到了這一點,把我安排到了運維支持部,開始讓我干一些運維的活(公司在初創期用人是有這個特點)。 那個時候,開始逐個上線一個一個的服務。這是我從零開始了解公司技術現狀的開端,也是一個加班到死的開端。其實加班的原因我是非常理解的,這個和公司的技術現狀是離不開的,且聽我慢慢道來。
艱難的系統部署:了解技術全貌
在一個晚上,領導拿了一份文檔,列了一系列要上線的服務和服務要部署在的服務器,然后我們就逐漸開干了。
新服務器都是極為純凈的,連一些基礎命令都沒有,只好慢慢學,慢慢裝。yum install xxxx,run xxx , ps aux|grep,telnet ....,一天過去了,什么jdk,tomcat,全都裝好了。
(ps. 做運維這段時間***的收獲就是linux命令玩得很熟練。)
把服務部署后,啟動時就遇上了難題:以前的服務是打war包放到tomcat的,但是現在的服務,需要java -jar 來啟動。 嘗試了可以后臺啟動的nohup java -jar , 直接翻車。 無奈去詢問之前的開發,人家說得用screen 命令后臺啟動程序。我是一個刨根問底的人,后來發現,開發在為了保持進程不退出,錯誤的使用了監聽控制臺事件的方式,導致了nohup啟動異常。
那個時候,正在搞核心業務開發的人因為一直忙于開發和調試,和我們的溝通少之又少;而負責部署的我們,又不了解業務,有些問題當時的開發也懶得詳細解釋,于是我們只能靠猜,來部署程序。
混亂的程度可見一斑, 但這不是幾個程序員所能左右的。
我在那個時候學習到的***件事就是:在信息不足和溝通受限的時候,你要嘗試學會必要的自行推理,根據已有信息的上下文來補全缺少的信息。
上線要緊,暫時就screen啟動程序吧。但是程序啟動以后,又遇到了更加尷尬的事情。
當時服務之間的調用方式,全部都是通過HttpClient直接調用目標服務。假如,我先啟動A服務,A服務依賴B服務,B服務沒啟動,A服務初始化時會報錯。那么,到底先啟動誰后啟動誰成了一個棘手的問題。
我查看了每一個服務的工程配置示例,發現每一個服務的config.properties,都有一個配置為root的選項,標識該服務的發布路徑,例如,用戶服務,他的config.properties 會配置 root=/userservice/ ,我就知道了,調用該服務肯定是這樣的http路徑:http://ip:port/userservice/xxxx。config.properties中,也會有一些帶有如下特征的配置 reference_user=http://ip:port/userservice/xxxx。我很自然的明白了,這肯定是配置了該服務依賴的其他服務的調用地址。因此,我就想到,我可以根據每一個服務的配置文件理順服務之間的調用關系。
為了確保我的猜想是正確的,我用網上的工具反編譯了一個工程,發現,果然原來服務之間都是通過HttpClient調用的。
然后我就畫了工程依賴圖,仔細抽絲剝繭的梳理出來了程序的具體的依賴關系。***,我終于對服務的部署順序和方式有了思路,而有些程序員,私下已經有人開始說干不下去了要離職。
怎么部署終于明白了,可是緊接著的一個尷尬的問題就是,程序都啟動了,也調通了,但是領導要求負載均衡,集群化,一個服務里直接調用另外一個服務使用了HttpClient直連的方式,怎么搞負載均衡?怎么搞?怎么集群?
當時離DeadLine不遠了,上線重要,于是忍痛購買了阿里云的SLB實現每一個服務的負載均衡,當時最痛苦的事情就是要理順這種蜘蛛網式的服務調用關系,好在我之前已經畫了工程依賴圖,這個也就好辦了。
天天加班折騰到凌晨兩點,一個半月以后,終于把全部的Service部署成功,難以想象的是,我一個完全不懂業務的人,完全根據日志信息和配置關系,搭建成功的服務,居然有80%的功能正常可用,這給了我這個苦哈哈加班的程序員不小的成就感,雖然搞不定的20%的功能后來由負責這塊的開發親自去搞了(領導出面,不能敷衍了)。雖然,后面還有一些小的插曲,雖然這個應用剛開始可能全是窟窿,但好在也終于如期上線。
這一個半月,我掌握了公司的技術架構,在一些程序員以不會為理由拒絕部署kafka,nginx,zookeeper,activemq等基礎設施的時候,我出手去部署,其實我也不會,但是從學怎么用,到部署完,時間也是夠的,我還學習了這些技術是干什么的,不虧。
最終,我整理了公司當時的技術全貌,一些網絡轉發和機房架構由于保密原因不予描述(雖然我也已經掌握了),我只單純說一下代碼層面的:
一,服務與服務之間,通過RESTful風格的HTTP調用,使用了HttpClient,需要自己維護蜘蛛網一樣的服務調用關系。
二,配置文件有在本地的,有在數據庫的,代碼里每次獲取配置每次都要load本地,select數據庫。(影響性能)。
三,無用的配置文件和無用代碼散落在各處,給運維造成了干擾,代碼有歷史遺留的味道。
四,技術棧不規范,有的人外置tomcat,有的人內置tomcat,有的人用了Netty,簡直就是群魔亂舞。
五,命名不規范不統一,詞不達意,公司用了elastic-job作為分布式任務調度平臺,elastic-job要求每一個job啟動時需要指定job-name,但是,運維經常在控制臺上無法根據job-name來定位到底是哪個job,比如,工程job為payServiceAllJob,在控制臺上卻注冊為AllJob,讓人摸不著頭腦。
痛點一:缺乏服務的自動發現
服務上線以后,由于本身項目就是在沒有規范,沒有制度的情況下放肆生長出來的,因此服務上線以后還是會瘋狂的加班,來彌補一個又一個補不完的窟窿。那個時候我還沒有接觸業務,但是也要和開發一起加班,人工值班監控。
已經一個月沒有寫增刪改查了,我就有機會開始搞一些事情了。那個時候,我請我的領導去說動技術部使用Dubbo,因為它能做服務自動發現,免配置擴容服務,是一個挺流行的RPC框架。但是由于當時我們沒有技術權限,這個事情是很難推動的,一切以穩定為主。
我使用過Dubbo,但是我一直不理解的是,為什么Dubbo使用了ZooKeeper就能自動擴容?就能服務自動發現?這個肯定和ZooKeeper有關系。由于我是刨根問底型的開發,因此我開始在周末時間和晚上和上下班路上自己學習ZooKeeper,什么順序節點,***節點,臨時節點,什么樹形存儲,動態監聽和通知。
終于,我就在沒有看Dubbo源碼的情況下突然恍然大悟:Dubbo的服務提供者和服務消費者都配置了服務name,如果我在ZooKeeper的一個name節點下存儲服務路徑集合,那么每次新增服務或者下線服務都會通知到任何監聽這個name的客戶端?(后來知道這叫做namespace命名服務),Dubbo肯定是使用了Zookeeper的命名服務來實現服務的動態發現的!
大部分程序員使用HttpClient都是使用的一個叫HttpHelper的工具類,我的改造就開始從HttpHelper這個工具類開始吧,讓程序員傳遞自己的服務的ip,port,se rviceName,destinctName,zookeeperUrl,然后我在工具類里封裝了獲取調用路徑列表,并封裝了兩個負載均衡算法:ip_hash,隨機數。(其實我只會寫這兩種)。
在公司的四次迭代里,他們在一邊飛速業務開發,一邊逐漸把自己的HttpHelper工具類替換給我寫的這個,終于可以卸下slb,實現簡單的HTTP服務動態發現了。
當時的反對聲音其實還是挺大的,不過運維的需求呼聲更高,配置URL太麻煩了!
那個時候,公司架構重組,得領導賞識,我被重新劃分到了技術部,做了基礎平臺研發部門的Team Leader,管理幾個程序員。
痛點二:缺乏配置中心
解決了***個痛點以后,我一邊寫增刪改查,一邊又想解決第二個痛點:配置問題。
之前每次部署程序到線上時,每次都要一個服務器一個服務器的改配置,我就想,我把我的時間浪費在這種一個服務器一個服務器改配置的事情上,真的是不甘心。
而且這么改配置,還容易出錯。雖然公司沒有主動要求寫什么。(公司的主要眼睛還是放在了實現功能和業務上)那個時候加班沒有那么狠了,程序員寫完增刪改查就回家了,我還在想配置中心怎么實現。那個時候微服務的概念火熱,我了解到了Spring-cloud-config。知道有叫做配置中心的這種東東,對,我們也需要配置中心,把配置集中抽出來管理。由于我已經掌握了ZooKeeper,自然知道了做配置中心的思路。
半個月時間我就寫完了,時間主要浪費在了寫頁面上。(汗,作為一個后臺程序員我承認我的頁面能力比較差)當然,這期間也經過了一些改版。我竟然不知道ZooKeeper有Curator這么好用的客戶端,之前一直使用的原生的org.apache.zookeeper這個原生客戶端操作,監聽消費以后還要重新監聽。
配置中心寫完以后,我也在一個周末發到了群里,并推廣了出去。這次,由于公司的人員團結力已經大有改觀,而且我也已經是組內Team Leader,先在組內普及了。
而且我這次組外的游說和推廣也沒有那么困難了,最終也都一個服務一個服務的落地了配置中心,并實現了一處改動,處處生效,也實現了傳說中的配置熱更新。這些,并沒有什么高深的技術,僅僅就是依賴了一個ZooKeeper。
弄完這些,我已經感受到了做技術的樂趣,同時,我的領導也覺得好像讓我寫增刪改查有點浪費,要求我把我手頭的任務全部分給組員,自己則是去解決別人的問題......我贏得了更多的時間去自我驅動,改善公司的基礎設置。
痛點三:缺乏緩存框架
接下來我本以為自己要沒事做了,但是,得益于左耳聽風的一個專欄,他說,描述一個業務(DSL),要比編寫一個業務更具有維護性,公司很多的人都在使用Redis緩存,但是,用的庫各不一樣,還經常出各種奇怪的問題,調試起來及其繁瑣。
當然,最終讓我受不了開始決定寫緩存框架的是因為我看到了我的組員寫的代碼與業務嚴重耦合,一會操作Redis,一會操作數據庫。我發現了這個痛點,研究了一下Spring的AOP和他的@Transtional 注解如何實現以后,終于決定手寫一個微型的緩存框架。基礎思路就是通過可插拔的@CacheEnable(key=xxxx,timeout=xxx......)實現Redis緩存,完全不侵入業務代碼。如果不用了,把注解在方法上移除了就好了。
手寫這個Redis緩存框架使用了半個月,畢竟自己的技術能力還是有限,如何實現AOP和Spring集成,怎么抽象等等,都會讓我每天都思考半天才下筆,甚至有時候一天寫不了一行代碼。
期間看了一本叫做《面向對象編程》的書籍,里面說,面向接口編程,依賴抽象,而不是依賴具體的實現......我突然就像打通了任督二脈。我在CacheHandler里面依賴了CacheStorage接口,而把RedisCacheStorage作為一個實現類注入了進去,因此,后來我這個框架可以同時支持redis,memcache,local。
寫完這個框架以后,我總結了三個可抽象的點:
1. 序列化方式(jdk,protobuf,thift,json)
2. 腳本解析器方式(就是解析key的方式:比如,可以使用spel或者ognl:"userId"+#user.id )
3. 緩存實現方式(redis,memcache)。
后來我從劉大的碼農翻身公眾號的一篇將日志系統的設計里知道了正交一詞,大概這就是正交吧。最尷尬的是,原來Spring早就實現了,叫做SpringCache。汗。
(碼農翻身注:那篇文章叫做《一個著名的日志系統是怎么設計的》)
后來的事情,我也不詳細的講了,大概就是從私有框架,換成了公共成熟的庫,我們也終于用上了Dubbo框架。
而對我自己本身,我只是更加理解了一些之前無法理解的事情:Dubbo為什么能改一個組件的配置那個組件就換了一種實現。(面向接口編程)看Mybatis的源碼也沒那么困難了,兩年前完全下不去手。當然,我也變得更加熱愛技術了。
我的心得體會
那就給大家分享一下我近兩年來的心得體會。
1. 作為一個程序員,一定要懂得自我挖掘,而不是僅僅實現業務功能就好了,也不能干等著領導分配任務。
功能耦合了,寫代碼慢了,運維麻煩了,這些,都是潛在的需求,我們現在的加班,是為了以后不加班,是為了提高自己的效率,是為了不能原地踏步。
2. 一定要真正地學會一個技術,我在實際工作中,發現有的同學使用了一年的Git,竟然不知道如何把GitHub的項目拉取到本地,但是他會把GitLib的項目拉取到本地。
程序啟動異常,有同學從網上找了一個jar包導入到了工程里,但是問他為什么把jar包導入到工程里代碼就啟動正常了,竟然不知道。
有的同學使用了一年的Maven,不知道mvn compile 是什么含義,只知道mvn package是打包。
這些,就是沒有真正的學會了一個技術,僅僅是工作時機械式的使用了,僅僅是復制粘貼了,下一個項目再復制粘貼過來就行了,模仿著別人的代碼寫邏輯,這是學不到東西的。
我覺得,你一定會有時間去學習這些東西,網上大把大包的資料和教程。一定要知其然而知其所以然,一定不要一模一樣的配置換了一種配置方式你就看不懂了。
3. 要掌握公司的技術棧,要刨根問底,公司用的什么rpc框架?怎么使用?原理是什么?公司用的nginx,nginx怎么配置的。公司用了配置中心,配置中心是什么?公司的負載均衡框架用什么做的,存在什么問題?
我寫了一個緩存框架,你有沒有沖動研究一下到底是怎么實現的?保持技術好奇心是十分重要的。我認識的一個同學,來我們公司以后,簡歷上寫的,會SSM。后來經歷了我的配置中心演進之路,還學會用了。后來他離職了,簡歷上寫的還是會SSM,沒別的了。本來,如果他把配置中心講一講會是一個很大的亮點。工作了一年,最怕的就是揮一揮衣袖,不帶走一片云彩。
4. 要善于發掘,總有一些東西是亮點,是金子,只是你發現不了。
我在我們公司學到的一個安全方面的設計就是token機制,這改變了我的觀念,以前一直以為做單點登陸只能session共享,現在才知道,還能使用時間換空間的方式,使用token簽名代替sessionId,使登錄這個過程變得成為無狀態的計算......這是我發現的,很多人程序員根本不能意識到這些技術亮點,并融化到自己腦海里成為自己的東西。
5. 使用碎片化時間去學習,不要總抱怨沒有時間。
學習是枯燥的,但是,當你收到了學習帶來的巨大紅利,你會越來越想學習。我親身的體驗就是,當我寫的配置中心和緩存框架投放到了生產中,并得到了我想要的效果后,我現在更加喜歡學習,更加癡迷于技術了,我的技術能力并不強,很多的東西,都是因為我的興趣逐漸被激發,因為我的熱愛,才發生了質變。
6. 多思考,正確地實現業務。
有沒有觀察過,同樣的業務,有的人寫出來的實現就非常的豐滿和穩定,而有的人寫出來的接口可能直接調用一下就拋出了錯誤。很多程序員寫代碼只關心正常分支的邏輯,從來不考慮異常邏輯的處理。在寫業務代碼的時候多去分析用戶場景,也會規避很多問題。
舉個例子,接口的參數的驗證,你可以根據使用場景來編寫額外的適配和容錯。比如,接口中有一個字段A,這個字段大小寫敏感嗎?這個字段萬一用戶多輸入了一個空格怎么辦?如果你能根據業務分析到,字段大小寫不敏感,我要全部轉成小寫來比較,空格不是正常參數的一部分,我要進行去空格來簡單的容錯。那么,你可能會避免有一天某個業務人員跑過來跟你說,我明明寫的正確為什么你告訴我參數不存在。
不要小看業務代碼,不必掌握多么高深的技術原理,只要能把業務邏輯實現的健壯,也已經很考驗能力了,說明了你是一個喜歡思考和注重細節的人。
結束語
現在的我,越來越喜歡研究源碼,研究底層,并伴隨著的是,我以前的一些疑惑都迎刃而解,這當然是因為我得到了正向的反饋,付出會有收獲。現在被公司內部的部分程序員誤認為是大神,當然,我還差得很遠。
現在公司技術,代碼逐漸在變好,工程規范逐漸在行程和標準化,統一化。這是值得欣慰的,我最近在研究API網關,公司的業務并發量在上升,我覺得需要一個入口統一管理API,負責鑒權,認證,限流,熔斷等一系列的功能。(這些詞都是在微服務課堂上學習的)。我已經開發了一部分了。使用Netty接收請求,使用HttpClientPool轉發請求,中間使用責任鏈模式做Handler中轉攔截處理,不知道何時能竣工。