天天看点

【架构】Frameworks设计和实现 定义标准服务driver

作者:TCloudTech

概述

微服务是一种软件架构风格,它将大型应用程序拆分成更小、更独立的部分,每个部分都是一个独立的服务单元。这些服务单元可以通过轻量级的通信机制进行交互,从而实现整个应用程序的功能。微服务的内容通常包括:服务发现、负载均衡、容错机制、API网关、日志记录、监控、安全等方面的组件和工具。这些组件和工具可以帮助开发者更好地管理和维护微服务架构,从而提高应用程序的可靠性、安全性和可扩展性。

架构设计主要是指设计和规划软件系统的结构和组织,以满足系统需求并实现最佳的性能、可靠性、可扩展性和安全性等方面的要求。架构设计涉及到对系统的各个组件和模块之间的关系进行分析和设计,以确保系统的各个部分能够协作运行并且满足整体的需求。同时,架构设计还需要考虑到未来的扩展和升级,以确保系统能够适应未来的需求变化。在软件开发过程中,良好的架构设计可以提高系统的可维护性和可扩展性,从而降低开发和维护成本,同时提高系统的稳定性和性能表现。

在微服务架构中,框架设计和实现是非常重要的一部分。设计一个好的框架可以帮助开发者更快速、高效地开发微服务,同时也可以提高系统的质量和可维护性。为了实现这一目标,开发者需要定义标准服务driver,以确保不同的微服务之间可以相互协作。同时,还需要考虑到如何对服务进行部署和管理,以确保系统的高可用性和可靠性。总之,通过良好的架构设计和实现,可以让微服务架构更加健壮和可靠,从而为用户提供更好的服务体验。

服务接口

定义标准服务接口是为了提高不同系统之间的互操作性和协作效率。在服务接口定义的基础上,不同的系统可以更加方便地进行数据交换和通信,从而更好地完成各自的任务。此外,标准化的服务接口也可以降低系统集成的难度和成本,提高整个系统的可维护性和可扩展性。定义服务接口涉及到技术和规范方面的问题。

定义一个服务interface

// Service 基础服务接口
type Service interface {

	// Init 初始化操作。如返回非nil,driver不执行Start函数
	Init() error

	// Start 启动,可以在该函数内部 block
	// 在Init 函数返回成功后调用
	Start() error

	// Stop 停止服务 可在内部 block
	Stop() error

	// ForceStop 强制停止,不执行退休策略
	ForceStop() error
}           

定义一些生命周期,让服务实现更清晰明了。每个服务都必须要实现这个接口。

Init : 实现服务初始化,例如RPC初始化,各类对象初始化,配置文件的加载等。

Start :启动服务,例如执行Redis缓存,作业服务启动等

Stop :服务停止的一些处理

ForceStop :强制停止服务需要处理的逻辑

具体实现

// 环境配置设置
func setup() {
	flag.String(dict.SysWordConfig, "", "config file (default is ./config.yml)")
	flag.Int(dict.ConfigTcpPort, 0, "tcp port")
	flag.Int(dict.ConfigRpcPort, 0, "intranet port")

	pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
	pflag.Parse()
	err := viper.BindPFlags(pflag.CommandLine)
	if err != nil {
		return
	}

	if cfgFile := viper.GetString(dict.SysWordConfig); cfgFile != "" {
		viper.SetConfigFile(cfgFile)
	} else {
		viper.SetConfigFile(dict.SysDefaultConfig)
	}
	viper.AutomaticEnv() // 匹配环境变量
	if err := viper.ReadInConfig(); err != nil {
		panic(fmt.Sprintf("Read config file fail:%v", err))
	}

	// Notice : 开启Prometheus上报服务
	//http.Handle("/metrics", promhttp.Handler())
}

// 日志设置
func setupLog() {
	fileName := viper.GetString(dict.ConfigLogPrefix) + "_" + viper.GetString(dict.ConfigRpcPort)
	logger.SetupLog(fileName,
		viper.GetString(dict.ConfigLogDir),
		viper.GetString(dict.ConfigLogLevel),
		viper.GetBool(dict.ConfigLogStd),
		viper.GetBool(dict.ConfigLogJson))

	rpcSrvName := viper.GetString(dict.ConfigRpcServerName)
	logrus.Infof("**************************************************************")
	logrus.Infof("*** {%s} run beginning...", rpcSrvName)
	logrus.Infof("**************************************************************")
}

// 监听配置变化
func watchConfigChange() {
	go func() {
		interval := time.NewTicker(5 * time.Minute)
		defer interval.Stop()

		for {
			select {
			case <-interval.C:
				if err := viper.ReadInConfig(); err != nil {
					panic(fmt.Sprintf("读取配置文件失败:%v", err))
				}

				if lv, err := logrus.ParseLevel(viper.GetString(dict.ConfigLogLevel)); err == nil {
					logrus.Tracef("config reset log level : {%s}", viper.GetString(dict.ConfigLogLevel))
					logrus.SetLevel(lv)
				} else {
					logrus.Errorf("reset log lever %s error", viper.GetString(dict.ConfigLogLevel))
				}
			}
		}
	}()
}

// Run 启动服务
func Run(service Service) {
	rand.Seed(time.Now().UnixNano())

	// 初始设置
	setup()

    // 消息中间件的设置
	nsq.Setup()

    // 日志设置
	setupLog()

	// 服务初始化
	if err := service.Init(); err != nil {
		panic(err)
	}
	logrus.Infof("[%s]服务初始化完成", viper.GetString("rpc_server_name"))

	wg := sync.WaitGroup{}

	//启动RPC服务
	wg.Add(1)
	go func() {
		defer wg.Done()
		if err := rpc.StartServer(); err != nil {
			panic(err)
		}
		logrus.Infof("关闭 intranet 服务完成")
	}()

	//调用服务Start
	wg.Add(1)
	go func() {
		defer wg.Done()
		defer logger.RecoverAndLog("runsvc")
		if err := service.Start(); err != nil {
			panic(err)
		}
	}()

	// http服务
	viper.SetDefault(dict.ConfigHttpPort, dict.SysDefaultHttpPort)
	var httpSvr = &http.Server{
		Addr:    "0.0.0.0:" + viper.GetString(dict.ConfigHttpPort),
		Handler: nil, // 使用默认的 mux
	}

	wg.Add(1)
	go func() {
		defer wg.Done()
		logrus.Infof("开启 http 服务,addr: %s", httpSvr.Addr)
		if err := httpSvr.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			panic(err)
		}
	}()

	//定时拉取日志配置级别
	watchConfigChange()

	//设置优雅关闭,提供内部http接口,可以curl,也可以供给管理平台调用
	ElegantRetirement(service)

	logrus.Infoln("服务开始关闭...")

	// 先反注册,停止新的RPC请求过来
	rpc.Deregister()

	if err := service.Stop(); err != nil {
		logrus.Errorf("stop service failed[%v]", err)
	}

	// 停止RPC
	rpc.StopServer()

	// 停止nsq
	nsq.Stop()

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := httpSvr.Shutdown(ctx); err != nil {
		logrus.Errorf("停止 http 服务失败")
	} else {
		logrus.Infof("关闭 http 服务成功")
	}
	logrus.Infoln("服务关闭完成")
	logger.StopLog()
	wg.Wait()
    os.Exit(0)
}           

使用举例

package main

import (
	"github.com/sirupsen/logrus"
	"github.com/spf13/viper"
	"math/rand"
	"runtime"
	"tcgo/server/frameworks/core/kit/driver"
	"tcgo/server/frameworks/core/kit/redisfactory"
	"tcgo/server/frameworks/dict"
	"tcgo/server/frameworks/pkg/forbid"
	"tcgo/server/frameworks/pkg/id"
	"tcgo/server/loginsrv/internal/dao"
	loginRpcServer "tcgo/server/loginsrv/internal/server/rpc"
	"time"
)

type LoginService struct {
}

func (g LoginService) Init() error {

	//RPC服务初始化
	loginRpcServer.InitLoginRpc()

	//Redis初始化
	playRedis := redisfactory.DefaultFactory.GetRedisConfig(dict.ConfigRedisDbPlayer)
	redisSource := playRedis.Address + "@" + playRedis.Passwd
	dao.DefaultTokenCache = dao.NewSessionCache(redisSource)

	return nil
}

func (g LoginService) Start() error {
    //加载 showid 
    id.InitUsedShowIDs()
  
  	//敏感字初始化
	forbidPath := dict.SysDefaultForbidPath
	if runtime.GOOS == "linux" {
		if cfgFile := viper.GetString(dict.SysWordForbid); cfgFile != "" {
			forbidPath = cfgFile
		}
	}
    forbid.InitForbid(forbidPath)

	logrus.Infoln("登录服务启动成功")
	return nil
}

func (g LoginService) Stop() error {
	logrus.Infoln("登录服务关闭中...")
	return nil
}

func (g LoginService) ForceStop() error {
	logrus.Infoln("ForceStop登录服务强制关闭中...")
	return nil
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())

	rand.Seed(time.Now().UnixNano())
	driver.Run(&LoginService{})
}
           

配置文件

log_level: debug
log_dir: ../.././log/login
err_sql_dir: ../.././log/login/err_sqls
log_prefix: login
log_stderr: true
# 是否使用mmap log
mmap_log: true
# mmap页缓存大小,单位页(每页4096)
page_cache: 1000

# rpc config
rpc_addr: 127.0.0.1
rpc_port: 36201
http_port: 36202
rpc_server_name: login
rpc_server_tags: normal

# redis config
redis_addr: 127.0.0.1:6379
redis_passwd:

redis_list:
  player:
    addr: 127.0.0.1:6379
    passwd:

# nsq config
nsqd_addr: 127.0.0.1:4150
nsqd_http_addr: 127.0.0.1:4151
nsqlookupd_addrs:
  - 127.0.0.1:4161

# consul config
consul_addr: 127.0.0.1:8500

# mysql config
mysql_list:
  Player:
    user: player
    passwd: playerxxx
    addr: 127.0.0.1:3306
    db: player
    params:
      charset: utf8mb4
  Log:
    user: logger
    passwd: logxxx
    addr: 127.0.0.1:3306
    db: log
    params:
      charset: utf8mb4

# id generator config
node: 1

# 屏蔽字路径
forbid_config_path: ../loginsrv/forbid.txt
           

这是一个关于框架设计和实现的示例,其中定义了标准服务的各种配置信息,包括驱动程序地址、nsqd和nsqlookupd地址、consul和mysql配置信息以及ID生成器配置信息等。此外,还有一个屏蔽字路径的配置。这些信息对于系统的正常运行非常重要,而框架的设计和实现则是保证系统高效运行的关键。虽然其中涉及到一些技术细节,但我们应该积极乐观地看待这些挑战,相信通过不断学习和努力,我们一定能够克服困难,打造出更加优秀的系统。

继续阅读