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
}
相关推荐
只因在人海中多看了你一眼2 小时前
分布式缓存 + 数据存储 + 消息队列知识体系
分布式·缓存
zhixingheyi_tian5 小时前
Spark 之 Aggregate
大数据·分布式·spark
求积分不加C6 小时前
-bash: ./kafka-topics.sh: No such file or directory--解决方案
分布式·kafka
nathan05296 小时前
javaer快速上手kafka
分布式·kafka
谭震鸿10 小时前
Zookeeper集群搭建Centos环境下
分布式·zookeeper·centos
007php00711 小时前
GoZero 上传文件File到阿里云 OSS 报错及优化方案
服务器·开发语言·数据库·python·阿里云·架构·golang
高 朗13 小时前
【GO基础学习】基础语法(2)切片slice
开发语言·学习·golang·slice
码上有前13 小时前
解析后端框架学习:从单体应用到微服务架构的进阶之路
学习·微服务·架构
IT书架13 小时前
golang面试题
开发语言·后端·golang
天冬忘忧15 小时前
Kafka 工作流程解析:从 Broker 工作原理、节点的服役、退役、副本的生成到数据存储与读写优化
大数据·分布式·kafka