多執行緒在微服務API統計和健康檢查中的使用
API統計
在服務呼叫的時候,統計每個介面的呼叫次數,從而做到對介面的限流或統計。
在下面的程式碼中,使用了多執行緒的方式進行統計,主要使用瞭如下概念
執行緒池 Executor
ConcurrentHashMap
CountDownLatch
其中列舉了四種實現方式
1 使用ConcurrentHashMap統計:不過該方法存在問題,統計的increase不是執行緒安全的,所以得到的結果不對
2 使用CAS理念對ConcurrentHashMap進行改進,從而解決自增方法increase的問題
3 使用Google的AtomicLongMap,原理同CAS一致,程式碼量小,比較優雅
4 對HashMap加鎖ReentrantReadWriteLock
本文程式碼示例:
使用ConcurrentHashMap統計
package concurrent;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * Java 併發實踐- ConcurrentHashMap 與 CAS * API呼叫次數統計 * 涉及概念: 多執行緒/執行緒池/ConcurrentHashMap/CountDownLatch * @author billjiang * @createTime 2017-08-04 */public class CounterDemo { private final Map<String, Long> urlCounter = new ConcurrentHashMap<>(); /** * 介面呼叫次數,此方法存在問題,ConcurrentHashMap的原子方法是同步的,但increase方法沒有同步 * @param url * @return */ public long increase(String url) { Long oldValue=urlCounter.get(url); Long newValue=(oldValue==null)?1l:oldValue+1; urlCounter.put(url,newValue); return newValue; } //獲取呼叫次數 public long getCount(String url){ return urlCounter.get(url); } public static void main(String[] args) { ExecutorService executorService= Executors.newFixedThreadPool(10); final CounterDemo counterDemo=new CounterDemo(); int callTime=100000; final String url=""; CountDownLatch countDownLatch=new CountDownLatch(callTime); //模擬併發情況下的介面呼叫統計 for (int i = 0; i < callTime; i++) { executorService.execute(new Runnable() { @Override public void run() { counterDemo.increase2(url); countDownLatch.countDown(); } }); } try{ countDownLatch.await(); }catch (InterruptedException e){ e.printStackTrace(); } executorService.shutdown(); //等待所有執行緒統計完成後輸出呼叫次數 System.out.println("呼叫次數:"+counterDemo.getCount(url)); } }
ConcurrentHashMap
從結果上看,使用ConcurrentHashMap存在問題,沒有輸出預期結果,這是因為ConcurrentHashMap雖然是執行緒安全的,不過它的執行緒安全指的是get
和put
等原子方法。而方法increase卻不是執行緒安全的,當然可以透過對increase方法加鎖(使用synchonized關鍵字),不過synchonized是悲觀鎖,其他執行緒要掛起等待,影響效能。可以使用類似樂觀鎖CAS對increase改進。
使用CAS對increase方法改進
關於CAS,可參考這篇文章:
改進後的increase方法如下:
/** * CAS 樂觀鎖/自旋 * @param url * @return */ public long increase2(String url){ Long oldValue,newValue; while(true){ oldValue=urlCounter.get(url); if(oldValue==null){ newValue=1l; //初始化成功,退出迴圈 if(urlCounter.putIfAbsent(url,1l)==null) break; //如果初始化失敗,說明其他執行緒已經初始化了 }else{ newValue=oldValue+1; //+1成功,退出迴圈 if(urlCounter.replace(url,oldValue,newValue)){ break; //如果+1失敗,則說明其他執行緒已經修改過了舊值 } } } return newValue; }
不過還有更簡單的方法,就是使用AtomicLongMap
使用Google的AtomicLongMap
AtomicLongMap<String> urlCounter3 = AtomicLongMap.create(); //執行緒安全,支援併發public long increase3(String url){ return urlCounter3.incrementAndGet(url); }
傳統做法,對HashMap加鎖
Map<String, Integer> map = new HashMap<String, Integer>(); //執行緒不安全 ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); //為map2增加併發鎖 public long increase4(String url){ //對map2新增寫鎖,可以解決執行緒併發問題 lock.writeLock().lock(); try{ if(map.containsKey(key)){ map.put(key, map.get(key)+1); }else{ map.put(key, 1); } }catch(Exception ex){ ex.printStackTrace(); }finally{ lock.writeLock().unlock(); } }
上文中提到的CountDownLatch的概念可參考:
CountDownLatch
健康檢查
場景:服務註冊中心需要定時對服務提供者進行心跳檢測,即定時呼叫服務提供者的特定藉口,如果返回正常狀態嗎,則認為服務正常,否則,認為服務提供者異常,在註冊中心顯示為Down
狀態,如Consul的服務健康檢查機制與之類似。
下面使用CountDownLatch和執行緒池模擬這種實現。
思路
首先定義一個應用程式啟動類,它開始時啟動了n個執行緒類,這些執行緒將檢查外部系統並通知閉鎖,並且啟動類一直在閉鎖上等待著。一旦驗證和檢查了所有外部服務,那麼啟動類恢復執行。
實現
BaseHealthChecker:基礎健康檢查類,實現Runable介面,包含CountDownLatch, ServiceName(服務名稱),ServiceUp(服務狀態),其中verifyService 為具體繼承該類的子類要實現的方法。
package concurrent.health;import java.util.concurrent.CountDownLatch;public abstract class BaseHealthChecker implements Runnable { private CountDownLatch countDownLatch; private String serviceName; private boolean serviceUp; public BaseHealthChecker(String serviceName,CountDownLatch countDownLatch){ super(); this.serviceName=serviceName; this.countDownLatch=countDownLatch; this.serviceUp=false; } @Override public void run() { try{ verifySerivce(); serviceUp=true; }catch (Throwable t){ t.printStackTrace(System.err); serviceUp=false; }finally { if(countDownLatch!=null) countDownLatch.countDown(); } } public String getServiceName() { return serviceName; } public boolean isServiceUp() { return serviceUp; } //this method need to be implemented by all specific service checker public abstract void verifySerivce(); }
DatabaseHealthChecker: 資料庫健康檢查類
package concurrent.health;import java.util.concurrent.CountDownLatch;public class DataBaseHealthChecker extends BaseHealthChecker { public DataBaseHealthChecker(CountDownLatch countDownLatch) { super("database service", countDownLatch); } @Override public void verifySerivce() { System.out.println("Checking " + this.getServiceName()); try { Thread.sleep(7000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(this.getServiceName() + " is UP"); } }
FileHealthChecker:檔案服務健康檢查(UserHealthChecker類似)
package concurrent.health;import java.util.concurrent.CountDownLatch;public class FileHealthChecker extends BaseHealthChecker { public FileHealthChecker(CountDownLatch countDownLatch) { super("file service", countDownLatch); } @Override public void verifySerivce() { System.out.println("Checking " + this.getServiceName()); try { Thread.sleep(7000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(this.getServiceName() + " is UP"); } }
ApplicationStartupUtil:服務註冊中心呼叫發起方的主類,在系統啟動的時候發起健康檢測請求。
package concurrent.health;import java.util.ArrayList;import java.util.List;import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class ApplicationStartupUtil { //list of service checker private static List<BaseHealthChecker> checkers; //this latch will be used to wait on private static CountDownLatch countDownLatch; //singleton private ApplicationStartupUtil() { } private static ApplicationStartupUtil applicationStartupUtil = new ApplicationStartupUtil(); public static ApplicationStartupUtil getInstance() { return applicationStartupUtil; } public static boolean checkExternalServices() throws InterruptedException { //init the latch with the number of service checks countDownLatch = new CountDownLatch(3); //add all service checks into the list checkers = new ArrayList<>(); checkers.add(new DataBaseHealthChecker(countDownLatch)); checkers.add(new UserHealthChecker(countDownLatch)); checkers.add(new FileHealthChecker(countDownLatch)); //start service checks using executor framework ExecutorService executor = Executors.newFixedThreadPool(checkers.size()); for (BaseHealthChecker checker : checkers) { executor.execute(checker); } //now wait all services checked countDownLatch.await(); //service checkers are finished and now proceed startup for (BaseHealthChecker checker : checkers) { if (!checker.isServiceUp()) { return false; } } return true; } }
測試
測試方法
package concurrent.health;public class TestMain { public static void main(String[] args) { boolean result = false; try { result = ApplicationStartupUtil.checkExternalServices(); } catch (Exception ex) { ex.printStackTrace(); } System.out.println("External services validation completed !! Result was :: " + result); } }
結果
Checking database service Checking file service Checking user service database service is UP user service is UP file service is UP External services validation completed !! Result was :: true
作者:billJiang
連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/854/viewspace-2820477/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 在Web應用程式中執行計劃任務(多執行緒) (轉)Web執行緒
- 多執行緒-執行緒組的概述和使用執行緒
- 多執行緒-執行緒池的概述和使用執行緒
- ArcGISEngine中的多執行緒使用執行緒
- Android中後臺的服務和多執行緒Android執行緒
- 多執行緒和多執行緒同步執行緒
- C#Invoke委託在多執行緒中的使用C#執行緒
- .NET多執行緒程式設計(1):多工和多執行緒 (轉)執行緒程式設計
- 多執行緒:執行緒池理解和使用總結執行緒
- 多執行緒程式設計基礎(一)-- 執行緒的使用執行緒程式設計
- 多執行緒查詢執行緒
- 多執行緒-程式和執行緒的概述執行緒
- Java多執行緒-執行緒池的使用Java執行緒
- boost中asio網路庫多執行緒併發處理實現,以及asio在多執行緒模型中執行緒的排程情況和執行緒安全。執行緒模型
- MFC多執行緒的建立,包括工作執行緒和使用者介面執行緒執行緒
- Java中命名執行器服務執行緒和執行緒池Java執行緒
- .net使用Task多執行緒執行任務 .net限制執行緒數量執行緒
- 多執行緒統計多個檔案的單詞數目---C++0x多執行緒使用示例執行緒C++
- 使用Windows API和多執行緒進行串列埠通訊[1] (轉)WindowsAPI執行緒串列埠
- 使用執行緒池優化多執行緒程式設計執行緒優化程式設計
- Swift多執行緒:使用Thread進行多執行緒間通訊,協調子執行緒任務Swift執行緒thread
- 程式設計思想之多執行緒與多程式(3):Java 中的多執行緒程式設計執行緒Java
- 多執行緒程式設計基礎(二)-- 執行緒池的使用執行緒程式設計
- 執行緒和程式基礎以及多執行緒的基本使用(iOS)執行緒iOS
- C#中的執行緒(三)多執行緒C#執行緒
- 執行緒轉儲:命名你的執行緒和檢視系統(轉)執行緒
- java多執行緒之執行緒的基本使用Java執行緒
- 多執行緒使用執行緒
- 在.NET Core 中實現健康檢查
- 使用多執行緒提高rest服務效能執行緒REST
- .NET應用架構設計—服務端開發多執行緒使用小結(多執行緒使用常識)應用架構服務端執行緒
- 程式設計思想之多執行緒與多程式(4):C++ 中的多執行緒程式設計執行緒C++
- Java中的多執行緒Java執行緒
- RxJava 中的多執行緒RxJava執行緒
- Qt 中的多執行緒QT執行緒
- 多執行緒中的ManualResetEvent執行緒
- Go微服務 - 第六部分 - 健康檢查Go微服務
- Java多執行緒——獲取多個執行緒任務執行完的時間Java執行緒