高CPU業務場景下的任務分發方案Gearman搭建一覽

一線碼農發表於2017-08-29

  

    Gearman是當年LiveJournal用來做圖片resize的,大家也明白圖片resize是一個高CPU的操作,如果讓web網站去做這個高CPU的功能,有可能會拖垮你的

web應用,那本篇我們來看看gearman是如何解決這個問題的,它的架構圖類似下面這樣:

從上面這張圖,你應該會看到,Gearman是由三個部分組成:

1. Job Server

    這個就是Gearman的Job Server,通過它對Client 和 jobwork 進行橋接,是不是想起來了中介者模式。。。

2. Client

   Gearman提供了Client API 給客戶端呼叫,Client只需要將一個高CPU的業務函式名丟給Job Server,然後等待JobServer的返回執行結果。

3. jobwork

   Gearman提供了work API 給work客戶端進行呼叫。jobserver會根據後端的work叢集的負載情況,分發給一個合適的work去執行,並等待結果。

 

說到這裡,你應該就明白了,本質上它屬於那種分散式的RPC呼叫,而且非常牛逼的地方在於Client 和 Work 可以用不同的語言實現。

 

一:安裝部署

1.  下載地址:https://github.com/gearman/gearmand/releases

         目前gearman的JobServer 有C,JAVA,Perl三種語言實現,由於C版本的JobServer是最活躍的,所以這裡採用目前最新的1.1.17版本的gearmand在CentOS

上進行安裝部署。

2.  快速安裝

    可以通過官網http://gearman.org/getting-started/中的getting-started進行快速安裝。

 <1> 基礎依賴庫安裝和gearmand下載

 1 yum -y install boost-devel gperf libevent-devel libuuid-devel gcc44 gcc-c++ 
 2 wget https://github.com/gearman/gearmand/releases/download/1.1.17/gearmand-1.1.17.tar.gz
 3 cd gearmand-1.1.17.tar.gz
 4 tar xzvf gearmand-1.1.17.tar.gz
 5 cd gearmand-1.1.17
 6 [root@localhost gearmand-1.1.17]# ls
 7 aclocal.m4  build-aux     configure.ac  gear_config.in  libgearman-1.0     libhashkit-1.0  Makefile.am  rpm      THANKS
 8 AUTHORS     ChangeLog     COPYING       gearmand        libgearmancore     libhostile      Makefile.in  scripts  util
 9 benchmark   configmake.h  docs          HACKING         libgearman-server  libtest         man          support  version.m4
10 bin         configure     examples      libgearman      libhashkit         m4              NEWS         tests

 

<2> 然後就是常規的./configure --prefix=/usr/myapp/gearman && make && make install  這個過程超級慢,可以出去抽跟煙,

        順便再去拉泡屎。。。

1 ./configure --prefix=/usr/myapp/gearman && make && make install

 

<3> 若干年後,當你看到這個就算安裝成功了。。。還是得恭喜一下。。。。至少沒讓你踩到缺少各種依賴包的介面。

 1 See any operating system documentation about shared libraries for
 2 more information, such as the ld(1) and ld.so(8) manual pages.
 3 ----------------------------------------------------------------------
 4  /usr/bin/mkdir -p '/usr/myapp/gearman/sbin'
 5  /usr/bin/install -c -m 644 man/gearman_worker_create.3 man/gearman_worker_define_function.3 man/gearman_worker_echo.3 man/gearman_worker_errno.3 man/gearman_worker_error.3 man/gearman_worker_free.3 man/gearman_worker_function_exist.3 man/gearman_worker_grab_job.3 man/gearman_worker_options.3 man/gearman_worker_register.3 man/gearman_worker_remove_options.3 man/gearman_worker_remove_servers.3 man/gearman_worker_set_context.3 man/gearman_worker_set_log_fn.3 man/gearman_worker_set_namespace.3 man/gearman_worker_set_options.3 man/gearman_worker_set_timeout.3 man/gearman_client_has_option.3 man/gearman_client_options_t.3 man/gearman_task_attr_init.3 man/gearman_task_attr_init_background.3 man/gearman_task_attr_init_epoch.3 man/gearman_task_attr_t.3 man/gearman_worker_set_identifier.3 man/gearman_worker_set_workload_free_fn.3 man/gearman_worker_set_workload_malloc_fn.3 man/gearman_worker_st.3 man/gearman_worker_timeout.3 man/gearman_worker_unregister.3 man/gearman_worker_unregister_all.3 man/gearman_worker_wait.3 man/gearman_worker_work.3 man/libgearman.3 '/usr/myapp/gearman/share/man/man3'
 6   /bin/sh ./libtool   --mode=install /usr/bin/install -c gearmand/gearmand '/usr/myapp/gearman/sbin'
 7 libtool: install: /usr/bin/install -c gearmand/gearmand /usr/myapp/gearman/sbin/gearmand
 8  /usr/bin/mkdir -p '/usr/myapp/gearman/bin'
 9   /bin/sh ./libtool   --mode=install /usr/bin/install -c bin/gearman bin/gearadmin '/usr/myapp/gearman/bin'
10 libtool: install: /usr/bin/install -c bin/.libs/gearman /usr/myapp/gearman/bin/gearman
11 libtool: install: /usr/bin/install -c bin/gearadmin /usr/myapp/gearman/bin/gearadmin
12 make[3]: Leaving directory `/usr/myapp/gearmand-1.1.17'
13 make[2]: Leaving directory `/usr/myapp/gearmand-1.1.17'
14 make[1]: Leaving directory `/usr/myapp/gearmand-1.1.17'

 

<4> 啟動gearmand,你也可以用 -d 開啟後臺執行的模式,這裡加上DEBUG只是看一下實時的DEBUG資訊,如下所示:

 1 [root@localhost myapp]# cd /usr/myapp/gearman
 2 [root@localhost gearman]# ls
 3 bin  include  lib  sbin  share
 4 [root@localhost gearman]# cd bin
 5 [root@localhost bin]# ls
 6 gearadmin  gearman
 7 [root@localhost bin]# cd /usr/myapp/gearman
 8 [root@localhost gearman]# cd sbin
 9 [root@localhost sbin]# ls
10 gearmand
11 [root@localhost sbin]# ./gearmand --verbose DEBUG
12 ./gearmand: Could not open log file "/usr/myapp/gearman/var/log/gearmand.log", from "/usr/myapp/gearman/sbin", switching to stderr. (No such file or directory)
13   DEBUG 2017-08-29 02:31:10.796259 [  main ] THREADS: 4 -> libgearman-server/gearmand.cc:263
14    INFO 2017-08-29 02:31:10.796374 [  main ] Initializing Gear on port 4730 with SSL: false
15    INFO 2017-08-29 02:31:10.796487 [  main ] Starting up with pid 40299, verbose is set to DEBUG
16   DEBUG 2017-08-29 02:31:10.796637 [  main ] Method for libevent: epoll -> libgearman-server/gearmand.cc:364
17   DEBUG 2017-08-29 02:31:10.798874 [  main ] Trying to listen on 0.0.0.0:4730 -> libgearman-server/gearmand.cc:646
18    INFO 2017-08-29 02:31:10.800151 [  main ] Listening on 0.0.0.0:4730 (8)
19   DEBUG 2017-08-29 02:31:10.800175 [  main ] Trying to listen on :::4730 -> libgearman-server/gearmand.cc:646
20    INFO 2017-08-29 02:31:10.800307 [  main ] Listening on :::4730 (9)
21   DEBUG 2017-08-29 02:31:10.800333 [  main ] Creating wakeup pipe -> libgearman-server/gearmand.cc:915
22   DEBUG 2017-08-29 02:31:10.800344 [  main ] Creating 4 threads -> libgearman-server/gearmand.cc:378
23   DEBUG 2017-08-29 02:31:10.800357 [  main ] Initializing libevent for IO thread -> libgearman-server/gearmand_thread.cc:224
24   DEBUG 2017-08-29 02:31:10.800406 [  main ] Creating IO thread wakeup pipe -> libgearman-server/gearmand_thread.cc:495
25   DEBUG 2017-08-29 02:31:10.800467 [  main ] Thread 1 created -> libgearman-server/gearmand_thread.cc:273
26   DEBUG 2017-08-29 02:31:10.800507 [  main ] Initializing libevent for IO thread -> libgearman-server/gearmand_thread.cc:224
27   DEBUG 2017-08-29 02:31:10.800550 [  main ] Creating IO thread wakeup pipe -> libgearman-server/gearmand_thread.cc:495
28   DEBUG 2017-08-29 02:31:10.800585 [  main ] Thread 2 created -> libgearman-server/gearmand_thread.cc:273
29   DEBUG 2017-08-29 02:31:10.800594 [  main ] Initializing libevent for IO thread -> libgearman-server/gearmand_thread.cc:224
30   DEBUG 2017-08-29 02:31:10.800632 [  main ] Creating IO thread wakeup pipe -> libgearman-server/gearmand_thread.cc:495
31   DEBUG 2017-08-29 02:31:10.800669 [  main ] Thread 3 created -> libgearman-server/gearmand_thread.cc:273
32   DEBUG 2017-08-29 02:31:10.800677 [  main ] Initializing libevent for IO thread -> libgearman-server/gearmand_thread.cc:224
33   DEBUG 2017-08-29 02:31:10.800714 [  main ] Creating IO thread wakeup pipe -> libgearman-server/gearmand_thread.cc:495
34   DEBUG 2017-08-29 02:31:10.800753 [  main ] Thread 4 created -> libgearman-server/gearmand_thread.cc:273
35   DEBUG 2017-08-29 02:31:10.800761 [  main ] replaying queue: begin -> libgearman-server/gearmand.cc:391
36   DEBUG 2017-08-29 02:31:10.800766 [  main ] __replay -> libgearman-server/plugins/queue/default/queue.cc:101
37   DEBUG 2017-08-29 02:31:10.800774 [  main ] replaying queue: end -> libgearman-server/gearmand.cc:397
38    INFO 2017-08-29 02:31:10.800780 [  main ] Adding event for listening socket (8)
39    INFO 2017-08-29 02:31:10.800787 [  main ] Adding event for listening socket (9)
40   DEBUG 2017-08-29 02:31:10.800794 [  main ] Adding event for wakeup pipe -> libgearman-server/gearmand.cc:966
41   DEBUG 2017-08-29 02:31:10.800801 [  main ] Entering main event loop -> libgearman-server/gearmand.cc:406
42   DEBUG 2017-08-29 02:31:10.801186 [     2 ] Entering thread event loop -> libgearman-server/gearmand_thread.cc:463
43   DEBUG 2017-08-29 02:31:10.801277 [     3 ] Entering thread event loop -> libgearman-server/gearmand_thread.cc:463
44   DEBUG 2017-08-29 02:31:10.801507 [  main ] staring up Epoch thread -> libgearman-server/timer.cc:61
45   DEBUG 2017-08-29 02:31:10.801635 [     1 ] Entering thread event loop -> libgearman-server/gearmand_thread.cc:463
46   DEBUG 2017-08-29 02:31:10.802426 [     4 ] Entering thread event loop -> libgearman-server/gearmand_thread.cc:463

 

<5> 最後通過netstat,lsof, ps -ef 三板斧可以找出來gearmand大概佔用的埠號,就如你看到的預設佔用的4370埠,

        當然你也可以在啟動的時候用help命令也是能夠知道的。

 1 [root@localhost ~]# netstat -tln
 2 Active Internet connections (only servers)
 3 Proto Recv-Q Send-Q Local Address           Foreign Address         State      
 4 tcp        0      0 192.168.122.1:53        0.0.0.0:*               LISTEN     
 5 tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN     
 6 tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN     
 7 tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN     
 8 tcp        0      0 0.0.0.0:4730            0.0.0.0:*               LISTEN     
 9 tcp6       0      0 :::8009                 :::*                    LISTEN     
10 tcp6       0      0 :::8080                 :::*                    LISTEN     
11 tcp6       0      0 :::22                   :::*                    LISTEN     
12 tcp6       0      0 ::1:631                 :::*                    LISTEN     
13 tcp6       0      0 ::1:25                  :::*                    LISTEN     
14 tcp6       0      0 :::4730                 :::*                    LISTEN     
15 tcp6       0      0 127.0.0.1:8005          :::*                    LISTEN     
16 [root@localhost ~]# ps -ef | grep gearmand
17 root      40299  15869  0 02:31 pts/1    00:00:00 ./gearmand --verbose DEBUG
18 root      40364  40327  0 02:33 pts/2    00:00:00 grep --color=auto gearmand
19 [root@localhost ~]# lsof -i :4730
20 COMMAND    PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
21 gearmand 40299 root    8u  IPv4 322550      0t0  TCP *:gearman (LISTEN)
22 gearmand 40299 root    9u  IPv6 322551      0t0  TCP *:gearman (LISTEN)
23 [root@localhost ~]# 

 

二:Java Driver 在 Gearman上的使用

       為了演示,我可以做一個簡單的 “字串.ToUpper”的業務邏輯來驗證一下這個架構是否可以跑的起來。

 

1. java 充當 Gearman 的 work 

    首先需要在mvn倉庫中拉一下jar包:http://www.mvnrepository.com/artifact/org.gearman/gearman-java/0.6。

 

<1> UpperFunction類,這個類用於定義work具體的業務邏輯:

 1 package com.datamip.gearmanwork;
 2 
 3 import java.text.SimpleDateFormat;
 4 import java.util.Date;
 5 
 6 import org.gearman.client.GearmanJobResult;
 7 import org.gearman.client.GearmanJobResultImpl;
 8 import org.gearman.util.ByteUtils;
 9 import org.gearman.worker.AbstractGearmanFunction;
10 
11 //字串大寫的業務Function
12 public class UpperFunction extends AbstractGearmanFunction {
13 
14     @Override
15     public GearmanJobResult executeFunction() {
16 
17         String param = ByteUtils.fromUTF8Bytes((byte[]) this.data);
18 
19         byte[] mybytes = param.toUpperCase().getBytes();
20 
21         GearmanJobResultImpl result = new GearmanJobResultImpl(mybytes, true, mybytes, null, null, -1, -1);
22 
23         SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
24 
25         String dateString = formatter.format(new Date());
26 
27         System.out.println(String.format("當前時間:%s, 過來的字串:%s,返回的字串:%s", dateString, param,new String(mybytes)));
28 
29         return result;
30     }
31 }

 

<2>  將UpperFunction註冊到gearmand中,從紅色程式碼可以看到,其實是一個kv模式,這裡的key="myUpperFunc”的對應執行業務就是new UpperFunction。

        這樣Client只需要傳遞一個"myUpperFunc",Gearmand就知道這個“字串”對應是哪一個處理函式。。。

 1 public class App {
 2     public static void main(String[] args) {
 3 
 4         GearmanWorker worker = new GearmanWorkerImpl();
 5 
 6         GearmanNIOJobServerConnection conn = new GearmanNIOJobServerConnection("192.168.23.170", 4730);
 7         worker.addServer(conn);
 8 
 9         // 將‘將轉大寫的函式註冊’ 到gearmand中 
10         worker.registerFunctionFactory(new GearmanFunctionFactory() {
11 
12             public String getFunctionName() {
13                 return "myUpperFunc";
14             }
15 
16             public GearmanFunction getFunction() {
17                 return new UpperFunction();
18             }
19         });
20         
21         System.out.println("啟動服務。。。。");
22 
23         worker.work();
24     }
25 }

2. java 充當 Gearman 的 client

 

    <1> GearSubmit類【簡單的一個包裝類,隨便定義】

 1 package com.datamip.gearmanclient;
 2 
 3 import java.util.concurrent.ExecutionException;
 4 import java.util.concurrent.ExecutorService;
 5 import java.util.concurrent.Executors;
 6 import java.util.concurrent.Future;
 7 
 8 import org.gearman.client.GearmanClient;
 9 import org.gearman.client.GearmanClientImpl;
10 import org.gearman.client.GearmanJob;
11 import org.gearman.client.GearmanJobImpl;
12 import org.gearman.client.GearmanJobResult;
13 import org.gearman.common.GearmanJobServerConnection;
14 import org.gearman.common.GearmanNIOJobServerConnection;
15 import org.gearman.util.ByteUtils;
16 
17 public class Gearsubmit {
18 
19     public  void process() throws InterruptedException, ExecutionException {
20     
21         GearmanJobServerConnection conn = new GearmanNIOJobServerConnection("192.168.23.170", 4730);
22 
23         GearmanClient client = new GearmanClientImpl();
24 
25         client.addJobServer(conn); // 新增連線
26 
27         String functionName = "myUpperFunc";
28 
29         byte[] data = ByteUtils.toUTF8Bytes("hello,world");
30 
31         // 建立後臺任務
32         GearmanJob job = GearmanJobImpl.createJob(functionName, data, null);
33 
34         GearmanJobResult jobResult = null;
35 
36         Future<GearmanJobResult> gearmanJobResult = client.submit(job);
37 
38         jobResult = gearmanJobResult.get();
39 
40         byte[] resultBytes = jobResult.getResults();
41 
42         // 獲取job的返回值
43         String value = ByteUtils.fromUTF8Bytes(resultBytes);
44 
45         System.out.println(value);
46 
47         System.out.println("執行結束");
48 
49         client.shutdown();
50     }
51 }

 

<2> 主程式,開多執行緒併發的去執行。

 1 public class App {
 2     public static void main(String[] args) throws InterruptedException, ExecutionException, IOException {
 3 
 4         ExecutorService executorService = Executors.newFixedThreadPool(100);
 5 
 6         for (int i = 0; i < 10000; i++) {
 7             executorService.execute(new Runnable() {
 8                 
 9                 @Override
10                 public void run() {
11                     Gearsubmit submit=new Gearsubmit();
12                     
13                     try {
14                         submit.process();
15                     } catch (InterruptedException | ExecutionException e) {
16                         // TODO Auto-generated catch block
17                         e.printStackTrace();
18                     }
19                 }
20             });
21         }
22         
23         System.in.read();
24     }
25 }

 

好了,一切都準備好了,接下來為了演示,演示就是解釋,我用Jar2Exe把work程式匯出成jar再轉換成exe,如下圖:

 

然後我把3.exe開成5個例項,client用100個執行緒的執行緒池併發呼叫,當然一切都是模擬。。。。可以看到,當我client啟動的時候,5個work都在執行,

如果這個時候,你把某一個work停止了,jobserver也不再將任務丟給它,而是轉給其他負載相對小的work繼續執行。

 

好了,本篇就說到這裡,希望對你有幫助。

 

相關文章