自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一文讀懂微內(nèi)核架構(gòu)

開發(fā) 架構(gòu)
微內(nèi)核是一種典型的架構(gòu)模式 ,區(qū)別于普通的設(shè)計模式,架構(gòu)模式是一種高層模式,用于描述系統(tǒng)級的結(jié)構(gòu)組成、相互關(guān)系及相關(guān)約束。

 [[361063]]

本文轉(zhuǎn)載自微信公眾號「 JAVA日知錄」,作者 JAVA日知錄 。轉(zhuǎn)載本文請聯(lián)系 JAVA日知錄公眾號。

什么是微內(nèi)核架構(gòu)?

微內(nèi)核是一種典型的架構(gòu)模式 ,區(qū)別于普通的設(shè)計模式,架構(gòu)模式是一種高層模式,用于描述系統(tǒng)級的結(jié)構(gòu)組成、相互關(guān)系及相關(guān)約束。微內(nèi)核架構(gòu)在開源框架中的應(yīng)用非常廣泛,比如常見的 ShardingSphere 還有Dubbo都實現(xiàn)了自己的微內(nèi)核架構(gòu)。那么,在介紹什么是微內(nèi)核架構(gòu)之前,我們有必要先闡述這些開源框架會使用微內(nèi)核架構(gòu)的原因。

為什么要使用微內(nèi)核架構(gòu)?

微內(nèi)核架構(gòu)本質(zhì)上是為了提高系統(tǒng)的擴(kuò)展性 。所謂擴(kuò)展性,是指系統(tǒng)在經(jīng)歷不可避免的變更時所具有的靈活性,以及針對提供這樣的靈活性所需要付出的成本間的平衡能力。也就是說,當(dāng)在往系統(tǒng)中添加新業(yè)務(wù)時,不需要改變原有的各個組件,只需把新業(yè)務(wù)封閉在一個新的組件中就能完成整體業(yè)務(wù)的升級,我們認(rèn)為這樣的系統(tǒng)具有較好的可擴(kuò)展性。

就架構(gòu)設(shè)計而言,擴(kuò)展性是軟件設(shè)計的永恒話題。而要實現(xiàn)系統(tǒng)擴(kuò)展性,一種思路是提供可插拔式的機(jī)制來應(yīng)對所發(fā)生的變化。當(dāng)系統(tǒng)中現(xiàn)有的某個組件不滿足要求時,我們可以實現(xiàn)一個新的組件來替換它,而整個過程對于系統(tǒng)的運(yùn)行而言應(yīng)該是無感知的,我們也可以根據(jù)需要隨時完成這種新舊組件的替換。

比如在 ShardingSphere 中提供的分布式主鍵功能,分布式主鍵的實現(xiàn)可能有很多種,而擴(kuò)展性在這個點上的體現(xiàn)就是, 我們可以使用任意一種新的分布式主鍵實現(xiàn)來替換原有的實現(xiàn),而不需要依賴分布式主鍵的業(yè)務(wù)代碼做任何的改變 。

微內(nèi)核架構(gòu)模式為這種實現(xiàn)擴(kuò)展性的思路提供了架構(gòu)設(shè)計上的支持,ShardingSphere 基于微內(nèi)核架構(gòu)實現(xiàn)了高度的擴(kuò)展性。在介紹如何實現(xiàn)微內(nèi)核架構(gòu)之前,我們先對微內(nèi)核架構(gòu)的具體組成結(jié)構(gòu)和基本原理做簡要的闡述。

什么是微內(nèi)核架構(gòu)?

從組成結(jié)構(gòu)上講, 微內(nèi)核架構(gòu)包含兩部分組件:內(nèi)核系統(tǒng)和插件 。這里的內(nèi)核系統(tǒng)通常提供系統(tǒng)運(yùn)行所需的最小功能集,而插件是獨立的組件,包含自定義的各種業(yè)務(wù)代碼,用來向內(nèi)核系統(tǒng)增強(qiáng)或擴(kuò)展額外的業(yè)務(wù)能力。在 ShardingSphere 中,前面提到的分布式主鍵就是插件,而 ShardingSphere 的運(yùn)行時環(huán)境構(gòu)成了內(nèi)核系統(tǒng)。

那么這里的插件具體指的是什么呢?這就需要我們明確兩個概念,一個概念就是經(jīng)常在說的 API ,這是系統(tǒng)對外暴露的接口。而另一個概念就是 SPI(Service Provider Interface,服務(wù)提供接口),這是插件自身所具備的擴(kuò)展點。就兩者的關(guān)系而言,API 面向業(yè)務(wù)開發(fā)人員,而 SPI 面向框架開發(fā)人員,兩者共同構(gòu)成了 ShardingSphere 本身。

可插拔式的實現(xiàn)機(jī)制說起來簡單,做起來卻不容易,我們需要考慮兩方面內(nèi)容。一方面,我們需要梳理系統(tǒng)的變化并把它們抽象成多個 SPI 擴(kuò)展點。另一方面, 當(dāng)我們實現(xiàn)了這些 SPI 擴(kuò)展點之后,就需要構(gòu)建一個能夠支持這種可插拔機(jī)制的具體實現(xiàn),從而提供一種 SPI 運(yùn)行時環(huán)境 。

如何實現(xiàn)微內(nèi)核架構(gòu)?

事實上,JDK 已經(jīng)為我們提供了一種微內(nèi)核架構(gòu)的實現(xiàn)方式,就是JDK SPI。這種實現(xiàn)方式針對如何設(shè)計和實現(xiàn) SPI 提出了一些開發(fā)和配置上的規(guī)范,ShardingSphere、Dubbo 使用的就是這種規(guī)范,只不過在這基礎(chǔ)上進(jìn)行了增強(qiáng)和優(yōu)化。所以要理解如何實現(xiàn)微內(nèi)核架構(gòu),我們不妨先看看JDK SPI 的工作原理。

JDK SPI

SPI(Service Provider Interface)主要是被框架開發(fā)人員使用的一種技術(shù)。例如,使用 Java 語言訪問數(shù)據(jù)庫時我們會使用到 java.sql.Driver 接口,不同數(shù)據(jù)庫產(chǎn)品底層的協(xié)議不同,提供的 java.sql.Driver 實現(xiàn)也不同,在開發(fā) java.sql.Driver 接口時,開發(fā)人員并不清楚用戶最終會使用哪個數(shù)據(jù)庫,在這種情況下就可以使用 Java SPI 機(jī)制在實際運(yùn)行過程中,為 java.sql.Driver 接口尋找具體的實現(xiàn)。

下面我們通過一個簡單的示例演示一下JDK SPI的使用方式:

首先我們定義一個生成id鍵的接口,用來模擬id生成

  1. public interface IdGenerator { 
  2.     /** 
  3.      * 生成id 
  4.      * @return 
  5.      */ 
  6.     String generateId(); 

然后創(chuàng)建兩個接口實現(xiàn)類,分別用來模擬uuid和序列id的生成

  1. public class UuidGenerator implements IdGenerator { 
  2.     @Override 
  3.     public String generateId() { 
  4.         return UUID.randomUUID().toString(); 
  5.     } 
  6.  
  7. public class SequenceIdGenerator implements IdGenerator { 
  8.     private final AtomicLong atomicId = new AtomicLong(100L); 
  9.     @Override 
  10.     public String generateId() { 
  11.         long leastId = this.atomicId.incrementAndGet(); 
  12.         return String.valueOf(leastId); 
  13.     } 

在項目的resources/META-INF/services 目錄下添加一個名為com.github.jianzh5.spi.IdGenerator的文件,這是 JDK SPI 需要讀取的配置文件,內(nèi)容如下:

  1. com.github.jianzh5.spi.impl.UuidGenerator 
  2. com.github.jianzh5.spi.impl.SequenceIdGenerator 

創(chuàng)建main方法,讓其加載上述的配置文件,創(chuàng)建全部IdGenerator 接口實現(xiàn)的實例,并執(zhí)行生成id的方法。

  1. public class GeneratorMain { 
  2.     public static void main(String[] args) { 
  3.         ServiceLoader<IdGenerator> serviceLoader = ServiceLoader.load(IdGenerator.class); 
  4.         Iterator<IdGenerator> iterator = serviceLoader.iterator(); 
  5.         while(iterator.hasNext()){ 
  6.             IdGenerator generator = iterator.next(); 
  7.             String id = generator.generateId(); 
  8.             System.out.println(generator.getClass().getName() + "  >>id:" + id); 
  9.         } 
  10.     } 

執(zhí)行結(jié)果如下:

JDK SPI 源碼分析

通過上述示例,我們可以看到 JDK SPI 的入口方法是 ServiceLoader.load() 方法,在這個方法中首先會嘗試獲取當(dāng)前使用的 ClassLoader,然后調(diào)用 reload() 方法,調(diào)用關(guān)系如下圖所示:

調(diào)用關(guān)系

在 reload() 方法中,首先會清理 providers 緩存(LinkedHashMap 類型的集合),該緩存用來記錄 ServiceLoader 創(chuàng)建的實現(xiàn)對象,其中 Key 為實現(xiàn)類的完整類名,Value 為實現(xiàn)類的對象。之后創(chuàng)建 LazyIterator 迭代器,用于讀取 SPI 配置文件并實例化實現(xiàn)類對象。

  1. public void reload() { 
  2.  providers.clear(); 
  3.  lookupIterator = new LazyIterator(service, loader); 

在前面的示例中,main() 方法中使用的迭代器底層就是調(diào)用了 ServiceLoader.LazyIterator 實現(xiàn)的。Iterator 接口有兩個關(guān)鍵方法:hasNext() 方法和 next() 方法。這里的 LazyIterator 中的 next() 方法最終調(diào)用的是其 nextService() 方法,hasNext() 方法最終調(diào)用的是 hasNextService() 方法,我們來看看 hasNextService()方法的具體實現(xiàn):

  1. private static final String PREFIX = "META-INF/services/";  
  2. Enumeration<URL> configs = null;  
  3. Iterator<String> pending = null;  
  4. String nextName = null;  
  5. private boolean hasNextService() { 
  6.  if (nextName != null) { 
  7.   return true
  8.  } 
  9.  if (configs == null) { 
  10.   try { 
  11.    //META-INF/services/com.github.jianzh5.spi.IdGenerator 
  12.    String fullName = PREFIX + service.getName(); 
  13.    if (loader == null
  14.     configs = ClassLoader.getSystemResources(fullName); 
  15.    else 
  16.     configs = loader.getResources(fullName); 
  17.   } catch (IOException x) { 
  18.    fail(service, "Error locating configuration files", x); 
  19.   } 
  20.  } 
  21.  // 按行SPI遍歷配置文件的內(nèi)容  
  22.  while ((pending == null) || !pending.hasNext()) { 
  23.   if (!configs.hasMoreElements()) { 
  24.    return false
  25.   } 
  26.   // 解析配置文件  
  27.   pending = parse(service, configs.nextElement()); 
  28.  } 
  29.  // 更新 nextName字段  
  30.  nextName = pending.next(); 
  31.  return true

在 hasNextService() 方法中完成 SPI 配置文件的解析之后,再來看 LazyIterator.nextService() 方法,該方法「負(fù)責(zé)實例化 hasNextService() 方法讀取到的實現(xiàn)類」,其中會將實例化的對象放到 providers 集合中緩存起來,核心實現(xiàn)如下所示:

  1. private S nextService() {  
  2.     String cn = nextName;  
  3.     nextName = null;  
  4.     // 加載 nextName字段指定的類  
  5.     Class<?> c = Class.forName(cn, false, loader);  
  6.     if (!service.isAssignableFrom(c)) { // 檢測類型  
  7.         fail(service, "Provider " + cn  + " not a subtype");  
  8.     }  
  9.     S p = service.cast(c.newInstance()); // 創(chuàng)建實現(xiàn)類的對象  
  10.     providers.put(cn, p); // 將實現(xiàn)類名稱以及相應(yīng)實例對象添加到緩存  
  11.     return p;  
  12. }  

以上就是在 main() 方法中使用的迭代器的底層實現(xiàn)。最后,我們再來看一下 main() 方法中使用 ServiceLoader.iterator() 方法拿到的迭代器是如何實現(xiàn)的,這個迭代器是依賴 LazyIterator 實現(xiàn)的一個匿名內(nèi)部類,核心實現(xiàn)如下:

  1. public Iterator<S> iterator() {  
  2.     return new Iterator<S>() {  
  3.         // knownProviders用來迭代providers緩存  
  4.         Iterator<Map.Entry<String,S>> knownProviders  
  5.             = providers.entrySet().iterator();  
  6.         public boolean hasNext() {  
  7.             // 先走查詢緩存,緩存查詢失敗,再通過LazyIterator加載  
  8.             if (knownProviders.hasNext())   
  9.                 return true;  
  10.             return lookupIterator.hasNext();  
  11.         }  
  12.         public S next() {  
  13.             // 先走查詢緩存,緩存查詢失敗,再通過 LazyIterator加載  
  14.             if (knownProviders.hasNext())  
  15.                 return knownProviders.next().getValue();  
  16.             return lookupIterator.next();  
  17.         }  
  18.         // 省略remove()方法  
  19.     };  
  20. }  

JDK SPI 在 JDBC 中的應(yīng)用

了解了 JDK SPI 實現(xiàn)的原理之后,我們再來看實踐中 JDBC 是如何使用 JDK SPI 機(jī)制加載不同數(shù)據(jù)庫廠商的實現(xiàn)類。

JDK 中只定義了一個 java.sql.Driver 接口,具體的實現(xiàn)是由不同數(shù)據(jù)庫廠商來提供的。這里我們就以 MySQL 提供的 JDBC 實現(xiàn)包為例進(jìn)行分析。

在 mysql-connector-java-*.jar 包中的 META-INF/services 目錄下,有一個 java.sql.Driver 文件中只有一行內(nèi)容,如下所示:

  1. com.mysql.cj.jdbc.Driver 

在使用 mysql-connector-java-*.jar 包連接 MySQL 數(shù)據(jù)庫的時候,我們會用到如下語句創(chuàng)建數(shù)據(jù)庫連接:

  1. String url = "jdbc:xxx://xxx:xxx/xxx";  
  2. Connection conn = DriverManager.getConnection(url, username, pwd);  

「DriverManager 是 JDK 提供的數(shù)據(jù)庫驅(qū)動管理器」,其中的代碼片段,如下所示:

  1. static {  
  2.     loadInitialDrivers(); 
  3.     println("JDBC DriverManager initialized");  
  4. }  

在調(diào)用 getConnection() 方法的時候,DriverManager 類會被 Java 虛擬機(jī)加載、解析并觸發(fā) static 代碼塊的執(zhí)行;在 loadInitialDrivers()方法中通過 JDK SPI 掃描 Classpath 下 java.sql.Driver 接口實現(xiàn)類并實例化,核心實現(xiàn)如下所示:

  1. private static void loadInitialDrivers() {  
  2.     String drivers = System.getProperty("jdbc.drivers")  
  3.     // 使用 JDK SPI機(jī)制加載所有 java.sql.Driver實現(xiàn)類  
  4.     ServiceLoader<Driver> loadedDrivers =   
  5.            ServiceLoader.load(Driver.class);  
  6.     Iterator<Driver> driversIterator = loadedDrivers.iterator();  
  7.     while(driversIterator.hasNext()) {  
  8.         driversIterator.next();  
  9.     }  
  10.     String[] driversList = drivers.split(":");  
  11.     for (String aDriver : driversList) { // 初始化Driver實現(xiàn)類  
  12.         Class.forName(aDriver, true,  
  13.             ClassLoader.getSystemClassLoader());  
  14.     }  
  15. }  

在 MySQL 提供的 com.mysql.cj.jdbc.Driver 實現(xiàn)類中,同樣有一段 static 靜態(tài)代碼塊,這段代碼會創(chuàng)建一個 com.mysql.cj.jdbc.Driver 對象并注冊到 DriverManager.registeredDrivers 集合中(CopyOnWriteArrayList 類型),如下所示:

  1. static {  
  2.    java.sql.DriverManager.registerDriver(new Driver());  

在 getConnection() 方法中,DriverManager 從該 registeredDrivers 集合中獲取對應(yīng)的 Driver 對象創(chuàng)建 Connection,核心實現(xiàn)如下所示:

  1. private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {  
  2.     // 省略 try/catch代碼塊以及權(quán)限處理邏輯  
  3.     for(DriverInfo aDriver : registeredDrivers) {  
  4.         Connection con = aDriver.driver.connect(url, info);  
  5.         return con;  
  6.     }  
  7. }  

小結(jié)

本文我們詳細(xì)講述了微內(nèi)核架構(gòu)的一些基本概念并通過一個示例入手,介紹了 JDK 提供的 SPI 機(jī)制的基本使用,然后深入分析了 JDK SPI 的核心原理和底層實現(xiàn),對其源碼進(jìn)行了深入剖析,最后我們以 MySQL 提供的 JDBC 實現(xiàn)為例,分析了 JDK SPI 在實踐中的使用方式。

 

掌握了JDK的SPI機(jī)制就等于掌握了微內(nèi)核架構(gòu)的核心,以上,希望對你有所幫助!

 

 

責(zé)任編輯:武曉燕 來源: JAVA日知錄
相關(guān)推薦

2021-05-18 09:48:58

前端開發(fā)架構(gòu)

2022-04-26 06:04:11

零信任網(wǎng)絡(luò)安全

2023-11-08 08:21:52

MVPMVVMMVI

2023-12-22 19:59:15

2021-08-04 16:06:45

DataOps智領(lǐng)云

2018-09-28 14:06:25

前端緩存后端

2022-09-22 09:00:46

CSS單位

2025-04-03 10:56:47

2022-11-06 21:14:02

數(shù)據(jù)驅(qū)動架構(gòu)數(shù)據(jù)

2022-07-05 06:30:54

云網(wǎng)絡(luò)網(wǎng)絡(luò)云原生

2023-05-20 17:58:31

低代碼軟件

2023-11-27 17:35:48

ComponentWeb外層

2022-10-20 08:01:23

2022-12-01 17:23:45

2021-12-29 18:00:19

無損網(wǎng)絡(luò)網(wǎng)絡(luò)通信網(wǎng)絡(luò)

2022-07-26 00:00:03

語言模型人工智能

2023-08-27 21:02:14

2018-08-22 17:58:01

數(shù)據(jù)平臺數(shù)據(jù)倉庫架構(gòu)

2020-05-14 14:52:05

HDFS數(shù)據(jù)集架構(gòu)

2019-05-28 10:30:16

Java架構(gòu)微服務(wù)
點贊
收藏

51CTO技術(shù)棧公眾號