Go語言網路爬蟲元件註冊器

2020-07-16 10:04:49
在講解下載器介面設計時,我們介紹過元件註冊方面的設計和元件註冊器介面 Registrar,它宣告在 module 包中。根據前面的介面描述,我們會讓元件註冊器按照型別儲存已註冊的元件。該介面的宣告如下:
//細件註冊器的實現型別
type myRegistrar struct {
    //元件型別與對應元件範例的對映
    moduleTypeMap map[Type]map[MID]Module
    //元件註冊專用讀寫鎖
    rwlock sync.RWMutex
}
在元件註冊器的實現型別 myRegistrar 中只有兩個欄位,一個用於分類儲存元件範例,另一個用於讀防寫。由於註冊和登出元件範例的動作肯定不會太頻繁,所以這裡簡單地使用讀寫鎖實施保護就足夠了。

moduleTypeMap 表示一個雙層的字典結構,其中第一層提供了分型別註冊和獲取元件範例集合的能力,而第二層則負責儲存元件範例集合。

Registrar 介面的 Register 方法只需做兩件事:檢查引數和註冊元件範例。在檢查引數時,Register 方法用到了 module 包中的一些工具類方法和變數。該方法的實現如下:
func (registrar *myRegistrar) Register(module Module) (bool, error) {
    if module == nil {
        return false, errors.NewIllegalParameterError("nil module instance")
    }
    mid := module.ID()
    parts, err := SplitMID(mid)
    if err != nil {
        return false, err
    }
    moduleType := legalLetterTypeMap[parts[0]]
    if !CheckType(moduleType, module) {
        errMsg := fmt.Sprintf("incorrect module type: %s", moduleType)
        return false, errors.NewIllegalParameterError(errMsg)
    }
    //省略部分程式碼
}
前面已經介紹過 NewIllegalParameterError 和 SplitMID 函數。legalLetterTypeMap 變數是一個以元件型別字母為鍵、以元件型別為元素的字典,是前面介紹的 legalTypeLetterMap 的反向對映。CheckType 函數的功能是檢查從元件 ID 解析出來的元件型別與元件範例的實際型別是否相符。

如果所有檢查都通過了,那麼 Register 方法就會把元件範例儲存在 moduleTypeMap 中。當然,肯定會在 rwlock 的保護之下操作 moduleTypeMap。

Unregister 方法會把與給定的元件 ID 對應的元件範例從 moduleTypeMap 刪除掉。在真正進行查詢和刪除操作前,它會先通過呼叫 SplitMID 函數檢查那個元件 ID 的合法性。

Get 方法的實現包含負載均衡的策略,並返回最“空閒”的那個元件範例:
// Get用於獲取一個指定型別的元件的範例。
//本函數會基於負載均衡策略返回範例
func (registrar *myRegistrar) Get(moduleType Type) (Module, error) {
    modules, err := registrar.GetAllByType(moduleType)
    if err != nil {
        return nil, err
    }
    minScore := uint64(0)
    var selectedModule Module
    for _, module := range modules {
        SetScore(module)
        if err != nil {
            return nil, err
        }
        score := module.Score()
        if minScore == 0 || score < minScore {
            selectedModule = module
            minScore = score
        }
    }
    return selectedModule, nil
}
該方法先呼叫註冊器的 GetAllByType 方法以獲得指定型別的元件範例的集合,然後在遍歷它們時計算其評分,並找到評分最低者,最後返回。其中 SetScore 是一個工具類函數,它通過元件範例的 ScoreCalculator 方法獲得它的評分計算器。若該方法返回 nil,則使用預設的計算函數。計算其評分後,再通過元件範例的 SetScore 方法設定評分。

為了讓 *myRegistrar 型別成為 Registrar 介面的實現型別,還需要實現它的 GetAllByType、GetAll 和 Clear 方法。不過這幾個方法的實現都非常簡單,這裡就不展示了。這裡需要注意的是對 rwlock 欄位的合理運用。

一旦把所有方法都編寫好,下面這個函數就可以編譯通過了:
//用於建立一個元件註冊器的範例
func NewRegistrar() Registrar {
    return &myRegistrar{
        moduleTypeMap: map[Type]map[MID]Module{},
    }
}