1. 程式人生 > >elastic-job分散式定時器實踐

elastic-job分散式定時器實踐

專案的定時任務比較多。主管想要做成分散式定時器。說起來,我們要用springcloud+docker做分散式叢集服務,又要做分散式分片資料庫,用spring-session和redis做分散式快取,rocketmq做訊息中介軟體,幾乎所有環節都做成分散式系統了。雖然我不覺得這個專案有這麼大的業務需求,但是反正我是學到很多東西了。我們的主管挺奇怪的,他說他都沒用過這些東西,但是他十分樂於用這些新東西,平時總是關注行業的最新動態,這種戰略思維也是我需要學習的。而現在他要用什麼我就學什麼就好了,也算是給我指明瞭方向。

主管給我的方案是elastic-job+zookeeper,zookeeper我之前瞭解分散式伺服器的時候瞭解過(主要為了面試),其實就是一個伺服器叢集地址的管理軟體。elastic-job底層還是使用的quartz,相當於用zookeeper實現的一個quartz分散式方案。那麼第一步,肯定是學習zookeeper了。我會把參考的文章連結都貼出來。

因為文章很詳細,我就不復制貼上了,就簡單地說一下總結和一些感悟。我發現像zookeeper或者rockmq這種叢集方案都有類似主從的關係來保持高可用性,確立主從關係以後,所有分節點資料能夠跟主節點同步,如果分節點失效,那麼沒關係,還有其他分節點,如果主節點失效,這些方案都會有方式選出新的節點取而代之。因此除非所有節點失效,不管是新增節點還是減少節點,對外界來說都是工作的。保持了高度的靈活性,擴充套件性和可用性,這也是分散式系統的優勢吧,缺點就是需要額外的花費來同步資料,處理併發以及執行順序的問題,以及需要抽象出一層對叢集進行管理的體系。感覺跟現代人類的組織體系有異曲同工之妙呢,不管缺少哪個環節,都有可用的人頂上去。

不說廢話了,說些緊要的東西,zookeeper有三個埠號,一個是工作埠號,一個是主從zookeeper伺服器之間通訊的埠號(也用來同步資料),以及一個專門用來選舉的埠號(當然並不是民主的選舉,而是程式設計師欽定的),後兩個埠號在佈置zookeeper叢集時有用。

想要使用什麼工具,最基礎和最重要的部分就是環境和配置了,之後呼叫api什麼的都已經設定好了,照著用就行。

zookeeper配置檔案是根目錄下的conf檔案中的zoo.cfg檔案,配置項如下

tickTime=2000 通訊的心跳時間
initLimit=10 伺服器叢集間能容忍的心跳間隔數(多少心跳時間沒有訊息就選新領導,這說明保持聯絡是多麼重要,多少反賊都是這麼造反的)
syncLimit=5 同步的心跳間隔數,主從節點需要保持同步
dataDir=/tmp/zookeeper 儲存資料的目錄,這是基本的,什麼程式都要儲存資料的地方,像魚一樣七秒記憶不存在的

clientPort=2181 工作埠

server.1=192.168.211.1:2888:3888 下面是複製貼上的

server.A=B:C:D:其中 A 是一個數字,表示這個是第幾號伺服器;B 是這個伺服器的 ip 地址;C 表示的是這個伺服器與叢集中的 Leader 伺服器交換資訊的埠(上面的埠Y);D 表示的是萬一叢集中的 Leader 伺服器掛了,需要一個埠來重新進行選舉,選出一個新的 Leader,而這個埠就是用來執行選舉時伺服器相互通訊的埠(上面的埠Z)。如果是偽叢集的配置方式,由於 B 都是一樣,所以不同的 Zookeeper 例項通訊埠號不能一樣,所以要給它們分配不同的埠號。 
* 在上面配置的dataDir目錄下建立myid檔案,填寫這個節點上的id號,就是server.A=B:C:D配置的A那個號碼 

其實配置項很少了,然後啟動zookeeper,啟動俺就不說了。

然後我最看重的是視覺化工具,雖然用命令列看著很帥,很有高手的感覺,但是效率太低,不夠直觀,像redis我一定要用RedisDesktopManager,不然根本不用(其實是懶)

找了個可以用的,效果如下


不錯,使用難度傻瓜級別,不用配置資料庫,部署tomcat什麼的(吐槽某款本地網頁版視覺化工具),而且這介面一看就很喜歡,沒這麼多花裡胡哨的。

關於zookeeper的使用,其實zookeeper本質上還是一個數據的媒介,只不過它儲存了每個客戶端伺服器的資訊,從這一點上講,網際網路其實就是資料的儲存和通訊

然後是elastic-job,關於這個我主要參考兩個文章

上面這個文章非常詳細地說明了elastic-job的原理,還是系列文章,下面的就只是一個demo了。

elastic-job分片的原理就是每當有新的Job例項加入zookeeper中就會進行分片演算法


leader是zookeeper中的領導,servers是zookeeper叢集中的伺服器,現在只有一個,instances是job例項,現在只有一個。關鍵是sharding分片,比如我在springboot中設定了分片為10,zookeeper會自動為這十個分片分配例項,就是我選中的instance。169.254的ip我查了下,好像是保留ip,就是說並沒有查到ip,難道是因為我本地測試的原因。總之單定時器是可以正常執行的。下面我試試單臺機器多個埠的定時器。


可以看到instances裡多了一個10436,並且5,6,7,8,9分片被分配到了這個新的10436。



可以看到任務是可以正常進行的,注意看分片項。

那麼能夠正常分片以後就可以考慮分片演算法了,結合我過去看過的mycat,和hashMap的原理,我想出了一個初步的方案

因為所有的定時器都是跟公司有關的,所以要按照公司id進行分片

select CompanyId%30 cuter,count(1) from base_company group by cuter


發現分片並不均勻,然後換用select hex(CompanyId)%30 cuter,count(1) from base_company group by cuter


這個就好一點,總之初步方案決定用這個,但是這個研究分片的過程是最重要的,我以後可以隨時改變分片的演算法,甚至可以寫個程式研究不同演算法分片的方差之類的

好吧,根本沒有用到hashMap的hash演算法,因為mysql的方法我並不是很熟,而且mysql的方法可以進行二進位制的運算嗎?不清楚,不過思路就是這樣。我還要研究一下,畢竟上面的方差也很大,分佈不均勻

順便說一下hashMap的hash演算法

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

key.hashCode()是32位的int型。h>>>16相當於把前面16位移到了後十六位,然後前十六位用0補充。^是異或運算

運算規則:0^0=0;   0^1=1;   1^0=1;   1^1=0;

用自然語言說明就是當一方是0,那麼結果等於另一方的值,當一方是1,那麼結果等於另一方的否值

當int小於等於16位時,h>>>16就是0那麼沒有任何影響,當int大於16位時,h>>>16等於int的前十六位移到後十六位。所以int前十六位不變,但是後十六位會受到前十六位的影響

    public static String convert(String str, int length) {
        if (str.length() < length) {
            String temp = "";
            for (int i = 0; i < length - str.length(); i++) {
                temp += "0";
            }
            return temp + str;
        }
        return str;
    }
    public static String toBinaryString(int a,int length) {
    	return convert(Integer.toBinaryString(a), length);
    }
    public static void main(String[] args) {
    	int h = 1911111111;
    	System.out.println("         h:"+toBinaryString(h, 32));
    	System.out.println("    h>>>16:"+toBinaryString(h>>>16, 32));
	System.out.println("h^(h>>>16):"+toBinaryString(h^(h>>>16), 32));
    }


以上差不多就是初步的實踐了