程序員的重復勞動陷阱

程序員的重復勞動陷阱

作者: ChaosYang1987 來源: 博客園 發布時間: 2019-12-15 15:17 閱讀: 3310 次 推薦: 68 原文鏈接 [收藏]

  同樣是一樣的計算機專業畢業,進入職場的職位和工作都差不多,為何有些程序員短短幾年就成長為全能選手或領域專家,有些程序員還在做CRUD?

  程序員的重復勞動陷阱

  不知道大家有沒有這樣的感覺,每次加入一個新的公司/組,一開始總是要學這個學那個,可能會花很多時間看現有的代碼,然后花一些時間實現一點點小的功能,等到經過一段時間后,自己對工作越來越得心應手,提來的類似需求馬上就可以做,以做得多做得快為驕傲,覺的這樣可以更受老板青睞,可以升職加薪。

  我在畢業第三年的時候加入前公司,在加入公司的第一個季度,我主要再做一些邊緣工具以及理解系統,從第二個季度開始在組里的核心業務上開發。當時自己為了能夠快速的出成果,會從組里所有的任務里挑看著比較容易實現的做,往往一天就可以做完一個或者兩個任務。做完一個任務后,發現backlog里面有相似的任務,我也“趕緊”搶過來assign給自己,然后快速的做完,提交code review。從那個季度開始我每個季度做的工單越來越多,超過組里的所有其他成員,自己也對自己的“高效”洋洋得意,覺的自己工作的非常充實,進步很大。

  然而在這個過程中,我已經不知不覺得掉到“重復勞動”的陷阱中去了。

  我們在寫代碼的時候,有一個原則交叫DRY(Don’t Repeat Youself)原則,簡單通俗的說就是不要copy paste代碼,能抽象成函數的抽象成函數,能抽象成基類的抽象成基類。但是程序員的工作本身也應該遵循一樣的道理,那就是盡量不要做重復的工作。

  重復勞動對程序員的危害

  回到開篇的問題,同樣是一樣的計算機專業畢業,進入職場的職位和工作都差不多,為何有些程序員短短幾年就成長為全能選手或領域專家,有些程序員還在做CRUD?

  大部分的技術學習曲線類似于上圖,經歷過短暫的入門期和相對長一些的積累期之后,可能大部分技術都會進入到高效期。在入門期和積累期的時候可能技能使用的效率會低一些,進入到高效期之后,隨著技能使用的效率大大提高,工作所產生的“輸出”也越來越大。因此“高效期“給人以充實的假象。

  一旦自己的某項技術進入到高效期,在此基礎上的提升會非常困難,可能之前工作三個月所掌握的新知識,比之后一年在工作中積累的要多。有時候我們看一個程序員工作了5年,但是他可能第一年學習并熟悉所用的技術,接下來4年都在做相同的工作,解決類似的需求,那么他的5年工作經驗等于1年乘以5。

  而有些程序員,他每工作一段時間之后,都會鉆研技術更深的部分,或者去學習新的技術,總是保持著在嘗試自己并不擅長的領域,那么這樣的程序員,他的5年工作經驗會比前一種程序員要多。

  如何擺脫重復勞動的循環

  既然重復勞動的危害這么大,那么我們是否可以擺脫重復勞動的循環呢?

  有的時候,程序員自己也不想老是重復的干類似的東西,但是無奈被派發的任務重復的很多,似乎自己可以選擇的不多。

  在我自己在第三年大量重復勞動之后,我的經理找到我談話,說我不應該這樣重復自己,同樣的事情做一兩次就好了,再重復的做對自己的幫助不大。我分享一下我是怎么樣避免重復的勞動的:

  1. 找到Pattern,解決一類問題而不是一個問題。當你解決了N次類似的需求的時候,是否可以把這些問題抽象出來,是否可以去自動化的實現這類需求?改了N次bug之后,是否可以發現bug的規律,能夠開發出靜態分析工具來抓住這些bug?
  2. 嘗試用新的技術解決同樣的問題。有時當項目的實現并沒有多少規定的時候,我們可以在一定的自由度下嘗試新的工具。今年年初的時候我去嘗試修改一個已有的內部工具前段,持著學習新技術的心理,我用Redux重新實現了前段,而不是在原有的jQuery的前段基礎上修改。
  3. 嘗試換崗。換崗位可以直接的讓你接受不同的項目,做一些不同的事情。我在上家公司的第一組待了近三年才換組,現在來看應該更早的時候嘗試不同的事情。換崗位也會帶來一些其他的問題,比如到新崗位之后可能會影響晉升速度,需要重新建立自己的權威等等。
  4. 換工作。換工作是一個終極大招,它會帶來很多其他的變化,不建議只是為了脫離重復勞動而換工作。如果沒有養成良好的學習習慣,那么換一份新工作之后也很有可能陷入到新的重復勞動的循環中。

  重復勞動不可以完全避免

  重復勞動是否可以完全避免呢?

  我覺的是不可以避免的。以上的內容都是基于程序員成長的角度去分析問題,重復勞動是有害的。但是將程序員的勞動視為價值輸出的話,熟練的價值輸出確實也是程序員的價值之一,可以爭取到更高的薪酬。

  我們站在組織的角度上來看,重復的需求永遠存在,這些重復的需求需要被完成。如果在人員配置有限的情況下,不可避免的單個個體成員需要去進行一定的重復勞動。而由于時間上的緊迫性,可能必須要用高效粗暴的方法來實現。

  如果你是公司的初創成員,需要在初期做大量的重復工作來從無到有的實現新的產品,那毫無疑問這是應該做的,因為這樣的重復勞動帶來的收益可能是巨大的。

  希望大家在工作中都可以正確的認識到重復勞動的陷阱,讓自己能夠保持持久的成長。

68
2

請先登錄 提交中…

標簽:程序員 重復勞動

文章列表

從零開始入門 K8s | 手把手帶你理解 etcd

從零開始入門 K8s | 手把手帶你理解 etcd

作者: 阿里巴巴云原生 來源: 博客園 發布時間: 2020-01-13 11:16 閱讀: 1075 次 推薦: 3 原文鏈接 [收藏]

  作者 | 曾凡松(逐靈) 阿里云容器平臺高級技術專家

  本文整理自《CNCF x Alibaba 云原生技術公開課》第 16 講。

導讀:etcd 是用于共享配置和服務發現的分布式、一致性的 KV 存儲系統。本文從 etcd 項目發展所經歷的幾個重要時刻開始,為大家介紹了 etcd 的總體架構及其設計中的基本原理。希望能夠幫助大家更好的理解和使用 etcd。

  一、etcd 項目的發展歷程

  etcd 誕生于 CoreOS 公司,它最初是用于解決集群管理系統中 OS 升級的分布式并發控制以及配置文件的存儲與分發等問題。基于此,etcd 被設計為提供高可用、強一致的小型 keyvalue 數據存儲服務。

  項目當前隸屬于 CNCF 基金會,被 AWS、Google、Microsoft、Alibaba 等大型互聯網公司廣泛使用。

1.png

  最初,在 2013 年 6 月份由 CoreOS 公司向 GitHub 中提交了第一個版本的初始代碼。

  到了 2014 年的 6 月,社區發生了一件事情,Kubernetes v0.4 版本發布。這里有必要介紹一下 Kubernetes 項目,它首先是一個容器管理平臺,由谷歌開發并貢獻給社區,因為它集齊了谷歌在容器調度以及集群管理等領域的多年經驗,從誕生之初就備受矚目。在 Kubernetes v0.4 版本中,它使用了 etcd 0.2 版本作為實驗核心元數據的存儲服務,自此 etcd 社區得到了飛速的發展。

  很快,在 2015 年 2 月份,etcd 發布了第一個正式的穩定版本 2.0。在 2.0 版本中,etcd 重新設計了 Raft 一致性算法,并為用戶提供了一個簡單的樹形數據視圖,在 2.0 版本中 etcd 支持每秒超過 1000 次的寫入性能,滿足了當時絕大多數的應用場景需求。2.0 版本發布之后,經過不斷的迭代與改進,其原有的數據存儲方案逐漸成為了新時期的性能瓶頸,之后 etcd 啟動了 v3 版本的方案設計。

  2017 年 1 月份的時候,etcd 發布了 3.1 版本,v3 版本方案基本上標志著 etcd 技術上全面成熟。在 v3 版本中 etcd 提供了一套全新的 API,重新實現了更高效的一致性讀取方法,并且提供了一個 gRPC 的 proxy 用于擴展 etcd 的讀取性能。同時,在 v3 版本的方案中包含了大量的 GC 優化,在性能優化方面取得了長足的進步,在該版本中 etcd 可以支持每秒超過 10000 次的寫入。

  2018 年,CNCF 基金會下的眾多項目都使用了 etcd 作為其核心的數據存儲。據不完全統計,使用 etcd 的項目超過了 30 個,在同年 11 月份,etcd 項目自身也成為了 CNCF 旗下的孵化項目。進入 CNCF 基金會后,etcd 擁有了超過 400 個貢獻組,其中包含了來自 AWS、Google、Alibaba 等 8 個公司的 9 個項目維護者。

  2019 年,etcd 即將發布全新的 3.4 版本,該版本由 Google、Alibaba 等公司聯合打造,將進一步改進 etcd 的性能及穩定性,以滿足在超大型公司使用中苛刻的場景要求。

  二、架構及內部機制解析

  總體架構

  etcd 是一個分布式的、可靠的 key-value 存儲系統,它用于存儲分布式系統中的關鍵數據,這個定義非常重要。

2.png

  一個 etcd 集群,通常會由 3 個或者 5 個節點組成,多個節點之間通過 Raft 一致性算法的完成分布式一致性協同,算法會選舉出一個主節點作為 leader,由 leader 負責數據的同步與數據的分發。當 leader 出現故障后系統會自動地選取另一個節點成為 leader,并重新完成數據的同步。客戶端在多個節點中,僅需要選擇其中的任意一個就可以完成數據的讀寫,內部的狀態及數據協同由 etcd 自身完成。

  在 etcd 整個架構中,有一個非常關鍵的概念叫做 quorum,quorum 的定義是 (n+1)/2,也就是說超過集群中半數節點組成的一個團體,在 3 個節點的集群中,etcd 可以容許 1 個節點故障,也就是只要有任何 2 個節點可用,etcd 就可以繼續提供服務。同理,在 5 個節點的集群中,只要有任何 3 個節點可用,etcd 就可以繼續提供服務,這也是 etcd 集群高可用的關鍵。

  在允許部分節點故障之后繼續提供服務,就需要解決一個非常復雜的問題:分布式一致性。在 etcd 中,該分布式一致性算法由 Raft 一致性算法完成,這個算法本身是比較復雜的有機會再詳細展開,這里僅做一個簡單的介紹以方便大家對其有一個基本的認知。Raft 一致性算法能夠工作的一個關鍵點是:任意兩個 quorum 的成員之間一定會有一個交集(公共成員),也就是說只要有任意一個 quorum 存活,其中一定存在某一個節點(公共成員),它包含著集群中所有的被確認提交的數據。正是基于這一原理,Raft 一致性算法設計了一套數據同步機制,在 Leader 任期切換后能夠重新同步上一個 quorum 被提交的所有數據,從而保證整個集群狀態向前推進的過程中保持數據的一致。

3.png

  etcd 內部的機制比較復雜,但 etcd 給客戶提供的接口是簡單直接的。如上圖所示,我們可以通過 etcd 提供的客戶端去訪問集群的數據,也可以直接通過 http 的方式(類似 curl 命令)直接訪問 etcd。在 etcd 內部,其數據表示也是比較簡單的,我們可以直接把 etcd 的數據存儲理解為一個有序的 map,它存儲著 key-value 數據。同時 etcd 為了方便客戶端去訂閱數據的變更,也支持了一個 watch 機制,通過 watch 實時地拿到 etcd 中數據的增量更新,從而實現與 etcd 中的數據同步等業務邏輯。

  API 介紹

  接下來我們看一下 etcd 提供的接口,這里將 etcd 的接口分為了 5 組:

4.png

  • 第一組是 Put 與 Delete。上圖可以看到 put 與 delete 的操作都非常簡單,只需要提供一個 key 和一個 value,就可以向集群中寫入數據了,刪除數據的時候只需要指定 key 即可;
  • 第二組是查詢操作。etcd 支持兩種類型的查詢:第一種是指定單個 key 的查詢,第二種是指定的一個 key 的范圍;
  • 第三組是數據訂閱。etcd 提供了 Watch 機制,我們可以利用 watch 實時訂閱到 etcd 中增量的數據更新,watch 支持指定單個 key,也可以指定一個 key 的前綴,在實際應用場景中的通常會采用第二種形勢;
  • 第四組事務操作。etcd 提供了一個簡單的事務支持,用戶可以通過指定一組條件滿足時執行某些動作,當條件不成立的時候執行另一組操作,類似于代碼中的 if else 語句,etcd 確保整個操作的原子性;
  • 第五組是 Leases 接口。Leases 接口是分布式系統中常用的一種設計模式,其用法后面會具體展開。

  數據版本機制

  要正確使用 etcd 的 API,必須要知道內部對應數據版本號的基本原理。

  首先 etcd 中有個 term 的概念,代表的是整個集群 Leader 的任期。當集群發生 Leader 切換,term 的值就會 +1。在節點故障,或者 Leader 節點網絡出現問題,再或者是將整個集群停止后再次拉起,都會發生 Leader 的切換。

  第二個版本號叫做 revision,revision 代表的是全局數據的版本。當數據發生變更,包括創建、修改、刪除,其 revision 對應的都會 +1。特別的,在集群中跨 Leader 任期之間,revision 都會保持全局單調遞增。正是 revision 的這一特性,使得集群中任意一次的修改都對應著一個唯一的 revision,因此我們可以通過 revision 來支持數據的 MVCC,也可以支持數據的 Watch。

  對于每一個 KeyValue 數據節點,etcd 中都記錄了三個版本:

  • 第一個版本叫做 create_revision,是 KeyValue 在創建時對應的 revision;
  • 第二個叫做 mod_revision,是其數據被操作的時候對應的 revision;
  • 第三個 version 就是一個計數器,代表了 KeyValue 被修改了多少次。

  這里可以用圖的方式給大家展示一下:

5.png

  在同一個 Leader 任期之內,我們發現所有的修改操作,其對應的 term 值始終都等于 2,而 revision 則保持單調遞增。當重啟集群之后,我們會發現所有的修改操作對應的 term 值都變成了 3。在新的 Leader 任期內,所有的 term 值都等于3,且不會發生變化,而對應的 revision 值同樣保持單調遞增。從一個更大的維度去看,可以發現在 term=2 和 term=3 的兩個 Leader 任期之間,數據對應的 revision 值依舊保持了全局單調遞增。

  mvcc & streaming watch

  了解 etcd 的版本號控制后,接下來如何使用 etcd 多版本號來實現并發控制以及數據訂閱(Watch)。

  在 etcd 中支持對同一個 Key 發起多次數據修改,每次數據修改都對應一個版本號。etcd 在實現上記錄了每一次修改對應的數據,也就就意味著一個 key 在 etcd 中存在多個歷史版本。在查詢數據的時候如果不指定版本號,etcd 會返回 Key 對應的最新版本,當然 etcd 也支持指定一個版本號來查詢歷史數據。

6.png

  因為 etcd 將每一次修改都記錄了下來,使用 watch 訂閱數據時,可以支持從任意歷史時刻(指定 revision)開始創建一個 watcher,在客戶端與 etcd 之間建立一個數據管道,etcd 會推送從指定 revision 開始的所有數據變更。etcd 提供的 watch 機制保證,該 Key 的數據后續的被修改之后,通過這個數據管道即時的推送給客戶端。

  如下圖所示,etcd 中所有的數據都存儲在一個 b+tree 中(灰色),該 b+tree 保存在磁盤中,并通過 mmap 的方式映射到內存用來支持快速的訪問。灰色的 b+tree 中維護著 revision 到 value 的映射關系,支持通過 revision 查詢對應的數據。因為 revision 是單調遞增的,當我們通過 watch 來訂閱指定 revision 之后的數據時,僅需要訂閱該 b+ tree 的數據變化即可。

7.png

  在 etcd 內部還維護著另外一個 btree(藍色),它管理著 key 到 revision 的映射關系。當客戶端使用 key 查詢數據時,首先需要經過藍色的 btree 將 key 轉化為對應的 revision,再通過灰色的 btree 查詢到對應的數據。

  細心的讀者會發現,etcd 將每一次修改都記錄下來會導致數據持續增長,這會帶來內存及磁盤的空間消耗,同時也會影響 b+tree 的查詢效率。為了解決這一問題,在 etcd 中會運行一個周期性的 Compaction 的機制來清理歷史數據,將一段時間之前的同一個 Key 的多個歷史版本數據清理掉。最終的結果是灰色的 b+tree 依舊保持單調遞增,但可能會出現一些空洞。

  mini-transactions

  在理解了 mvcc 機制及 watch 機制之后,繼續看 etcd 提供的 mini-transactions 機制。etcd 的 transaction 機制比較簡單,基本可以理解為一段 if-else 程序,在 if 中可以提供多個操作,如下圖所示:

8.png

  If 里面寫了兩個條件,當 Value(key1) 大于 “bar" 并且 Version(key1) 的版本等于 2 的時候,執行 Then 里面指定的操作:修改 Key2 的數據為 valueX,同時刪除 Key3 的數據。如果不滿足條件,則執行另外一個操作:Key2 修改為 valueY。

  在 etcd 內部會保證整個事務操作的原子性。也就是說 If 操作所有的比較條件,其看到的視圖一定是一致的。同時它能夠確保多個操作的原子性不會出現 Then 中的操作僅執行了一半的情況。

  通過 etcd 提供的事務操作,我們可以在多個競爭中去保證數據讀寫的一致性,比如說前面已經提到過的 Kubernetes 項目,它正是利用了 etcd 的事務機制,來實現多個 KubernetesAPI server 對同樣一個數據修改的一致性。

  lease 的概念及用法

  lease 是分布式系統中一個常見的概念,用于代表一個分布式租約。典型情況下,在分布式系統中需要去檢測一個節點是否存活的時,就需要租約機制。

9.png

  上圖示例中的代碼示例首先創建了一個 10s 的租約,如果創建租約后不做任何的操作,那么 10s 之后,這個租約就會自動過期。接著將 key1 和 key2 兩個 key value 綁定到這個租約之上,這樣當租約過期時 etcd 就會自動清理掉 key1 和 key2,使得節點 key1 和 key2 具備了超時自動刪除的能力。

  如果希望這個租約永不過期,需要周期性的調用 KeeyAlive 方法刷新租約。比如說需要檢測分布式系統中一個進程是否存活,可以在進程中去創建一個租約,并在該進程中周期性的調用 KeepAlive 的方法。如果一切正常,該節點的租約會一致保持,如果這個進程掛掉了,最終這個租約就會自動過期。

  在 etcd 中,允許將多個 key 關聯在同一個 lease 之上,這個設計是非常巧妙的,可以大幅減少 lease 對象刷新帶來的開銷。試想一下,如果有大量的 key 都需要支持類似的租約機制,每一個 key 都需要獨立的去刷新租約,這會給 etcd 帶來非常大的壓力。通過多個 key 綁定在同一個 lease 的模式,我們可以將超時間相似的 key 聚合在一起,從而大幅減小租約刷新的開銷,在不失靈活性同時能夠大幅提高 etcd 支持的使用規模。

  三、典型的使用場景介紹

  元數據存儲

  Kubernetes 將自身所用的狀態存儲在 etcd 中,其狀態數據的高可用交給 etcd 來解決,Kubernetes 系統自身不需要再應對復雜的分布式系統狀態處理,自身的系統架構得到了大幅的簡化。

10.png

  Server Discovery (Naming Service)

  第二個場景是 Service Discovery,也叫做名字服務。在分布式系統中,通常會出現的一個模式就是需要多個后端(可能是成百上千個進程)來提供一組對等的服務,比如說檢索服務、推薦服務。

11.png

  對于這樣一種后端服務,通常情況下為了簡化后端服務的運維成本(節點故障時隨時被替換),后端的這一進程會被類似 Kubernetes 這樣的集群管理系統所調度,這樣當用戶(或上游服務)調用過來時,我們就需要一個服務發現機制來解決服務路由問題。這一服務發現問題可以利用 etcd 來高效解決,方式如下:

  • 在進程內部啟動之后,可以將自身所在的地址注冊到 etcd;
  • API 網關夠通過 etcd 及時感知到后端進程的地址,當后端進程發生故障遷移時會重新注冊到 etcd 中,API 網關也能夠及時地感知到新的地址;
  • 利用 etcd 提供的 Lease 機制,如果提供服務的進程運行過程中出現了異常(crash),API 網關也可以摘除其流量避免調用超時。

  在這一架構中,服務狀態數據被 etcd 接管,API 網關本身也是無狀態的,可以水平地擴展來服務更多的客戶。同時得益于 etcd 的良好性能,可以支持上萬個后端進程的節點,使得這一架構可以服務于大型的企業。

  Distributed Coordination: leader election

  在分布式系統中,有一種典型的設計模式就是 Master+Slave。通常情況下,Slave 提供了 CPU、內存、磁盤以及網絡等各種資源 ,而 Master 用來調和這些節點以使其對外提供一個服務(比如分布式存儲,分布式計算)。典型的分布式存儲服務(HDFS)以及分布式計算服務(Hadoop)它們都是采用了類似這樣的設計模式。這樣的設計模式會有一個典型的問題:Master 節點的可用性。當 Master 故障以后,整個集群的服務就掛掉了,沒有辦法再服務用戶的請求。

  為了解決這個問題,典型的做法就是啟動多個 Master 節點。因為 Master 節點內會包含控制邏輯,多個節點之間的狀態同步是非常復雜的,這里最典型的做法就是通過選主的方式,選出其中一個節點作為主節點來提供服務,另一個節點處于等待狀態。

12.png

  通過 etcd 提供的機制可以很容易的實現分布式進程的選主功能,比如可以通過對同一個 key 的事務寫來實現搶主的邏輯。一般而言,被選主的 Leader 會將自己的 IP 注冊到 etcd 中,使得 Slave 節點能夠及時獲取到當前的 Leader 地址,從而使得系統按照之前單個 Master 節點的方式繼續工作。當 Leader 節點發生異常之后,通過 etcd 能夠選取出一個新的節點成為主節點,并且注冊新的 IP 之后,Slave 又能夠拉取新的主節點的 IP,繼續恢復服務。

  Distributed Coordination 分布式系統并發控制

  在分布式系統中,當我們去執行一些任務,比如說去升級 OS、或者說升級 OS 上的軟件的時候、又或者去執行一些計算任務的時候,出于對后端服務的瓶頸或者是業務穩定性的考慮,通常情況下需要控制任務的并發度。如果該任務缺少一個調和的 Master 節點,可以通過 etcd 來完成這樣的分布式系統工作。

13.png

  在這個模式中通過 etcd 去實現一個分布式的信號量,并且可以利用 etcd leases 機制來實現自動地剔除掉故障節點。在進程執行過程中,如果進程的運行周期比較長,我們可以將進程運行過程中的一些狀態數據存儲到 etcd,從而使得當進程故障之后且需要恢復到其他地方時,能夠從 etcd 中去恢復一些執行狀態,而不需要重新去完成整個的計算邏輯,以此來加速整個任務的執行效率。

  本文總結

  本文分享的主要內容就到此為止了,這里為大家簡單總結一下:

  • 第一部分,為大家介紹了 etcd 項目是如何誕生的,以及在 etcd 發展過程中經歷的幾個重要時刻;
  • 第二部分,為大家介紹了 etcd 的架構以及其內部的基本操作接口,在理解 etcd 是如何實現高可用的基礎之上,展示了 etcd 數據的一些基本操作以及其內部的實現原理;
  • 第三部分,介紹了三種典型的 etcd 使用場景,以及在對應的場景下,分布式系統的設計思路。

3
0

請先登錄 提交中…

標簽:kubernetes etcd

文章列表

從頭到尾徹底解析Hash表算法

從頭到尾徹底解析Hash表算法

發布時間: 2013-10-02 10:26 閱讀: 86717 次 推薦: 29 原文鏈接 [收藏]

  作者:July、wuliming、pkuoliver

  說明:本文分為三部分內容, 第一部分為一道百度面試題Top K算法的詳解;第二部分為關于Hash表算法的詳細闡述;第三部分為打造一個最快的Hash表算法。

  第一部分:Top K 算法詳解

  問題描述(百度面試題):

  搜索引擎會通過日志文件把用戶每次檢索使用的所有檢索串都記錄下來,每個查詢串的長度為1-255字節。假設目前有一千萬個記錄(這些查詢串的重復度比較高,雖然總數是1千萬,但如果除去重復后,不超過3百萬個。一個查詢串的重復度越高,說明查詢它的用戶越多,也就是越熱門。),請你統計最熱門的10個查詢串,要求使用的內存不能超過1G。

  必備知識:

  什么是哈希表?

  哈希表(Hash table,也叫散列表),是根據key而直接進行訪問的數據結構。也就是說,它通過把key映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫做散列函數,存放記錄的數組叫做散列表。

  哈希表的做法其實很簡單,就是把key通過一個固定的算法函數即所謂的哈希函數轉換成一個整型數字,然后就將該數字對數組長度進行取余,取余結果就當作數組的下標,將value存儲在以該數字為下標的數組空間里。

而當使用哈希表進行查詢的時候,就是再次使用哈希函數將key轉換為對應的數組下標,并定位到該空間獲取value,如此一來,就可以充分利用到數組的定位性能進行數據定位文章第二、三部分,會針對Hash表詳細闡述

  問題解析:

  要統計最熱門查詢,首先就是要統計每個Query出現的次數,然后根據統計結果,找出Top 10。所以我們可以基于這個思路分兩步來設計該算法。

  即,此問題的解決分為以下兩個步驟:

  第一步:Query統計

  Query統計有以下倆個方法,可供選擇:

  1、直接排序法

  首先我們最先想到的的算法就是排序了,首先對這個日志里面的所有Query都進行排序,然后再遍歷排好序的Query,統計每個Query出現的次數了。

  但是題目中有明確要求,那就是內存不能超過1G,一千萬條記錄,每條記錄是255Byte,很顯然要占據2.375G內存,這個條件就不滿足要求了。

  讓我們回憶一下數據結構課程上的內容,當數據量比較大而且內存無法裝下的時候,我們可以采用外排序的方法來進行排序,這里我們可以采用歸并排序,因為歸并排序有一個比較好的時間復雜度O(nlogn)。

  排完序之后我們再對已經有序的Query文件進行遍歷,統計每個Query出現的次數,再次寫入文件中。

  綜合分析一下,排序的時間復雜度是O(nlogn),而遍歷的時間復雜度是O(n),因此該算法的總體時間復雜度就是O(n+nlogn)=O(nlogn)。

  2、Hash Table法

  在第1個方法中,我們采用了排序的辦法來統計每個Query出現的次數,時間復雜度是O(nlogn),那么能不能有更好的方法來存儲,而時間復雜度更低呢?

  題目中說明了,雖然有一千萬個Query,但是由于重復度比較高,因此事實上只有300萬的Query,每個Query 255Byte,因此我們可以考慮把他們都放進內存中去,而現在只是需要一個合適的數據結構,在這里,Hash Table絕對是我們優先的選擇,因為Hash Table的查詢速度非常的快,幾乎是O(1)的時間復雜度。

  那么,我們的算法就有了:維護一個Key為Query字串,Value為該Query出現次數的HashTable,每次讀取一個Query,如果該字串不在Table中,那么加入該字串,并且將Value值設為1;如果該字串在Table中,那么將該字串的計數加一即可。最終我們在O(n)的時間復雜度內完成了對該海量數據的處理。

  本方法相比算法1:在時間復雜度上提高了一個數量級,為O(n),但不僅僅是時間復雜度上的優化,該方法只需要IO數據文件一次,而算法1的IO次數較多的,因此該算法2比算法1在工程上有更好的可操作性。

  第二步:找出Top 10

  算法一:普通排序

我想對于排序算法大家都已經不陌生了,這里不在贅述,我們要注意的是排序算法的時間復雜度是O(nlogn),在本題目中,三百萬條記錄,用1G內存是可以存下的。

  算法二:部分排序

  題目要求是求出Top 10,因此我們沒有必要對所有的Query都進行排序,我們只需要維護一個10個大小的數組,初始化放入10個Query,按照每個Query的統計次數由大到小排序,然后遍歷這300萬條記錄,每讀一條記錄就和數組最后一個Query對比,如果小于這個Query,那么繼續遍歷,否則,將數組中最后一條數據淘汰,加入當前的Query。最后當所有的數據都遍歷完畢之后,那么這個數組中的10個Query便是我們要找的Top10了。

  不難分析出,這樣,算法的最壞時間復雜度是N*K, 其中K是指top多少。

  算法三:堆

  在算法二中,我們已經將時間復雜度由NlogN優化到NK,不得不說這是一個比較大的改進了,可是有沒有更好的辦法呢?

  分析一下,在算法二中,每次比較完成之后,需要的操作復雜度都是K,因為要把元素插入到一個線性表之中,而且采用的是順序比較。這里我們注意一下,該數組是有序的,一次我們每次查找的時候可以采用二分的方法查找,這樣操作的復雜度就降到了logK,可是,隨之而來的問題就是數據移動,因為移動數據次數增多了。不過,這個算法還是比算法二有了改進。

  基于以上的分析,我們想想,有沒有一種既能快速查找,又能快速移動元素的數據結構呢?回答是肯定的,那就是堆。

  借助堆結構,我們可以在log量級的時間內查找和調整/移動。因此到這里,我們的算法可以改進為這樣,維護一個K(該題目中是10)大小的小根堆,然后遍歷300萬的Query,分別和根元素進行對比。

  思想與上述算法二一致,只是算法在算法三,我們采用了最小堆這種數據結構代替數組,把查找目標元素的時間復雜度有O(K)降到了O(logK)。

  那么這樣,采用堆數據結構,算法三,最終的時間復雜度就降到了N‘logK,和算法二相比,又有了比較大的改進。

  總結:

  至此,算法就完全結束了,經過上述第一步、先用Hash表統計每個Query出現的次數,O(N);然后第二步、采用堆數據結構找出Top 10,N*O(logK)。所以,我們最終的時間復雜度是:O(N)+N’*O(logK)。(N為1000萬,N’為300萬)。如果各位有什么更好的算法,歡迎留言評論。

  第二部分、Hash表算法的詳細解析

  什么是Hash

  Hash,一般翻譯做“散列”,也有直接音譯為“哈希”的,就是把任意長度的輸入(又叫做預映射, pre-image),通過散列算法,變換成固定長度的輸出,該輸出就是散列值。這種轉換是一種壓縮映射,也就是,散列值的空間通常遠小于輸入的空間,不同的輸入可能會散列成相同的輸出,而不可能從散列值來唯一的確定輸入值。簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數。

  Hash主要用于信息安全領域中加密算法,它把一些不同長度的信息轉化成雜亂的128位的編碼,這些編碼值叫做HASH值. 也可以說,Hash就是找到一種數據內容和數據存放地址之間的映射關系。

  數組的特點是:尋址容易,插入和刪除困難;而鏈表的特點是:尋址困難,插入和刪除容易。那么我們能不能綜合兩者的特性,做出一種尋址容易,插入刪除也容易的數據結構?答案是肯定的,這就是我們要提起的哈希表,哈希表有多種不同的實現方法,我接下來解釋的是最常用的一種方法——拉鏈法,我們可以理解為“鏈表的數組”,如圖:

左邊很明顯是個數組,數組的每個成員包括一個指針,指向一個鏈表的頭,當然這個鏈表可能為空,也可能元素很多。我們根據元素的一些特征把元素分配到不同的鏈表中去,也是根據這些特征,找到正確的鏈表,再從鏈表中找出這個元素。

  元素特征轉變為數組下標的方法就是散列法。散列法當然不止一種,下面列出三種比較常用的:

  1,除法散列法

  最直觀的一種,上圖使用的就是這種散列法,公式:

index = value % 16

  學過匯編的都知道,求模數其實是通過一個除法運算得到的,所以叫“除法散列法”。

  2,平方散列法

  求index是非常頻繁的操作,而乘法的運算要比除法來得省時(對現在的CPU來說,估計我們感覺不出來),所以我們考慮把除法換成乘法和一個位移操作。公式:

index = (value * value) >> 28 右移,除以2^28。記法:左移變大,是乘。右移變小,是除。

  如果數值分配比較均勻的話這種方法能得到不錯的結果,但我上面畫的那個圖的各個元素的值算出來的index都是0——非常失敗。也許你還有個問題,value如果很大,value * value不會溢出嗎?答案是會的,但我們這個乘法不關心溢出,因為我們根本不是為了獲取相乘結果,而是為了獲取index。

  3,斐波那契(Fibonacci)散列法

  平方散列法的缺點是顯而易見的,所以我們能不能找出一個理想的乘數,而不是拿value本身當作乘數呢?答案是肯定的。

  1,對于16位整數而言,這個乘數是40503。

  2,對于32位整數而言,這個乘數是2654435769。

  3,對于64位整數而言,這個乘數是11400714819323198485。

  這幾個“理想乘數”是如何得出來的呢?這跟一個法則有關,叫黃金分割法則,而描述黃金分割法則的最經典表達式無疑就是著名的斐波那契數列,即如此形式的序列:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946,…。另外,斐波那契數列的值和太陽系八大行星的軌道半徑的比例出奇吻合。

  對我們常見的32位整數而言,公式:

  index = (value * 2654435769) >> 28

  如果用這種斐波那契散列法的話,那上面的圖就變成這樣了:

  很明顯,用斐波那契散列法調整之后要比原來的取摸散列法好很多。

  適用范圍

  快速查找,刪除的基本數據結構,通常需要總數據量可以放入內存。

  基本原理及要點

  hash函數選擇,針對字符串、整數、排列,具體相應的hash方法。

  碰撞處理,一種是open hashing,也稱為拉鏈法;另一種就是closed hashing,也稱開地址法,opened addressing。

  擴展

  d-left hashing中的d是多個的意思,我們先簡化這個問題,看一看2-left hashing。2-left hashing指的是將一個哈希表分成長度相等的兩半,分別叫做T1和T2,給T1和T2分別配備一個哈希函數,h1和h2。在存儲一個新的key時,同時用兩個哈希函數進行計算,得出兩個地址h1[key]和h2[key]。這時需要檢查T1中的h1[key]位置和T2中的h2[key]位置,哪一個位置已經存儲的(有碰撞的)key比較多,然后將新key存儲在負載少的位置。如果兩邊一樣多,比如兩個位置都為空或者都存儲了一個key,就把新key存儲在左邊的T1子表中,2-left也由此而來。在查找一個key時,必須進行兩次hash,同時查找兩個位置。

  問題實例(海量數據處理)

  我們知道hash 表在海量數據處理中有著廣泛的應用,下面,請看另一道百度面試題:

  題目:海量日志數據,提取出某日訪問百度次數最多的那個IP。

  方案:IP的數目還是有限的,最多2^32個,所以可以考慮使用hash將IP直接存入內存,然后進行統計。

  第三部分、最快的Hash表算法

  接下來,咱們來具體分析一下一個最快的Hash表算法。

  我們由一個簡單的問題逐步入手:有一個龐大的字符串數組,然后給你一個單獨的字符串,讓你從這個數組中查找是否有這個字符串并找到它,你會怎么做?有一個方法最簡單,老老實實從頭查到尾,一個一個比較,直到找到為止,我想只要學過程序設計的人都能把這樣一個程序作出來,但要是有程序員把這樣的程序交給用戶,我只能用無語來評價,或許它真的能工作,但…也只能如此了。

  最合適的算法自然是使用HashTable(哈希表),先介紹介紹其中的基本知識,所謂Hash,一般是一個整數,通過某種算法,可以把一個字符串"壓縮" 成一個整數。當然,無論如何,一個32位整數是無法對應回一個字符串的,但在程序中,兩個字符串計算出的Hash值相等的可能非常小,下面看看在MPQ中的Hash算法:

  函數一、以下的函數生成一個長度為0x500(合10進制數:1280)的cryptTable[0x500]

void prepareCryptTable() { unsigned long seed = 0x00100001, index1 = 0, index2 = 0, i; for( index1 = 0; index1 < 0x100; index1++ ) { for( index2 = index1, i = 0; i < 5; i++, index2 += 0x100 ) { unsigned long temp1, temp2; seed = (seed * 125 + 3) % 0x2AAAAB; temp1 = (seed & 0xFFFF) << 0x10; seed = (seed * 125 + 3) % 0x2AAAAB; temp2 = (seed & 0xFFFF); cryptTable[index2] = ( temp1 | temp2 ); } } } 

  函數二、以下函數計算lpszFileName字符串的hash值,其中dwHashType為hash的類型(在下面的函數三GetHashTablePos函數中調用此函數二),其可以取的值為0、1、2;該函數返回lpszFileName 字符串的hash值:

unsigned long HashString( char *lpszFileName, unsigned long dwHashType ) { unsigned char *key = (unsigned char *)lpszFileName; unsigned long seed1 = 0x7FED7FED; unsigned long seed2 = 0xEEEEEEEE; int ch; while(*key != 0) { ch = toupper(*key++); seed1 = cryptTable[(dwHashType << 8) + ch] ^ (seed1 + seed2); seed2 = ch + seed1 + seed2 + (seed2 << 5) + 3; } return seed1; }

  Blizzard的這個算法是非常高效的,被稱為"One-Way Hash"(A one-way hash is a an algorithm that is constructed in such a way that deriving the original string (set of strings, actually) is virtually impossible)。舉個例子,字符串"unitneutralacritter.grp"通過這個算法得到的結果是0xA26067F3。

  是不是把第一個算法改進一下,改成逐個比較字符串的Hash值就可以了呢?答案是,遠遠不夠。要想得到最快的算法,就不能進行逐個的比較,通常是構造一個哈希表(Hash Table)來解決問題。哈希表是一個大數組,這個數組的容量根據程序的要求來定義,例如1024,每一個Hash值通過取模運算 (mod) 對應到數組中的一個位置。這樣,只要比較這個字符串的哈希值對應的位置有沒有被占用,就可以得到最后的結果了,想想這是什么速度?是的,是最快的O(1),現在仔細看看這個算法吧:

typedef struct { int nHashA; int nHashB; char bExists; ...... } SOMESTRUCTRUE;

  一種可能的結構體定義?

  函數三、下述函數為在Hash表中查找是否存在目標字符串,有則返回要查找字符串的Hash值,無則,return -1.

int GetHashTablePos( har *lpszString, SOMESTRUCTURE *lpTable ) //lpszString要在Hash表中查找的字符串,lpTable為存儲字符串Hash值的Hash表。 { int nHash = HashString(lpszString); //調用上述函數二,返回要查找字符串lpszString的Hash值。 int nHashPos = nHash % nTableSize; if ( lpTable[nHashPos].bExists && !strcmp( lpTable[nHashPos].pString, lpszString ) ) //如果找到的Hash值在表中存在,且要查找的字符串與表中對應位置的字符串相同 { return nHashPos; //則返回上述調用函數二后,找到的Hash值 } else { return -1; } }

看到此,我想大家都在想一個很嚴重的問題:“如果兩個字符串在哈希表中對應的位置相同怎么辦?”,畢竟一個數組容量是有限的,這種可能性很大。解決該問題的方法很多,我首先想到的就是用“鏈表”,感謝大學里學的數據結構教會了這個百試百靈的法寶,我遇到的很多算法都可以轉化成鏈表來解決,只要在哈希表的每個入口掛一個鏈表,保存所有對應的字符串就OK了。事情到此似乎有了完美的結局,如果是把問題獨自交給我解決,此時我可能就要開始定義數據結構然后寫代碼了。

  然而Blizzard的程序員使用的方法則是更精妙的方法。基本原理就是:他們在哈希表中不是用一個哈希值而是用三個哈希值來校驗字符串。

  MPQ使用文件名哈希表來跟蹤內部的所有文件。但是這個表的格式與正常的哈希表有一些不同。首先,它沒有使用哈希作為下標,把實際的文件名存儲在表中用于驗證,實際上它根本就沒有存儲文件名。而是使用了3種不同的哈希:一個用于哈希表的下標,兩個用于驗證。這兩個驗證哈希替代了實際文件名。

  當然了,這樣仍然會出現2個不同的文件名哈希到3個同樣的哈希。但是這種情況發生的概率平均是:1:18889465931478580854784,這個概率對于任何人來說應該都是足夠小的。現在再回到數據結構上,Blizzard使用的哈希表沒有使用鏈表,而采用"順延"的方式來解決問題,看看這個算法:

  函數四、lpszString為要在hash表中查找的字符串;lpTable為存儲字符串hash值的hash表;nTableSize 為hash表的長度:

int GetHashTablePos( char *lpszString, MPQHASHTABLE *lpTable, int nTableSize ) { const int HASH_OFFSET = 0, HASH_A = 1, HASH_B = 2; int nHash = HashString(lpszString, HASH_OFFSET); int nHashA = HashString(lpszString, HASH_A); int nHashB = HashString(lpszString, HASH_B); int nHashStart = nHash % nTableSize; int nHashPos = nHashStart; while ( lpTable[nHashPos].bExists ) { /* 如果僅僅是判斷在該表中時候存在這個字符串,就比較這兩個hash值就可以了,不用對結構體中的字符串進行比較。這樣會加快運行的速度?減少hash表占用的空間?這種 方法一般應用在什么場合?*/ if (lpTable[nHashPos].nHashA == nHashA && lpTable[nHashPos].nHashB == nHashB ) { return nHashPos; } else { nHashPos = (nHashPos + 1) % nTableSize; } if (nHashPos == nHashStart) break; } return -1; }

  上述程序解釋:

  1. 計算出字符串的三個哈希值(一個用來確定位置,另外兩個用來校驗)

  2. 察看哈希表中的這個位置

  3. 哈希表中這個位置為空嗎?如果為空,則肯定該字符串不存在,返回-1。

  4. 如果存在,則檢查其他兩個哈希值是否也匹配,如果匹配,則表示找到了該字符串,返回其Hash值。

  5. 移到下一個位置,如果已經移到了表的末尾,則反繞到表的開始位置起繼續查詢 

  6. 看看是不是又回到了原來的位置,如果是,則返回沒找到

  7. 回到3

  ok,這就是本文中所說的最快的Hash表算法。什么?不夠快?:D。歡迎,各位批評指正。

  

60年前美國軍方的這個編程原則,造就了多少偉大的框架

60年前美國軍方的這個編程原則,造就了多少偉大的框架

作者: 賀卓凡 來源: ImportSource 發布時間: 2020-01-23 19:52 閱讀: 1605 次 推薦: 15 原文鏈接 [收藏]

  大約在60年前,美國軍方的軟件開發開始遵循一個原則,叫KISS原則。他們希望武器系統中所用的每個指令都是極其簡單和傻瓜式的。這個原則后來在編程領域中被廣泛采用,如今好多著名的開源框架都是遵循這一原則來開發,并最終取得了巨大的成功。

  在上一文中《Apache的架構師們遵循的30條設計原則》,第一個原則便是KISS原則,幾年前簡單的了解過這個原則,前幾日又翻出來,仔細查看后,倍感震驚,這篇原文可以說道破了編程的天機。以下原文的翻譯:

  KISS表示的是什么?

  KISS 是Keep It Stupid Simple 或 Keep It Simple,Stupid的縮寫。

  KISS的含義

  該原則在我的多年的軟件工程生涯中取得關鍵、巨大的成功。當今的軟件工程師和開發者們有個共同的問題,那就是他們總是慢慢地使得問題復雜化。正確的做法應該是當開發者遇到一個問題后,把問題拆分成一個個能夠明白的小塊,然后進入編碼階段。但我會說,10個開發者中有8個或9個都沒有把問題分解成足夠小或可以理解的足夠小的部分。這就導致了即使是一個非常簡單的問題最后也變成了非常復雜的實現,另外一個副作用就是意大利面代碼,在BASIC里只是一個goto語句的事情,在Java中卻需要500到1000行代碼,每個方法都有幾百行代碼。

  你需要先想好問題的解決步驟一共分為幾步,然后再進入編碼。而不是拿到需求后,就開始一邊寫代碼一邊去滿足需求。這樣做的好處就是你的代碼會變的足夠容易理解和足夠清晰。

  我們能從KISS中獲取到什么好處?

  1. 你可以更好地解決更多問題。
  2. 你將可以通過很少的幾行代碼去解決復雜的問題。
  3. 你將可以產出高質量的代碼。
  4. 你將可以構建更大更易維護的系統。
  5. 當新的需求來了后,你的代碼將會更加的靈活,易于擴展、易于修改和重構。
  6. 你將完成比你想象得更多的事情。
  7. 你將能夠工作在一個大型開發團隊和大型項目中,因為所有的代碼都是stupid simple。

  我如何把KISS原則用到我的工作中?

  這里有幾個簡單的步驟可供執行,但有一定挑戰。就像說起來的那么簡單,keep it simple,主要是需要耐心,更多的靠你自己。

  • 要謙虛,不要認為自己是個天才,這是你第一個誤解。只有謙虛了,你才能真正達到超級天才的水平,即使不行,who cares!你的代碼那么stupid simple,所以你不需要是個天才!
  • 將你的任務分解為4-12小時的子任務。
  • 把你的問題拆分成多個小問題。每個問題用一個或者很少的幾個類來解決掉。
  • 保持你的方法足夠小,每個方法永遠不要超過30-40行代碼。每個方法都應該只處理一個小小的問題,不要搞太多uses case進去。如果你的方法中有多個分支,嘗試把他們拆分成多個小的方法。這樣不僅容易閱讀和維護,找bug也更快。慢慢的你將學會愛。
  • 讓你的類也小點,原則和上面的方法是一樣的。
  • 先解決問題,然后開始編碼。不要一邊編碼,一邊解決問題。這樣做也沒什么錯,但你有能力提前把事情切分成多個小的塊,然后開始編碼可能是比較好的。但也請你不要害怕一遍遍重構你的代碼。另外行數還不是為了衡量質量的標準,只是有個基本的尺子而已。
  • 不要害怕干掉代碼。重構和重做是兩個非常重要的方面。如果你遵循上面的建議,重寫代碼的數量將會最小化,如果你不遵循,那么代碼很可能會被重寫。
  • 其他的任何場景,都請你嘗試盡可能的簡單,simple,這也是最難的一步,但一旦你擁有了它,你再回頭看,就會說,之前的事情就是一坨屎。

  有沒有一些KISS原則的例子

  太多了,以后我會把一些不錯的案例po到這里。但現在我想先給你一些我的觀點和想法:

  世界上的很多偉大的算法幾乎總是有很少的數行代碼。當我們去看這些的時候,會發現很容易理解。這些算法發明者,他們把問題拆分,直到容易理解的程度,然后去實現它。

Many great problem solvers were not great coders, but yet they produced great code!

  許多偉大的問題解決者(problem solver)都曾不是偉大的程序員,但他們卻產出了偉大的代碼!

  KISS只能用于java代碼?

  顯然不是,它可以用于很多編程語言,并且甚至可用于你生活的其他的領域。當然不能用于你的感情、愛,更重要的是,不能用在你的婚姻上。

  相關閱讀:

  Apache的架構師們遵循的30條設計原則

  如何寫一個清晰明了的bug

15
4

請先登錄 提交中…

標簽:編程原則 框架

文章列表

如果你已經這樣了,那你必須要跳槽了。 – zuoxiaolong

文章出處

引言

  

  俗話說,領完年終獎,一身無牽掛。

  過年了,大家對跳槽的熱度持續攀升,相信有不少人已經蠢蠢欲動了。不過LZ今天要談的東西與這些已經準備好跳槽的同學們關系不大,既然這部分同學已經決定了跳槽,那么相信你已經做好了充足的準備,LZ能說的,只能是祝你們順利了。

  LZ今天要談的事情主要針對的是不打算跳槽的這部分同學,之所以談論這個,是因為不管是LZ的現實身邊,還是LZ的交流群里,都有不少猿(媛)友對于跳槽猶豫不決,最長的說跳槽能說上兩三年,但是到最后,依然還是在原地。

  這實在是讓LZ寢食難安,夜夜不眠。所以今天要談的就是這部分同學了。

  

跳槽的充分不必要條件

  

  過完年不打算跳槽的猿友們,LZ這里跟大家談一個跳槽的充分不必要條件,那就是你確定你的薪水在跳槽后,哪怕到一個和你當前公司規模相差不大的公司里,也一定可以達到你當前薪水的150%(150%只是根據市場簡單評估出來的數字,不要糾結于這個數字哦)。

  通俗的說,就是如果你確定你跳到一個和當前一樣規模的公司后,你的薪水可以漲一半,那么這個時候你就必須跳槽了。

  其實這個時候,大部分同學已經果斷跳槽了,如果你這么做了,恭喜你,你做的非常正確。但是卻有那么一部分人,寧可拿著少于自己實際身價一半以上的錢,也不愿意踏出這一步。

  有的猿友可能會問了,怎么可能會有這種人呢?明明知道出去工資就可以有一個跳躍式的增長,還不跳槽?

  很遺憾的告訴你,現實當中有很多這樣的人。他(她)們不跳的原因有很多,據LZ觀察,多數屬于以下情況。

  1、極度不自信,不敢出去嘗試面試。因為不敢出去嘗試面試,當然無法確認自己的實際身價是否已經達到了當前的150%,然后就自我安慰的一直呆在原公司。

  對于這部分猿友,LZ只能說,不管你在當前公司呆的多么愉快,都應該在一定的時間內,出去面試一下(哪怕你不打算跳槽)。這樣才可以看清你這段時間的進步,市場是鑒定你進步的唯一手段。哪怕你這兩年看了100本成神之書,你已經覺得自己無所不能了,但是如果你面試后發現,市場上能offer你的工資依舊沒有多大變動,那么說明你的進步其實只是表象,至少沒有你想象中的那么大。

  當然了,如果你每日虛度年華,出來面試的結果或許會大大的打擊到你。很多人也正是因為這個原因不敢出去面試,因為怕被打擊。對于這種心理,LZ只能說,怕被打擊?那就不要出來混了。

  如果你不甘于做一個平凡的程序員,那么就請勇敢的走出去,只有這樣,你才能在不斷的打擊和肯定中,快速成長。

  2、充滿責任感,不愿拋棄自己原來的公司,做一個不仁不義之人。

  對于這部分猿友,LZ已經徹底無語了。社會是很現實的,如果你是一個房(yi)車(shi)無憂的二代同學,那么你如果非要講究仁義,LZ無話可說。但如果不是,你未來需要自己豐房(yi)足車(shi),那么你現在的仁義和責任感將會變的非常廉價。

  跳槽與沒有責任感并不能劃等號,如果真是這樣的話,那么明年準備跳槽的小伙伴豈不都是沒有責任感的人嗎。

  只要你在跳槽期間,盡職盡責的進行交接,已經算是對老東家仁至義盡了。千萬不要讓那些無謂的道德綁架了你,問心無愧即可。

  3、因為一些奇葩的原因,被束縛在原地。

  奇葩的原因是什么?說到這個那就多了,LZ親耳聽到了很多讓LZ無語問蒼天的原因。  

  比如,覺得找到的那個公司離現在住的地方太遠了。對于這個LZ只想說,對于一個租房的人來說,你竟然拿這樣的理由來搪塞LZ,你自己不覺得羞羞嗎。

  再比如,和公司簽訂了某某協議,如果離職的話,需要交若干錢才行,再過兩年,就不用交了。對于這種原因,LZ真是哭笑不得,你糾結那點錢,說不定出去后幾個月就掙回來了,沒有什么比自己的前途更重要,不要讓任何事情阻攔你的腳步(違法的除外,0-0)。

  綜上所述,LZ只想說,不管你是什么理由,只要你能在與當前公司規模差不多的公司內,拿到能達到你當前工資的150%的offer,那么就必須跳槽。

  

為什么

  

  既然LZ說得這么肯定,那么就一定要給大家一個理由。否則的話,猿友們肯定會問,你憑什么說達到上面的條件,就一定要跳槽呢?接下來,LZ就給大家一個理由。

  說到這個理由,就必須要說一下,一個人工資的增長速度,到底取決于什么?

  因為LZ是數學專業的,因此咱們來用數學的思維來解決這個問題,接下來,咱們來看一個公式。

你工資增長的K數=增長因子*你當前的工資

  好吧,這個公式有點簡單。但是不要忘了,大道至簡,簡單就是硬道理,因為誰都看的明白嘛。

  看到這個公式,相信大部分人會問,增長因子到底是什么?

  這個問題就非常復雜了,增長因子和很多因素都有關系,比如市場上的程序員的價格、你自己平時的積累和學習等等。總之,這個增長因子是非常復雜的因素組成的,咱們不要太care這個東西。

  猿友們應該注意的是,乘號后面的內容。乘號后面是什么?是你當前的工資。

  沒錯,從數學的角度來解釋,那就是你工資增長的速度與你當前的工資成正比。因此,你當前的工資越高,你工資增長的速度會越快。說一個極端的例子,一個工資4K的人,他如果想要漲4K工資,將會非常困難。但是對于一個工資20K的人來說,可能4K的工資增長對他來說,只是一次普通的調薪就可以達到。

  換句話說,如果在同樣的增長因子下,工資20K的那個人,可能一次漲工資就是5-10K,但是4K的那個人能有個1K估計就謝天謝地了。

  如果你覺得這個例子太夸張,無法說服你的話。那LZ再來說一個投資的事,這個例子LZ在交流群里提到過,至少LZ個人覺得,這個例子與一個人工資的增長是非常相似的。

  假設你有本金10000塊,你拿去投資,利息是10%。那么一年下來,你的利息就是1000塊。如何才能讓你第二年的利息達到最高?

  相信這個大家都知道怎么辦,肯定是把利息取出來當作本金再投進去啊。這樣的話,第二年算利息的時候,就是1100塊了。以此類推,累計下來,你每年的利息都會有一個不錯的提高。

  是的,這個道理很簡單,簡單點說,就是每年及時把你的利息套現,并且當做本金再存進去。

  那么同樣的,如果你把自己的身價當做一次投資,你也應該及時把自己的利息(薪水增長)套現,然后當做本金(當前工資)再存進去,才能讓你的利息(薪水增長K數)最大化。如果你把自己的利息(薪水增長)一直存在銀行(當前公司),這些利息(薪水增長)可就全都浪費了。

  所以,記得,及時套現你的薪水漲幅,才能讓你的身價快速飆升。

  

結語與友情提示

  

  好了,話題討論到這里就差不多結束了。最后LZ再友情提醒一下,以上所說的都只是充分不必要條件,而不是必要條件,所以千萬不要只拘泥于自己的工資。

  通俗的說,就是當你達到了以上的薪水增長條件,你就必須該跳槽了。但是并不是說,非要達到以上的薪水條件,你才可以跳槽。跳槽可以有很多種原因,LZ本文討論的,只是其中的一種罷了。

  最后,祝大家在新的一年,跳槽的都能拿到自己如意的offer,沒跳槽的,爭取下一年可以跳,0-0。

  

文章列表