Go链接Mongo失败问题的分析与联想

cuixiaogang

如果需要了解Mongo的使用方法,可以参考这篇文章:《分布式NoSQL数据库——MongoDB

前言

在使用go语言操作Mongo数据库时,出现了BUG。可是用的都是一套代码,之前的Mongo就没有出现过问题。以下是错误内容:

1
err:server at 10.242.225.93:17085 reports wire version 5, but this version of the Go driver requires at least 6 (MongoDB 3.6

从错误中可以看到,显示的事wire的版本是5,而当前版本的Go驱动仅支持6版本以上(Mongo版本至少为3.6)

这里说明一下各个组件的版本:

  • Golang:1.21.4
  • Golang链接Mongo的驱动:go.mongodb.org/mongo-driver@v1.13.1
  • Mongo数据库:3.4版本

引发的思考

  1. wire是什么?与Mongo是什么关系?
  2. mongo-driver、wire、Mongo这三者之间的版本关联关系是什么?

Wire介绍

Wire Protocol(线协议)是MongoDB客户端与服务器之间通信的底层二进制协议

Wire主要决定了如下内容:

  • 客户端如何向服务器发送请求
  • 服务器如何向客户端返回响应
  • 消息的格式、结构和编码方式
  • 支持的操作类型(如插入、查询、更新等)

不同的Wire版本之间,新增了那些特性?下面表格式AI的总结:

MongoDB 版本 Wire Version 发布时间 主要新特性
2.6 2 2014年 基础 CRUD 操作
3.0 3 2015年 SCRAM-SHA1 认证
3.2 4 2015年 OP_MSG 命令支持
3.4 5 2016年 聚合管道改进、$lookup 等
3.6 6 2017年 OP_MSG 成为主要协议、会话支持
4.0 7 2018年 事务支持
4.2 8 2019年 分布式事务
4.4 9 2020年 流式聚合
5.0 13 2021年 时间序列集合

不同的GO驱动版本支持的Wire版本,AI的总结:

驱动版本 支持的 MongoDB 版本 最低 Wire Version 说明
v1.4.4 MongoDB 3.4+ 5 该版本是支持 MongoDB 3.4 的版本,最低要求 Wire Version 5
v1.5.0 - v1.12.0 MongoDB 3.4+ 5 从 v1.4.4 到 v1.12.0 都支持 MongoDB 3.4+
v1.13.0 - v1.15.0 MongoDB 3.6+ 6 从 v1.13.0 开始移除对 MongoDB 3.4 的支持
v1.16.0+ MongoDB 4.2+ 8 最新版本支持 MongoDB 4.2+,Wire Version 8+

Mongo的底层数据

从上面的Wire协议介绍中发现,Wire决定了消息的格式、结构和编码方式,那么使用不同的Wire版本,查询出来的数据就会不同(注意:这里说的数据不是反序列化后的数据,而是二进制数据)

为什么要确认这种情况?

在Go使用Mongo的场景下,经常会有数据的字段未能保持一致(虽然Mongo3.2+增加了Schema,但是还是有人未做限制,针对于Schema,后面会详细介绍),这时,通常需要开发者自己手动转化。在GO查询的过程中,可以使用下面这种方式,来对查询出来的字段的字节码进行自定义转化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
type FieldAValue struct {
MD5 string `bson:"md5"`
Score string `bson:"score"`
SourceMd5 string `bson:"source_md5"`
Similarity string `bson:"similarity"`
SubLevel string `bson:"sublevel"`
VirusName string `bson:"virus_name"`
}

type FieldA struct {
Value *FieldAValue
}

// UnmarshalBSON FieldA字段存在两种情况bool类型和object类型
func (e *FieldA) UnmarshalBSON(data []byte) error {
// 尝试将数据解码为 FieldAValue
var model FieldAValue
if err := bson.Unmarshal(data, &model); err == nil {
e.Value = &model
return nil
}

// 如果解码失败(如布尔值/其他类型),直接返回 nil
e.Value = nil
return nil
}

type FieldB struct {
Value int
}


// UnmarshalBSON FieldA字段存在两种情况int类型和string类型
func (e *FieldB) UnmarshalBSON(data []byte) error {
// 尝试将数据解码为 FieldAValue
var modelInt int
if err := bson.Unmarshal(data, &modelInt); err == nil {
e.Value = modelInt
return nil
}

var modelStr string
var err error
if err := bson.Unmarshal(data, &modelStr); err == nil {
e.Value,err = convstr.itoa(modelStr)
if err != nil {
e.Value = 0
}
return nil
}

// 如果解码失败(如布尔值/其他类型),直接返回 nil
e.Value = 0
return nil
}

type ModelELFMongoV2 struct {
// ...
FieldA FieldA `bson:"field_a"`
}

而在Wire 5的版本中,针对int类型、string类型的数据,则无法使用bson.Unmarshal(data, &modelStr)的方法进行转义,因为他们的数据结构不同。以下是Wire 5 中的数据格式(即data值):

1
2
3
numberInt(0):00 00 00 00 (4字节)
numberLong(0):00 00 00 00 00 00 00 00 (8字节)
"0":02 00 00 00 30 00(6字节:前2字节标记类型,02 00 是字符串类型;00 00 30 00就是0,可参考ASCII表,注意是16进制的"30")

Mongo的Schema验证

Mongo3.2+的版本中,增加了对集合字段的约束,被称为“Schema”。使用这一特性,我们就可以对写入到集合中的数据做验证,不仅能保证字段的类型一致,还可以保证枚举值、范围值等符合要求

查询验证规则

1
2
3
db.getCollectionInfos({
name: "collection1"
});
  • 结果(脱敏后的数据)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
[
{
"name": "collection1",
"type": "collection",
"options": {
"validator": {
"$jsonSchema": {
"bsonType": "object",
// 要求必须存在那些字段
"required": [
"field1",
"md5",
"sha1",
"sha256",
"field2",
"field3",
],
"properties": {
"field1": {
"bsonType": "long",
"description": "xxxxx"
},
"md5": {
"bsonType": "string",
"minLength": 32,
"maxLength": 32,
"description": "must be a string and is required"
},
"sha1": {
"bsonType": "string",
"minLength": 40,
"maxLength": 40,
"description": "must be a string and is required"
},
"sha256": {
// 多种可能得情况:可能没有该字段,为空字符串
"anyOf": [
{
"bsonType": "string",
"minLength": 64,
"maxLength": 64,
"description": "must be a string and is required"
},
{
"bsonType": "string",
"minLength": 0,
"maxLength": 0
}
]
},
// 变长内容
"field2": {
"bsonType": "string",
"minLength": 0,
"maxLength": 40,
"description": "xxxxxxxxxxxx"
},
"field3": {
"bsonType": "string",
"description": "xxxx"
},
"field4": {
// 格式是一个数组,数组内饰字符串,字符串长度32/0,数组长度至少为0
"anyOf": [
{
"bsonType": "array",
"minItems": 0,
"items": {
"bsonType": "string",
"minLength": 32,
"maxLength": 32,
"description": "xxxxxxx"
}
},
{
"bsonType": "array",
"minItems": 0,
"items": {
"bsonType": "string",
"minLength": 0,
"maxLength": 0,
"description": "xxxxxx"
}
}
]
},
"field5": {
// 可能存在两种类型:int(int32) / long(int64)
"anyOf": [
{
"bsonType": "long",
"description": "xxxxx"
},
{
"bsonType": "int",
"description": "xxxx"
}
]
},
"field6": {
// 是一个枚举的int类型
"bsonType": "int",
"enum": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10
],
"description": "xxxxxxxxxxxxx
"field7": {
// 是一个long的数组
"bsonType": "array",
"items": {
"bsonType": "long"
}
},
"field8": {
// 是一个object的数组,每个object有两个字段(md5、path),都是字符串类型
"bsonType": "array",
"items": {
"bsonType": "object",
"properties": {
"md5": {
"bsonType": "string",
"minLength": 32,
"maxLength": 32
},
"path": {
"bsonType": "string"
}
}
}
}
}
}
},
// 验证级别:strict(默认,严格验证所有写入)、moderate(仅验证新字段,不影响已有字段)
"validationLevel": "strict",
// 验证动作:error(默认,拒绝不满足约束的写入)、warn(仅警告,允许写入)
"validationAction": "error"
},
"info": {
"readOnly": false,
"uuid": UUID("30f6acff-98d6-42c9-83a9-0ccdd75649ce")
},
"idIndex": {
"v": 2,
"key": {
"_id": 1
},
"name": "_id_",
"ns": "database.collection1"
}
}
]

options.validator字段就是该集合的Schema验证规则

创建验证规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 示例:创建 "users" 集合,限制字段类型和约束
db.createCollection("collection1", {
validator: {
$jsonSchema: {
bsonType: "object", // 根文档必须是对象类型
required: [
"field1",
"md5",
"sha1",
"sha256",
"field2",
"field3",
],
properties: {
// 参考上面查询的配置
}
}
},
validationLevel: "strict",
validationAction: "error"
});

更新验证规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
db.runCommand({
collMod: "collection1",
validator: {
$jsonSchema: {
bsonType: "object",
required: [//... ],
properties: {
// 注意:这里要包含原有所有约束,否则会覆盖!
}
}
},
validationLevel: "strict",
validationAction: "error"
});

常用的约束关键字

关键字 作用 示例值
bsonType 限制字段的 BSON 类型(核心) stringintbooldate
required 标记字段为必填(数组形式,包含字段名) ["name", "age"]
minimum/maximum 限制数字类型的范围(仅对 int/double 有效) minimum: 0maximum: 120
minLength/maxLength 限制字符串长度 minLength: 2maxLength: 20
pattern 限制字符串匹配正则表达式 ^[a-zA-Z0-9]+$(字母数字)
enum 限制字段值只能是枚举列表中的元素 ["male", "female", "other"]
anyOf 指定一个字段可以匹配多个的任意一个 [{"bsonType":"string"},{"bsonType":"int"}]
items 用于定义数组类型字段中元素
description 描述信息
validationLevel 指定数据在更新或插入文档时的验证级别 "strict"、"moderate"、"off"
validationAction 指定当文档违反验证规则时采取的操作 "error"、"warn"