GO·NOTE

一份 Go 开发工程师的学习笔记

0%

review的项目代码

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

import (
"fmt"
"github.com/go-xorm/xorm"
_ "github.com/lib/pq"
"log"
"xorm.io/core"
)

type People struct {
Id int `xorm:"not null pk autoincr"`
Age int `xorm:"unique"` // 增加 unique
Name string
}

var engine *xorm.Engine

func init() {
host := "localhost"
port := 5432
user := "postgres"
password := "root"
dbName := "test"
psqlInfo := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbName)
var err error
engine, err = xorm.NewEngine("postgres", psqlInfo)
if err != nil {
log.Fatal(err)
}
engine.SetTableMapper(core.SameMapper{})
err = engine.Sync2(new(People)) // 在表存在的情况下,结构增加 unique 后,sync2() 时会自动给表建立索引。
if err != nil {
log.Fatal(err)
}
engine.SetMaxIdleConns(100)
engine.SetMaxIdleConns(100)
engine.ShowSQL(false)
}

main.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
61
62
63
64
package main

import (
"fmt"
"github.com/go-xorm/xorm"
"github.com/lib/pq"
"log"
"sync"
)

var _pgUniqueViolationErrorCode = "23505"

func main() {
engine.Delete(new(People))
wg := &sync.WaitGroup{}
for i := 0; i <= 100; i++ {
wg.Add(1)
go TestTransaction(i, wg)
}
wg.Wait()
}

func TestTransaction(i int, wg *sync.WaitGroup) {
defer wg.Done()

engine.Transaction(func(session *xorm.Session) (interface{}, error) {
p := &People{}
// 对本条记录增加读写锁,其他协程不可读写,
session = session.ForUpdate()
READ:
exist, err := session.Where("name = ?", "xmge").Get(p)
fmt.Println(i, p)
if err != nil {
log.Fatal(err)
}
if !exist {
//如果本条记录不存在,则没有枷锁,会出现同时创建的情况,因此在这里需要增加唯一索引来控制
p := People{Age: 27, Name: "xmge"}
_, err := session.InsertOne(&p)
if err != nil {
pgErr := err.(*pq.Error)
if string(pgErr.Code) == _pgUniqueViolationErrorCode {
// 使用 goto 一定要谨慎,否则容易造成死循环
// 考虑一下是否可以接受错误的返回,提示服务器异常,让用户再点一下
goto READ
}
log.Fatal(err)
}
fmt.Printf("%d:,name 为 xmge 的记录不存在,进行创建\n", i)
return nil, nil
} else {
fmt.Printf("%d,查询时已存在\n", i)
p.Age++
if p.Age > 50 {
return nil, nil
}
_, err := session.Cols("age").Update(p)
if err != nil {
log.Fatal(err)
}
}
return nil, err
})
}

xorm-联合索引

挂载是 “/‘ 和 ”/home“ 不是重复了吗?

inux下面所有的文件、目录、设备都有一个路径,这个路径永远以/开头,用/分隔,如果一个路径是另一个路径的前缀,则这两个路径有逻辑上的父子关系。但是并不是所有逻辑上的父子关系都必须要是同一个设备,决定不同路径对应到哪个设备的机制就叫做mount(挂载)。

通过mount,可以设置当前的路径与设备的对应关系。每个设备会设置一个挂载点,挂载点是一个空目录。一般来说必须有一个设备挂载在/这个根路径下面,叫做rootfs。其他挂载点可以是/tmp,/boot,/dev等等,通过在rootfs上面创建一个空目录然后用mount命令就可以将设备挂载到这个目录上。挂载之后,这个目录下的子路径,就会映射到被挂载的设备里面。当访问一个路径时,会选择一个能最大匹配当前路径前缀的挂载点。比如说,有/var的挂载点,也有/var/run的挂载点的情况下,访问/var/run/test.pid,就会匹配到/var/run挂载点设备下面的/test.pid。同一个设备可以有多个挂载点,同一个挂载点同时只能加载一个设备。访问非挂载点的路径的时候,按照前面所说,其实是访问最接近的一个挂载点,如果没有其他挂载点那么就是rootfs上的目录或者文件了。

实际上并不只有linux支持挂载点,Windows也是一样支持的。去控制面板/管理工具/计算机管理 里面,挑一个磁盘(比如D盘),然后给它分一个新的挂载点试试,比如C:\data

转载:https://www.zhihu.com/question/266907637/answer/315386532

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

import (
"encoding/json"
"fmt"
"time"
)

type Student struct {
Id int64 `json:"id,string"`
Name string `json:"name"`
CreateTime MyTime `json:"create_time"`
UpdateTime time.Time `json:"update_time"`
}
func main() {
s1 := Student{Id:1,Name: "xmge"}
b,err := json.Marshal(s1)
fmt.Println(string(b),err)

s2 := new(Student)
err = json.Unmarshal(b,s2)
fmt.Println(s2,err)

b2,err := json.Marshal(s2)
fmt.Println(string(b2),err)
}

type MyTime time.Time

func (mt MyTime)MarshalJSON() ([]byte,error) {
t := time.Time(mt)
if t.IsZero() {
return []byte("null"),nil
}
return t.MarshalJSON()
}

通过阅读源码/注释有什么用呢?

  1. 可以让你发现你使用的工具包有哪些你不知道的功能,
  2. 可以编写更高性能更优雅的代码,
  3. 可以排除一些疑难杂症。

例子:如果把 int 类型在 json.Marshal() 时转为string。

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

import (
"encoding/json"
"fmt"
"time"
)

type Student struct {
Id int64 `json:"id,string"`
Name string `json:"name"`
CreateTime time.Time `json:"create_time,string"`
}

// The "string" option signals that a field is stored as JSON inside a
// JSON-encoded string. It applies only to fields of string, floating point,
// integer, or boolean types. This extra level of encoding is sometimes used
// when communicating with JavaScript programs:
//
// Int64String int64 `json:",string"`
func main() {
s := Student{Id:1,Name: "xmge" }
b,err := json.Marshal(s)
fmt.Println(string(b),err)
}

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

import (
"fmt"
)

func main() {
a:= If(5>3,1,2) // go vet can't pass
fmt.Println(a)
}


func If(condition bool, trueVal, falseVal interface{})interface{} {
if condition {
return trueVal
}
return falseVal
}

一、发现问题

首先通过监控工具查看到某个项目的机器内存在部署之后总是不断上涨,但是用户量并不多,很明显是内存泄漏的问题。

监控

二、如何解决

项目中引入了以下代码,自然可以通过 pprof 工具进行分析。

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

import (
"net/http"
_"net/http/pprof"
)

func init() {
go func() {
http.ListenAndServe(":8000",nil)
}()
}

三、解决步骤

解决问题尝试了很多种方案,最后方案如下:

1、通过 pprof 工具获取内存相差较大的两个时间点 heap 数据。

curl localhost:8000/debug/pprof/heap > heap.base

等待一段时间,通过 htop 可以查看到内存又涨了很多,然后再采集内存情况

curl localhost:8000/debug/pprof/heap > heap.current

2、通过 go tool pprof 工具比较两个内存的情况,找到是什么对象多创建了

go tool pprof -http=:8080 -base heap.base heap.current

选择当前分配的对象(insue_objects):

option

得到如图所示:

pprof-heap-base

图中可以看出 withdraw_record.GetByUserId.FindAndCount() 在这段时间创建了 624110 个对象,于是怀疑是这里出了问题,于是去查看代码:

1
count, err = statement.Where("user_id = ? ", userId).FindAndCount(&records)

发现 statement 这个数据库 session 没有关闭,怀疑是因为没有关闭造成的问题

3、测试服问题复现

既然怀疑是这里的问题,然后就写了个 for 循环,不断地请求嫌疑接口,通过htop 发现,内存果然蹭蹭蹭网上涨,问题复现成功

4、将嫌疑代码修复了

修复就很简单了,加了一个 defer statement.Close()

5、部署修复后的代码到测试服务器验证

代码修复后,部署到测试服上,再用 for 循环去测,发现内存不再上涨,到此应该算是问题解决

6、查找项目中有没有类似代码并加以改正

四、注意点

1、不要在同一台机器一边跑项目,一边压测,否则两个程序都跑不满。

想 ssh 远程连接服务器,并批量执行命令,结果发现 echo $(pwd) 在本地执行,echo "bbb" >> b.txt 却在服务器端执行

通过给第一个 remotessh 加双引号即可解决。

img

  1. 少用标志位,多用方法。能不加变量就不要加变量,最好不用变量。
  2. 尽量少用 init(),什么时候使用? 当你的其他程序运行时与包含 init 函数无关,只有在使用它时自动 init 即可

  1. 通过设置get/set方法,可以保证内部变量的安全性,而且还可以在get中设置默认值。否则在程序中,如果 configVersion 不存在,程序会进行不下去。
  2. _sharedConfig 是 uber 的非导出常量的规范。
    img

1
2
3
// Make sure a Hash implements the hash.Hash and hash.Hash64 interfaces.
var _ hash.Hash = &Hash{}
var _ hash.Hash64 = &Hash{}