上篇文章中介绍了轮询调度算法, 忽略后端元素的差异性, 将请求均匀的”涂抹”到后端节点. 本篇介绍更为复杂的加权轮询调度算法, 可以根据后端节点实际承载能力, 单独调整每个节点的权重

阅读全文 »

实现轮询调度算法(Round-Robin/rr)的原理是通过取模拿到数组/切片的下标, 从而提取出下一次即将调度的元素. 公式为i = (i + 1) mod n
i为数组下标, n为数组长度
轮询调度算法的优点是无状态, 均匀的将请求调度到后端的每个节点, 并不关心后端每个节点的实际差异

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
package main

import "fmt"

// 声明后端元素的数据结构
type volume struct {
name string
quota int
}

// 声明数据类型存放元素的Slice
type volumes []volume

// 声明并赋值索引下标
var i = -1

// 轮询调度算法
func (v volumes) Select() string {
i = (i + 1) % len(v)
return v[i].name
}

func main(){
v1 := volume{
name: "a",
quota: 4,
}
v2 := volume{
name: "b",
quota: 4,
}
v3 := volume{
name: "c",
quota: 8,
}
v4 := volume{
name: "d",
quota: 2,
}

vo := volumes{
v1, v2, v3, v4,
}

for j := 0; j < 20; j++ {
fmt.Println(j+1, vo.Select())
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1 a
2 b
3 c
4 d
5 a
6 b
7 c
8 d
9 a
10 b
11 c
12 d
13 a
14 b
15 c
16 d
17 a
18 b
19 c
20 d

通过运行结果可以看出, 每次Select请求都被均匀的调度到”后端的节点”. 考虑到后端节点可能存在的差异性, 下篇文章将介绍更为复杂的加权轮询调度算法

最大公约数简介

最大公约数(GCD/Greatest Common Divisor)指几个整数中共有约数中最大的一个

欧几里德算法

欧几里德算法又称辗转相除法, 该算法用于计算两个整数的最大公约数. 定理如下:

1
gcd(a,b) = gcd(b,a mod b)

意思就是说: ab的最大公约数 = ba➗b的余数的最大公约数

  • a➗b的余数为0时, 最大公约数为b
  • a➗b的余数不为0时, 则将a=b, b=a➗b的余数, 递归运算, 直到余数为0

具体证明的过程在此不再赘述, 如有兴趣可以自行谷歌一下, 下面直接给出基于go语言的实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

func gcd(a,b int) int {
if b == 0 {
return a
}
return gcd(b, a % b)
}

func main() {
fmt.Println(gcd(5, 10))
}

运行结果:

1
5

解析:

第一次调用:

调用gcd(5, 10)a=5, b=10, 进入函数后判断if b == 0, 此时b=10, 判断条件为假, 递归调用gcd(b, a % b), 此时实际值为 gcd(10, 5 % 10), 5与10计算取余商0, 余5, 则实际调用参数为gcd(10, 5)

第二次调用:
调用gcd(10, 5)a=10, b=5, 进入函数后判断if b == 0, 此时b=5, 判断条件为假, 递归调用gcd(b, a % b), 此时实际值为 gcd(5, 10 % 5), 10与5计算取余商2, 余0, 则实际调用参数为gcd(5, 0)

第三次调用:
调用gcd(5, 0)a=5, b=0, 进入函数后判断if b == 0, 此时b=0, 判断条件为真, 直接返回a 此时a即为两个数的最大公约数

多个数求最大公约数

实现思路: 先求出头两个数的最大公约数, 再拿计算出的最大公约数依次与后面所有的数求最大公约数

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
package main

import "fmt"

func gcd(a,b int) int {
if b == 0 {
return a
}
return gcd(b, a % b)
}

func GetGcd(n []int) int {
// 先取得Slice中的第一个数, 并假设第一个数为最大公约数
g := n[0]
// 依次与Slice中的每个数字求最大公约数
// 这里i=1, 因为第0个数字已经取出并假设为最大公约数
for i:=1; i<len(n)-1; i++ {
oldGcd := g
// 使用两个数字比较的最大公约数函数进行计算, 得出当前两个数字的最大公约数
// 循环开始后, 依次将当前最大公约数与后面的数字一一进行运算, 求最大公约数
g = gcd(g, n[i])
fmt.Printf("第%d次运算, 当前最大公约数为%d, 与%d运算得出的新最大公约数为: %d \n", i, oldGcd, n[i], g)
}
return g
}

func main() {
nums := []int{4,4,6,2,8,10}
fmt.Printf("运算结果: \n\t数列: %v 的最大公约数为: %d", nums, GetGcd(nums))
}

运行结果:

1
2
3
4
5
6
第1次运算, 当前最大公约数为4, 与4运算得出的新最大公约数为: 4 
第2次运算, 当前最大公约数为4, 与6运算得出的新最大公约数为: 2
第3次运算, 当前最大公约数为2, 与2运算得出的新最大公约数为: 2
第4次运算, 当前最大公约数为2, 与8运算得出的新最大公约数为: 2
运算结果:
数列: [4 4 6 2 8 10] 的最大公约数为: 2

intstr 这个包是偶然在k8s client-go中发现的, 在编写deployment结构体定义时, apiv1.Container实例下LivenessProbe中用到的Port, 就是intstr.IntOrString. 特地看下来这个函数, 发现这个函数恰巧解决了一直困扰我的一个问题: 在restful接口中, 如果要求json格式body体内传递port端口参数, 那么port这个字段, 我应该在服务端定义成整型好呢, 还是字符串类型好呢, 一直很纠结~ 有了intstr.IntOrString再也不用为这事儿发愁了

简介

以往我们接收jsonyaml字符串要转成结构体时, 数据类型必须一一对应. 比如数据中的端口字段, 如果json 中传递是的整型, 则为以下样式 {"port": 8080}; 如果传递的是字符串类型, 则为以下形式{"port": "8080"}

服务端接收到这些字符串后, 需要将数据与结构体中的字段一一绑定, 当然数据类型也必须一致. 如果在服务端定义了port字段为int, 但是json字符串传递过来的是{"port": "8080"}, 则绑定数据时将会报错. 反之也一样, 数据类型必须一致.

我自己开发过得项目中, 很多接口的body体内都出现了端口字段, 有的时候定义为整型, 有的时候定义为字符串类型, api doc里也是有的写得整型, 有的写的字符串, 每次都纠结用什么类型好

intstr.IntOrString 数据类型顾名思义, 该类型可以既可以接受整型, 也可以接受字符串类型. 再也不用纠结这种用整型和字符串类型都合适的字段到底用什么数据类型了

intstr.IntOrString 不仅可以兼容接收整型或字符串类型, 而且可以通过其内置的函数, 在两个数据类型之间自由转换

json/yaml串转结构体

以下demo的场景为, 拿到json/yaml串, 转换成结构体的使用场景

端口字段为整型

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
package main

import (
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/util/intstr"
)

type service struct {
Name string `json:"name"`
Host string `json:"host"`
Port intstr.IntOrString `json:"port"`
}

func main() {

// 定义json字符串, port字段的值设置为整型
jsonStr := `{"name": "nginx", "host": "192.168.1.2", "port": 8080}`

nginxService := &service{}

// 将json字符串的数据, 绑定到结构体实例中
json.Unmarshal([]byte(jsonStr), nginxService)

fmt.Printf("提取数据类型: %T 实际数据类型(编号): %v 值: %#v \n", nginxService.Port.IntVal, nginxService.Port.Type, nginxService.Port.IntVal)
fmt.Printf("提取数据类型: %T 实际数据类型(编号): %v 值: %#v \n", nginxService.Port.StrVal, nginxService.Port.Type, nginxService.Port.StrVal)
}

运行结果:

1
2
提取数据类型: int32 实际数据类型(编号): 0 值: 8080 
提取数据类型: string 实际数据类型(编号): 0 值: ""

上面的例子中, json字符串中的端口字段为整型, 绑定到结构体的实例后, 通过nginxService.Port.Type 可以取得json串中实际传递的是哪种数据类型, 在本例中, 调用nginxService.Port.Type的结果是0, 通过查看源码得知, 0表示json串中传递的数据是整型

1
2
3
4
5
6
7
8
// 源码定义
// Type represents the stored type of IntOrString.
type Type int

const (
Int Type = iota // The IntOrString holds an int.
String // The IntOrString holds a string.
)

在实际使用时, 可以先判断nginxService.Port.Type的结果是0, 还是1

  • 如果是0, 则取整型nginxService.Port.IntVal
  • 如果是1, 则取字符串类型nginxService.Port.StrVal

还有一种更直接的用法, 就是根据需要, 显式的类型转换

比如上面的例子中, json传递的端口值为整型, 但是服务端绑定到结构体之后, 当使用该端口时, 需要该端口值得数据类型为字符串类型, 你可以直接这样做:

fmt.Printf("%#v", nginxService.Port.String())

运行结果:

"8080"

可以看到, 整型的数据, 已经帮你转换成为字符串类型, 后期使用非常灵活, 可以随时提取两种数据类型的值

端口为字符串类型

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
package main

import (
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/util/intstr"
)

type service struct {
Name string `json:"name"`
Host string `json:"host"`
Port intstr.IntOrString `json:"port"`
}

func main() {

// 定义json字符串, port字段的值设置为字符串类型
jsonStr := `{"name": "nginx", "host": "192.168.1.2", "port": "8080""}`

nginxService := &service{}

// 将json字符串的数据, 绑定到结构体实例中
json.Unmarshal([]byte(jsonStr), nginxService)

fmt.Printf("提取数据类型: %T 实际数据类型(编号): %v 值: %#v \n", nginxService.Port.IntVal, nginxService.Port.Type, nginxService.Port.IntVal)
fmt.Printf("提取数据类型: %T 实际数据类型(编号): %v 值: %#v \n", nginxService.Port.StrVal, nginxService.Port.Type, nginxService.Port.StrVal)
fmt.Printf("值: %#v 实际数据类型: %T \t \n", nginxService.Port.String(), nginxService.Port.String())
fmt.Printf("值: %#v 实际数据类型: %T \t \n", nginxService.Port.String(), nginxService.Port.IntValue())
}

运行结果:

1
2
3
4
提取数据类型: int32 实际数据类型(编号): 1 值: 0 
提取数据类型: string 实际数据类型(编号): 1 值: "8080"
值: "8080" 实际数据类型: string
值: "8080" 实际数据类型: int

结构体转json/yaml

数据类型: 整型

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
package main

import (
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/util/intstr"
)

type service struct {
Name string `json:"name"`
Host string `json:"host"`
Port intstr.IntOrString `json:"port"`
}

func main() {

nginxService := service{
Name: "nginx",
Host: "192.168.1.2",
Port: intstr.IntOrString{
Type: 0, // 这里设置为0, 意为数据类型为整型
IntVal: 2222,
StrVal: "1111",
},
}

jsonBytes, _ := json.Marshal(nginxService)
fmt.Println(string(jsonBytes))

}

注意上面intstr.IntOrString.Type的值设置为0, 运行结果:

1
{"name":"nginx","host":"192.168.1.2","port":2222}

intstr.IntOrString.Type的值设置为0, 表示该结构体实例内的数据为整型, 故转json时, 按照IntVal中的值来生成字符串

*注意: * 如果没有显式为intstr.IntOrString.Type赋值, 则该值默认为0, 默认会取IntVal中的数据, 也就是说, 可以简写为以下形式

1
2
3
4
5
nginxService := service{
Name: "nginx",
Host: "192.168.1.2",
Port: intstr.IntOrString{IntVal: 2222},
}

数据类型: 字符串

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
package main

import (
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/util/intstr"
)

type service struct {
Name string `json:"name"`
Host string `json:"host"`
Port intstr.IntOrString `json:"port"`
}

func main() {

nginxService := service{
Name: "nginx",
Host: "192.168.1.2",
Port: intstr.IntOrString{
Type: 1, // 这里设置为1, 意为数据类型为str
IntVal: 2222,
StrVal: "1111",
},
}

jsonBytes, _ := json.Marshal(nginxService)
fmt.Println(string(jsonBytes))

}

注意上面intstr.IntOrString.Type的值设置为1, 运行结果:

1
{"name":"nginx","host":"192.168.1.2","port":"1111"}

intstr.IntOrString.Type的值设置为1, 表示该结构体实例内的数据为字符串类型, 故转json时, 按照StrVal中的值来生成字符串

上篇结构体排序的文档介绍了如果依据多个字段进行单因素排序, 举例来说, 一个学生实例, 可以单独按照姓名排序, 可以单独按照年龄排序, 可以单独按照体重排序. 本篇文章介绍如果进行多因素排序, 也就是按照姓名排序, 当姓名相同时, 按照年龄排序…

以官方文档的demo来做演示

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
package main

import (
"fmt"
"sort"
)

// 数据类型
type Change struct {
user string
language string
lines int
}


// sort接口方法之一(Less)
type lessFunc func(p1, p2 *Change) bool

// 数据集类型, 与上一篇排序文章(多字段单独排序)比较, less字段的数据类型不再是 func(p1, p2 *Change) bool
// 而是 []func(p1, p2 *Change) bool 因为在第一个比较的值相等的情况下, 还要比较第二个值, 所以这里需要多个比较函数
type multiSorter struct {
changes []Change
less []lessFunc
}

// Sort 函数有两个作用
// 第一, 将参数(实际的数据集)赋值给ms对象
// 第二, 调用内置sort函数进行排序操作
func (ms *multiSorter) Sort(changes []Change) {
ms.changes = changes
sort.Sort(ms)
}

// OrderedBy 函数的作用是返回一个multiSorter实例, 并将所有的实际排序函数赋值给实例的less字段,
// 上面已经为multiSorter结构体定义了Sort方法, 所以该函数的返回值可以直接调用Sort方法进行排序
// 该函数中, 为multiSorter结构体中的less字段赋值, Sort方法中又将实际数据集传入, 赋值给multiSorter的changes字段
// 一个函数, 一个方法调用过后, multiSorter实例中两个字段就已经全部被正确赋值, 可以调用系统sort函数进行排序
// 该函数也可看作是一个工厂方法, 用来生成less字段已经被赋值的multiSorter实例
func OrderedBy(less ...lessFunc) *multiSorter {
return &multiSorter{
less: less,
}
}

// Len 为sort接口方法之一
func (ms *multiSorter) Len() int {
return len(ms.changes)
}

// Swap 为sort接口方法之一
func (ms *multiSorter) Swap(i, j int) {
ms.changes[i], ms.changes[j] = ms.changes[j], ms.changes[i]
}

// Less 为sort接口方法之一
func (ms *multiSorter) Less(i, j int) bool {
// 为了后面编写简便, 这里将需要比较的两个元素赋值给两个单独的变量
p, q := &ms.changes[i], &ms.changes[j]
// Try all but the last comparison.
var k int
// 由于可能有多个需要排序的字段, 也就对应了多个less函数, 当第一个字段的值相等时,
// 需要依次尝试比对后续其他字段的值得大小, 所以这里需要获取比较函数的长度, 以便遍历比较
for k = 0; k < len(ms.less)-1; k++ {
// 提取比较函数, 将函数赋值到新的变量中以便调用
less := ms.less[k]
switch {
case less(p, q):
// 如果 p < q, 返回值为true, 不存在两个值相等需要比较后续字段的情况, 所以这里直接返回
// 如果 p > q, 返回值为false, 则调到下一个case中处理
return true
case less(q, p):
// 如果 p > q, 返回值为false, 不存在两个值相等需要比较后续字段的情况, 所以这里直接返回
return false
}
// 如果代码走到这里, 说明ms.less[k]函数比较后 p == q; 重新开始下一次循环, 更换到下一个比较函数处理
continue
}
// 如果代码走到这里, 说明所有的比较函数执行过后, 所有比较的值都相等
// 直接返回最后一次的比较结果数据即可
return ms.less[k](p, q)
}

var changes = []Change{
{"gri", "Go", 100},
{"ken", "C", 150},
{"glenda", "Go", 200},
{"rsc", "Go", 200},
{"r", "Go", 100},
{"ken", "Go", 200},
{"dmr", "C", 100},
{"r", "C", 150},
{"gri", "Smalltalk", 80},
}


func main() {
// 预定义排序函数: 按照姓名升序排列
user := func(c1, c2 *Change) bool {
return c1.user < c2.user
}
// 预定义排序函数: 按照语言升序排列
language := func(c1, c2 *Change) bool {
return c1.language < c2.language
}
// 预定义排序函数: 按照行数升序排列
increasingLines := func(c1, c2 *Change) bool {
return c1.lines < c2.lines
}
//预定义排序函数: 按照行数降序排列
decreasingLines := func(c1, c2 *Change) bool {
return c1.lines > c2.lines
}

// 按照姓名升序排列
OrderedBy(user).Sort(changes)
fmt.Println("By user:\t\t", changes)

// 按照姓名升序排列, 姓名相同的按行数升序排列
OrderedBy(user, increasingLines).Sort(changes)
fmt.Println("By user,<lines:\t\t", changes)

// 按姓名升序排列, 姓名相同的按行数降序排列
OrderedBy(user, decreasingLines).Sort(changes)
fmt.Println("By user,>lines:\t\t", changes)

// 按语言升序排列, 语言相同按行数升序排列
OrderedBy(language, increasingLines).Sort(changes)
fmt.Println("By language,<lines:\t", changes)

// 按语言升序排列, 语言相同按行数升序排列, 行数也相同的, 按姓名升序排列
OrderedBy(language, increasingLines, user).Sort(changes)
fmt.Println("By language,<lines,user:", changes)

}

运行结果:

1
2
3
4
5
By user:                 [{dmr C 100} {glenda Go 200} {gri Go 100} {gri Smalltalk 80} {ken C 150} {ken Go 200} {r Go 100} {r C 150} {rsc Go 200}]
By user,<lines: [{dmr C 100} {glenda Go 200} {gri Smalltalk 80} {gri Go 100} {ken C 150} {ken Go 200} {r Go 100} {r C 150} {rsc Go 200}]
By user,>lines: [{dmr C 100} {glenda Go 200} {gri Go 100} {gri Smalltalk 80} {ken Go 200} {ken C 150} {r C 150} {r Go 100} {rsc Go 200}]
By language,<lines: [{dmr C 100} {ken C 150} {r C 150} {r Go 100} {gri Go 100} {ken Go 200} {glenda Go 200} {rsc Go 200} {gri Smalltalk 80}]
By language,<lines,user: [{dmr C 100} {ken C 150} {r C 150} {gri Go 100} {r Go 100} {glenda Go 200} {ken Go 200} {rsc Go 200} {gri Smalltalk 80}]

与上一篇文章<go语言结构体排序>(简称A)中<结构体多字段单独排序>的代码区别 (本篇简称B):

A:

1
2
3
4
5
6
7
8
9
10
type By func(i, j student) bool
func (b By) Sort (students []student) {
// 生成数据集
ss := studentSorter{
students: students,
by: b,
}
// 对数据集进行排序
sort.Sort(ss)
}

B:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type multiSorter struct {
changes []Change
less []lessFunc
}

// 在数据集类型中编写Sort排序方法
func (ms *multiSorter) Sort(changes []Change) {
ms.changes = changes
// 对数据集进行排序
sort.Sort(ms)
}

func OrderedBy(less ...lessFunc) *multiSorter {
return &multiSorter{
less: less,
}
}

A B两篇中各截取了部分代码, 均涉及两项操作

  • 生成数据集
  • 排序

A篇的操作为: 单独声明一个By数据类型, 为该数据类型编写方法, 方法中的第一步为生成数据集, 第二步为排序操作. 调用时By(name).Sort(students)中的By(name)是类型转换操作, 后续的.Sort()方法是调用By自己的Sort方法得到的排序结果

B篇的操作为: 单独编写一个OrderBy函数, 该OrderBy函数仅用于生成数据集并返回, 需要注意的是, 这里生成的数据集, 仅包含有排序函数, 不包含实际数据. 由于数据集类型本身自带Sort方法, 所以返回的数据可以直接调用Sort方法执行排序操作, 排序之前, 将参数中的数据填充到数据集中, 形成完整的数据集. 调用时OrderedBy(user).Sort(changes)中的OrderedBy(user)与A篇不同, 此处不是类型转换, 而是函数调用, 后续的.Sort()方法是由OrderedBy()函数返回的multiSorter对象, 调用multiSorter中的Sort方法得到的排序结果

A B两篇的排序方法略有差别, 但本质都是一样的, 核心操作都是生成数据集排序, 只是B篇中, Less方法需要根据多因素排序, 略微复杂一些. 上面的例子是官方的demo, 当然我们也可以完全按照A篇的风格改写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 删除multiSorter数据类型中的Sort方法

// 声明OrderedBy数据类型
type OrderedBy []lessFunc
// 与A篇风格保持一致, 在OrderedBy的方法中进行数据集的完整生成和排序操作
func (ob OrderedBy) Sort (changes []Change) {
// 生成完整的数据集
ms := &multiSorter{
changes: changes,
less: ob,
}
// 排序
sort.Sort(ms)
}

这样修改之后, 调用方式也发生了变更

OrderedBy()是一个类型转换的操作, 根据上面的声明, 它只能转换[]lessFunc类型的数据, 所以需要先声明并赋值[]lessFunc{}对象, 然后才能交给OrderedBy()函数进行类型转换. 转换后其自身拥有Sort方法可供调用

1
2
3
4
5
6
   OrderedBy([]lessFunc{user}).Sort(changes)
fmt.Println("By user:\t\t", changes)

// 按照姓名升序排列, 姓名相同的按行数升序排列
OrderedBy([]lessFunc{user, increasingLines}).Sort(changes)
fmt.Println("By user,<lines:\t\t", changes)

以下是A篇风格的完整代码

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
package main

import (
"fmt"
"sort"
)

// 数据类型
type Change struct {
user string
language string
lines int
}


// sort接口方法之一(Less)
type lessFunc func(p1, p2 *Change) bool

// 数据集类型, 与上一篇排序文章(多字段单独排序)比较, less字段的数据类型不再是 func(p1, p2 *Change) bool
// 而是 []func(p1, p2 *Change) bool 因为在第一个比较的值相等的情况下, 还要比较第二个值, 所以这里需要多个比较函数
type multiSorter struct {
changes []Change
less []lessFunc
}

type OrderedBy []lessFunc
func (ob OrderedBy) Sort (changes []Change) {
ms := &multiSorter{
changes: changes,
less: ob,
}
sort.Sort(ms)
}


// Len 为sort接口方法之一
func (ms *multiSorter) Len() int {
return len(ms.changes)
}

// Swap 为sort接口方法之一
func (ms *multiSorter) Swap(i, j int) {
ms.changes[i], ms.changes[j] = ms.changes[j], ms.changes[i]
}

// Less 为sort接口方法之一
func (ms *multiSorter) Less(i, j int) bool {
// 为了后面编写简便, 这里将需要比较的两个元素赋值给两个单独的变量
p, q := &ms.changes[i], &ms.changes[j]
// Try all but the last comparison.
var k int
// 由于可能有多个需要排序的字段, 也就对应了多个less函数, 当第一个字段的值相等时,
// 需要依次尝试比对后续其他字段的值得大小, 所以这里需要获取比较函数的长度, 以便遍历比较
for k = 0; k < len(ms.less)-1; k++ {
// 提取比较函数, 将函数赋值到新的变量中以便调用
less := ms.less[k]
switch {
case less(p, q):
// 如果 p < q, 返回值为true, 不存在两个值相等需要比较后续字段的情况, 所以这里直接返回
// 如果 p > q, 返回值为false, 则调到下一个case中处理
return true
case less(q, p):
// 如果 p > q, 返回值为false, 不存在两个值相等需要比较后续字段的情况, 所以这里直接返回
return false
}
// 如果代码走到这里, 说明ms.less[k]函数比较后 p == q; 重新开始下一次循环, 更换到下一个比较函数处理
continue
}
// 如果代码走到这里, 说明所有的比较函数执行过后, 所有比较的值都相等
// 直接返回最后一次的比较结果数据即可
return ms.less[k](p, q)
}

var changes = []Change{
{"gri", "Go", 100},
{"ken", "C", 150},
{"glenda", "Go", 200},
{"rsc", "Go", 200},
{"r", "Go", 100},
{"ken", "Go", 200},
{"dmr", "C", 100},
{"r", "C", 150},
{"gri", "Smalltalk", 80},
}


func main() {
// 预定义排序函数: 按照姓名升序排列
user := func(c1, c2 *Change) bool {
return c1.user < c2.user
}
// 预定义排序函数: 按照语言升序排列
language := func(c1, c2 *Change) bool {
return c1.language < c2.language
}
// 预定义排序函数: 按照行数升序排列
increasingLines := func(c1, c2 *Change) bool {
return c1.lines < c2.lines
}
//预定义排序函数: 按照行数降序排列
decreasingLines := func(c1, c2 *Change) bool {
return c1.lines > c2.lines
}


// 按照姓名升序排列
OrderedBy([]lessFunc{user}).Sort(changes)
fmt.Println("By user:\t\t", changes)

// 按照姓名升序排列, 姓名相同的按行数升序排列
OrderedBy([]lessFunc{user, increasingLines}).Sort(changes)
fmt.Println("By user,<lines:\t\t", changes)

// 按姓名升序排列, 姓名相同的按行数降序排列
OrderedBy([]lessFunc{user, decreasingLines}).Sort(changes)
fmt.Println("By user,>lines:\t\t", changes)

// 按语言升序排列, 语言相同按行数升序排列
OrderedBy([]lessFunc{language, increasingLines}).Sort(changes)
fmt.Println("By language,<lines:\t", changes)

// 按语言升序排列, 语言相同按行数升序排列, 行数也相同的, 按姓名升序排列
OrderedBy([]lessFunc{language, increasingLines, user}).Sort(changes)
fmt.Println("By language,<lines,user:", changes)

}

有了go语言中的排序接口, 可以很容易的对结构体的任意一个字段进行排序, 具体以哪个字段排序, 定义在该结构体Slice的Less方法中. 一个结构体的Slice仅可以实现一次sort接口, 如果一个结构体中有多个字段需要单独进行排序, 最粗鲁的办法只能是定义多个Slice类型, 为每个类型都编写不同的Less方法

约定关键字:

多字段单独排序: 一个结构体中, 需要有多个字段作为依据, 单独进行排序, 单个字段排序时不受其他字段排序策略的影响
多字段多因素排序: 一个结构体中, 对第一个字段排序时, 如果该字段的数值相等, 按其他字段再次进行排序操作(例如: 按年龄进行排序, 年龄相同的按体重排序)

结构体单字段排序

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
package main

import (
"fmt"
"sort"
)

// 元素类型
type student struct {
name string
age uint
weight float32
}

// 数据集类型
type students []student

func (s students) Len() int {
return len(s)
}

func (s students) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}

func (s students) Less(i, j int) bool {
return s[i].age < s[j].age
}

// 数据集
var ss = students{
{"d", 7, 28},
{"c", 3, 40},
{"b", 8, 35},
{"a", 5, 30},
}

func main() {
sort.Sort(ss)
fmt.Println(ss)
}

运行结果:

1
[{c 3 40} {a 5 30} {d 7 28} {b 8 35}]

结构体多字段单独排序

多字段需要排序的, 最粗鲁的办法是为每一个需要排序的字段单独声明一个数据集类型, 然后在每个数据集类型中实现其对应的Less方法

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
package main

import (
"fmt"
"sort"
)

type student struct {
name string
age uint
weight float32
}

type studentsSortByName []student

func (s studentsSortByName) Len() int {
return len(s)
}

func (s studentsSortByName) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}

func (s studentsSortByName) Less(i, j int) bool {
return s[i].name < s[j].name
}

type studentsSortByAge []student

func (s studentsSortByAge) Len() int {
return len(s)
}

func (s studentsSortByAge) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}

func (s studentsSortByAge) Less(i, j int) bool {
return s[i].age < s[j].age
}

type studentsSortByWeight []student

func (s studentsSortByWeight) Len() int {
return len(s)
}

func (s studentsSortByWeight) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}

func (s studentsSortByWeight) Less(i, j int) bool {
return s[i].weight < s[j].weight
}

// 数据集
var ss = []student{
{"d", 7, 28},
{"c", 3, 40},
{"b", 8, 35},
{"a", 5, 30},
}

func main() {
sort.Sort(studentsSortByName(ss))
fmt.Println(ss)

sort.Sort(studentsSortByAge(ss))
fmt.Println(ss)

sort.Sort(studentsSortByWeight(ss))
fmt.Println(ss)
}

运行结果:

1
2
3
[{a 5 30} {b 8 35} {c 3 40} {d 7 28}]  // 按姓名排序
[{c 3 40} {a 5 30} {d 7 28} {b 8 35}] // 按年龄排序
[{d 7 28} {a 5 30} {b 8 35} {c 3 40}] // 按体重排序

这个排序的办法的劣势是需要写很多重复代码, 通过这个例子也可以看出, 按不动字段排序实际只是Less方法的实现不同

结构体多字段单独排序(推荐)

这里介绍一个更优雅的方式, 解决上面多字段排序需要编写n * 3方法的问题

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
package main

import (
"fmt"
"sort"
)

// 定义元素类型
type student struct {
name string
age uint
weight float32
}

// 定义数据集类型
// 在单字段排序中, 数据集类型仅仅是元素类型的Slice, 但是在此方法中, 元素类型的Slice作为数据集类型的一个字段出现
// 由于需要对多个字段排序, 且每个字段实现的`Less`方法不同, 这里将排序方法(函数)作为数据集类型的一个字段
type studentSorter struct {
students []student // 具体的数据集, 待排序的数据都会存放在这里
by func(i, j student) bool // 排序函数, 排序的依据, 具体排序的策略, 会被放置在这里
// 可以明显的看出, by 函数的入参与返回 与 `Less` 函数的入参与返回是一致的
}

// 在数据集类型之上实现排序接口的Len方法
func (s studentSorter) Len() int {
return len(s.students)
}

// 在数据集类型之上实现交换元素的Swap方法
func (s studentSorter) Swap(i, j int) {
s.students[i], s.students[j] = s.students[j], s.students[i]
}

// 在数据集类型之上实现比较元素大小的Less方法
// 由于多个字段需要排序, 这里的返回值, 返回的是一个排序函数
// 上面的数据集结构体中, 已经明确定义了一个 by 字段, 该字段用来保存具体的排序函数
// 所以这里的返回值并没有直接指定数据的大小的比较方法, 而是调用了保存在数据集by字段中的函数去处理
// 可以简单的理解为, 将比较数据大小的操作委托给了 数据集类型.by 的函数, 具体的排序策略也就交给了 by 对应的具体函数
func (s studentSorter) Less(i, j int) bool {
return s.by(s.students[i], s.students[j])
}

// 声明并赋值测试数据集
var students = []student{
{"d", 7, 28},
{"c", 3, 40},
{"b", 8, 35},
{"a", 5, 30},
}

// 使用工厂方法定义排序数据集, 并进行排序
// 上面的 students 仅仅是实际的待排序数据, 仅仅是数据集对象中的 studentSorter.students 字段的值
// 工厂方法就是用来根据实际的排序策略, 为数据集对象中的by赋值, 生成一个完整的数据集, 从而完成排序操作
type By func(i, j student) bool
func (b By) Sort (students []student) {
// 生成数据集
ss := studentSorter{
students: students,
by: b,
}
// 对数据集进行排序
sort.Sort(ss)
}

// 预定义排序策略函数, 这些函数都实现了sort接口中的less方法

// 预先定义的排序函数: 按照姓名排序
var name = func(i, j student) bool {
return i.name < j.name
}

// 预先定义的排序函数: 按照年龄排序
var age = func(i, j student) bool {
return i.age < j.age
}

// 预先定义的排序函数: 按照体重排序
var weight = func(i, j student) bool {
return i.weight < j.weight
}

func main() {
By(name).Sort(students)
fmt.Println("By name:", students)

By(age).Sort(students)
fmt.Println("By age:", students)

By(weight).Sort(students)
fmt.Println("By weight:", students)
}

运行结果:

1
2
3
By name: [{a 5 30} {b 8 35} {c 3 40} {d 7 28}]
By age: [{c 3 40} {a 5 30} {d 7 28} {b 8 35}]
By weight: [{d 7 28} {a 5 30} {b 8 35} {c 3 40}]

这个多字段单独排序的方法, 省去了需要重复编写Len()``Swap()方法的麻烦, 也是sort包官方推荐的多字段排序案例

结构体多字段单独排序2(推荐)

官方文档中还介绍了另一种排序方式, 通过Go语言独特的组合特性来实现sort接口的三个方法

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
package main

import (
"fmt"
"sort"
)

type student struct {
name string
age int
weight int
}

type students []student

func (s students) Len() int {
return len(s)
}
func (s students) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}

type ByName struct {
students
}

func (s ByName) Less(i, j int) bool {
return s.students[i].name < s.students[j].name
}

type ByWeight struct {
students
}

func (s ByWeight) Less(i, j int) bool {
return s.students[i].weight < s.students[j].weight
}

var ss = []student{
{"d", 7, 28},
{"c", 3, 40},
{"b", 8, 35},
{"a", 5, 30},
}

func main() {
sort.Sort(ByWeight{ss})
fmt.Println("Organs by weight: ", ss)

sort.Sort(ByName{ss})
fmt.Println("Organs by name: ", ss)
}

go语言中预置了[]int []float64 []String三种数据类型的排序函数, 如果需要对其他数据类型进行排序, 需要自己实现sort interface

内置数据类型排序

[]int 类型排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
fmt.Println("------------ Int Slice ------------")

// 待排序的数据
is := []int{19, 26, 3, 78, 56}
fmt.Println("排序前的Int Slice", is)
fmt.Println("Int Slice是否已经按升序排序", sort.IntsAreSorted(is))

// 使用内置的排序函数进行排序
sort.Ints(is)

// 排序后
fmt.Println("排序后的Int Slice", is)
fmt.Println("Int Slice是否已经按升序排序", sort.IntsAreSorted(is))
sort.Sort(sort.Reverse(sort.IntSlice(is)))
fmt.Println("逆序排列(降序)后的Int Slice", is)
}

运行结果:

1
2
3
4
5
6
------------ Int Slice ------------
排序前的Int Slice [19 26 3 78 56]
Int Slice是否已经按升序排序 false
排序后的Int Slice [3 19 26 56 78]
Int Slice是否已经按升序排序 true
逆序排列(降序)后的Int Slice [78 56 26 19 3]

[]float64 类型排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
fmt.Println("------------ Float64 Slice ------------")

// 待排序数据
f64s := []float64{98.5, 53.2, 78.5, 12.78}
fmt.Println("排序前的Float64 Slice", f64s)
fmt.Println("Float64 Slice是否已经按升序排序", sort.Float64sAreSorted(f64s))

// 使用内置的排序函数进行排序
sort.Float64s(f64s)

// 排序后
fmt.Println("排序后的Float64 Slice", f64s)
fmt.Println("Float64 Slice是否已经按升序排序", sort.Float64sAreSorted(f64s))
sort.Sort(sort.Reverse(sort.Float64Slice(f64s)))
fmt.Println("逆序排列(降序)后的Float64 Slice", f64s)
}

运行结果:

1
2
3
4
5
6
------------ Float64 Slice ------------
排序前的Float64 Slice [98.5 53.2 78.5 12.78]
Float64 Slice是否已经按升序排序 false
排序后的Float64 Slice [12.78 53.2 78.5 98.5]
Float64 Slice是否已经按升序排序 true
逆序排列(降序)后的Float64 Slice [98.5 78.5 53.2 12.78]

[]string 类型排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
fmt.Println("------------ String Slice ------------")

// 待排序数据
ss := []string{"watermelon", "apple", "strawberry", "orange"}
fmt.Println("排序前的String Slice", ss)
fmt.Println("String Slice是否已经按升序排序", sort.StringsAreSorted(ss))

// 使用内置的排序函数进行排序
sort.Strings(ss)

// 排序后
fmt.Println("排序后的String Slice", ss)
fmt.Println("String Slice是否已经按升序排序", sort.StringsAreSorted(ss))
sort.Sort(sort.Reverse(sort.StringSlice(ss)))
fmt.Println("逆序排列(降序)后的String Slice", ss)
}

运行结果:

1
2
3
4
5
6
------------ String Slice ------------
排序前的String Slice [watermelon apple strawberry orange]
String Slice是否已经按升序排序 false
排序后的String Slice [apple orange strawberry watermelon]
String Slice是否已经按升序排序 true
逆序排列(降序)后的String Slice [watermelon strawberry orange apple]

其他数据类型排序

int8 int16 int32 int64 float32 虽然也是常用的数据类型, 但是这些数据类型需要自己实现排序接口

1
2
3
4
5
6
7
8
9
10
11
12
// A type, typically a collection, that satisfies sort.Interface can be
// sorted by the routines in this package. The methods require that the
// elements of the collection be enumerated by an integer index.
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less reports whether the element with
// index i should sort before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}

[]int32 类型排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type Int32Slice []int32

func (s Int32Slice) Len() int {
return len(s)
}

func (s Int32Slice) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}

func (s Int32Slice) Less(i, j int) bool {
return s[i] < s[j]
}

func main() {
i32s := Int32Slice{19, 26, 3, 78, 56}
sort.Sort(i32s)
fmt.Println(i32s)
}

运行结果:

1
[3 19 26 56 78]

[]float32 类型排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type Float32Slice []float32

func (s Float32Slice) Len() int {
return len(s)
}

func (s Float32Slice) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}

func (s Float32Slice) Less(i, j int) bool {
return s[i] < s[j]
}

func main() {
f32s := Float32Slice{98.5, 53.2, 78.5, 12.78}
sort.Sort(f32s)
fmt.Println(f32s)
}

运行结果:

1
[12.78 53.2 78.5 98.5]

中间件的作用

在我们熟悉的一个完整的http请求中, 客户端先向服务端发送的http request, 服务端接收到request之后, 根据实际需要, 给客户端返回http response

站在服务端的角度来看, 就是接收request, 响应response, 而中间件的作用, 就在requestresponse之间

  • 收到request之后, 在request还未到达处理的handler之前, 可以通过中间件做出一系列的数据处理, 可以看做是前置校验或过滤
  • request到达handler之后, 中间件还可以根据需要对响应体做统一的数据处理

gin内置的中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
func BasicAuth(accounts Accounts) HandlerFunc
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc
func Bind(val interface{}) HandlerFunc
func ErrorLogger() HandlerFunc
func ErrorLoggerT(typ ErrorType) HandlerFunc
func Logger() HandlerFunc
func LoggerWithConfig(conf LoggerConfig) HandlerFunc
func LoggerWithFormatter(f LogFormatter) HandlerFunc
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc
func Recovery() HandlerFunc
func RecoveryWithWriter(out io.Writer) HandlerFunc
func WrapF(f http.HandlerFunc) HandlerFunc
func WrapH(h http.Handler) HandlerFunc

中间件的作用域

1
2
r := gin.Default()  //默认带Logger(), Recovery()这两个内置中间件
r := gin.New() //不带任何中间件

全局作用域

1
2
r := gin.New()  //不带任何中间件
r.Use(gin.Recovery()) //在全局使用内置中间件

组作用域

1
2
3
4
5
6
r := gin.New()
vm := r.Group("vm", gin.Logger(),gin.Recovery())
{
vm.GET("cpu", func(context *gin.Context) {})
vm.GET("ram", func(context *gin.Context) {})
}

单个路由作用域

1
2
3
4
r := gin.New()
router.GET("/healthcheck",gin.Recovery(),gin.Logger(),func(c *gin.Context){
c.JSON(200,"ok")
})

自定义中间件

  • 第一种形式
1
2
3
4
//定义中间件
func middleware1(c *gin.Context){
//中间件逻辑
}
1
2
3
//引用中间件
r := gin.Default()
r.Use(middleware1)

注意: r.Use()中的中间件函数没有()

  • 第二种形式
1
2
3
4
5
6
func middleware2() gin.HandlerFunc {
//自定义逻辑
return func(c *gin.Context) {
//中间件逻辑
}
}
1
2
3
//引用中间件
r := gin.Default()
r.Use(middleware2())

数据传递

gin.Context中提供了Set``Get函数用来存储和提取数据

1
2
3
4
5
6
//中间件set数据
func middleware2() gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("name", c.Request.Header.Get("name"))
}
}
1
2
3
4
5
6
7
//handler中get数据
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"name": c.Get("name"),
"message": "ok",
})
})

Set方法存储的值为interface{}类型, Get方法中提供以下常用的数据类型方便提取数据

1
2
3
4
5
6
7
8
9
10
11
func (c *Context) GetBool(key string) (b bool)
func (c *Context) GetDuration(key string) (d time.Duration)
func (c *Context) GetFloat64(key string) (f64 float64)
func (c *Context) GetInt(key string) (i int)
func (c *Context) GetInt64(key string) (i64 int64)
func (c *Context) GetString(key string) (s string)
func (c *Context) GetStringMap(key string) (sm map[string]interface{})
func (c *Context) GetStringMapString(key string) (sms map[string]string)
func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string)
func (c *Context) GetStringSlice(key string) (ss []string)
func (c *Context) GetTime(key string) (t time.Time)

拦截request请求

  • request请求添加header
1
2
3
4
5
//中间件
//在请求到达`handler`之前, 为`request`请求添加`header`
func middleware1(c *gin.Context) {
c.Request.Header.Set("abc", "123")
}
1
2
3
4
5
6
7
8
//handler
r.GET("/", func(c *gin.Context) {
//获取请求头
fmt.Println(c.Request.Header.Get("abc"))
c.JSON(200, gin.H{
"message": "ok",
})
})
  • response响应添加header
1
2
3
4
5
6
func middleware2() gin.HandlerFunc {
return func(c *gin.Context) {
//在接收request请求时就设置好response时要添加的header
c.Header("xyz", "789")
}
}

追加handler处理后的动作

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
package main

import (
"github.com/gin-gonic/gin"
)

func middleware2() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
c.String(200, "handler ok\n")
}
}

func main() {
r := gin.Default()

r.Use(middleware2())

r.GET("/", func(c *gin.Context) {
c.String(200, "handler ok\n")
})

if err := r.Run(":8080"); err != nil {
panic(err)
}
}
1
2
3
curl http://127.0.0.1:8080/
handler ok
middleware ok

c.Next()

c.Next()函数允许我们在中间件中控制调度的逻辑, 每执行到c.Next()一次, 执行权就会别切换到其他中间件或handler函数中

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
package main

import (
"fmt"
"github.com/gin-gonic/gin"
)

func middleware1() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("start middleware1")
c.Next()
fmt.Println("end middleware1")
}
}

func middleware2() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("start middleware2")
c.Next()
fmt.Println("end middleware2")
}
}

func middleware3() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("start middleware3")
c.Next()
fmt.Println("end middleware3")
}
}

func main() {
r := gin.Default()

r.Use(middleware1(), middleware2(), middleware3())

r.GET("/", func(c *gin.Context) {
fmt.Println("start handler")
c.String(200, "ok\n")
fmt.Println("end handler")
})

if err := r.Run(":8080"); err != nil {
panic(err)
}
}

运行:

1
go run main.go

访问:

1
curl http://127.0.0.1:8080  # 返回结果: ok

程序日志:

1
2
3
4
5
6
7
8
start middleware1
start middleware2
start middleware3
start handler
end handler
end middleware3
end middleware2
end middleware1

依据注册中间件的顺序, 每次遇到c.Next()函数就跳转到下一个中间件执行, 直到跳转到最后一个中间件, 再次遇到c.Next()的时候, 就跳到handler主函数中运行, 当handler执行完毕后, 再逐层向上返回, 继续执行c.Next()之后的代码

c.Abord()

c.Abord()用来终止request, 阻止其到达handler 一般情况下用在鉴权与认证的中间件中

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
package main

import (
"fmt"
"github.com/gin-gonic/gin"
)

func middleware1() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("start middleware1")
c.Next()
fmt.Println("end middleware1")
}
}

//跟上面c.Next()的代码相比, 只是middleware2中的Next换成了Abort
func middleware2() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("start middleware2")
c.Abort()
fmt.Println("end middleware2")
}
}

func middleware3() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("start middleware3")
c.Next()
fmt.Println("end middleware3")
}
}

func main() {
r := gin.Default()

r.Use(middleware1(), middleware2(), middleware3())

r.GET("/", func(c *gin.Context) {
fmt.Println("start handler")
c.String(200, "ok\n")
fmt.Println("end handler")
})

if err := r.Run(":8080"); err != nil {
panic(err)
}
}

访问后, 程序日志:

1
2
3
4
start middleware1
start middleware2
end middleware2
end middleware1

可以看到即使在middleware2中执行了c.Abord(), c.Abord()后面的代码依然被完整的执行, 并且返回到了middleware1跳转的地方, 继续执行了后续代码

go语言中使用关键字const来定义常量. 最近在项目开发中, 在ovirt-go sdk中经常会用到其内置常量, 善用go的常量, 不仅可以增加代码的可读性, 还能省去一些”小麻烦”~

常量的定义

常量可以单个定义

1
2
const HttpStatusOk = 200
const HttpStatusNotFound = 404

也可以批量定义

1
2
3
4
5
const (
VMStatusDown = "down"
VMStatusUp = "up"
VMStatusImageLocked = "image_locked"
)

常量的优势

go在代码编译前, 变量的类型就已经确定, 并不可更改. 但是常量就比较特殊, 常量在代码编译的时候, 类型才会确定下来, 下面来举个例子🌰

  • 变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

var a int16

func main() {
var b int32

a = 1
b = 2

fmt.Println(a + b)
}

执行结果:

1
2
# command-line-arguments
./demo.go:19:16: invalid operation: a + b (mismatched types int16 and int32)

很显然, 代码在编译前, a,b 两个变量的数据类型已经确定, 两个不同的数据类型, 是不予许进行运算操作的.

  • 常量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

const a = 1
var aa = 1

func main() {
fmt.Printf("type: %v\n", reflect.TypeOf(aa))

var b float32
b = 2.1
fmt.Println(a + b)

var c int32
c = 3
fmt.Println(a + c)
}

运行结果:

1
2
3
type: int
3.1
4

如果其中一个数字是常量的情况, 运行结果就大不相同了, 可以看到, 该程序可以正常执行, 而且拿到了正确的结果. 这是因为, 虽然b变量在编译前确定了数据类型, 但是a常量并没有, a常量是在编译时才根据实际情况确定了数据类型, 常量会根据编译时的运算, 自动执行类型转换

上面的例子中, 声明了a常量, 同时声明了aa变量, 赋值均为1, 通过编译执行, 得到aa的数据类型自动判定为int, 而a常量却可以先和float32类型的b运算, 而后又和int32类型的c进行运算. 说明a常量的数据类型, 是在编译时, 根据实际的运算需要而变化的

所以, 项目中的一些数字可以多多定义为常量, 这样在运算的时候, 也可以减少显式的数据类型转换

枚举

go 语言中没有专门的枚举关键字, 是通过常量const关键字实现的, 枚举的标志性关键字为iota.

iota关键字在const关键字出现的时候, 被重置为0, 且const中每新增一行常量的声明, iota计数器将被+1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

const (
a = iota
b
c
d
)

func main() {
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
}

运行结果:

1
2
3
4
0
1
2
3

同行声明的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

const (
a, b = iota, iota
c, d = iota, iota
)

func main() {
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
}

运行结果:

1
2
3
4
0
0
1
1

注意: iota 仅会在新增的常量声明中才会自增, 声明在同一行的常量, iota的值是相同的

跨行声明的情况

  • 情况1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

const (
a = iota
b = 1024
c
d
)

func main() {
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
}

运行结果:

1
2
3
4
0
1024
1024
1024

const批量声明的特点, 如果后面的常量没有显式赋值, 则它们的值等于上一个显式赋值的值

  • 情况2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

const (
a = iota
b = 1024
c = iota
d
)

func main() {
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
}

运行结果:

1
2
3
4
0
1024
2
3

由于iota的特点, 每新增一行const的定义, 计数器就会自增1, 所以即使中间显式赋值了一些常量, 依然不会影响iota的自增

枚举的应用

自定义自增大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

const (
_ = iota // 0
a = iota * 2 // 1 * 2
b // 2 * 2
c // 3 * 2
d // 4 * 2
)

func main() {
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
}

运行结果:

1
2
3
4
2
4
6
8

数据单位运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

const (
b = 1 << (10 * iota) // 1 << (10 * 0)
kb
mb
gb
tb
pb
)

func main() {
fmt.Println(b)
fmt.Println(kb)
fmt.Println(mb)
fmt.Println(gb)
fmt.Println(tb)
fmt.Println(pb)
}

运行结果:

1
2
3
4
5
6
1
1024
1048576
1073741824
1099511627776
1125899906842624
  • b = 1 << (10 * iota)

第一个常量: iota被初始化为0, 则表达式为1 << (10 * 0) –> 1 << 0, 1 向左位移0位, 还是1

  • kb

第二个常量: 没有显式赋值, 该值应该等于上一行显式赋值的”值”, 由于上一行显式赋值的值为一个表达式, 所以将该表达式完整的继承下来. 且该行为新增常量声明, 所以iota变量自增1, 则赋值表达式实际为: 1 << (10 * 1) –> 1 << 10, 1 向左位移 10 位, 二进制表示为10000000000(2的10次方), 转换为10进制为1024, 所以 kb 经过表达式运算后, 值为: 1024

  • mb

第三个常量: 同理, iota 自增 1 后, 赋值表达式实际为: 1 << (10 * 2) —> 1 << 20, 1 向左位移 20 位, 二进制表示为100000000000000000000(2的20次方), 转换为10进制为1048576

  • 以此类推