Go 注意坑

2020/02/23 Go 知识点

http请求后需关闭句柄

大量请求没有关闭,会造成go的内存泄露。这也是平时编码习惯没有养成,需谨记。

务必请求后释放资源:

response.Body.Close()

解析请求参数中带”;“,解析出错

http://tj-adc.wtzw.com/click?source=wolong12;123344&project=reader_free&callback=__CALLBACK_URL__&channel=qi-guanfang_hc&ua=Dalvik%5C/2.1.0%20%28Linux;%20U;%20Android%209;%20V1813BA%20Build%5C/PKQ1.181030.001%29

解析请求的时候发现ua参数“;”开始之后的字符串都被过滤了。

查看 net/url包的源码,

...
key := query
if i := strings.IndexAny(key, "&;"); i >= 0 {
	key, query = key[:i], key[i+1:]
} else {
	query = ""
}
...

发现go在解析参数的时候按照&和;分割。

解决:

    // 对;进行url编码,再次解析
    rawQuery := ctr.Ctx.Request.URL.RawQuery
	if strings.Contains(rawQuery, ";") {
		rawQuery = strings.Replace(rawQuery, ";", "%3B", -1)
		paramsRaw, errs := url.ParseQuery(rawQuery)
		if errs == nil {
			for k, v := range paramsRaw {
				params.Set(k, v[0])
			}
		}
	}

最有效的解决方案是url编码规范化。

时区

t, err := time.Parse("2006-01-02 15:04:05", time.Now().Format("2006-01-02 15:04:05"))
fmt.Println(t)

结果:

// 假设当前时间 2017-12-03 12:00:00 +0000 UTC
2020-03-09 20:00:00 +0000 UTC

发现时间多了8个小时

在windows下,time.Parse()的时区和time.Format()的时区是一致的。

但是在linux环境下,time.Parse()的默认时区是UTC,time.Format()的时区默认是本地,两者如果不处理好就会导致错误

解决:

使用time.ParseInLocation()而不是time.Parse():

t, _ := time.ParseInLocation("2006-01-02 15:04:05", time.Now().Format("2006-01-02 15:04:05"), time.Local)

map顺序遍历

map遍历的顺序跟添加的顺序无关,随机输出。如果逻辑需要顺序输出,需要注意这个点,避免发生错误。

描述个场景,签名的生成是根据map添加的顺序。

    m := make(map[string]string)
	m["a"] = "1"
	m["b"] = "2"
	m["c"] = "3"
	m["d"] = "4"
	m["e"] = "5"

	for k,v := range m{
		fmt.Println(k,v)
	}

	keys := []string{"a","b","c","d","e"}
	signStr := ""
	salt := "qimao"
	for _,k := range keys {
		signStr += m[k] + "_"
	}
	sign := md5.Sum([]byte(signStr + salt))
	fmt.Println(fmt.Sprintf("%x",sign))

另外对map中key排序,也参照使用切片排序遍历。

gorutine使用注意

        ...
        page := 1
		size := 1000
		for {
		    // 查询1000数量数据
			list := user.CheckList(project, date, size, page)
			if len(list) == 0 {
				break
			}
			group := sync.WaitGroup{}
			
			// 活跃用户遍历
			for _, uid := range list {
				group.Add(1)

                // 起1000个gorutine,用户信息推到mq队列
				go func(uid string) {
				    ...
						err1 := mq.Push(jsons)
						utils.CheckError(err1, false)
					...
					group.Done()
				}(uid)
			}
			group.Wait()

			page++

执行的时候,mq会出现超时等待,重试。原因是mq的连接数不够用,旧的连接没有被释放,一直等待重试。

改良下代码,使用chan阻塞执行(类似令牌桶):

        ...
        page := 1
		size := 50000
		for {
		    // 查询50000数量活跃用户
			list := user.CheckList(project, date, size, page)
			if len(list) == 0 {
				break
			}
			group := sync.WaitGroup{}
			buckets := make(chan bool, 500)  // 桶的容量是500
			
			for _, uid := range list {
				buckets <- true              // 桶中入“令牌”,如果桶塞满,阻塞等待
				group.Add(1)

				go func(uid string) {
				    ...
						err1 := mq.Push(jsons)
						utils.CheckError(err1, false)
					...
					<-buckets              // 业务处理,桶中取出令牌     

					group.Done()
				}(uid)
			}
			group.Wait()

			page++
        }

【xorm】 fatal error: sync: unlock of unlocked mutex

报错信息:

fatal error: sync: unlock of unlocked mutex

goroutine 29697 [running]:
runtime.throw(0x16ffb3c, 0x1e)
	/root/.gvm/gos/go1.15/src/runtime/panic.go:1116 +0x72 fp=0xc0005ab4a0 sp=0xc0005ab470 pc=0x437892
sync.throw(0x16ffb3c, 0x1e)
	/root/.gvm/gos/go1.15/src/runtime/panic.go:1102 +0x35 fp=0xc0005ab4c0 sp=0xc0005ab4a0 pc=0x467cb5
sync.(*Mutex).unlockSlow(0xc0005763d0, 0xc0ffffffff)
	/root/.gvm/gos/go1.15/src/sync/mutex.go:196 +0xd8 fp=0xc0005ab4e8 sp=0xc0005ab4c0 pc=0x489a38
sync.(*Mutex).Unlock(...)
	/root/.gvm/gos/go1.15/src/sync/mutex.go:190
sync.(*Map).Store(0xc0005763d0, 0x168fdc0, 0x15f4a80, 0x1622060, 0xc00bbdfb30)
	/root/.gvm/gos/go1.15/src/sync/map.go:162 +0x2a5 fp=0xc0005ab590 sp=0xc0005ab4e8 pc=0x487e45
xorm.io/xorm/tags.(*Parser).ParseWithCache(0xc000576380, 0x15f4a80, 0xc00e5a2200, 0x199, 0x199, 0x20, 0x14aca20)
	/go/pkg/mod/xorm.io/xorm@v1.0.7/tags/parser.go:78 +0x150 fp=0xc0005ab5f8 sp=0xc0005ab590 pc=0x72a790
xorm.io/xorm/internal/statements.(*Statement).SetRefBean(0xc00e5a8f00, 0x1445a60, 0xc00e5a2200, 0x15f4a80, 0xc00e5a2200)
	/go/pkg/mod/xorm.io/xorm@v1.0.7/internal/statements/statement.go:290 +0x6c fp=0xc0005ab668 sp=0xc0005ab5f8 pc=0x751bec
xorm.io/xorm.(*Session).get(0xc00e59eb60, 0x1445a60, 0xc00e5a2200, 0xc0005ab800, 0x0, 0x0)
	/go/pkg/mod/xorm.io/xorm@v1.0.7/session_get.go:43 +0xd98 fp=0xc0005ab7e0 sp=0xc0005ab668 pc=0x78c878
xorm.io/xorm.(*Session).Get(0xc00e59eb60, 0x1445a60, 0xc00e5a2200, 0xc00e5b2100, 0x0, 0x0)
	/go/pkg/mod/xorm.io/xorm@v1.0.7/session_get.go:25 +0x7f fp=0xc0005ab838 sp=0xc0005ab7e0 pc=0x78ba5f
stat/pkg/repository/dmp.(*newDuration).GetByChannel(0xc00e5b2140, 0xc009627d20, 0xa, 0xc004847ad0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
	/root/workspace/market_stat_iBZB/pkg/repository/dmp/new_duration.go:72 +0x24d fp=0xc0005ab8a8 sp=0xc0005ab838 pc=0xf92d4d

背景:常驻任务,执行table update操作;

解决:翻查代码,发现获取表实例非单列,每次执行都会重新获取实例,应该并发执行发生竞争,mutex锁我们知道解锁一个已经释放的锁会发生panic。表实例获取改为单列问题解决,没有发生panic。

kafka阻塞问题

消费某个分区发生阻塞不执行,也没报错;重启可以修复,但是还是会偶发;

业务背景:消费者获取消息,业务处理逻辑之后,推入另一个kafka topic,供下游处理。

问题定位在kafka发布消息出现错误,错误的chan是一个无缓冲通道,所以发生阻塞。解决:

  var err error
	con := sarama.NewConfig()
	con.Version = sarama.V2_2_0_0
	con.Producer.Partitioner = sarama.NewRandomPartitioner
	con.Producer.Compression = sarama.CompressionGZIP
	emrProducer, emrTopics, err = kafka.NewProducer("emr", con)
	if err != nil {
		panic(err)
	}
// 错误channel消费
	go func() {
		for {
			e := <-emrProducer.Errors()
			check.Error(e)
		}
	}()

Search

    Table of Contents