Go语言开发IM与推送业务

来源:互联网 发布:菲波那切数列python 编辑:程序博客网 时间:2024/05/16 14:22

背景简介:

前VMware云平台首席技术官Derek Collison曾断言Go语言两年内将成为云计算的主流语言,这也许有些耸人听闻,但Go语言在Google内部确实取得了令人瞩目的成绩。Wired最近发表文章讲述了Go语言在Google内部崛起的故事,IT经理网编译整理如下:

当一部分“老弱”程序代码不能正常工作时,Google就会派出负责Go语言项目的“地鼠特工队”负责除旧迎新。

Brad Fitzpatrick在使用Google软件最常用的功能时卡住了——下载进度条在99%的地方停下了。虽然下载失败是很常见的问题,但Fitzpatrick却并无迁就之意,他是地球上最懂技术的企业——Google的工程师,他不能容忍此类问题发生在Google身上。

“1k大的文件都托管不好对Google来说是很丢人的事情,”Fitzpatrick在上周俄勒冈的开源大会上说道,“所以我在内部的Google+站点上就此问题大吐其槽。”

其实Fitzpatrick并不孤单,多年来很多人都曾抱怨dl.google.com系统不够稳定,而该系统极其重要,处理几乎所有Google的下载任务,包括Chrome浏览器安装文件、Android源代码,到体积小些的JavaScript库。

问题的根源是该文件服务器系统的底层代码的年龄已经超过五年,可谓破败不堪。

“如果代码不能得到持续的关爱就会变成一堆屎,”Fitzpatrick说道。

最初的C++代码缺乏规范的文档,自动化测试也不达标,没有人能搞明白它是如何工作的。人们只是不停地做一些增量改变,最后在负责维护的程序员眼里,这些代码变成了“一坨面条”。系统的不稳定差不多要把服务器运营团队逼疯了,但是没有人有时间重写技术上还能使用的代码。于是Fitzpatrick自告奋勇接手这个任务,他来自Google的一个25位工程师组成的团队——地鼠队,负责开发一种定制化的编程语言——Go.

Fitzpatrick对重写依然在生产环境使用的代码感到非常兴奋,因为他终于有机会能在真实的系统中测试Go了。

Go由计算机科学的三位大神Robert Griesemer、Rob Pike和Ken Thompson共同创建。Thompson也是Unix操作系统的联合开发者,同时也是C语言之父。Pike则是Thompson在贝尔实验室的同事。Griesemer因在Java编译器方面的工作而广为人知。

Go的初衷是实现即能像古怪的电信语言Erlang或流行的服务器端javaScript平台Node.js那样处理大规模并发用户,同时又能像C++那样迅捷。一个很够处理大规模并发下载请求的分布式文件系统应当是最能发挥Go长处的应用。

事实也是如此,新的用Go编写的dl.google.com软件代码上简洁不少,内存开销也更少,更重要的是,更加可靠。Fitzpatrick表示在开发过程中更加深入了解了Go,开发了一个新的开源缓存库,同时也发现了Go语言的一些不足。

“很荣幸我是第一个体验此类痛点的人,”在接受开源大会上接受采访时Fitzpatrick表示,Google的团队在用Go处理集群和文件传输时进行了很多改进。项目大获成功,如今Go团队开始经常帮助其他团队完成小项目。正如Griesemer所言:这只“地鼠特工队”对研究团队、Chrome团队、客户调查团队以及其他“有关团队”也提供了很多帮助。

虽然Go语言在Google内部战绩彪炳,但是“地鼠特工队”并未在Google内部呼吁用Go全面取代其他编程语言。不少团队不希望改变目前使用的语言,也不愿做重复开发。Google开源经理Chris DiBona认为:对于不同的任务我们会使用最好的软件来完成,无论这些软件是Java、Python或是什么别的语言编写的。对于这一点,Fitzpatrick也表示赞同。

Go语言目前在Google以外的应用还很少,但在业界依然引起了重视。

前VMware云平台首席技术官Derek Collison曾断言Go语言两年内将成为云计算的主流语言:

管理层和基础架构层决定了云计算的交付模型,两年内大多数云程序都将用Go语言编写。

此外,网游公司Zynga和NTT DoCoMo都在使用的开源数据库Couchbase就是部分用Go编写的,其开发者还用到了Erlang、C和C++。但是Couchbase的软件架构师J.Chris Anderson表示Go是最棒的:

我们发现Go的并发模型与Erlang、C和C++的几乎一样强大,而且是对开发者非常友好的一种系统。

Collison对Go的预言也许有些夸大,但他新创立的云计算公司Apcera确实已经在使用Go语言,而且目前Go语言已经有了一个重大的成功案例,那就是Google自己,新的dl.google.com系统的可靠性得到了大幅提升。

Go语言测试:

一、基础测试技术

1、测试Go代码

Go语言内置测试框架。

内置的测试框架通过testing包以及go test命令来提供测试功能。

下面是一个完整的测试strings.Index函数的完整测试文件:

//strings_test.go (这里样例代码放入strings_test.go文件中)
package strings_test

import (
    "strings"
    "testing"
)

func TestIndex(t *testing.T) {
    const s, sep, want = "chicken", "ken", 4
    got := strings.Index(s, sep)
    if got != want {
        t.Errorf("Index(%q,%q) = %v; want %v", s, sep, got, want)//注意原slide中的got和want写反了
    }
}

$go test -v strings_test.go
=== RUN TestIndex
— PASS: TestIndex (0.00 seconds)
PASS
ok      command-line-arguments    0.007s

go test的-v选项是表示输出详细的执行信息。

将代码中的want常量值修改为3,我们制造一个无法通过的测试:

$go test -v strings_test.go
=== RUN TestIndex
— FAIL: TestIndex (0.00 seconds)
    strings_test.go:12: Index("chicken","ken") = 4; want 3
FAIL
exit status 1
FAIL    command-line-arguments    0.008s

2、表驱动测试

Golang的struct字面值(struct literals)语法让我们可以轻松写出表驱动测试。

package strings_test

import (
        "strings"
        "testing"
)

func TestIndex(t *testing.T) {
        var tests = []struct {
                s   string
                sep string
                out int
        }{
                {"", "", 0},
                {"", "a", -1},
                {"fo", "foo", -1},
                {"foo", "foo", 0},
                {"oofofoofooo", "f", 2},
                // etc
        }
        for _, test := range tests {
                actual := strings.Index(test.s, test.sep)
                if actual != test.out {
                        t.Errorf("Index(%q,%q) = %v; want %v",
                             test.s, test.sep, actual, test.out)
                }
        }
}

$go test -v strings_test.go
=== RUN TestIndex
— PASS: TestIndex (0.00 seconds)
PASS
ok      command-line-arguments    0.007s

3、T结构

*testing.T参数用于错误报告:

t.Errorf("got bar = %v, want %v", got, want)
t.Fatalf("Frobnicate(%v) returned error: %v", arg, err)
t.Logf("iteration %v", i)

也可以用于enable并行测试(parallet test):
t.Parallel()

控制一个测试是否运行:

if runtime.GOARCH == "arm" {
    t.Skip("this doesn't work on ARM")
}

4、运行测试

我们用go test命令来运行特定包的测试。

默认执行当前路径下包的测试代码。

$ go test
PASS

$ go test -v
=== RUN TestIndex
— PASS: TestIndex (0.00 seconds)
PASS

要运行工程下的所有测试,我们执行如下命令:

$ go test github.com/nf/…

标准库的测试:
$ go test std

注:假设strings_test.go的当前目录为testgo,在testgo目录下执行go test都是OK的。但如果我们切换到testgo的上一级目录执行go test,我们会得到什么结果呢?

$go test testgo
can't load package: package testgo: cannot find package "testgo" in any of:
    /usr/local/go/src/pkg/testgo (from $GOROOT)
    /Users/tony/Test/GoToolsProjects/src/testgo (from $GOPATH)

提示找不到testgo这个包,go test后面接着的应该是一个包名,go test会在GOROOT和GOPATH下查找这个包并执行包的测试。

5、测试覆盖率

go tool命令可以报告测试覆盖率统计。

我们在testgo下执行go test -cover,结果如下:

go build _/Users/tony/Test/Go/testgo: no buildable Go source files in /Users/tony/Test/Go/testgo
FAIL    _/Users/tony/Test/Go/testgo [build failed]

显然通过cover参数选项计算测试覆盖率不仅需要测试代码,还要有被测对象(一般是函数)的源码文件。

我们将目录切换到$GOROOT/src/pkg/strings下,执行go test -cover:

$go test -v -cover
=== RUN TestReader
— PASS: TestReader (0.00 seconds)
… …
=== RUN: ExampleTrimPrefix
— PASS: ExampleTrimPrefix (1.75us)
PASS
coverage: 96.9% of statements
ok      strings    0.612s

go test可以生成覆盖率的profile文件,这个文件可以被go tool cover工具解析。

在$GOROOT/src/pkg/strings下面执行:

$ go test -coverprofile=cover.out

会再当前目录下生成cover.out文件。

查看cover.out文件,有两种方法:

a) cover -func=cover.out

$sudo go tool cover -func=cover.out
strings/reader.go:24:    Len                66.7%
strings/reader.go:31:    Read                100.0%
strings/reader.go:44:    ReadAt                100.0%
strings/reader.go:59:    ReadByte            100.0%
strings/reader.go:69:    UnreadByte            100.0%
… …
strings/strings.go:638:    Replace                100.0%
strings/strings.go:674:    EqualFold            100.0%
total:            (statements)            96.9%

b) 可视化查看

执行go tool cover -html=cover.out命令,会在/tmp目录下生成目录coverxxxxxxx,比如/tmp/cover404256298。目录下有一个 coverage.html文件。用浏览器打开coverage.html,即可以可视化的查看代码的测试覆盖情况。

 

关于go tool的cover命令,我的go version go1.3 darwin/amd64默认并不自带,需要通过go get下载。

$sudo GOPATH=/Users/tony/Test/GoToolsProjects go get code.google.com/p/go.tools/cmd/cover

下载后,cover安装在$GOROOT/pkg/tool/darwin_amd64下面。

二、高级测试技术

1、一个例子程序

outyet是一个web服务,用于宣告某个特定Go版本是否已经打标签发布了。其获取方法:

go get github.com/golang/example/outyet

注:
go get执行后,cd $GOPATH/src/github.com/golang/example/outyet下,执行go run main.go。然后用浏览器打开http://localhost:8080即可访问该Web服务了。

2、测试Http客户端和服务端

net/http/httptest包提供了许多帮助函数,用于测试那些发送或处理Http请求的代码。

3、httptest.Server

httptest.Server在本地回环网口的一个系统选择的端口上listen。它常用于端到端的HTTP测试。

type Server struct {
    URL      string // base URL of form http://ipaddr:port with no trailing slash
    Listener net.Listener

    // TLS is the optional TLS configuration, populated with a new config
    // after TLS is started. If set on an unstarted server before StartTLS
    // is called, existing fields are copied into the new config.
    TLS *tls.Config

    // Config may be changed after calling NewUnstartedServer and
    // before Start or StartTLS.
    Config *http.Server
}

func NewServer(handler http.Handler) *Server

func (*Server) Close() error

4、httptest.Server实战

下面代码创建了一个临时Http Server,返回简单的Hello应答:

    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "Hello, client")
    }))
    defer ts.Close()

    res, err := http.Get(ts.URL)
    if err != nil {
        log.Fatal(err)
    }

    greeting, err := ioutil.ReadAll(res.Body)
    res.Body.Close()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%s", greeting)

5、httptest.ResponseRecorder

httptest.ResponseRecorder是http.ResponseWriter的一个实现,用来记录变化,用在测试的后续检视中。

type ResponseRecorder struct {
    Code      int           // the HTTP response code from WriteHeader
    HeaderMap http.Header   // the HTTP response headers
    Body      *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
    Flushed   bool
}

6、httptest.ResponseRecorder实战

向一个HTTP handler中传入一个ResponseRecorder,通过它我们可以来检视生成的应答。

    handler := func(w http.ResponseWriter, r *http.Request) {
        http.Error(w, "something failed", http.StatusInternalServerError)
    }

    req, err := http.NewRequest("GET", "http://example.com/foo", nil)
    if err != nil {
        log.Fatal(err)
    }

    w := httptest.NewRecorder()
    handler(w, req)

    fmt.Printf("%d – %s", w.Code, w.Body.String())

7、竞争检测(race detection)

当两个goroutine并发访问同一个变量,且至少一个goroutine对变量进行写操作时,就会发生数据竞争(data race)。

为了协助诊断这种bug,Go提供了一个内置的数据竞争检测工具。

通过传入-race选项,go tool就可以启动竞争检测。

$ go test -race mypkg    // to test the package
$ go run -race mysrc.go  // to run the source file
$ go build -race mycmd   // to build the command
$ go install -race mypkg // to install the package

注:一个数据竞争检测的例子

例子代码:

//testrace.go

package main

import "fmt"
import "time"

func main() {
        var i int = 0
        go func() {
                for {
                        i++
                        fmt.Println("subroutine: i = ", i)
                        time.Sleep(1 * time.Second)
                }
        }()

        for {
                i++
                fmt.Println("mainroutine: i = ", i)
                time.Sleep(1 * time.Second)
        }
}

$go run -race testrace.go
mainroutine: i =  1
==================
WARNING: DATA RACE
Read by goroutine 5:
  main.func·001()
      /Users/tony/Test/Go/testrace.go:10 +0×49

Previous write by main goroutine:
  main.main()
      /Users/tony/Test/Go/testrace.go:17 +0xd5

Goroutine 5 (running) created at:
  main.main()
      /Users/tony/Test/Go/testrace.go:14 +0xaf
==================
subroutine: i =  2
mainroutine: i =  3
subroutine: i =  4
mainroutine: i =  5
subroutine: i =  6
mainroutine: i =  7
subroutine: i =  8

8、测试并发(testing with concurrency)

当测试并发代码时,总会有一种使用sleep的冲动。大多时间里,使用sleep既简单又有效。

但大多数时间不是”总是“。

我们可以使用Go的并发原语让那些奇怪不靠谱的sleep驱动的测试更加值得信赖。

9、使用静态分析工具vet查找错误

vet工具用于检测代码中程序员犯的常见错误:
    – 错误的printf格式
    – 错误的构建tag
    – 在闭包中使用错误的range循环变量
    – 无用的赋值操作
    – 无法到达的代码
    – 错误使用mutex
    等等。

使用方法:
    go vet [package]

10、从内部测试

golang中大多数测试代码都是被测试包的源码的一部分。这意味着测试代码可以访问包种未导出的符号以及内部逻辑。就像我们之前看到的那样。

注:比如$GOROOT/src/pkg/path/path_test.go与path.go都在path这个包下。

11、从外部测试

有些时候,你需要从被测包的外部对被测包进行测试,比如测试代码在package foo_test下,而不是在package foo下。

这样可以打破依赖循环,比如:

    – testing包使用fmt
    – fmt包的测试代码还必须导入testing包
    – 于是,fmt包的测试代码放在fmt_test包下,这样既可以导入testing包,也可以同时导入fmt包。

12、Mocks和fakes

通过在代码中使用interface,Go可以避免使用mock和fake测试机制。

例如,如果你正在编写一个文件格式解析器,不要这样设计函数:

func Parser(f *os.File) error

作为替代,你可以编写一个接受interface类型的函数:

func Parser(r io.Reader) error

和bytes.Buffer、strings.Reader一样,*os.File也实现了io.Reader接口。

13、子进程测试

有些时候,你需要测试的是一个进程的行为,而不仅仅是一个函数。例如:

func Crasher() {
    fmt.Println("Going down in flames!")
    os.Exit(1)
}

为了测试上面的代码,我们将测试程序本身作为一个子进程进行测试:

func TestCrasher(t *testing.T) {
    if os.Getenv("BE_CRASHER") == "1" {
        Crasher()
        return
    }
    cmd := exec.Command(os.Args[0], "-test.run=TestCrasher")
    cmd.Env = append(os.Environ(), "BE_CRASHER=1")
    err := cmd.Run()
    if e, ok := err.(*exec.ExitError); ok && !e.Success() {
        return
    }
    t.Fatalf("process ran with err %v, want exit status 1", err)
}

基于Go开发的IM与推送:

goim 是一个支持集群的im及实时推送服务(支持websocket,http和tcp协议)

特性:

  • 轻量级

  • 高性能

  • 纯Golang实现

  • 支持单个、多个以及广播消息推送

  • 支持单个Key多个订阅者(可限制订阅者最大人数)

  • 心跳支持(应用心跳和tcp、keepalive、http log pulling)

  • 支持安全验证(未授权用户不能订阅)

  • 多协议支持(websocket,tcp,http)

  • 可拓扑的架构(job、logic模块可动态无限扩展)

  • 基于Kafka做异步消息推送

系统架构:

Goim.png

0 0
原创粉丝点击