持续监控文件变化时使用filepath遇坑,填坑记....持续更新

huhuyou2 发表了文章 • 1 个评论 • 91 次浏览 • 20 小时前 • 来自相关话题

最近在做个处理日志的功能,首先就是要把日志文件的变化扫描出来,之前看过相关实现方法,有如下两种主流的方式:

//第一种
Lis... 			查看全部
					

最近在做个处理日志的功能,首先就是要把日志文件的变化扫描出来,之前看过相关实现方法,有如下两种主流的方式:


//第一种
Listfunc(path string, f os.FileInfo, err error){}
filepath.Walk(path, Listfunc){}

//第二种
files,_ := ioutil.ReadDir(path)
for file := range files{
if file.IsDir(){

}else{

}
}

我在实际开发中面对的是不断增加的多个文件下的多份日志文件,唯一的判定标识是日志文件后缀,整个日志空间不到800M左右会远程转储备份。


----x1
-x1_标识.log (最大10M,10M后转储成带时间戳的日志文件)
-x1_标识_2018-1-18.log
----x2
-x2_标识.log

这样的场景下,我选择了loop filepath.Walk的方案,在loop频率设置3分钟的情况下稳定跑了大概2周了。可是最近几天业务量大了,为了更快的得到日志变化的内容,把loop频率设置成了3s。在跑了1个晚上后出现了panic。


于是,我使用pprof查看了3s场景下半天内的heap数据,发现heap持续增长。使用pprof -alloc_space查看了下内存分配情况,显示如下:


ROUTINE ======================== os.(*File).readdirnames in C:/Go/src/os/dir_unix.go
4.24GB 4.60GB (flat, cum) 56.78% of Total
. . 43:}
. . 44:
. . 45:func (f *File) readdirnames(n int) (names []string, err error) {
. . 46: // If this file has no dirinfo, create one.
. . 47: if f.dirinfo == nil {
25MB 25MB 48: f.dirinfo = new(dirInfo)
. . 49: // The buffer must be at least a block long.
2.91GB 2.91GB 50: f.dirinfo.buf = make([]byte, blockSize)
. . 51: }
. . 52: d := f.dirinfo
. . 53:
. . 54: size := n
. . 55: if size <= 0 {
. . 56: size = 100
. . 57: n = -1
. . 58: }
. . 59:
1.31GB 1.31GB 60: names = make([]string, 0, size) // Empty with room to grow.
. . 61: for n != 0 {
. . 62: // Refill the buffer if necessary

看来这种遍历文件路径的方式在资源消耗上存在很大问题。


突然想起来谢大Beego配套的Bee工具貌似也有文件变化感知的功能,准备尝试其中的fsnotify来解决我的问题,明天有会议,计划周六动手试试。有结果了继续更新。

发现一个高逼格的golang“构造函数”写法

hank 回复了问题 • 21 人关注 • 10 个回复 • 1514 次浏览 • 2018-01-08 16:14 • 来自相关话题

recover 救不了所有的 panic

Xargin 发表了文章 • 9 个评论 • 513 次浏览 • 2017-12-31 02:05 • 来自相关话题

曾经天真地认为只要严格遵守有 go 必有 recover 就能保证程序永不宕机。直到有人对开源库有了类似下面这样的误用:

package main

func mai... 			查看全部
					

曾经天真地认为只要严格遵守有 go 必有 recover 就能保证程序永不宕机。直到有人对开源库有了类似下面这样的误用:


package main

func main() {
var a = map[int]int{}
defer func() {
if err := recover(); err != nil {
println("oh no")
}
}()

go func() {
defer func() {
if err := recover(); err != nil {
println("oh no")
}
}()
for {
a[1] = 1
}
}()

for {
a[1] = 1
}
}

当然了,实际的场景不会有这么弱智,这样写主要是为了方便复现。


go 在 1.6 增加了 map 并发读写时,直接让程序崩溃的新 feature,崩溃是用 panic(这里cholerae 订正,内部使用 throw 而非 panic 来崩溃),然而这种 panic(fix:throw) 通过 recover 是捕获不到的。因为官方认为这是一种不可恢复的错误。程序继续运行结果不可知了吧。


具体参考这里


在 go 1.9 官方引入了 sync.Map,然而这个新玩具就只是为了解决官方自己在 grpc 中遇到的性能瓶颈,即由于读写锁的 cpu cache contention 导致的性能下降问题。而小版本升级中也没有为这个 sync.Map 设计特权语法,store 和 load 接口均需要操作 interface 类型,实际上虽然性能好了一点,但如果你真的要用,那结果就是满天飞的 interface。


扯远了。说回 panic。


你可能以为在 go 里,map 的并发读写是唯一一种捕获不到的 panic,那么我们再来看一个 demo。


package main

import "reflect"

func main() {
defer func() {
if err := recover(); err != nil {
println("panic")
}
}()
fv := reflect.ValueOf(func(int) struct{} { return struct{}{} })
args := []reflect.Value{reflect.ValueOf(0)}

for {
fv.Call(args)
}
}

你手边有任意的环境都可以尝试一下,实际上这段代码会触发 golang 自己的 bug(汗。。


直到 1.9.2,这个 bug 才修复。触发条件就像 demo 里一样,高并发下调用 reflect.Call,一调一个准。且也无法用 recover 来恢复。


具体参考这里:
https://github.com/golang/go/issues/21717


结论:go 服务不用 supervisor 又不加监控就等着被开除吧




滴滴招聘 golang 工程师,欢迎发简历到:


caochunhui@didichuxing.com


年前面试年后入职也不错对不对。

golang echo 代码详解之模版篇

Laily 发表了文章 • 0 个评论 • 256 次浏览 • 2017-12-12 23:54 • 来自相关话题

在 echo 里使用模版则必须先注册一个,如果不注册就会报出下面这样的错误

{"time":"2017-12-12T23:... 			查看全部
					

在 echo 里使用模版则必须先注册一个,如果不注册就会报出下面这样的错误


{"time":"2017-12-12T23:03:57.939138716+08:00","level":"ERROR","prefix":"echo","file":"echo.go","line":"284","message":"Renderer not registered"}

注册就是给 echo.Renderer 赋值。


echo 的 Renderer 属性是一个接口


Renderer interface {
Render(io.Writer, string, interface{}, Context) error
}

一、使用标准库模版


echo 的文档给出了使用官方模版注册的方式


// 实现 Renderer 接口
type Template struct {
templates *template.Template
}

func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
}
// 处理 view 目录下的文件生成对应的模版
// 关于 ExecuteTemplate 和 ParseGlob 方法可以查看
// https://wizardforcel.gitbooks. ... plate
t := &Template{
templates: template.Must(template.ParseGlob("public/views/*.html")),
}
// 赋值
e.Renderer = t

二、使用 pongo2


gopkg.in/flosch/pongo2.v3 是一个很不错的模版引擎,很多时候会选择它来渲染模版。


首先还是实现 Renderer 接口


type Template struct {
// 这里使用一个 map 来存储预处理了的模版
tmplMap map[string]*pongo2.Template
}

func (t *Template) Render(w io.Writer, templateName string, data interface{}, c echo.Context) error {
// 这里根据传来的 name 从 tmplMap 里查找模版来渲染
// 注意 ExcuteWriter 的参数必须是 map[string]interface{} 的
dataMap := data.(map[string]interface{})
template, exist := t.tmplMap[templateName]
if !exist {
return errors.New("template " + templateName + " not found")
}
return template.ExecuteWriter(dataMap, w)
}

然后预编译模版


// 读取目录下的文件预处理
func preCompile(dir string) *Template {
tmplMap := make(map[string]*pongo2.Template)

dirPath := filepath.Dir(dir)
fileInfos, _ := ioutil.ReadDir(dirPath)

for _, fileInfo := range fileInfos {
t, err := pongo2.FromFile(path.Join(dir, fileInfo.Name()))
if err != nil {
log.Fatalf("\"%s\": %v", fileInfo.Name(), err)
}
tmplMap[strings.Replace(fileInfo.Name(), path.Ext(fileInfo.Name()), "", -1)] = t

}

return &Template{tmplMap}
}

最后赋值


func NewTemplates(dir string) *Template {
return preCompile(dir)
}

t := NewTemplates("./views/")
e.Renderer = t

这样自定义的 renderer 就算完成了。


原文地址:laily.net

跪求一份没有错误的jaeger-client-go包

byssh1989 回复了问题 • 2 人关注 • 1 个回复 • 218 次浏览 • 2017-12-11 09:36 • 来自相关话题

大家说说看都用啥写Go

MarkYang 回复了问题 • 68 人关注 • 84 个回复 • 6196 次浏览 • 2017-11-30 16:33 • 来自相关话题

GOLANG探测HTTP连接断开

winlin 发表了文章 • 1 个评论 • 445 次浏览 • 2017-11-22 12:22 • 来自相关话题

考虑基于HTTP的RPC,或者HTTP服务器主动通知客户端的机制,就是HTTP Long-Polling,意思... 查看全部

考虑基于HTTP的RPC,或者HTTP服务器主动通知客户端的机制,就是HTTP Long-Polling,意思就是客户端发起一个长连接,服务器阻塞忍住不响应直到:



  1. 超时,比如5秒后,我们给客户端响应一个keepalive,意思是现在还没有啥事,请继续polling。

  2. 拿到结果,这个可能是任何时候,比如300毫秒、1100毫秒、2300毫秒拿到一个事件,响应给客户端,实现了有事件异步通知。


这样客户端和服务器之间RPC的效率就非常高,只有在有事件时才会通知。但是,实际上还有一种情况需要处理:



  1. 当客户端断开连接,比如客户端设置了3秒钟TCP请求超时,或者因为客户端Crash时OS回收了FD等等,这个时候服务器应该要终止polling事务,停止获取事件。因为如果这个时候获取了事件,那么如何处理这个事件?只能丢弃,如果客户端再次发起请求,就拿不到这个事件了。


问题就来了,如何在HTTP Handler中探测客户端断开?例如:


var incoming chan []byte
http.HandleFunc("/polling", func(w http.ResponseWriter, r *http.Request) {
select {
case b := <- incoming:
w.Write(b)
case <-time.After(5 * time.Second):
w.Write("keepalive")
// how to detect TCP disconnect event?
}
})

可能有以下方式:



  1. 读取r.Body,如果发现断开应该会有错误。

  2. 有朋友用reflect或hijack取到底层的TCPConn,然后Peek。

  3. w转换成http.CloseNotifier,在TCP连接关闭时拿到事件。


r.Body Read


这种方式是不靠谱的,假设没有Body内容,直接读取检测是否有error:


nn,err := io.Copy(ioutil.Discard, r.Body)

实际上返回的是nn=0err=nil,也就是没有Body,没有错误。因为这个读取的含义是指Request结束。


如果读取完Body后再读呢?收到的是io.EOF,在没有发送Response之前,Request已经结束了,所以就是io.EOF,并不能检测到底层TCP断开。


Peek TcpConn


使用reflect获取底层的TCPConn对象,是知道w http.ResponseWriter实际上是http.response


// A response represents the server side of an HTTP response.
type response struct {
conn *conn

它有个Field就是conn,再转成TCPConn就可以Peek。


这样做的风险就是,不同的GOLANG版本,可能会对底层实现进行变更,在升级时会有风险。


Reflect方式始终不是最好的。


另外,还有一种方式,就是用http hijack方式,这种方式虽然是http库提供的接口,但是很多地方注释都说hijack需要特殊处理,因此也不是最好的方式。参考When to use hijack


Close Notifier


在GO1.1提供了http.CloseNotifier接口,参考Close Notifier,但是也注意会有一些问题,参考net/http: CloseNotifier fails to fire when underlying connection is gone。用法如下:


var incoming chan []byte
http.HandleFunc("/polling", func(w http.ResponseWriter, r *http.Request) {
select {
case <- w.(http.CloseNotifier).CloseNotify():
fmt.Println("connection closed")
}
})

实际上,超时机制始终是需要的,加上之前的逻辑,考虑context.Context取消事件,http-long polling的完整实现应该是:


func polling(ctx context.Context, incoming chan []byte) {
http.HandleFunc("/polling", func(w http.ResponseWriter, r *http.Request) {
select {
case <- ctx.Done():
fmt.Println("system quit")
case b := <- incoming:
w.Write(b)
case <-time.After(5 * time.Second):
w.Write("keepalive")
case <- w.(http.CloseNotifier).CloseNotify():
fmt.Println("connection closed")
}
})
}

大家有什么好的嵌入式k-v内存数据库推荐一下?

woshicixide 回复了问题 • 9 人关注 • 7 个回复 • 617 次浏览 • 2017-11-14 09:24 • 来自相关话题

golang doc.go package 后面的注释会影响编译

Tig 回复了问题 • 2 人关注 • 3 个回复 • 506 次浏览 • 2017-11-01 23:16 • 来自相关话题

go添加多证书的问题

meilihao 回复了问题 • 2 人关注 • 1 个回复 • 423 次浏览 • 2017-10-25 13:28 • 来自相关话题

GOLANG实现的HTTP转HTTPS的代理

winlin 发表了文章 • 0 个评论 • 518 次浏览 • 2017-10-13 12:17 • 来自相关话题

有时候需要将后端的HTTP服务,转成HTTPS,可以用一个代理。

Reamark: 如果是GOLANG的后端服务,可以直接用库go-oryx-lib/https

查看全部

有时候需要将后端的HTTP服务,转成HTTPS,可以用一个代理。



Reamark: 如果是GOLANG的后端服务,可以直接用库go-oryx-lib/https



这个代理支持自签名的证书,也支持letsencrypt的证书。



Remark: Letsencrypt只支持少量域名的情况,比如自己的网站,它会有请求次数限制,另外CA是letsencrypt的,商业用户不适合用。



我们有个HTTP API, SRS Version:


{
"code": 0,
"server": 12504,
"data": {
"major": 2,
"minor": 0,
"revision": 243,
"version": "2.0.243"
}
}

下面演示实现HTTPS的代理。


Self-sign Certificate


自签名证书可以用在测试中,先生成私钥server.key和证书server.crt


openssl genrsa -out server.key 2048 &&
openssl req -new -x509 -key server.key -out server.crt -days 365


Remark: 生成证书时会有很多提问,直接回车就好了。还可以参考openssl的文档,直接在命令行设置这些参数。



生成私钥和证书后,下载HTTPS代理:


go get github.com/ossrs/go-oryx/httpx-static


Remark: GOLANG的设置请参考GO环境配置


Note: 详细参数可以直接运行httpx-static程序不带参数,会显示help。



启动服务,代理到SRS Version:


sudo $GOPATH/bin/httpx-static -http 80 -https 443 \
-proxy http://ossrs.net:1985/api/v1/versions \
-ssc server.crt -ssk server.key

访问本机HTTP和HTTPS就可以:



  1. HTTP: http://localhost/api/v1/versions

  2. HTTPS: https://localhost/api/v1/versions



Remark: 浏览器访问自签名证书时,可能会提示不安全,选择高级然后继续浏览就可以了。



LetsEncrypt Certificate


可以使用letsencrypt签名的证书,在浏览器中会显示合法的绿色,不会提示有错误。参考:ossrs.net


ossrs.net也是使用httpx-static,参数如下:


sudo $GOPATH/bin/httpx-static -http 80 -https 443 \
-lets=true -domains ossrs.net


Remark: 注意在局域网的机器无法使用,因为ACME会有反向验证,也就是你的服务器得能在公网访问到。



Advance Proxy


如果需要代理所有的API怎么办呢?直接指定父目录就好,如果指定/则代理所有的请求。例如:


下面的命令,代理所有的/api请求:


sudo $GOPATH/bin/httpx-static -http 80 -https 443 \
-proxy http://ossrs.net:1985/api \
-ssc server.crt -ssk server.key

下面的命令,代理所有的请求,相当于做了镜像:


sudo $GOPATH/bin/httpx-static -http 80 -https 443 \
-proxy http://ossrs.net/ \
-ssc server.crt -ssk server.key

其他的参数请参考httpx-static的参数。

感觉 sync.Pool 很容易用错

tpkeeper 回复了问题 • 5 人关注 • 5 个回复 • 1051 次浏览 • 2017-10-13 10:30 • 来自相关话题

是否可用openresty作为grpc的路由(更新:目前看我觉得不行)

kevin 回复了问题 • 4 人关注 • 2 个回复 • 797 次浏览 • 2017-09-29 17:52 • 来自相关话题

关于GO反射的简单定律

gundamzaku 发表了文章 • 0 个评论 • 326 次浏览 • 2017-09-22 12:52 • 来自相关话题

自己待业在家,花了两天时间翻译出来的。。
虽然有些不完美,不过也算是费心之作了。
现在分享出来以供学习,
查看全部

自己待业在家,花了两天时间翻译出来的。。

虽然有些不完美,不过也算是费心之作了。

现在分享出来以供学习,

https://github.com/gundamzaku/golang_study_note/blob/master/%E5%8F%8D%E5%B0%84%E7%9A%84%E7%AE%80%E5%8D%95%E5%AE%9A%E5%BE%8B.md


当然,更详细的反射学习也有,不过是自己看代码整理出来的。花了不少的工作

https://github.com/gundamzaku/golang_study_note/tree/master/%E5%8F%8D%E5%B0%84reflect


有兴趣学习的可以看看,这几块全是关于反射的内容,也顺便能指出有问题的地方:)

大家都用什么web框架

songtianyi 回复了问题 • 20 人关注 • 20 个回复 • 4752 次浏览 • 2017-09-20 16:21 • 来自相关话题