rpcx client plugin 源码阅读

代码组织

client plugin的代码组织如下:
RpcxClientPlugin.png

这里提供了一种容器类代码的组织方式。pluginContainer 作为 plugin 的容器,其中的方法有两类:第一类就是 Add , RemoveAll这类的方法,作为容器的基本方法;第二类就是剩下的方法,其中逻辑大致就是遍历 plugin 并调用每一个 plugin 的方法。

Plugin 接口没有定义任何方法。而是又额外的定义了一堆 interfacePreCallPlugin interface。这样做的好处就是可以在自己实现 plugin 接口的时候,不用实现所有已经定义的函数,只需定义自己需要的就可以。其他接口不一定是一个 interface 只有一个函数,可以有多个,作为一组,成为一个约束(如果要实现,就得实现一组全部函数,可以参考rpcx server plugin)。

可以参考下图,对这两种实现方式考虑下优缺点:
rpxc client plugin.png

技术细节

1 接口和实现的关系

pluginContainer 实现了 PluginContainer 接口。因为在具体实现中的方法的接口者一般都是指针类型(因为要修改成员变量plugin []Plugin的值)。所以一般&pluginContainerPluginContainer的类型是一致的。

2 struct 的相等判定条件

pluginContainer.Remove() 函数中,有如下代码:

    var plugins []Plugin
    for _, pp := range p.plugins {
        if pp != plugin {
            plugins = append(plugins, pp)
        }
    }
    p.plugins = plugins

这里有两个点,第一点就是 struct 的相等判定条件,就是只有值和类型都相等的时候,相等判定条件才为 true,具体看下面代码:

package main

import "fmt"

type pluginContainer struct {
    plugins []Plugin
}

type Plugin interface {
}

type APlugin struct {
    A int
}

type BPlugin struct {
    B int
}

func main() {
    var a Plugin
    var b Plugin
    //类型相同,实现类型相同,值相同
    a = APlugin{A: 1}
    b = APlugin{A: 1}
    if a == b {
        fmt.Println("APlugin{A: 1} == APlugin{A: 1}")
    }

    //类型相同,实现类型相同,值不同
    a = APlugin{A: 1}
    b = APlugin{A: 2}
    if a == b {
        fmt.Println("APlugin{A: 1} == APlugin{A: 2}")
    }

    //类型相同,实现类型不同,值相同
    a = APlugin{A: 1}
    b = BPlugin{B: 1}
    if a == b {
        fmt.Println("APlugin{A: 1} == BPlugin{B: 1}")
    }

    //类型相同,实现类型不同,值不同
    a = APlugin{A: 1}
    b = BPlugin{B: 2}
    if a == b {
        fmt.Println("APlugin{A: 1} == BPlugin{B: 2}")
    }
}
3 删除操作的一般写法

如上述 pluginContainer.Remove() 函数,第二点就是从 slice 删除元素的一般写法。这里我们是对 plugin []Plugin 进行删除,判定等于传入的 plugin 的元素就删除掉。一般如果我们知道要删除的index,我们可以这样删除 plugin = append(plugin[:index], plugin[index:])。如果要删除特定元素,就按照源码中写的删除就可以了,记得要重新定义个变量var plugins []Plugin,之后再赋值p.plugins = plugins

4 slice append是线程不安全的

源码中 pluginContainer.Add() 函数,直接 p.plugins = append(p.plugins, plugin) 这样如果并发访问的话,会丢数据。所以要额外注意。一般 slice 并发 append 会丢数据,map 并发赋值会 panic

这里额外说下 map 容易 panic 的情况,以及我们如何在实际写代码的时候避免。

  • map 声明后是个 nil 类型,直接赋值会 panic。所以一般最好用 make 初始化。
  • 用别人的包的时候如果有 confutil.GetConfMap() 获取一个外部 map 这种代码,要注意不要直接把这个直接放在并发环境中。因为它可能是个全局的 map 并且 GetConfMap() 函数中有delete或者赋值这些导致并发 panic 的操作。
  • map[int]map[int]int 这种嵌套的 map ,深层的 map 注意也要 make

发表评论

电子邮件地址不会被公开。 必填项已用*标注