75 提供错误的time duration #
没有单位写法会带来误导.
ticker := time.NewTicker(1000)
附带单位
ticker := time.NewTicker(1000 * time.Nanosecond)
76 time.After内存泄漏 #
func f1(ch <-chan int) {
for {
select {
case event := <-ch:
fmt.Println(event)
case <-time.After(time.Hour):
log.Println("warning: no messages received")
}
}
}
每次走select分支都是新创建time.After
,time.After
里面包裹着chan且要等待1小时候释放.
修复问题
func f3(ch <-chan int) {
for {
ctx, cancel := context.WithTimeout(context.Background(), time.Hour)
select {
case event := <-ch:
cancel() // chan得到释放
fmt.Println(event)
case <-ctx.Done():
log.Println("warning: no messages received")
}
}
}
或者最佳实践(因为变量复用)
func f4(ch <-chan int) {
timeD := time.Hour
timer := time.NewTimer(timeD)
for {
timer.Reset(timeD)
select {
case event := <-ch:
fmt.Println(event)
case <-timer.C:
log.Println("warning: no messages received")
}
}
}
77 json处理 #
类型内嵌导致的意外
func json1() {
event := Event{ID: 1234, Time: time.Now()}
b, err := json.Marshal(event)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(b))
}
type Event struct {
ID int
time.Time
}
"2023-10-27T18:54:09.695182+08:00"
上文ID: 1234
消失了?
因为
time.Time
也类似的实现了MarshalJSON() ([]byte, error)
这个接口.
影响了json.Marshal
的行为
修复方法
都导出
type Event struct {
ID int
Time time.Time
}
或者修改实现
func (e Event) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
ID int `json:"id"`
Time time.Time
}{
ID: e.ID,
Time: e.Time,
})
}
时钟变化
time.Time包含wall
和monotonic
2种类型.
其中wall
用于一天中确定时间. 比如network time protocol(NTP)中的时间.
测量持续时间时用的是monotonic
json例子:
type Event3 struct {
Time time.Time
}
func run1() {
t := time.Now()
event1 := Event3{Time: t}
b, err := json.Marshal(event1)
if err != nil {
fmt.Println(err)
return
}
var event2 Event3
err = json.Unmarshal(b, &event2)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(event1 == event2)
fmt.Println(event1)
fmt.Println(event2)
}
false
{2023-10-27 19:26:59.523668 +0800 CST m=+0.000127335}
{2023-10-27 19:26:59.523668 +0800 CST}
其中2023-10-27 19:26:59.523668 +0800 CST m=+0.000127335
- wall指的是
2023-10-27 19:26:59.523668 +0800 CST
- monotonic指的是
m=+0.000127335
时间通过json的encode decode后不相等,可用fmt.Println(event1.Time.Equal(event2.Time))
判断
可以设置t.Truncate(0)
剥离monotonic
t := time.Now()
event1 := Event3{
Time: t.Truncate(0),
}
文中还点到了time.Now().In(location)
切换时区.
any时数值变成了float64根据JSON规范,数值类型默认解码为float64
func f5() {
var i int = 12 // int
d, err := json.Marshal(i)
if err != nil {
fmt.Println(err)
}
var m any // any
err = json.Unmarshal(d, &m)
if err != nil {
fmt.Println(err)
}
fmt.Printf("%T\n", m)
}
float64
78 SQL #
sql.Open("mysql", dsn)
只验证参数,不创建连接
准备就绪后可用db.Ping()
真正建立连接
连接池功能
- SetMaxOpenConns
- SetMaxIdleConns
- SetConnMaxIdleTime
- SetConnMaxLifetime,一般小于数据库的wait_timeout
SQL预编译
- 提高性能,避免重复编译
- 参数化SQL值安全性高
- 方便语句重用
stmt, err := db.Prepare("SELECT * FROM ORDER WHERE ID = ?")
记住还有context传播函数,PrepareContext
和 QueryContext
sql.NullString
主要用来处理数据库表字段可能为空(NULL)的字符串(string)类型的数据.
类似处理null字段还有sql.NullBool
等等
rows遍历存在错误处理
rows, err := db.QueryContext(ctx, "select id, name from users")
if err != nil {
// handle error
return
}
defer rows.Close()
for rows.Next() {
var id int
var name string
err := rows.Scan(&id, &name)
if err != nil {
// handle scan error
return
}
// process row data
}
err = rows.Err()
if err != nil {
// The query failed
return
}
// No error, all good
执行查询错误
rows, err := db.QueryContext
关闭rows错误
err := rows.Close()
扫描rows错误
err := rows.Scan(&department, &age)
是为了判断结果集迭代是否正常完成,还是因错误而提前终止的
rows.Err()
79 忘记关闭资源 #
HTTP客户端
避免资源泄漏
resp, err := h.client.Post
if err != nil {
return
}
defer func(){
// HTTP客户端请求后的body记得关闭.无论是否读取body
err=resp.Body.Close()
}
服务端则会自动执行,只是客户端需要关闭
关于无论如何客户端body都要执行关闭原因
- 如果不读取body内容,直接关闭body,默认HTTP客户端可能会主动关闭连接
- 如果读取了body的部分或全部内容后再关闭,默认HTTP客户端不会主动关闭连接
所以执行resp.Body.Close()
是必要的保证
使用keep-alive
时,即使不用body还是需要执行读取.
_, _ = io.Copy(io.Discard, resp.Body)
具体原因 https://go.dev/src/net/http/response.go#L63
The default HTTP client’s Transport may not
// reuse HTTP/1.x “keep-alive” TCP connections if the Body is
// not read to completion and closed.
为了复用 HTTP/1.x “keep-alive"的TCP链接,如果body没有读取完整和关闭的情况下是无法做到的.
sql.Rows
rows, err := db.Query
if err!=nil{
return
}
rows.Close() //不使用则会连接泄漏
os.File
f, err := os.OpenFile
if err!=nil{
return
}
f.Close() //记得
文件写入时f.Sync()
可安全忽略f.Close()
错误.
80 reply HTTP request后忘记return #
func Handler(w http.ResponseWriter, r *http.Request) {
// 处理请求逻辑
// 返回响应
w.WriteHeader(200)
w.Write([]byte("Hello World"))
return //记得return 防止后面还有逻辑
}
若迟迟不return 发生覆盖现象,出现重复的header头之类.
81 使用默认的HTTP端 #
客户端和服务端都有config配置,多个timeout参数,需要一一浏览.
客户端一般流程:
- dial
- TLS 握手
- 请求
- 读取header
- 读取body
客户端timeout配置可以控制以上流程节点超时设置.
默认启用连接池,若主动禁止则http.Transport.DisableKeepAlives
连接空闲超时http.Transport.IdleConnTimeout
http.Transport.MaxIdleConns
最大空闲数
http.Transport.MaxIdleConnsPerHost
主机最大空闲连接数
服务端一般流程:
- 等待客户端请求
- TLS握手
- 读取请求header
- 读取body
- 响应
服务端timeout配置可以控制以上流程节点超时设置.