項目須要一個分佈式Id生成的功能,原本以爲蠻簡單的東西,就主動接下來了。結果寫的時候才發現,想要寫好並不容易。web
先說下思路,其實很簡單。咱們最經常使用的id生成,就是數據庫的自增主鍵,由於每次都要操做庫,因此效率不高。很天然的就想着每操做一次庫,能夠設置多個Id。因而能夠在程序中緩存一個計數器,當計數值達到設置的值後,在讀庫,更新id。這樣來減小操做庫的次數,提升效率。數據庫
另外,操做庫是一個相對來講耗時的操做,因此它能夠異步來作。這樣就須要在程序中在緩存一個計數器,作二級緩存了,爲了不過多的異步操做,此處可使用線程池。緩存
以下是個人表結構:tomcat
key_name:用於記錄某個應用或某個表生成id的標識併發
start_id:用於每次記錄生成id的起始值框架
step:用於設置,內存中計數的上限,簡稱步長。異步
當時老大讓把這個功能作成一個jar包,給每一個項目用。因此它是分佈式的,爲了防止不一樣的機器在對start_id更改衝突,因此start_id要加樂觀鎖。分佈式
由於程序中的計數器num,是多個線程共享的,因此操做num的方法,也是要加鎖的。性能
可是程序中鎖的粒度很難控制,最終我把鎖加到了方法上,這讓我很鬱悶,由於在入口方法上加鎖它就至關於單線程了。對鎖優化,讓我花費了很常一段時間,最終仍是放棄了。測試
簡單的記錄下。
首先想到的是用讀寫鎖,ReentrantReadWriteLock ,可是對它深刻研究了下,發現讀寫鎖並不適用個人場景。由於每次生成id,會先讀num,而後用startId+num,而後修改num,這三個操做必須是原子的。而讀寫鎖的使用場景,是讀與寫互斥,讀與讀不互斥。
而後我想到的是使用threadLocal,用空間換時間。只能說本身基礎不紮實吧,研究了下,發現也不行。我設想經過threadLocal來設置num,可是這樣,線程並無真正的共享num,生成的id,確定是會重複的。
再而後,就是用線程池,可是感受有些複雜,並且我使用的是Spring框架,個人service是單例的,它就已經至關於一個單線程的線程池了。
好吧,說下效率。公司的測試團隊有點low,我把它打包成web項目,用3臺tomcat壓測,大概3000併發,qps只有12000左右。由於tomcat自己也有性能瓶頸,經過本地測試,不依賴tomcat,qps大概是8W,徹底能夠知足公司的需求。
感受仍是基礎太差,只是仍是要常總結