Golang整合Doris查询功能封装
一、目标
- 实现基于配置查询Doris数据库
- 实现将查询结构封装:[]map[string]interface{},支持单条/多条结果集
- 支持interface转目标struct,实现结果集解析
- 实现数据分片,便于后续的分批处理
二、Doris查询功能
-
数据库配置
用于连接数据库,可以抽离到公共配置,便于统一维护管理,减少耦合
type DorisDB struct { UserName string // 用户名 Password string // 密码 Network string // TCP Server string // server Port int // 端口 DataBase string // 数据库 }
-
建立连接
连接数据库,需要指定数据库,依赖上面的配置建立连接,为数据查询做准备
//1.创建连接 dsn := fmt.Sprintf("%s:%s@%s(%s:%d)/%s", d.db.UserName, d.db.Password, d.db.Network, d.db.Server, d.db.Port, d.db.DataBase) db, err := sql.Open("mysql", dsn) defer func(db *sql.DB) { err := db.Close() if err != nil { } }(db) if err != nil { d.log.Fatal("err", "info:", err) }
-
数据查询
sqlString:支持外部传入的sql自定义查询语句,灵活自由,便于扩展
db.Query: 基于上面的建连,进行数据查询
dataRows.Close(): 关闭数据库查询,节约资源
//2.数据查询 dataRows, err := db.Query(sqlString) defer dataRows.Close() if err != nil { d.log.Fatal("err", "info:", err) }
-
结果集封装
1、输入:数据查询db.Query(sqlString)返回的结果
2、输出:[]map[string]interface{}存储返回
3、核心:
3.1、// 预设取值地址 for i := range vs { scans[i] = &vs[i] } 3.2、_ = dataRows.Scan(scans...) :// 取值填入预设的结构体 3.2、更新目标存储: for i, col := range vs {......}
/* * 进行结果集处理,使用[]map[string]interface{}存储 */ // 读取字段名称 columns, err := dataRows.Columns() vs := make([]sql.RawBytes, len(columns)) scans := make([]interface{}, len(columns)) // 预设取值地址 for i := range vs { scans[i] = &vs[i] } var result []map[string]interface{} for dataRows.Next() { // 取值填入预设的结构体 _ = dataRows.Scan(scans...) each := make(map[string]interface{}) for i, col := range vs { if col != nil { each[columns[i]] = FilterHolder(string(col)) } else { each[columns[i]] = nil } } result = append(result, each) }
-
数据过滤
用于拦截处理数据中的无效数据,脏数据等
// FilterHolder 过滤/xfffd 其他过滤可以自行拓展 func FilterHolder(content string) string { newContent := "" for _, value := range content { if value != 65533 { newContent += string(value) } } return newContent }
三、结构体转interface <—> interface转结构体
灵活使用json.Marshal和 json.Unmarshal能够帮助我们从一个固定的数据结果转为另一个数据相近的结构
-
结构体转interface :
// 例如: var data []*map[string]string // 10 条数据 for i := 0; i < 10; i++ { data = append(data, &map[string]string{ "a": "1", }) } if data == nil { data = make([]*map[string]string, 0) } // 目标interface var interfaceItem []*interface{} b, _ := json.Marshal(&data) err := json.Unmarshal(b, &interfaceItem) if err != nil { fmt.Println("err", err) }
-
interface转结构体
// res :interface resByre, resByteErr := json.Marshal(res) if resByteErr != nil { fmt.Printf("%v", resByteErr) return } // 目标结构:struct、map var newData [][]*map[string]string jsonRes := json.Unmarshal(resByre, &newData) if jsonRes != nil { fmt.Printf("%v", jsonRes) return }
四、数据分片【假设Doris数据庞大】
在实际生产中,数据产生的量级往往是巨大的,一般来说,每次查询的数据量级也是很大的,如果数据消费线程的效率低,容易引发数据的实时性,系统的稳健性等问题,因此可以考虑对数据进行分片处理,便于数据每次消费过大导致的资源消费、系统性能问题。
-
目标
- 支持自定义分配份数
- 支持根据默认参数分配
-
代码
// 以[]*interface为输入,[][]*interface为输出 const ( DEFAULT_DIVLEN = 2 // 默认份数 DEFAULT_DATALEN float64 = 3 // 默认的每一份最大包含数量 ) /** 如果defaultFlag: true 则需要传入份数,根据份数divLen 更新每份长度 如果defaultFlag: false 则受用默认参数计算,divLen传入0值即可,并且不生效,根据默认的每份长度更新divLen */ func transFormat(data []*interface{}, divLen float64, defaultFlag bool) [][]*interface{} { if len(data) <= DEFAULT_DIVLEN || divLen == 0 { return [][]*interface{}{ 1: data, } } quantity := DEFAULT_DATALEN if defaultFlag { dLen := float64(len(data)) / DEFAULT_DATALEN // 使用默认的每份的数量,则divLen不生效 divLen = math.Ceil(dLen) } quantity = math.Ceil(float64(len(data)) / divLen) // 不使用默认,根据divLen计算 fmt.Println(quantity) var segmens = make([][]*interface{}, 0) end := 0 newDivLen := int(divLen) for i := 1; i <= newDivLen; i++ { qu := i * int(quantity) if i != newDivLen { segmens = append(segmens, data[i-1+end:qu]) } else { segmens = append(segmens, data[i-1+end:]) } end = qu - i } return segmens }