天天看點

【架構】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生成器配置資訊等。此外,還有一個屏蔽字路徑的配置。這些資訊對于系統的正常運作非常重要,而架構的設計和實作則是保證系統高效運作的關鍵。雖然其中涉及到一些技術細節,但我們應該積極樂觀地看待這些挑戰,相信通過不斷學習和努力,我們一定能夠克服困難,打造出更加優秀的系統。

繼續閱讀