GoCN每日新闻(2018-01-19)

回复

每日新闻astaxie 发起了问题 • 1 人关注 • 0 个回复 • 134 次浏览 • 4 小时前 • 来自相关话题

nginx反向代理多个服务

有问必答lrita 回复了问题 • 1 人关注 • 1 个回复 • 81 次浏览 • 6 小时前 • 来自相关话题

[北京]滴滴招聘golang工程师-特征系统[长期]

招聘应聘圈圈Kate 回复了问题 • 10 人关注 • 25 个回复 • 3828 次浏览 • 7 小时前 • 来自相关话题

thrift 的几个需要改进的地方

开源程序taowen 发表了文章 • 0 个评论 • 53 次浏览 • 8 小时前 • 来自相关话题

两次拷贝

因为 thrift IDL 生成出来的 struct 不是很好用,比如:

  • 用 *int 表示 optional
  • 无法增加不在 IDL 里的业务字段

所以为... 查看全部

两次拷贝


因为 thrift IDL 生成出来的 struct 不是很好用,比如:



  • 用 *int 表示 optional

  • 无法增加不在 IDL 里的业务字段


所以为了避免和 thrift 的 codegen 做搏斗,业务就自己再定义了一个struct。然后解析的过程就变成了


[]byte => thrift struct => 业务的 struct

这就多了一次内存的拷贝,同时增加了GC负担。这个问题最佳的解决办法就是直接对象绑定到业务 struct 上,也就是 Go 的 struct 不用和 IDL 字段一一对应。这种映射关系和常见 JSON 绑定是类似的。


没 IDL 无法编解码


不是所有人都喜欢代码生成的。有的时候为了开发方便,会希望能不能用反射之类的方式直接绑定到对象上。


即便排除掉口味偏好。仍然有一些场景下无法用Codegen来解决。比如,我们需要一个 thrift proxy。这个代理可以在网络传输过程中修改 thrift message 的第0个参数的内容。我们不能提前知道所有可能的 thrift message 的 IDL,当然也无法对这些 IDL 提前代码生成。使用官方的 thrift lib 是很难优雅地解决这个问题地。


官方的库有性能问题


根据第三方 benchmark: https://github.com/smallnest/gosercomp


这个是 protobuf 的结果


BenchmarkMarshalByGogoProtoBuf-4 20000000   109 ns/op   48 B/op 1 allocs/op
BenchmarkUnmarshalByGogoProtoBuf-4 3000000 408 ns/op 144 B/op 8 allocs/op

这个是 thrift 的结果


BenchmarkMarshalByThrift-4 3000000  462 ns/op   64 B/op 1 allocs/op
BenchmarkUnmarshalByThrift-4 1000000 1356 ns/op 656 B/op 11 allocs/op

性能差距还是非常大的。到底有多慢呢?我们看一下 JSON + 反射的速度


BenchmarkMarshalByJsoniter-4 2000000    721 ns/op   800 B/op    5 allocs/op
BenchmarkUnmarshalByJsoniter-4 3000000 473 ns/op 112 B/op 6 allocs/op

也就是说,在 Go 里面使用 thrift 未必比 JSON 要快。再加上前面提到的第一个问题。事实上,如果不改进官方的Go版本的库(其实Java版本也很糟糕),还不如选择JSON。


为什么会慢呢?主要是这么两点



  • 完全无buffer的解析。所有的数据都假设从 io.Reader 里读取出来。

  • 中途大量的 check err 的代码


thrift 改进计划


thrift 这个协议本身没啥问题。主要是库实现得不好。我们给 Go 写一个新的库吧: https://github.com/thrift-iterator/go



  • 通过静态代码生成支持对象绑定,基于纯 Go 实现的代码生成框架 https://github.com/v2pro/wombat ,从 Go struct 直接生成代码。

  • 通过 jsoniter 类似的反射支持对象绑定,实现性能不受太大影响的情况下,不用代码生成也能使用 thrift协议

  • 支持无 IDL 解析,移植 jsoniter 的 api

    • 支持 iterator api,支持不同字段用不同的解析模式

    • 能 skip 字段并返回 []byte

    • 支持把thrift解析成map


  • 重写解析代码,达到和Protobuf一样的性能水平

从零开始学golang之gin加上gorm

回复

有问必答freedbg 发起了问题 • 1 人关注 • 0 个回复 • 91 次浏览 • 19 小时前 • 来自相关话题

持续监控文件变化时使用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来解决我的问题,明天有会议,计划周六动手试试。有结果了继续更新。

go里面时间加减法是否有问题

有问必答Xargin 回复了问题 • 2 人关注 • 1 个回复 • 84 次浏览 • 22 小时前 • 来自相关话题

从零开始学golang-RedBlackTree-Delete-fix2

回复

Golangfreedbg 发起了问题 • 1 人关注 • 0 个回复 • 60 次浏览 • 1 天前 • 来自相关话题

[上海/北京]上市软件公司求go开发主管/架构师

招聘应聘bigheadx 发表了文章 • 0 个评论 • 233 次浏览 • 1 天前 • 来自相关话题

GO开发经理/架构师 @上海或北京

北明软件:A股上市软件公司

岗位职责:

  • 负责核心产品或核心模块开发;
  • 能与产品经理和业务同事协同配合,参与需求评审,能准确理解;
  • <... 查看全部

GO开发经理/架构师 @上海或北京


北明软件:A股上市软件公司


岗位职责:



  • 负责核心产品或核心模块开发;

  • 能与产品经理和业务同事协同配合,参与需求评审,能准确理解;

  • 快速原型开发,快速迭代并迅速投入产品;

  • 对已有系统在可用性,可运维,性能上进行持续优化;


岗位要求:



  • 全日制统招本科以上学历,具有5年以上软件开发经验,至少1年以上Go语言开发经验;

  • 精通网络通信的协议和实现,熟悉TCP/IP通信机制,对socket通信和http通信有较深刻的理解和经验;

  • 精通Go语言,熟悉Python或Java等其他编程语言

  • 熟悉使用MySQL,Redis,MongDB等数据库

  • 具有一定的大数据处理分析能力,对分布式熟悉,了解NoSQL;

  • 具备良好的文档技术方案、标准规范等文档编写能力;

  • 良好的沟通能力、团队合作精神;认真负责、具有高度责任感;优秀的学习能力,能承受一定的工作压力;


email:wangchaozhen@bmsoft.com.cn

第四届 Gopher China 大会正式启动,讲师火爆招募中……

回复

文章分享astaxie 发起了问题 • 1 人关注 • 0 个回复 • 103 次浏览 • 1 天前 • 来自相关话题

GoCN每日新闻(2018-01-18)

回复

每日新闻data_worm 发起了问题 • 2 人关注 • 0 个回复 • 261 次浏览 • 1 天前 • 来自相关话题

goweb,基于go语言的API框架

开源程序Alber 发表了文章 • 0 个评论 • 188 次浏览 • 1 天前 • 来自相关话题

goweb

一个基于go语言的快速开发WebAPI的工具,这个工具受到了SpringMVC的启发,结合了go语言本身的特性,整体比较简单,接下来,看看如何使用它。

下载安装:

查看全部
					

goweb


一个基于go语言的快速开发WebAPI的工具,这个工具受到了SpringMVC的启发,结合了go语言本身的特性,整体比较简单,接下来,看看如何使用它。


下载安装:


go get github.com/alberliu/goweb

1.核心功能


请求体参数注入


package main

import "github.com/alberliu/goweb"

type User struct {
Id int `json:"id"`
Name string `json:"name"`
}

func handler(user User) User {
return user
}

func main() {
goweb.HandlePost("/test", handler)
goweb.ListenAndServe(":8000")
}

请求体:


{
"id": 1,
"name": "alber"
}

响应体:


{
"id": 1,
"name": "alber"
}

上面的代码是一个最简的例子,HandlePost(string, interface{})会将一个handler注册到一个全局的内置的goweb实例defultGoWeb,ListenAndServe(":8000")会将defultGoWeb赋给Server的handler变量,然后启动这个Server。(是不是和内置的ServerMux有点像)


goweb会自动解析注册到它本身的handler,当请求到来时,会将请求体的json数据反序列化并注入到handler的参数,handler处理完逻辑返回时,会将handler的返回值序列化为json数据返回。goweb默认使用json的序列化和反序列化方式,当然你可以定义自己的序列化方式,这个在后面你可以看到。


例子给出的handler的参数和返回都是结构体类型,当然你也可以使用指针类型。


结构体goweb其实本质上就是一个路由,它实现了Handler接口。上面的例子都是默认的defultGoWeb,你也可以自己实例化一个goweb。


func main() {
goweb:=goweb.NewGoWeb();
goweb.HandlePost("/test", handler)
server := &http.Server{Addr: ":8000", Handler: goweb}
server.ListenAndServe()
}

url参数注入


package main

import "github.com/alberliu/goweb"

type User struct {
Id int64 `json:"id"`
Name string `json:"name"`
}

func handler(id int64, name string) User {
return User{id, name}
}

func main() {
goweb := goweb.NewGoWeb();
goweb.HandleGet("/test/{id}/{name}", handler)
goweb.ListenAndServe(":8000")
}

执行上面的代码,然后访问url:http://localhost:8000/test/123456/alber


就可以返回下面的json数据


{
"id": 123456,
"name": "alber"
}

handler可以获取到url中的参数,并且注入到handler参数中。handler的第一个参数对应url中的第一个参数,第二个参数对应url中的的第二个参数,依次类推。不过暂时还有个限制,在url中使用参数时,handler中的参数必须与url中的参数个数一致,且类型必须为string或者int64。


2.handler


goweb可以注册多种形式的handler,goweb会利用反射自动解析函数,支持多种类型,但是不能超出它可以解析的范围。以下是它所有能解析的类型。



func handler(ctx goweb.Context) {
}

func handler(ctx goweb.Context) User {
return User{}
}

func handler(user User) User {
return User{}
}

func handler(ctx goweb.Context, user User) User {
return User{}
}

func handler(name string, id int64) User {
return User{}
}

func handler(ctx goweb.Context, name string, id int64) User {
return User{}
}

Context是一个请求上下文,他只有ResponseWriter和Request两个字段,它的内部结构如下所示。你可以根据自己的需求修改源码进行扩展,例如,把它作为一个请求的会话使用。


type Context struct {
w http.ResponseWriter
r *http.Request
}

3.用Group组织你的handler


func main() {
group1:=goweb.NewGroup("/group1")
group1.HandleGet("/handler1",handler)
group1.HandleGet("/handler2",handler)
group1.HandleGet("/handler3",handler)

group2:=goweb.NewGroup("/group2")
group2.HandleGet("/handler1",handler)
group2.HandleGet("/handler2",handler)
group2.HandleGet("/handler3",handler)

group3:=goweb.NewGroup("/group3")
group3.HandleGet("/handler1",handler)
group3.HandleGet("/handler2",handler)
group3.HandleGet("/handler3",handler)

goweb.HandleGroup(group1)
goweb.HandleGroup(group2)
goweb.HandleGroup(group3)
goweb.ListenAndServe(":8000")
}

group可以帮助你分层次的组织你的handler,使你的路由结构更清晰。


4.定义自己序列化和反序列化方式


var json = jsoniter.ConfigCompatibleWithStandardLibrary

func jsonUnmarshal(data []byte, v interface{}) error {
return json.Unmarshal(data, v)
}

func jsonMarshal(v interface{}) ([]byte, error){
return json.Marshal(v)
}

func main() {
goweb:=goweb.NewGoWeb();
goweb.Unmarshal=jsonUnmarshal
goweb.Marshal=jsonMarshal

goweb.ListenAndServe(":8000")

}

goweb默认采用json(使用的是开源的jsoniter)序列化和反序列化数据,goweb的Marshal、Unmarshal变量本身是一个函数.如果你想定义自己的序列化方式,只需要覆盖掉它就行,就像上面那样。


5.拦截器



func interceptor1(http.ResponseWriter, *http.Request) bool {
return true
}
func interceptor2(http.ResponseWriter, *http.Request) bool {
return true
}
func interceptor3(http.ResponseWriter, *http.Request) bool {
return true
}

func main() {
goweb := goweb.NewGoWeb();
goweb.AddInterceptor(interceptor1)
goweb.AddInterceptor(interceptor2)
goweb.AddInterceptor(interceptor3)
goweb.ListenAndServe(":8000")
}

goweb在执行handler之前,会执行一个或者多个interceptor,并且会根据AddInterceptor的先后顺序执行,当interceptor返回true时,会接着往下执行,返回false时,会终止执行。


6.过滤器


func filter(w http.ResponseWriter, r *http.Request, f func(http.ResponseWriter, *http.Request)) {
f(w, r)
}

func main() {
goweb := goweb.NewGoWeb();
goweb.Filter = filter
goweb.ListenAndServe(":8000")
}

你可以给goweb添加一个过略器,在过滤器中,如果你想执行完自己的逻辑之后,执行handler,只需要调用f(w, r)。


7.自定义错误处理



func handler400(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(400)
w.Write([]byte("bad request"))
}
func handler404(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(404)
w.Write([]byte("url not found"))
}
func handler405(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(405)
w.Write([]byte("method not found"))
}

func main() {
goWeb := goweb.NewGoWeb()
goWeb.Handler400 = handler400
goWeb.Handler404 = handler404
goWeb.Handler405 = handler405

goweb.ListenAndServe(":8000")
}

当请求执行失败时,goweb中给出了一些默认的错误处理方式,就像上面那样。当然,你也可以定义一些自己错误处理方式。


写在后面


如果你有什么好的建议,可以发我邮箱,一起交流。


alber_liu@qq.com

goweb,基于go语言API框架

回复

开源程序Alber 发起了问题 • 0 人关注 • 0 个回复 • 84 次浏览 • 1 天前 • 来自相关话题

请问go websocket server 如何实现文件上传呢?有具体的参考吗

有问必答tupunco 回复了问题 • 4 人关注 • 3 个回复 • 204 次浏览 • 1 天前 • 来自相关话题

一个辅助操作数据库的go lib[from滴滴]

开源程序caibirdme 发表了文章 • 0 个评论 • 160 次浏览 • 1 天前 • 来自相关话题

github地址: gendry

gendry是一个非常简单易用的sql builder,比如构建sql语句:

... 查看全部

github地址: gendry


gendry是一个非常简单易用的sql builder,比如构建sql语句:


where := map[string]interface{}{
"city in": []interface{}{"beijing", "shanghai"},
"score": 5,
"age >": 35,
"_orderby": "bonus desc",
"_grouoby": "department",
}
table := "some_table"
selectFields := []string{"name", "age", "sex"}
cond, values, err := builder.BuildSelect(table, where, selectFields)
rows,err := db.Query(cond, vals...) // db: *sql.DB
//cond = SELECT name,age,sex FROM g_xxx WHERE (city IN (?,?) AND score=? AND age>?) GROUP BY department ORDER BY bonus DESC
//values = []interface{}{"beijing", "shanghai", 5, 35}

读取返回:


type Person struct {
Name string `json:"name"`
Age int `json:"m_age"`
}

rows,err := db.Query("SELECT age as m_age,name from g_xxx where xxx")
defer rows.Close()

var students []Person

scanner.Scan(rows, &students)

gendry最开始是我在滴滴的一个对外接口服务中使用,经过一年多的迭代和多个线上系统大流量的验证,已经非常稳定了。gendry的一个显著特点是非侵入性,只要你的项目目前使用的是标准库,那你就可以无痛添加gendry,大多数时候它就是一个helper函数。如果你不想用,一行代码就可以去掉,不会像orm一样牵一发动全身。gendry还提供了一个cli工具可以根据数据库表结构自动生成该表对应的结构体和对该表的增删改查的dao层代码。


gendry是个很简单的lib,期待大家的使用和意见,热烈欢迎各种pr


这里简要地写了一下我们为什么会开发这个lib: https://github.com/didi/gendry/wiki" title="为什么开发gendry">为什么开发gendry