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繼續執行。
好了,本篇就說到這裡,希望對你有幫助。