golang 分布式微服务DAO层构建

构建云原生项目的dao层

配置读写分离的mysql集群

1. 编写yml配置文件

搭建一主二从的mysql集群、单机redis

db.yml

yml 复制代码
mysql:
  source: # 主数据库
    driverName: mysql
    host: 127.0.0.1
    port: 3309
    database: db_tiktok
    username: tiktokDB
    password: tiktokDB
    charset: utf8mb4

  replica1: # 从数据库
    driverName: mysql
    host: 127.0.0.1
    port: 3310
    database: db_tiktok
    username: tiktokDB
    password: tiktokDB
    charset: utf8mb4
  replica2: # 从数据库
    driverName: mysql
    host: 127.0.0.1
    port: 3311
    database: db_tiktok
    username: tiktokDB
    password: tiktokDB
    charset: utf8mb4

redis:
  addr: 127.0.0.1
  port: 6379
  password: 123456
  db: 0 # 数据库编号

2. 使用viper,读对应yml文件,保存到生成的对象中

java 复制代码
var (
	_db       *gorm.DB
	config    = viper.Init("db")
	zapLogger = zap.InitLogger()
)

其中viper init方法的逻辑如下:

java 复制代码
// Init 初始化Viper配置
func Init(configName string) Config {
	config := Config{Viper: V.New()}
	v := config.Viper
	v.SetConfigType("yml")      //设置配置文件类型
	v.SetConfigName(configName)
	//设置配置文件搜索路径
	v.AddConfigPath("./config") //设置配置文件路径 !!!注意路径问题
	v.AddConfigPath("../config")
	v.AddConfigPath("../../config")
	//读取配置文件的内容
	if err := v.ReadInConfig(); err != nil {
		//global.SugarLogger.Fatalf("read config files failed,errors is %+v", err)
		log.Fatalf("errno is %+v", err)
	}
	return config
}

3. 从viper的config中,构建目标数据库的dsn

java 复制代码
func getDsn(driverWithRole string) string {
	username := config.Viper.GetString(fmt.Sprintf("%s.username", driverWithRole))
	password := config.Viper.GetString(fmt.Sprintf("%s.password", driverWithRole))
	host := config.Viper.GetString(fmt.Sprintf("%s.host", driverWithRole))
	port := config.Viper.GetInt(fmt.Sprintf("%s.port", driverWithRole))
	Dbname := config.Viper.GetString(fmt.Sprintf("%s.database", driverWithRole))

	// data source name
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", username, password, host, port, Dbname)

	return dsn
}

例如现在要获取mysql "一主二从" 的主机dsn:

java 复制代码
dsn1 := getDsn("mysql.source")

4. 拿到dsn之后,可以使用gorm连接数据库

将一主二从都连接上

java 复制代码
	var err error
	_db, err = gorm.Open(mysql.Open(dsn1), &gorm.Config{
		Logger:                 logger.Default.LogMode(logger.Info),
		PrepareStmt:            true,
		SkipDefaultTransaction: true,
	})
	if err != nil {
		panic(err.Error())
	}

	dsn2 := getDsn("mysql.replica1")
	dsn3 := getDsn("mysql.replica2")

5. 配置mysql数据库读写分离

  • dbresolver 的作用是将数据库的读写操作分发到不同的数据库实例上。在配置中,Sources 字段表示主数据库(写操作)的连接信息,Replicas 字段表示从数据库(读操作)的连接信息。通过配置这些连接信息,dbresolver 可以根据一定的策略将读操作分发到从数据库实例上,以减轻主数据库的读压力,提高系统的并发性能。
  • 代码中,Sources 设置了一个主数据库连接,Replicas 则设置了两个从数据库连接。这样 dbresolver 就知道在进行数据库操作时应该选择主数据库还是从数据库。同时,Policy 字段指定了负载均衡策略,这里采用的是随机策略即 RandomPolicy。最后,TraceResolverMode 字段表示是否在日志中打印数据库的主从模式,对于调试和跟踪非常有用。
java 复制代码
	// 配置dbresolver
	_db.Use(dbresolver.Register(dbresolver.Config{
		// use `db1` as sources, `db2` as replicas
		Sources:  []gorm.Dialector{mysql.Open(dsn1)},
		Replicas: []gorm.Dialector{mysql.Open(dsn2), mysql.Open(dsn3)},
		// sources/replicas load balancing policy
		Policy: dbresolver.RandomPolicy{},
		// print sources/replicas mode in logger
		TraceResolverMode: false,
	}))

6. 创建表

java 复制代码
	// AutoMigrate会创建表,缺失的外键,约束,列和索引。如果大小,精度,是否为空,可以更改,则AutoMigrate会改变列的类型。出于保护您数据的目的,它不会删除未使用的列
	// 刷新数据库的表格,使其保持最新。即如果我在旧表的基础上增加一个字段age,那么调用autoMigrate后,旧表会自动多出一列age,值为空
	if err := _db.AutoMigrate(&User{}, &Video{}, &Comment{}, &FavoriteVideoRelation{}, &FollowRelation{}, &Message{}, &FavoriteCommentRelation{}); err != nil {
		zapLogger.Fatalln(err.Error())
	}

整体代码

java 复制代码
var (
	_db       *gorm.DB
	config    = viper.Init("db")
	zapLogger = zap.InitLogger()
)

func getDsn(driverWithRole string) string {
	username := config.Viper.GetString(fmt.Sprintf("%s.username", driverWithRole))
	password := config.Viper.GetString(fmt.Sprintf("%s.password", driverWithRole))
	host := config.Viper.GetString(fmt.Sprintf("%s.host", driverWithRole))
	port := config.Viper.GetInt(fmt.Sprintf("%s.port", driverWithRole))
	Dbname := config.Viper.GetString(fmt.Sprintf("%s.database", driverWithRole))

	// data source name
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", username, password, host, port, Dbname)

	return dsn
}

func init() {
	zapLogger.Info("Redis server connection successful!")

	dsn1 := getDsn("mysql.source")

	var err error
	_db, err = gorm.Open(mysql.Open(dsn1), &gorm.Config{
		Logger:                 logger.Default.LogMode(logger.Info),
		PrepareStmt:            true,
		SkipDefaultTransaction: true,
	})
	if err != nil {
		panic(err.Error())
	}

	dsn2 := getDsn("mysql.replica1")
	dsn3 := getDsn("mysql.replica2")
	// 配置dbresolver
	_db.Use(dbresolver.Register(dbresolver.Config{
		// use `db1` as sources, `db2` as replicas
		Sources:  []gorm.Dialector{mysql.Open(dsn1)},
		Replicas: []gorm.Dialector{mysql.Open(dsn2), mysql.Open(dsn3)},
		// sources/replicas load balancing policy
		Policy: dbresolver.RandomPolicy{},
		// print sources/replicas mode in logger
		TraceResolverMode: false,
	}))
	// AutoMigrate会创建表,缺失的外键,约束,列和索引。如果大小,精度,是否为空,可以更改,则AutoMigrate会改变列的类型。出于保护您数据的目的,它不会删除未使用的列
	// 刷新数据库的表格,使其保持最新。即如果我在旧表的基础上增加一个字段age,那么调用autoMigrate后,旧表会自动多出一列age,值为空
	if err := _db.AutoMigrate(&User{}, &Video{}, &Comment{}, &FavoriteVideoRelation{}, &FollowRelation{}, &Message{}, &FavoriteCommentRelation{}); err != nil {
		zapLogger.Fatalln(err.Error())
	}

	db, err := _db.DB()
	if err != nil {
		zapLogger.Fatalln(err.Error())
	}
	db.SetMaxOpenConns(100)
	db.SetMaxIdleConns(20)
}

func GetDB() *gorm.DB {
	return _db
}
相关推荐
KIDAKN28 分钟前
RabbitMQ 初步认识
分布式·rabbitmq
pan30350747932 分钟前
Kafka 和 RabbitMQ的选择
分布式·kafka·rabbitmq
YGGP2 小时前
3D 版接雨水
golang
hzulwy3 小时前
Kafka基础理论
分布式·kafka
明达智控技术4 小时前
MR30分布式IO在全自动中药煎药机中的应用
分布式·物联网·自动化
jakeswang5 小时前
细说分布式ID
分布式
LQ深蹲不写BUG6 小时前
微服务的保护方式以及Sentinel详解
微服务·云原生·架构
失散136 小时前
分布式专题——1.2 Redis7核心数据结构
java·数据结构·redis·分布式·架构
王中阳Go7 小时前
头一次见问这么多kafka的问题
分布式·kafka
鼠鼠我捏,要死了捏7 小时前
基于Apache Flink Stateful Functions的事件驱动微服务架构设计与实践指南
微服务·apache flink·实时处理