Golang(go)+mongodb开发个人遇到坑及用法总结汇总

Golang(go)+mongodb开发个人遇到坑及用法总结汇总

1为什么要选mongodb?

如果你是事务比较多的,就用mysql。

Mongodb存储的是json文档,不需要提前定义表结构。也支持事务。涉及关联查询的,可以选择json字段子文档(直接分别写一份或对象引用),这样就不用像mysql跨表关联查询,提高查询性能;涉及距离的查询$geoNear;非商业版mongodb每个表只支持建一个text文本搜索索引(全文检索)(中文要自己分词后建立索引);

2Mongodb部署:

Windows开发阶段,装个微软Docker Desktop,部署mongodb镜像,redis等单机开发非常爽,方便。

Linux上线部署,建议最少3台(复制集,多台的可以副本集),采用PSS(绝对不要采用PSA仲裁节点,故障一个节点主节点写数据复制判断容易出问题。PSS可以使用rs.reconfig()把差机器外其他机器priority值设大优先选主)

https://www.mongodb.com/docs/manual/reference/configuration-options/#core-options

配置

https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-red-hat/

vi /etc/yum.repos.d/mongodb-org-6.0.repo

sudo yum install mongodb-org -y

config={“_id”:“mySet”,“members”:[

{“_id”:0,“host”:“172.x.0.10:27017”},

{“_id”:1,“host”:“172.x.0.5:27017”},

{“_id”:2,“host”:“172.x.0.9:27017”}

]}

rs.initiate(config)

rs.status()

use admin

db.createUser(

{

user:“admin”,

pwd:“xxx”,

roles:[{role:“root”,db:“admin”}]

}

);

db.auth(“admin”,“xxx”)

优先级越高的成员越有可能被选为主节点。默认情况下,副本集成员的选举优先级都是1,可以通过修改配置文件或者使用rs.reconfig()命令来设置

cfg = rs.conf()

cfg.members[0].priority = 10

cfg.members[1].priority = 9

cfg.members[2].priority = 1

rs.reconfig(cfg)

采用systemd自启动以及配置主动拉取,上面的rpm安装已经写了这个文件。

vim /usr/lib/systemd/system/mongod.service (可以修改指定自己的执行命令 –f 配置文件名)

sudo systemctl daemon-reload #–修改后重载配置

设置 MongoDB 为系统服务,开机启动

sudo systemctl enable mongod

vim /etc/mongod.conf --配置文件

/usr/bin/mongod --服务

/usr/bin/mongo----客户端

3服务监控及自动拉取systemd:

比如我自己的go后台服务,配置成:

systemctl enable goSrv.service

vim /usr/lib/systemd/system/goSrv.service

sudo systemctl daemon-reload #–修改后重载配置

[Unit]

Description=Gosrv

After=network.target

[Service]

User=xx

Group=xx

ExecStart=/opt/goSrv/cmd/start.sh

#ExecReload=/bin/sh -c “/bin/kill -s USR1 $(ps -ef | grep goSrv | grep -v ‘grep’ | awk ‘{print $2}’| head -1)” # shell 要这样写/bin/sh -c

ExecStop=/bin/sh -c “/bin/kill -s SIGTERM $(ps -ef | grep goSrv | grep -v ‘grep’ | awk ‘{print $2}’| head -1)”

Restart=on-failure # 自动拉起

#RestartSec=60s

StartLimitInterval=36000s # 可以限制范围大一点,不然你修bug时手工拉取也算次数可能报错

StartLimitBurst=10

[Install]

WantedBy=multi-user.target

4 遇到的编译坑用法总结

4.1. Gojieba windows本地开发正常,linux上编译执行,CGO堆栈异常。

gojieba内部调用了静态文件(各种utf8文件),而且不是通过embed方式调用; 编译时无法包含静态文件,运行时会报错。可以单独建个assets目录把gojieba_dict下所有文件放入,在代码中初始化NewJieba时传入目录位置。

func InitGojieba() {

// 指定本地字典路径

exPath, err1 := utils.GetExPath("")

if err1 != nil {

global.G_LOG.Errorf("get path,err:%s", err1)

}

dictDir := filepath.Join(exPath, "assets/gojieba_dict")

global.G_LOG.Infof("gojeba,dictDir:%s", dictDir)

dpath := filepath.Join(dictDir, "jieba.dict.utf8")

hpath := filepath.Join(dictDir, "hmm_model.utf8")

upath := filepath.Join(dictDir, "user.dict.utf8")

ipath := filepath.Join(dictDir, "idf.utf8")

spath := filepath.Join(dictDir, "stop_words.utf8")

global.G_Jieba = gojieba.NewJieba(dpath, hpath, upath, ipath, spath)

}
4.2 配置文件目录位置问题

编译后配置conf目录找不到了,这就是如何获取golang当前文件目录位置的问题。

func GetFp() {

pathCurr, \_ := filepath.Abs(".")

fmt.Println(pathCurr)

// 不能用runtime.Caller(0) 它是写死编译时的堆栈获取的绝对路径(python \__file__可以,是因为它是解释执行,不是编译执行)

pathCurr2, \_ := os.Getwd()

fmt.Println(pathCurr2)

//

fmt.Println(os.Args[0])

//

exPath, err1 := os.Executable()

fmt.Printf("%s,err:%s\\n", exPath, err1)

}

主要4种方式,看看区别:

#ide run执行

DAP server listening at: 127.0.0.1:56269

d:\prj_sp\v_test2\cmd

d:\prj_sp\v_test2\cmd

d:\prj_sp\v_test2\cmd\__debug_bin.exe

d:\prj_sp\v_test2\cmd\__debug_bin.exe

#编译后目录执行

PS D:\prj_sp> .\run_test1\filePathDis.exe

D:\prj_sp

D:\prj_sp

D:\prj_sp\run_test1\filePathDis.exe

D:\prj_sp\run_test1\filePathDis.exe

# go run 执行 (还有直接单元测试)

PS D:\prj_sp\v_test2\cmd> go run .\main.go

D:\prj_sp\v_test2\cmd

D:\prj_sp\v_test2\cmd

C:\Users\zhao\AppData\Local\Temp\go-build2934688955\b001\exe\main.exe

C:\Users\zhao\AppData\Local\Temp\go-build2934688955\b001\exe\main.exe

我自己写的golang获取当前目录方法,可以参考

func GetExPath(traceId string) (exPathDir string, err error) {

\_, fl, line, ok := runtime.Caller(1)

if ok {

idx := strings.LastIndex(fl, "/")

if idx \> 0 {

fl = fl[idx+1:]

}

// fmt.Printf("[%s]caller %s to GetPathWd:%v\\n", traceId, fl, line)

}

// 不能用runtime.Caller(0) 它是写死编译时的堆栈获取的绝对路径

// pathCurr, err1 := os.Getwd() // 执行的目录

exPath, err1 := os.Executable()

if err1 != nil {

fmt.Printf("[%s]failed,caller %s\|%v 获取当前目录失败,err:%s\\n", traceId, fl, line, err1)

err = errors.New("获取当前目录失败")

} else {

// realPath, err1 := filepath.EvalSymlinks(exPath) // link

exPath = filepath.Dir(exPath)

exPathDir = filepath.Join(exPath, "../") // 移动main.go位置要修改这个文件,定位到工程主目录

//都定位到工程主目录

if IsWin() { // 开发时不是编译结果执行 go run或单元测试

//C:\\Users\\zhao\\AppData\\Local\\Temp\\go-build4236785995

if strings.Contains(exPath, "go-build") {

exPath, err1 = os.Getwd() //

if err1 != nil {

fmt.Printf("[%s]failed,caller %s\|%v 获取当前目录失败,err:%s\\n", traceId, fl, line, err1)

err = errors.New("获取当前目录失败")

} else { // 单元测试就是test的目录

if strings.Contains(exPath, "\\\\test\\\\") {

exPathDir = filepath.Join(exPath, "../../../")

}

}

}

}

//

exPathDir, err1 = filepath.Abs(exPathDir)

if err1 != nil {

fmt.Printf("[%s]failed,caller %s\|%v 获取当前目录失败,err:%s\\n", traceId, fl, line, err1)

err = errors.New("获取当前目录失败")

}

}

//

return

}

可以抽取一个config,一个assert目录,工程所有使用的读取文件都放这两个目录指定路径,开发及编译后使用都能正常。

4.3 golang启动脚本增加异常打印堆栈

#! /bin/bash

ulimit -c unlimited

export GOTRACEBACK=crash

/opt/goSrv/cmd/goSrv

5 go mongodb遇到的坑用法总结

5.1. mongodb子文档查询(兼容字段不存在子文档情况) preserveNullAndEmptyArrays
{

"\$unwind": bson.M{"path": "\$order_his", "preserveNullAndEmptyArrays": true},

},

非Group其他字段\$first

{

"\$group": bson.M{

"_id": "\$_id",

"brow_his": bson.M{"\$first": "\$brow_his"},

"order_his": bson.M{"\$push": "\$order_his"},

},

},
5.2 mongodb filter子文档字段查询条件查不到结果

filter := bson.M{“fd1”:bson.M{“sfd1”:”v1”}}字段写成这样查询条件查不到结果,原因暂时没研究。要写成:

filter := bson.M{“fd1.sfd1”:”v1”}

5.3 golang bson.M动态条件组合语句用法

var update bson.M = bson.M{“$inc”: bson.M{}, “$set”: bson.M{}}

注意这里的$inc等关键字一定要先赋值,否则interface转的时候.(bson.M),会panic

if req.Img != “” {

update[“$set”].(bson.M)[“img_mdy”] = req.Img

5.4 mongodb事务
s, err := r.C.Database().Client().StartSession() // 开启session

if err != nil {

global.G_LOG.Errorf("[%s] failed,修改前开启事务session错误,err:%s", r.TraceId, err)

err = errors.New("修改前开启事务session错误")

return

}

defer s.EndSession(context.TODO())

s.StartTransaction() // 开启事务

todo逻辑

if err != nil {

global.G_LOG.Errorf("[%s]设置%v失败,err:%s", r.TraceId, coinUse, err)

s.AbortTransaction(context.TODO())

return

}

//最后提交事务

s.CommitTransaction(context.TODO()) // 提交事务

return
5.5 mongodb update判断设置成功

如果自己有些可能已经更新的可以不判断ModifiedCount

resultU, err := r.C.UpdateOne(context.TODO(), filter, update)

if err == nil && resultU.MatchedCount != 1 && resultU.ModifiedCount == 0 {

global.G_LOG.Errorf("[%s] failed,Matched %v documents and updated %v documents.", r.TraceId, resultU.MatchedCount, resultU.ModifiedCount)

err = errors.New("查询及更新条数不正确")

}
5.6 mongodb 动态删除数组
delCntDo := int(float64(delCnt) \* 1.5)

strDelCntDo := strconv.Itoa(delCntDo)

// 删除大于条数,保留后面新的

//\$size needs a number

// filter = bson.M{"_id": objId, "brow_his": bson.M{"\$size": bson.M{"\$gte": delCntDo}}}

filter = bson.M{"_id": objId, "brow_his." + strDelCntDo: bson.M{"\$exists": true}}

// 超过一半一起删 //数组尾部加上新添加的数组each,然后进行切割 倒着保留

update = bson.M{"\$push": bson.M{"brow_his": bson.M{"\$each": bson.A{}, "\$slice": -delCnt}}}
5.7 mongodb $inc 小于0问题

如果golang结构体字段设置为uint等,查询后解析就会报错,查不出这条记录

// update["\$inc"].(bson.M)["fans_cnt"] = -1

updateDec = bson.M{"\$inc": bson.M{"fans_cnt": -1}}

filterDec = bson.M{"_id": objId, "fans_cnt": bson.M{"\$gt": 0}}

我是单独更新这个字段,判断\$gt:0

if err == nil {

if updateDec["\$inc"] != nil { // 避免 -1

\_, err2 := r.C.UpdateOne(context.TODO(), filterDec, updateDec)

if err2 != nil {

global.G_LOG.Errorf("[%s]failed,update err:%s", r.TraceId, err2)

}

}

}
5.8 mongodb SetArrayFilters 条件更新,及多个条件怎么写才对
update["\$set"].(bson.M)["peers.\$[item].mark"] = mu.PeerInfo.Mark

updateOption.SetArrayFilters(options.ArrayFilters{

Filters: []interface{}{

bson.M{"item.uinfo.uid": mu.PeerInfo.Uid},

}})

多个条件一定要用\$and 及数组bson.A,否则查出来的可能不是你预期的

updateOption := options.Update()

updateOption.SetArrayFilters(options.ArrayFilters{

Filters: []interface{}{

bson.M{"\$and": bson.A{bson.M{"item.refund_no": ru.RefundNo},

bson.M{"item.refund_state": ru.RefundOldState}}},

}})
5.9 mongodb 查询不到结果,db明明有记录,怎么分析

解析的golang结构体不对,也可能解析不到返回结果,可以用elem bson.M

去解析成map再去检查,是查询条件问题还是解析问题。

for cur.Next(context.TODO()) {

var elem models.ApprNtf

// var elem bson.M

err1 := cur.Decode(&elem)

if err1 != nil {

global.G_LOG.Errorf("[%s]failed,查询err: %s", r.TraceId, err1)

err = errors.New("解析出错")

}

results = append(results, elem)

}

if err1 := cur.Err(); err1 != nil {

global.G_LOG.Errorf("[%s]failed,查询err: %s", r.TraceId, err1)

err = errors.New("cur出错")

}

if len(results) == 0 {

err = mongo.ErrNoDocuments

}
5.10 mongodb $in一定要注意转成bson.A,直接传slice或array查询结果不对
"members.memb_state": bson.M{"\$in": bson.A{models.ResApprNotNeed, models.ResApprPass}},

rIds := bson.A{}

for \_, v := range urefRid {

objRid, \_ := w.ToMongoId(v)

rIds = append(rIds, objRid)

}

//

filter := bson.M{"_id": objId, "r_data": bson.M{"\$elemMatch": bson.M{"_id": bson.M{"\$in": rIds},
5.11 golang匿名结构体写mongodb 怎么写tag

一定要写成这样bson:“,inline”,不能空着。mongodb存的时候才不是子文档,而是匿名结构体中的每个字段保存。

type QryUserRefForMyRsp struct {

QryUserRefForMy \`bson:",inline"\`

PicMapVtime \*time.Time \`json:"pic_map_vtime" bson:"pic_map_vtime,omitempty"\`

}
5.12 欢迎大家关注使用我golang+mongodb做的小程序(搜索 用享)

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值