【源码阅读】osproxy对象存储分布式代理(2)--初始化底层服务和日志器

07-17 1797阅读

文章目录

  • 初始化底层服务
    • 函数返回类型
      • APP
      • Redis
      • newLangGoConfig()函数
      • initLangGoConfig()函数
      • 初始化日志器
        • 上一章【源码阅读】osproxy对象存储分布式代理(1)
        • 下一章

            这部分涉及了对于底层服务的初始化和日志器的初始化两部分

          【源码阅读】osproxy对象存储分布式代理(2)--初始化底层服务和日志器
          (图片来源网络,侵删)
          // 初始化底层服务,如数据库等
          lgConfig := bootstrap.NewConfig("conf/config.yaml")
          // 初始化日志器
          lgLogger := bootstrap.NewLogger()
          

          初始化底层服务

          NewConfig的代码如下

          unc NewConfig(confFile string) *config.Configuration {
          	// 如果配置已经生效了,就直接沿用配置
          	if lgConfig.Conf != nil {
          		return lgConfig.Conf
          	} else {
          		// 初始化配置,包括Configuration结构体和一个同步原语
          		lgConfig = newLangGoConfig()
          		// 如果用户没有指定配置文件的路径,则使用系统定义的默认路径"conf/config.yaml"
          		if confFile == "" {
          			lgConfig.initLangGoConfig(confFilePath)
          		} else { //否则使用用户给定的配置文件来初始化
          			lgConfig.initLangGoConfig(confFile)
          		}
          		return lgConfig.Conf
          	}
          }
          

          函数返回类型

            NewConfig函数接收一个字符串类型,返回一个*config.Configuration类型,关于这个类型的定义如下

          type Configuration struct {
          	App      App                 `mapstructure:"app" json:"app" yaml:"app"`
          	Log      Log                 `mapstructure:"log" json:"log" yaml:"log"`
          	Database []*plugins.Database `mapstructure:"database" json:"database" yaml:"database"`
          	Redis    *plugins.Redis      `mapstructure:"redis" json:"redis" yaml:"redis"`
          	Minio    *plugins.Minio      `mapstructure:"minio" json:"minio" yaml:"minio"`
          	Cos      *plugins.Cos        `mapstructure:"cos" json:"cos" yaml:"cos"`
          	Oss      *plugins.Oss        `mapstructure:"oss" json:"oss" yaml:"oss"`
          	Local    *plugins.Local      `mapstructure:"local" json:"local" yaml:"local"`
          }
          

            *config.Configuration内部也包含了许多自定义的结构体,由许多自定义类型,后面的mapstructure 标签需对应 config.yaml 中的配置名称

          APP

          type App struct {
          	Env     string `mapstructure:"env" json:"env" yaml:"env"`
          	Port    string `mapstructure:"port" json:"port" yaml:"port"`
          	AppName string `mapstructure:"app_name" json:"app_name" yaml:"app_name"`
          	AppUrl  string `mapstructure:"app_url" json:"app_url" yaml:"app_url"`
          }
          

            其对应的配置文件config.yaml中对应内容为

          app:
            env: prod
            port: 8888                # 服务端口
            app_name: osproxy         # 服务名称
            app_url: http://127.0.0.1
          

            也就是说,我们首先在App App `mapstructure:"app" json:"app" yaml:"app"` 匹配到app这个配置,获取其中env,port,app_name,app_url等字段,然后复制给结构体APP的变量Env,Port,AppName,AppUrl。

          Redis

          type Redis struct {
          	Host     string `mapstructure:"host" json:"host" yaml:"host"`
          	Port     string `mapstructure:"port" json:"port" yaml:"port"`
          	DB       int    `mapstructure:"db" json:"db" yaml:"db"`
          	Password string `mapstructure:"password" json:"password" yaml:"password"`
          }
          
          redis:
            host: 127.0.0.1        # 服务地址
            port: 6379             # 服务端口
            db: 0                  # 库选择
            password: 123456       # 密码
          

            对于Redis也是如此,此处对于其他结构体就不在赘述,总的来说,config.Configuration这个结构体中包括了项目中一些基础服务的配置信息。

          newLangGoConfig()函数

          func newLangGoConfig() *LangGoConfig {
          	return &LangGoConfig{
          		Conf: &config.Configuration{},
          		Once: &sync.Once{},
          	}
          }
          type LangGoConfig struct {
          	Conf *config.Configuration
          	Once *sync.Once
          }
          

            newLangGoConfig函数用于初始化Configuration结构体和一个同步原语,同步原语sync.Once只有一个导出的方法,即 Do,该方法接收一个函数参数。在 Do 方法被调用后,该函数将被执行,而且只会执行一次,即使在多个协程同时调用的情况下也是如此,提供一个优雅且并发安全的资源初始化方式。

            sync.Once内部为一个互斥锁,用于对指定函数进行加锁操作,具体源码分析在此

          initLangGoConfig()函数

          func (lg *LangGoConfig) initLangGoConfig(confFile string) {
          	// 基于并发原语对初始化进行并发控制,针对并发场景下的线程安全
          	lg.Once.Do(
          		func() {
          			initConfig(lg.Conf, confFile)
          		},
          	)
          }
          

            这里就是对并发原语Once的使用,它使用Do的方式使得initconfig函数唯一执行。既确保了配置文件的初始化在多线程的环境下只进行一次,也确保了只有在初始化后,才会调用这些配置信息。

          // 根据配置文件初始化底层依赖的配置
          func initConfig(conf *config.Configuration, confFile string) {
          	pflag.StringVarP(&configPath, "conf", "", filepath.Join(rootPath, confFile),
          		"config path, eg: --conf config.yaml")
          	if !filepath.IsAbs(configPath) {
          		configPath = filepath.Join(rootPath, configPath)
          	}
          	//lgLogger.Logger.Info("load config:" + configPath)
          	fmt.Println("load config:" + configPath)
          	v := viper.New()
          	v.SetConfigFile(configPath)
          	v.SetConfigType("yaml")
          	// 使用viper库读取配置文件
          	if err := v.ReadInConfig(); err != nil {
          		//lgLogger.Logger.Error("read config failed: ", zap.String("err", err.Error()))
          		// 读取失败并返回日志信息
          		fmt.Println("read config failed: ", zap.String("err", err.Error()))
          		panic(err)
          	}
          	// 解析配置文件
          	if err := v.Unmarshal(&conf); err != nil {
          		//lgLogger.Logger.Error("config parse failed: ", zap.String("err", err.Error()))
          		fmt.Println("config parse failed: ", zap.String("err", err.Error()))
          	}
          	// 启动对于文件的监控
          	v.WatchConfig()
          	// 使用viper及时加载文件的变化
          	v.OnConfigChange(func(in fsnotify.Event) {
          		//lgLogger.Logger.Info("", zap.String("config file changed:", in.Name))
          		fmt.Println("", zap.String("config file changed:", in.Name))
          		defer func() {
          			if err := recover(); err != nil {
          				//lgLogger.Logger.Error("config file changed err:", zap.Any("err", err))
          				fmt.Println("config file changed err:", zap.Any("err", err))
          			}
          		}()
          		if err := v.Unmarshal(&conf); err != nil {
          			//lgLogger.Logger.Error("config parse failed: ", zap.String("err", err.Error()))
          			fmt.Println("config parse failed: ", zap.String("err", err.Error()))
          		}
          	})
          	lgConfig.Conf = conf
          }
          

            以上代码便是将配置文件conf.yaml载入系统的过程。值得注意的是作者在这里设置了viper的WatchConfig()函数来实现了配置的热更新。

          初始化日志器

          NewLogger()的源码如下

          func NewLogger() *LangGoLogger {
          	if lgLogger.Logger != nil {
          		return lgLogger
          	} else {
          		lgLogger = newLangGoLogger()
          		lgLogger.initLangGoLogger(lgConfig.Conf)
          		return lgLogger
          	}
          }
          

          此处定义的结构体LangGoLogger和上面的LangGoConfig差不多,是一个zap库的logger加上一个同步原语。

          type LangGoLogger struct {
          	Logger *zap.Logger
          	Once   *sync.Once
          }
          

          接下来的代码也差不多,就是对日志库的logger对象进行初始化,并通过同步原语对其进行控制

          // NewLogger 生成新Logger
          func NewLogger() *LangGoLogger {
          	if lgLogger.Logger != nil {
          		return lgLogger
          	} else {
          		lgLogger = newLangGoLogger()
          		lgLogger.initLangGoLogger(lgConfig.Conf)
          		return lgLogger
          	}
          }
          // initLangGoLogger 初始化全局log
          func (lg *LangGoLogger) initLangGoLogger(conf *config.Configuration) {
          	lg.Once.Do(
          		func() {
          			lg.Logger = initializeLog(conf)
          		},
          	)
          }
          func initializeLog(conf *config.Configuration) *zap.Logger {
          	// 创建根目录
          	createRootDir(conf)
          	// 设置日志等级
          	setLogLevel(conf)
          	if conf.Log.ShowLine {
          		options = append(options, zap.AddCaller())
          	}
          	// 初始化zap
          	return zap.New(getZapCore(conf), options...)
          }
          
VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]