2023-08-30

## 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时传入目录位置。

```go

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当前文件目录位置的问题。

```go

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获取当前目录方法,可以参考

```go

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

```go

{

"\$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事务

```go

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

```go

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 动态删除数组

```go

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等,查询后解析就会报错,查不出这条记录

```go

// 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 条件更新,及多个条件怎么写才对

```go

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再去检查,是查询条件问题还是解析问题。

```go

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查询结果不对

```go

"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存的时候才不是子文档,而是匿名结构体中的每个字段保存。

```go

type QryUserRefForMyRsp struct {

QryUserRefForMy \`bson:",inline"\`

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

}

```

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


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,270评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,489评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,630评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,906评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,928评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,718评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,442评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,345评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,802评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,984评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,117评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,810评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,462评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,011评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,139评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,377评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,060评论 2 355

推荐阅读更多精彩内容

  • Let's start with a story.The classic trolley car problem....
    橄榄树张悦芊阅读 109评论 0 0
  • 1、precedev. 发生在...之前;先于;在前面,领先 Dinner was preceded by a s...
    林爽_7690阅读 90评论 0 0
  • 人的幸福大概有三个重要的来源。 一是人与外部物质世界的关系。 注意到物质世界,确实如此,幸福要有基本保证,马斯洛的...
    文芳读写阅读 81评论 0 2
  • midjourneyPapercut_v1 效果不要好控,衍纸的效果 (SFW:2),HDR,UHD,8K,bes...
    大法师的输出阅读 49评论 0 0
  • 崤函光影:月上柳梢头 #三门峡头条##中国手机摄影# #今日头条日签# #拍摄美丽城市# #手机随拍·原创# #手...
    任我游阅读 39评论 0 0