Go

4 22

第三届 GopherChina 大会

上周末参加 GopherChina 第三届大会,感受不错。经过三年时间,Go 的发展非常火爆。会议规模从原来的几百人到上千人,还有很多站在座位两侧听的朋友。大会的内容也是从 Go 本身,到架构,到容器等相关领域都有涉及,可以说干货不错。办大会大会是一件辛苦事,非常感谢 Astaxie 一直以来的努力。讲好一个主题,也会需要很多技巧的,也非常感谢参与大会的各个讲师。言归正传,来聊聊大会的内容。

语言上 的 Go

每种语言都有自己的特色,Go 也不例外。学习 Go 的时候,难免会带入别的语言经验,造成一定的麻烦。因此从 Go 的方式来理解 Go,是 Go 语言开发者必须经历的过程。Tony Bai 从 Go 语言的角度,分享了 Go 的思维模式。

Go is about orthogonal composition of simple concepts with preference in concurrency.
Go 是在偏好并发的环境下的简单概念/事物的正交组合

从这句话就可以总结出几个 Go 开发的原则:

  • 事物的简单化,逻辑单元不要太大;不是一个函数从头到尾,是多个小函数组合起来的大函数
  • 正交组合,逻辑单元之间的无关性;因为函数之间的无关性,才可以复用,也才可以并发
  • 并发背景的需要,即业务是可以同时的,不是线性的

这一些原则还是需要很多的开发技巧来实现的,比如接口的垂直组合,如:


type ReadWriter interface {
    Reader // 这里体现了逻辑单元的简单化
    Writer
}

根据接口来更适配的接受逻辑水平扩展:


func ReadAll(r io.Reader) ([]byte, error)
// 可以支持文件流,网络数据流等
ReadAll(*os.File)
ReadAll(*http.Response.Body)

最后我比较感兴趣的就是 error 处理的内敛,比如:


// *bufio.Writer
func (b *Writer) Write(p []byte) (nn int, err error) {
    if b.err != nil {
        return nn, b.err  // 错误在结构内部
    }
    ... ...
}

// usage
buf := bufio.NewWriter(fd)
buf.WriteString("hello, ") // 这样就不需要每行 if err != nil
buf.WriteString("gopherchina ")
buf.WriteString("2017")
if err := buf.Flush() ; err != nil {
    return err
}


上面提到接口的垂直扩展,francesc 的分享就更深入的聊了 Go 中 interface{} 的使用技巧。interface{} 我的理解有两个意思。当 interface{} 带有方法的时候,是行为的定义,如:


type Reader interface{
    Read(data []byte)(n int,err error) // 注意定义的时候写一下变量名,否则不是啥意思
}

这样的接口就可以弥补 Go 没有泛型的不足。泛型的时候返回如 <List,Map> 其实是不对的,真正的意义很可能是 <Iterator>。在 Go 的编程中就逼迫你需要这样的思维来理解,找出需要泛型的时候数据类型的共通之处,执行相同的操作(如果就是不同的行为,写俩函数不是更好?)。

interface{} 另一个特殊场景就是空接口,对应的代码就是需要类型推断:

func do(v interface{}){
    switch t := v.(type){
        case int:
            fmt.Printf("int - %d",t)
        case error:
            fmt.Printf("error - %s",t.Error())
        default:
            fmt.Printf("interface - %v",t)
    }
}

不到万不得已不要这么写代码。否则需要推断类型的 case 越来越多,代码可维护性瞬间下降。


ezbuy 的分享有很多微服务相关的内容,选型 gRPC 的使用,context tracing 等问题。不过我最在意的是他们的开发环境搭建工具 Goflow。Go 的包管理机制一直为人所诟病。就算 vendor 解决了一些第三方包依赖的问题,但是非常的粗糙和直接。同时项目管理时 GOPATH 中既有第三方库,又有自己的项目代码,也给工程实践造成麻烦。Goflow 从分享中看很像 gb + package registry。Goflow 修改系统变量 GOPATH 到当前目录,再从内网 registry 下载第三方包到 vendor 目录。真的非常方便,是个不错的解决方案。而且国内网络访问很多包资源不是很顺畅,内网有 registry 提供很大的便利。这一套东西在公司内部使用我觉得非常棒。

另外他是唯一提及用 internal 包的分享。例如代码结构:

--product
  |
  |---internal
  |   |
  |   |---product_get.go
  |   |---product_search.go
  |   |
  |---product.go

代码和功能更加清晰明确。回头我也尝试一下这样的使用技巧。

10 1

Go 内嵌静态资源

使用 Go 开发应用的时候,有时会遇到需要读取静态资源的情况。比如开发 Web 应用,程序需要加载模板文件生成输出的 HTML。在程序部署的时候,除了发布应用可执行文件外,还需要发布依赖的静态资源文件。这给发布过程添加了一些麻烦。既然发布单独一个可执行文件是非常简单的操作,就有人会想办法把静态资源文件打包进 Go 的程序文件中。下面就来看一些解决方案:

go-bindata

go-bindata 是目前我的程序 pugo 在用的嵌入静态资源的工具。它可以把静态文件嵌入到一个 go 文件中,并提供一些操作方法。

安装 go-bindata

go get -u github.com/jteeuwen/go-bindata/...

注意 go get 地址最后的三个点 ...。这样会分析所有子目录并下载依赖编译子目录内容。go-bindata 的命令工具在子目录中。(还要记得把 $GOPATH/bin 加入系统 PATH)。

使用命令工具 go-bindata ( pugo 的例子):

go-bindata -o=app/asset/asset.go -pkg=asset source/... theme/... doc/source/... doc/theme/... 

-o 输出文件到 app/asset/asset.go,包名 -pkg=asset,然后是需要打包的目录,三个点包括所有子目录。这样就可以把所有相关文件打包到 asset.go 且开头是 package asset 保持和目录一致。

pugo 里释放静态文件的代码:

dirs := []string{"source", "theme", "doc"} // 设置需要释放的目录

for _, dir := range dirs {
    // 解压dir目录到当前目录
    if err := asset.RestoreAssets("./", dir); err != nil {
        isSuccess = false
        break
    }
}
if !isSuccess {
    for _, dir := range dirs {
        os.RemoveAll(filepath.Join("./", dir))
    }
}

asset.go 内的静态内容还是根据实际的目录位置索引。所以我们可以直接通过目录或者文件地址去操作。

9 24

Go 开发 HTTP 的另一个选择 fasthttp

fasthttp 是 Go 的一款不同于标准库 net/http 的 HTTP 实现。fasthttp 的性能可以达到标准库的 10 倍,说明他魔性的实现方式。主要的点在于四个方面:

  • net/http 的实现是一个连接新建一个 goroutine;fasthttp 是利用一个 worker 复用 goroutine,减轻 runtime 调度 goroutine 的压力
  • net/http 解析的请求数据很多放在 map[string]string(http.Header) 或 map[string][]string(http.Request.Form),有不必要的 []byte 到 string 的转换,是可以规避的
  • net/http 解析 HTTP 请求每次生成新的 *http.Requesthttp.ResponseWriter; fasthttp 解析 HTTP 数据到 *fasthttp.RequestCtx,然后使用 sync.Pool 复用结构实例,减少对象的数量
  • fasthttp 会延迟解析 HTTP 请求中的数据,尤其是 Body 部分。这样节省了很多不直接操作 Body 的情况的消耗

但是因为 fasthttp 的实现与标准库差距较大,所以 API 的设计完全不同。使用时既需要理解 HTTP 的处理过程,又需要注意和标准库的差别。

package main

import (
    "fmt"

    "github.com/valyala/fasthttp"
)

// RequestHandler 类型,使用 RequestCtx 传递 HTTP 的数据
func httpHandle(ctx *fasthttp.RequestCtx) {
    fmt.Fprintf(ctx, "hello fasthttp") // *RequestCtx 实现了 io.Writer
}

func main() {
    // 一定要写 httpHandle,否则会有 nil pointer 的错误,没有处理 HTTP 数据的函数
    if err := fasthttp.ListenAndServe("0.0.0.0:12345", httpHandle); err != nil {
        fmt.Println("start fasthttp fail:", err.Error())
    }
}
9 20

Go 开发 HTTP

Go 是一门新语言。很多人都是用 Go 来开发 Web 服务。Web 开发很多同学急于求成,直接使用 beego, echoiris 等知名框架。对标准库 net/http 的了解甚少。这里我就主要聊一下标准库 net/http 开发 Web 服务时的使用细节。

创建 HTTP 服务

在 Go 中,创建 HTTP 服务很简单:

package main

// in main.go

import (
    "fmt"
    "net/http"
)

func main(){
    if err := http.ListenAndServe(":12345",nil); err != nil{
        fmt.Println("start http server fail:",err)
    }
}

这样就会启动一个 HTTP 服务在端口 12345。浏览器输入 http://localhost:12345/ 就可以访问。当然从代码看出,没有给这个 HTTP 服务添加实际的处理逻辑,所有的访问都是默认的 404 Not Found

6 24

Go 语言入门资料

Go 语言,自2012年发布 1.0,至今 1.7 ,历经5年。Go 的相关工具和生态已经逐渐完善,这里综述一下 Go 语言学习开发可以找到的入门资料。

入门教程

  • 官方文档

第一步,学习基本语法和命令操作。Go 的官方文档是第一选择。但因为众所周知的原因,官网无法访问。可以访问 godoc.golangtc.com 镜像网站查看。或者下载安装好 Go 语言后执行 godoc 命令:

godoc -http=:6060

访问 http://localhost:6060 浏览内置的官网镜像站点。另外,godoc 会自动分析 GOPATH 中的源码生成文档,可以在网站访问 /pkg 直接查看。

  • 视频教程

推荐 无闻《Go 编程基础》。无闻的视频教程简单直接的介绍 Go 的基本语法的命令操作,简单的介绍一些标准库的使用方法。学好基础语法和操作是入门必需,来不得半点敷衍。实际开发实践时,自己再去深入了解使用的标准库和第三方库细节。

如果偏向 Web 方面的开发者,看完《Go 编程基础》 后可以再去学习无闻的 《Go Web 基础》 。里面以开发博客程序为例子,对 Go 语言开发 Web 的过程有比较详细的说明。但是因为已经是比较早的视频,可能所使用的类库已经发生较大的版本更新,需要自己根据库类的相关文档实践修正。

4 19

第二届 Gopher China 大会

又一次,和同事去参加 GopherChina 2016 大会,了解 Go 语言相关的最新动态。和一年前不同,Go 语言已经受到许多企业青睐。一些知名企业开始使用 Go 语言开发。因而,本届大会更多的内容注重在 Go 实现的业务场景和架构上。

Go 与 高并发服务

Go 语言的 goroutine 特性,非常适合开发高并发网络服务。大会有几个题目聊起相关的内容。

百度前端接入团队分享了《Go在百度BFE的应用》。相关的内容其实在InfoQ有过分享。百度的服务体量太过巨大(日均千亿),代码优化手段 + Go的版本更新 对服务整体的提升作用不大,只能用特殊的措施 ———— 车轮大战。关闭 runtime 的GC,由代码根据目前程序的运行情况判断是否手动 runtime.GC()。以 master-worker的模式轮换正在服务和正在GC的程序。这种架构估计只有百度这种规模才用得上吧。但是私下的交流来说,小伙伴还是觉得 nginx + C 模块更适合。况且BFE之前那套也就是C写的,有足够的技术实力。

对比的来看是,吴小伟(skoo)的《Go在阿里云CDN系统的应用》。阿里 CDN 的网络接入系统还是 C 语言写的。CDN 的日志系统、调度系统和刷新系统是 Go 写的。这些业务对 Go 语言的 GC 不敏感,加上 Go 比 C 更简洁的语法特性,更快的开发效率,开发周围系统是很适合的。这里可以看到,同样是大流量系统,思考的角度也有不同。顺便说一下,skoo 是比较早研究 Go 语言的技术大神之一,博客有一些关于 Go 核心原理的文章。

12 9

我煞笔的被 bufio.Reader 小坑

最近在用 Go 做一个小型的 gateway 服务。PHP 请求 Go 的 tcp server,然后 Go 根据命令参数开启多个 goroutine 去调度 php-fpm 执行不同的脚本并组合结果返回。 想来只是利用 goroutine 的便利并发执行逻辑,如此简单直接。

不过在测试的时候 PHP 发送 socket 的 json 数据发生了明确的截断,后来发现是我煞笔的被 bufio.Reader 坑了,真是无言以对。

问题重现

因为是长连接,PHP 每次发一段 json ,都会加上换行符\n分割。我就很理所当然的用起了*bufio.Reader.ReadLine()。就像下面的代码:

func handleConn(conn net.Conn) {
    reader := bufio.NewReader(conn)
    for {
        // 读取一行数据,交给后台处理
        line,_,err := reader.ReadLine()
        if len(line) > 0{
            fmt.Printf("ReadData|%d \n",len(line))
            executeBytes(line)
        }
        if err != nil{
            break
        }
    }
    conn.Close()
}

可是当 PHPer 测试发送大json数据的时候,发现了明确的截断:

ReadData|4096|{"ename.com":........,"yunduo.com":{"check":[1
// 又一次
ReadData|4096|{"ename.com":........,"yunduo.com":{"getWhois"

想让 json 还没读完就被截断。我记得默认的 bufio.Reader 的大小是 4096,所以 bufio.Reader 的行为是读满了buffer就return出来啊。。我勒个去。我总不能写个很大的size吧。

    reader := bufio.NewReaderSize(conn,409600)

PHP 发送的测试数据最大可能得到100k左右,平均只有<10k。我服务端每次都开一个100k+的bufio.Reader太浪费啦。最后变成,开小了截断,开大了浪费,我煞笔了。

11 25

阅读 valyala/fasthttp —— 比官方库更快的 HTTP 包

valyala/fasthttp 是号称比官方net/http库更快的 http server 库。就去顺便研究了,发现一些细节的不同。

处理 net.Conn 的 goroutine

处理net.Conn的goroutine的使用方式,和标准库有很大差别。在标准库,net.Listener.Accept() 到一个连接,就会开启一个goroutine:

// Serve accepts incoming connections on the Listener l, creating a
// new service goroutine for each.  The service goroutines read requests and
// then call srv.Handler to reply to them.
func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()
    var tempDelay time.Duration // how long to sleep on accept failure
    for {
        rw, e := l.Accept()
        if e != nil {
            ......
        }
        ......
        c, err := srv.newConn(rw)
        if err != nil {
            continue
        }
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve() // 在这里创建一个goroutine处理net.Conn的实际逻辑
    }
}

但是在valyala/fasthttp中使用的是worker的形式,开启固定数量的goroutine处理net.Conn

10 14

PuGo 一次内存泄露的调优

我刚刚写好新的博客程序 Pugo,欢迎试用和体验。这两天我把个站 fuxiaohei.me 迁移到新的博客程序。但是,经过一天的运行,发现内存从启动的 14MB 上升到了 228 MB。显然程序发生内存泄露,所以也开始以下调优过程。

PPROF

pprof 是 Golang 自带的调试工具,有很多可用的工具。pprof 的调试方式有代码的方式和 HTTP 方式。其中 HTTP 调试比较方便,加入很简单的代码:

import _ "net/http/pprof" // pprof 的 http 路由注册在自带路由上

go func() {
    http.ListenAndServe("0.0.0.0:6060", nil) // 启动默认的 http 服务,可以使用自带的路由
}()

4 29

第一届 Gopher China 大会

前两天去上海参加 Gopher China 2015 大会,见到很多久闻大名的大神和朋友,感觉很好。而且看到许多的企业,尤其是大企业都已经开始成规模的使用 golang,说明 golang 本身的设计和性能,已经受到了大家的认可。当然其实有很多的话题,不局限在 golang 了。

Go 语言核心

最重量级的话题,就是 雨痕 的 《Go 1.4 Runtime》。主要说了 Go 的内存分配器、垃圾回收器和goroutine调度器三块内容。我之前阅读过他的 《Go 语言笔记》 ,非常不错。很详细的阐述了 Go 语言本身的实现设计,同时为更合理的利用 golang 提供一些参考。 再加上他本人也很低调,真的是 隐士高人 的感觉。这次的他的演讲也相当不错。流畅的思路和平和的语言,而且不时的有诙谐幽默,让人听起来很有意思。唯一的遗憾是,他准备的ppt其实可以讲到3个小时,可惜会程只有45min,尽管大家还是争取多听了一些,但还是意犹未尽。

另一个超级话题,是 Robert Griesemer 的演讲。他是 Go 的三位作者之一,也是 Google V8 和 Java Hotspot VM 开发者之一,相当巨大的光环!他的演讲是关于 gofmt 的。 Go语言本身有很多的外围工具,比如 gofmt, godoc。其中格式化工具 gofmt 帮助大家简单直接的就统一了代码风格。Go 本身就带有 parser 包,就能够很好的去解析go源码到语法树。 唯一要吐槽的是 Q&A 环节,提问的童鞋用谁都听不懂的“英语”去问,哈哈哈!

3 1

阅读 Beego - 路由机制 (1)

beego 是国产著名的 Golang Web 框架,基于 MVC 模型,支持自动路由和Restful,并且有Config,Cache,Session,Orm等模块可以直接使用。MVC中最重要的就是路由。它实现从Web请求到对应控制器方法的映射。一般而言,路由包括三个部分:添加规则解析规则匹配规则。正好我分为三个部分,来分析beego的路由实现机制。

这一篇是添加路由规则的分析,从哪里入手呢?Beego有个开源论坛系统 Wetalk,也就是 golanghome 的实现,从他的路由开始看起。

5 28

在做一些自己想做的事情

小站长草一个月,本来的计划有很多变化。我也不知道要说些什么好。生活总有如意不如意,自己负责就好。反正,终极的希望,做一些自己想做的事情就好

GoGs

GoGs是Go实现的类Github服务。最初这个项目是无闻倡导的,他和lunny最开始折腾。后来缺一个前端,就把我拉进来了。这是我第一次完整的参与一个开源项目的工作。GoGs在Github已经1900个star,非常开心。

其实我是知道自己的,UI设计和JavaScript功力都不是高手,只是有点熟悉罢了。真正专业的JavaScript代码我也是看得一头雾水,什么promiseevent-proxy我理解也有困难。不过现在来看,目前的能力还是足够处理这个工作的。

6月GoGs发布v0.4之后,前端会面临一次重构甚至重设计。希望我还可以做好。这就是我下班之余最忙活,也是最开心的事情吧。

4 30

Beego源码分析

beego@astaxie 开发的重量级Go语言Web框架。它有标准的MVC模式,完善的功能模块,和优异的调试和开发模式等特点。并且beego在国内企业用户较多,社区发达和Q群,文档齐全,特别是 @astaxie 本人对bug和issue等回复和代码修复很快,非常敬业。beego框架本身模块众多,无法简单描述所有的功能。我简单阅读了源码,记录一下beego执行过程。官方文档已经图示了beego执行过程,而我会比较详细的解释beego的源码实现。

4 24

Martini源码剖析

martini是非常优雅的Go Web框架。他基于依赖注入的思想,仿照Sinatra的路由设计,参考Express的中间件设计,而且核心微小,扩展方便,非常值得学习。但是由于本身API设计简洁,使很多细节无法从代码理解。所以,我写一点笔记记录martini的工作方式。

4 8

Fxh.Go的新计划

因为最近忙于工作和gogits的事情,Fxh.Go的进度落下了很多。而且,更多的想法出现了。 本来想要从原生Go语言开始,搭建框架,完善博客功能。但是希望用户从头研究代码,是非常不现实的。因此,索性从现在的库类里组合,重新开发博客功能。方便大家理解使用。这就是Fxh.Go的新计划。

3 31

Gogs:用二进制才是真正的部署

原文作者 无闻, http://wuwen.org/article/27/gogs-binary-is-what-called-real-deployment.html

本篇博客是随着 Gogs - Go Git Service v0.2.0 版本而发布的。

首先,请允许我代表开发团队感谢所有在 GitHub 上支持 Gogs 的同学。要知道,v0.2.0 是 Gogs 的首个公开发布版本,而在这之前一周的时间里,该项目已经获得了超过 650 个 Star。

然后,我代表个人致以开发团队所有成员最诚挚的敬意,每个成员都为首个版本的发布做出了非常大的努力,是我们的团结一心和默契配合成就了 Gogs 这个项目的建立与成长。

项目概述

既然是首个公开发布版本,那么自然有必要对 Gogs 这个项目进行一定的说明,让大家更好地了解我们为什么开发 Gogs、是如何进行开发的,以及目前的开发状况。

3 13

Go语言的Web框架

我去年开始研究Go语言,不知不觉快有一年了。以前我研究php和nodejs,都是弱类型的解释性语言。想找一个编译型的强类型语言继续学习,就选中了新奇的Go语言。我只关注Web方面的应用,看了很多有兴趣的开源的Go Web框架,随便吐槽一下。

revel

revel 是最早的Go语言Web框架,借鉴的java和scala语言的 play框架 的很多想法。最早我看play 1.x时期在java社区似乎带来一股全新的风气,感觉是很有意思的事情。后来 play 2.x 转投scala阵营,把java开发者带入深渊,被很多人无情的吐槽。如今,play社区还是不温不火的,国内应用也小众。

revel 这玩意儿带有和play一样的毛病,舍弃了原有的标准完全自己来。revel 完全不理 Go标准库的一套,全部是自己的概念;类似的play舍弃了servlet 3标准。结果就是,我看了半天,还是不晓得该怎么用。自带的概念太多,是个障碍啊!

当然,revel 的案例还是有的,比如 山坡网。他的作者的博客也有很多关于revel的教程文章。

2 19

Ubuntu 下 nginx , php , mysql 和 golang 的简单安装

我是搞php出身,自然安装lnmp是常规技能。以前的手段还是lnmp安装包,比如军哥的lnmp1.0。随着php和mysql的更新,大多数一键安装都开始版本老化,更新困难的问题。因此,重新研究了一下Ubuntu下lnmp的安装,发现现在简单的多,记录一下。

另外最近在学习golang,Ubuntu下安装自然也是必须的过程。不过golang的安装也有一些奥妙。当然,不是源码安装的啦。

2 18

Windows 下 gcc + golang 编译 git2go

最近研究用go语言操作git,除了直接走命令行用os/exec包,还可以使用libgit2的go绑定git2go操作。 但是libgit2是c语言库,go使用cgo连接c程序,需要cgo的支持。总之过程复杂,摔了一路。

1 19

换新博客了

终于,Go语言开发的博客算发布了,Fxh.Go。和原来的php相比,变化还是很大的。

首先是程序上:

  • 花了很长时间的学习和研究,终于完全用Go语言,搭建自己的框架,实现了这个程序。
  • 没用数据库,完全是json储存,数据全部维持在内存,读取速度非常快。
  • 再三思索,还是没用.me的域名,用回了更早买的.net。同时,没用大的512M的VPS,用了手里最小的128M的。
  • 前端都是自己设计实现的。