开始之前补充一个点:main包下引入了一堆其它模块的包,
这些包下有初始化函数init()
举两个例子:
一.第22行:
_ "github.com/polarismesh/polaris/apiserver/grpcserver/config"
点进去后发现:
// init 自注册到API服务器插槽
func init() {
_ = apiserver.Register("config-grpc", &ConfigGRPCServer{})
}
// Register 注册API服务器
func Register(name string, server Apiserver) error {
//全局变量:var Slots = make(map[string]Apiserver)
if _, exist := Slots[name]; exist {
return fmt.Errorf("apiserver name:%s exist", name)
}
Slots[name] = server
return nil
}
二.第50行:
_ "github.com/polarismesh/polaris/store/boltdb"
点进去,找到init(), 再点进去可看到本函数:
// RegisterStore 注册一个新的Store
func RegisterStore(s Store) error {
name := s.Name()
// 全局变量 var StoreSlots = make(map[string]Store)
if _, ok := StoreSlots[name]; ok {
return errors.New("store name already existed")
}
StoreSlots[name] = s
return nil
}
这里需要留意上图第43行代码:name := s.Name()
, 查看此接口实现:
这意味着我们不能随便写配置项!!!
因为上面一堆init()初始化函数已经定死了大部分配置项的名称,当然也有部分配置项允许我们自己编写,去覆盖默认选项,后面再说。
林林总总,都是这种套路,补充完毕,开始进入本篇的启动分析第二部分。
初始化存储层: 把存储相关配置保存到全局变量中
store.SetStoreConfig(&cfg.Store)
里面就一行代码:config = conf
这里再一次贴出截图,后续此类就不再赘述了。
同时再次提醒一次,请留意层次结构,保存这种配置的类在store包下的store.go中,这是因为。。。看下图👇:store这一类配置可能有不同的存储实现,比如boltdb/mysql/…其它甚至mock。。。。所以把配置放在统管全局的store包下的store.go类中,具体store下面的boltdb/mock/mysql等子包 是具体不同的实现。
后续很多类似代码组织风格,不再赘述。
进入s, err = store.GetStore()
看看:
看看上图第64行实现:initialize(store)
,它是本篇核心内容:
// initialize 包裹了初始化函数,在GetStore的时候会在自动调用,全局初始化一次
func initialize(s Store) {
once.Do(func() {
fmt.Printf("[Store][Info] current use store plugin : %s\n", s.Name())
// 看这里👉 进入此函数查看:
if err := s.Initialize(config); err != nil {
fmt.Printf("[ERROR] initialize store `%s` fail: %v", s.Name(), err)
os.Exit(1)
}
})
}
因为我们使用的是boltdb,所以进入下面实现类:
先放一张总代码图:
先根据配置初始化boltConfig对象:
存储boltdb文件位置:
// BoltConfig config to initialize boltdb
type BoltConfig struct {
// FileName boltdb store file
FileName string
}
进入boltConfig.Parse(c.Option):
核心代码就一行:把option配置值赋值给 c.FileName = value.(string)
重点在第110行:handler, err := NewBoltHandler(boltConfig)
,进入看看:
// NewBoltHandler create the boltdb handler
func NewBoltHandler(config *BoltConfig) (BoltHandler, error) {
db, _ := openBoltDB(config.FileName)
// 好多模块都引用了它!
return &boltHandler{db: db}, nil
}
// 这个对象封装了对boltdb的操作
type boltHandler struct {
db *bolt.DB
}
至此,底层boltdb操作对象数据模型已经准备好,那么有哪些模块用到它呢?
我们看一下boltStore数据模型:
type boltStore struct {
*namespaceStore
*clientStore
// 服务注册发现、治理
*serviceStore
*instanceStore
*l5Store
*routingStore
*rateLimitStore
*circuitBreakerStore
*faultDetectStore
*routingStoreV2
*serviceContractStore
*laneStore
// 配置中心stores
*configFileGroupStore
*configFileStore
*configFileReleaseStore
*configFileReleaseHistoryStore
*configFileTemplateStore
// adminStore store
*adminStore
// 工具
*toolStore
// 鉴权模块相关
*userStore
*groupStore
*strategyStore
*grayStore
handler BoltHandler
}
模块很多,但是幸运的是,除了 adminStore 和 toolStore稍微有点特殊之外,其它所有的数据模型都是下面这样的:只有一个handler引用,而且引用的对象正是上面介绍的boltHandler
对象
type namespaceStore struct {
handler BoltHandler
}
type clientStore struct {
handler BoltHandler
}
。。。
type grayStore struct {
handler BoltHandler
}
而且toolStore
模块更绝,整个类内容只有这点:
它们底层共用一个BoltHandler, 通过typ区分业务数据分类,再通过key区分一个分类在下不同业务。
接下来的代码正是围绕这些模块去做初始化操作的,下面一个一个来说:
进入115行的m.newStore()
里面:
代码风格分三类:
- 初始化模块,然后初始化数据(下图中的红框 部分)
- 只有初始化模块,没有初始化数据(下图中的蓝框 部分)
- 独立的初始化方法(下图中的 绿框部分)
因为是首次看到类似代码,我们分析得细点,我们进去看看 红框的 初始化数据方法:
一、l5.go 数据初始化:
通用Execute
方法:
l5模块数据初始化:
const (
tblNameL5 = "l5"
rowSidKey = "sidSequence"
colModuleId = "module_id"
colInterfaceId = "interface_id"
colRangeNum = "range_num"
)
func updateL5SidTable(rowBucket *bolt.Bucket, mid uint64, iid uint64, rnum uint64) error {
var err error
if err = rowBucket.Put([]byte(colModuleId), encodeUintBuffer(mid, typeUint32)); err != nil {
return err
}
if err = rowBucket.Put([]byte(colInterfaceId), encodeUintBuffer(iid, typeUint32)); err != nil {
return err
}
if err = rowBucket.Put([]byte(colRangeNum), encodeUintBuffer(rnum, typeUint32)); err != nil {
return err
}
return nil
}
二、namespace数据初始化:
FYI: 省略了err判断代码
const (
defaultNamespace = "default"
polarisNamespace = "Polaris"
)
// InitData initialize the namespace data
func (n *namespaceStore) InitData() error {
namespaces := []string{defaultNamespace, polarisNamespace}
for _, namespace := range namespaces {
ns, _ := n.GetNamespace(namespace)
if ns == nil {
// 我们进去看看这个实现内容:
err = n.AddNamespace(&model.Namespace{
Name: namespace,
Comment: namespaceToComment[namespace],
Token: namespaceToToken[namespace],
Owner: "polaris",
Valid: true,
CreateTime: time.Now(),
ModifyTime: time.Now(),
})
}
}
return nil
}
// AddNamespace add a namespace
func (n *namespaceStore) AddNamespace(namespace *model.Namespace) error {
// 先删除无效数据,再添加新数据
if err := n.cleanNamespace(namespace.Name); err != nil {
return err
}
tn := time.Now()
namespace.CreateTime = tn
namespace.ModifyTime = tn
namespace.Valid = true
// 最后再看看这里面的实现:
return n.handler.SaveValue(tblNameNamespace, namespace.Name, n.toStore(namespace))
}
// SaveValue insert data object, each data object should be identified by unique key
func (b *boltHandler) SaveValue(typ string, key string, value interface{}) error {
return b.db.Update(func(tx *bolt.Tx) error {
return saveValue(tx, typ, key, value)
})
}
看上述代码后知道了为什么我们登录Polaris控制台时,能看到默认有2个命名空间:default + Polaris
了吧?!
另外:初始化数据的最后,都回归到 boltHandler下的db操作,只不过不同模块初始化的数据不同。
第2类初始化跳过。。。
继续说第3类初始化:
m.newDiscoverModuleStore()
m.newAuthModuleStore()
m.newConfigModuleStore()
m.newMaintainModuleStore()
原来全是这样的初始化,那也先不用看:
总结
:这么多模块初始化工作:都持有m.handler
( BoltHandler类型)引用,然后根据模块本身的类型值 去做区分,对应不同的boltdb数据。
m.newStore();
方法介绍完毕,下面代码如下所示:
上图中的if… else 会走哪一分支呢?再复习下咱们的yaml配置内容:
咱们只配置了path项,没有loadFile
,所以走else. 如果想自定义加载配置option,可以看看代码,或者参照默认加载的配置内容 去配置,加载逻辑是相通的。另外,想必能看到这里的朋友也有点累了,咱们也看简单的默认分支代码部分:
进入第125行的m.loadByDefault()
func (m *boltStore) loadByDefault() error {
// 1.初始化授权存储数据
if err := m.initAuthStoreData(); err != nil {
_ = m.handler.Close()
return err
}
// 2.初始化命名存储数据
if err := m.initNamingStoreData(); err != nil {
_ = m.handler.Close()
return err
}
return nil
}
其中的saveValue(...)
也是boltdb底层bucket那些操作:
初始化两个namespace
: default + Polaris, 以及一个服务:polaris.checker
FYI: 方法名是AddNamespace
和 AddService
, 其实都是先删除再新增 操作。
至此,我们分析了整个 store.GetStore()
,代码进入下面:
本篇总结:
- 根据polaris-server.yaml中store相关配置初始化boltHandler
- 初始化一堆模块的初始化: 持有1中的boltHandler引用
- 初始化授权存储相关配置:主体账户+鉴权策略
- 初始化命名空间+检查服务