Nutch外掛系統

張蘭雲發表於2013-08-25

Nutch 基本情況

Nutch 是 Apache 基金會的一個開源專案,它原本是開原始檔索引框架 Lucene 專案的一個子專案,後來漸漸發展成長為一個獨立的開源專案。它基於 Java 開發,基於 Lucene 框架,提供 Web 網頁爬蟲功能。另外很吸引人的一點在於,它提供了一種外掛框架,使得其對各種網頁內容的解析、各種資料的採集、查詢、叢集、過濾等功能能夠方便的進行擴充套件,正是由於有此框架,使得 Nutch 的外掛開發非常容易,第三方的外掛也層出不窮,極大的增強了 Nutch 的功能和聲譽。本文就是主要描述這個外掛框架內部執行的機制和原理。

 

Nutch 的外掛體系結構

在 Nutch 的外掛體系架構下,有些術語需要在這裡解釋:

  1. 擴充套件點 ExtensionPoint

    擴充套件點是系統中可以被再次擴充套件的類或者介面,通過擴充套件點的定義,可以使得系統的執行過程變得可插入,可任意變化。

  2. 擴充套件 Extension

    擴充套件式外掛內部的一個屬性,一個擴充套件是針對某個擴充套件點的一個實現,每個擴充套件都可以有自己的額外屬性,用於在同一個擴充套件點實現之間進行區分。擴充套件必須在外掛內部進行定義。

  3. 外掛 Plugin

    外掛實際就是一個虛擬的容器,包含了多個擴充套件 Extension、依賴外掛 RequirePlugins 和自身釋出的庫 Runtime,外掛可以被啟動或者停止。

Nutch 為了擴充套件,預留了很多擴充套件點 ExtenstionPoint,同時提供了這些擴充套件點的基本實現 Extension,Plugin 用來組織這些擴充套件,這些都通過配置檔案進行控制,主要的配置檔案包括了多個定義擴充套件點和外掛(擴充套件)的配置檔案,一個控制載入哪些外掛的配置檔案。體系結構圖如下:


圖 1. Nutch 外掛體系結構圖
圖 1. Nutch 外掛體系結構圖 

 

外掛的內部結構


圖 2. 外掛的內部結構
圖 2. 外掛的內部結構 

  1. runtime 屬性描述了其需要的 Jar 包,和釋出的 Jar 包
  2. requires 屬性描述了依賴的外掛
  3. extension-point 描述了本外掛宣佈可擴充套件的擴充套件點
  4. extension 屬性則描述了擴充套件點的實現

典型的外掛定義:

<plugin
    id="query-url" 外掛的ID
    name="URL Query Filter"  外掛的名字
    version="1.0.0"  外掛的版本
    provider-name="nutch.org"> 外掛的提供者ID

    <runtime>
        <library name="query-url.Jar"> 依賴的Jar包
            <export name="*"/>  釋出的Jar包
        </library>
    </runtime>

    <requires>
        <import plugin="nutch-extensionpoints"/> 依賴的外掛
    </requires>

    <extension id="org.apache.nutch.searcher.url.URLQueryFilter" 擴充套件的ID
        name="Nutch URL Query Filter"  擴充套件的名字
        point="org.apache.nutch.searcher.QueryFilter"> 擴充套件的擴充套件點ID
        <implementation id="URLQueryFilter" 實現的ID
            class="org.apache.nutch.searcher.url.URLQueryFilter"> 實現類
            <parameter name="fields" value="url"/> 實現的相關屬性
        </implementation>
    </extension>
</plugin>

 

 

外掛主要配置

  1. plugin.folders:外掛所在的目錄,預設位置在 plugins 目錄下。
    <property>
        <name>plugin.folders</name>
        <value>plugins</value>
        <description>Directories where nutch plugins are located.  Each
        element may be a relative or absolute path.  If absolute, it is used
        as is.  If relative, it is searched for on the classpath.
        </description>
    </property>
    
  2. plugin.auto-activation:當被配置為過濾(即不載入),但是又被其他外掛依賴的時候,是否自動啟動,預設為 true。
    <property>
      <name>plugin.auto-activation</name>
      <value>true</value>
      <description>Defines if some plugins that are not activated regarding
      the plugin.includes and plugin.excludes properties must be automaticaly
      activated if they are needed by some actived plugins.
      </description>
    </property>
    
  3. plugin.includes:要包含的外掛名稱列表,支援正規表示式方式定義。
    <property>
      <name>plugin.includes</name>
      <value>protocol-http|urlfilter-regex|parse-(text|html|js)|index-(basic|anchor)
        |query-(basic|site|url)|response-(json|xml)|summary-basic|scoring-opic|
        urlnormalizer-(pass|regex|basic)
      </value>
      <description>Regular expression naming plugin directory names to
      include.  Any plugin not matching this expression is excluded.
      In any case you need at least include the nutch-extensionpoints plugin. By
      default Nutch includes crawling just HTML and plain text via HTTP,
      and basic indexing and search plugins. In order to use HTTPS please enable 
      protocol-httpclient, but be aware of possible intermittent problems with the 
      underlying commons-httpclient library.
      </description>
    </property>
    
  4. plugin.excludes:要排除的外掛名稱列表,支援正規表示式方式定義。
    <property>
      <name>plugin.excludes</name>
      <value></value>
      <description>Regular expression naming plugin directory names to exclude.  
      </description>
    </property>
    
 

外掛主要類 UML 圖


圖 3. 外掛主要類 UML 圖(檢視大圖
圖 3. 外掛主要類 UML 圖 

類包括:

  1. PluginRepository 是一個通過載入 Iconfiguration 配置資訊初始化的外掛庫,裡面維護了系統中所有的擴充套件點 ExtensionPoint 和所有的外掛 Plugin 例項
  2. ExtensionPoint 是一個擴充套件點,通過擴充套件點的定義,外掛 Plugin 才能定義實際的擴充套件 Extension,從而實現擴充套件,每個 ExtensionPoint 類例項都維護了宣佈實現了此擴充套件點的擴充套件 Extension.
  3. Plugin 是一個虛擬的組織,提供了一個啟動 start 和一個 shutdown 方法,從而實現了外掛的啟動和停止,他還有一個描述物件 PluginDescriptor,負責儲存此外掛相關的配置資訊,另外還有一個 PluginClassLoader 負責此外掛相關類和庫的載入。
 

外掛載入過程


圖 4 . 外掛載入過程時序圖(檢視大圖
圖 4 . 外掛載入過程時序圖 

通過序列圖可以發現,Nutch 載入外掛的過程需要 actor 全程直接呼叫每個關聯物件,最終得到的是外掛的實現物件。詳細過程如下:

  1. 首先通過 PluginRepository.getConf() 方法載入配置資訊,配置的內容包括外掛的目錄,外掛的配置檔案資訊 plugin.properties 等,此時 pluginrepository 將根據配置資訊載入各個外掛的 plugin.xml,同時根據 Plugin.xml 載入外掛的依賴類。
  2. 當 actor 需要載入某個擴充套件點的外掛的時候,他可以:
    1. 首先根據擴充套件點的名稱,通過 PluginRepository 得到擴充套件點的例項,即 ExtensionPoint 類的例項;
    2. 然後呼叫 ExtensionPoint 物件的 getExtensions 方法,返回的是實現此擴充套件點的例項列表(Extension[]);
    3. 對每個實現的擴充套件例項 Extension,呼叫它的 getExtensionInstance() 方法,以得到實際的實現類例項,此處為 Object;
    4. 根據實際情況,將 Object 轉型為實際的類物件型別,然後呼叫它們的實現方法,例如 helloworld 方法。
 

外掛的典型呼叫方式

得到某個語言例如“GBK”擴充套件點的例項:

this.extensionPoint.getExtensions();// 得到擴充套件點的所有擴充套件
    for (int i=0; i<extensions.length; i++) {// 遍歷每個擴充套件
        if (“GBK”.equals(extensions[i].getAttribute("lang"))) {// 找到某個屬性的擴充套件
            return extensions[i];// 返回
        } 
    } 
} 
extension.getExtensionInstance()// 得到此擴充套件實現的例項物件

 

 

外掛類載入機制

實際整個系統如果使用了外掛架構,則外掛類的載入是由 PluginClassLoader 類完成的,每個 Plugin 都有自己的 classLoader,此 classloader 繼承自 URLClassLoader,並沒有做任何事情:

public class PluginClassLoader extends URLClassLoader { 
    /** 
    * Construtor 
    * 
    * @param urls 
    *          Array of urls with own libraries and all exported libraries of 
    *          plugins that are required to this plugin 
    * @param parent 
    */ 
    public PluginClassLoader(URL[] urls, ClassLoader parent) { 
        super(urls, parent); 
    } 
} 

 

這個 classloader 是屬於這個外掛的,它只負責載入本外掛相關的類、本地庫和依賴外掛的釋出 (exported) 庫,也包括一些基本的配置檔案例如 .properties 檔案。

此類的例項化過程:

if (fClassLoader != null) 
    return fClassLoader; 
ArrayList<URL> arrayList = new ArrayList<URL>(); 
arrayList.addAll(fExportedLibs); 
arrayList.addAll(fNotExportedLibs); 
arrayList.addAll(getDependencyLibs()); 
File file = new File(getPluginPath()); 
try { 
    for (File file2 : file.listFiles()) { 
        if (file2.getAbsolutePath().endsWith("properties")) 
            arrayList.add(file2.getParentFile().toURL()); 
    } 
} catch (MalformedURLException e) { 
    LOG.debug(getPluginId() + " " + e.toString()); 
} 
URL[] urls = arrayList.toArray(new URL[arrayList.size()]); 
fClassLoader = new PluginClassLoader(urls, PluginDescriptor.class 
    .getClassLoader()); 
return fClassLoader; 

 

  • 首先判斷快取是否存在
  • 載入需要的 Jar 包、自身需要的 Jar 包,依賴外掛釋出的 Jar 包
  • 載入本地的 properties 檔案
  • 構造此 classloader,父 classloader 為 PluginDescriptor 的載入者,通常是 contextClassLoader
 

總結

Nutch 是一個非常出色的開源搜尋框架,它的外掛架構更加是它的一個技術亮點,通過此架構,可以保證 Nutch 方便的被靈活的擴充套件而不用修改原來的程式碼,通過配置檔案可以簡單方便的控制載入或者不載入哪些外掛,而且這些都不需要額外的容器支援。這些都是我們在系統架構設計的時候可以學習和參考的有益經驗。

相關文章