Go语言网络爬虫组件注册器

在讲解下载器接口设计时,我们介绍过组件注册方面的设计和组件注册器接口 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{},
    }
}