比较Go语言中的Memcache包
首先
使用Go语言编写的程序通常都可以在原样运行的情况下具备足够的高速性,但在某些情况下,可以使用缓存来加速运行。
Go 中存在一个强大的本地缓存包叫做 gocache,但是使用 gocache 有时会出现以下问题:
* 无法在主机、程序或服务之间共享缓存
* 程序结束后缓存会被清空
为了避免这些问题,通常我们会使用gocache来在本地缓存中保存可处理的缓存,而其他缓存则会使用memcache或redis等来存储。因此,我们将比较一下memcache包。
Go的memcache包
首先,我在GitHub上搜索了与Go的memcache包有关的存储库。结果发现,bradfitz/gomemcache获得了最多的星标,所以我们来试试这个。 (以下均为Go 1.5.1。)
导入包
$ go get github.com/bradfitz/gomemcache/memcache
执行示例
import (
"github.com/bradfitz/gomemcache/memcache"
)
func main() {
mc := memcache.New("10.0.0.1:11211", "10.0.0.2:11211", "10.0.0.3:11212")
mc.Set(&memcache.Item{Key: "foo", Value: []byte("my value")})
it, err := mc.Get("foo")
...
}
非常简单易懂。
既然如此,我们来编写并测量一下这个包的性能基准测试,顺便看看它的表现如何。
这也是Go的一个优势,能够轻松进行此类基准测试。
package main
import(
"testing"
"github.com/bradfitz/gomemcache/memcache"
)
func BenchmarkMemLib(b *testing.B){
b.ReportAllocs()
b.ResetTimer()
mc := memcache.New("127.0.0.1:11211")
for i:= 0; i < b.N; i++{
mc.Set(&memcache.Item{Key:"foo", Value:[]byte("unko")})
mc.Get("foo")
}
}
运行基准测试。
$ go test mem_test.go -bench=Bench
testing: warning: no tests to run
PASS
BenchmarkMemLib 20000 62516 ns/op 3169 B/op 60 allocs/op
ok command-line-arguments 1.881s
尽管这个简单的设置和获取基准测试,看起来有很多的分配……
与其他包装进行比较。
接下来,最多星星的是rainycape/memcache。README中还写着为了进一步提高速度而fork了bradfitz/gomemcache,所以可以期待一下。
我将导入软件包。
$ go get github.com/rainycape/memcache
运行示例
import (
"github.com/rainycape/memcache"
)
func main() {
mc, _ := memcache.New("10.0.0.1:11211", "10.0.0.2:11211", "10.0.0.3:11212")
mc.Set(&memcache.Item{Key: "foo", Value: []byte("my value")})
it, err := mc.Get("foo")
...
}
与bradfitz/gomemcache几乎相同,但是这里的New()返回的是多个值(memcache服务器都可以指定多个)。
让我们将此项也添加到先前的基准测试中进行测量。
BenchmarkMemLib2是一个附加性能基准测试。
package main
import(
"testing"
"github.com/bradfitz/gomemcache/memcache"
memcache2 "github.com/rainycape/memcache"
)
func BenchmarkMemLib(b *testing.B){
b.ReportAllocs()
b.ResetTimer()
mc := memcache.New("127.0.0.1:11211")
for i:= 0; i < b.N; i++{
mc.Set(&memcache.Item{Key:"foo", Value:[]byte("unko")})
mc.Get("foo")
}
}
func BenchmarkMemLib2(b *testing.B){
b.ReportAllocs()
b.ResetTimer()
mc,_ := memcache2.New("127.0.0.1:11211")
for i:= 0; i < b.N; i++{
mc.Set(&memcache2.Item{Key:"foo", Value:[]byte("unko")})
mc.Get("foo")
}
}
执行结果
$ go test mem_test.go -bench=Bench
testing: warning: no tests to run
PASS
BenchmarkMemLib 20000 63543 ns/op 3169 B/op 60 allocs/op
BenchmarkMemLib2 20000 64594 ns/op 208 B/op 7 allocs/op
ok command-line-arguments 3.854s
嗯,分配方式有很大的改变,但处理速度稍微变慢了。
除了访问memcache服务器外,可能是其他处理过程增加了负载。
自己创作
考虑到这一点,我决定亲自尝试写一下(在处理Memcache方面,vites的源代码非常有参考价值)。
package main
import (
"bufio"
"fmt"
"net"
"strings"
"strconv"
)
type Memcache struct {
conn net.Conn
buffered bufio.ReadWriter
}
func Mem(addr string) (conn *Memcache, err error) {
nc, err := net.Dial("tcp", addr)
if err != nil {
return nil, err
}
return &Memcache{
conn: nc,
buffered: bufio.ReadWriter{
Reader: bufio.NewReader(nc),
Writer: bufio.NewWriter(nc),
},
}, err
}
func (mc *Memcache) get(key string) (result []byte, err error) {
_, err = mc.buffered.WriteString("get "+key+"\n")
if err == nil {
err = mc.buffered.Flush()
if err == nil {
for {
b,_,err := mc.buffered.ReadLine()
l := string(b)
if err == nil {
if strings.HasPrefix(l, "END") {
break
}
if strings.Contains(l, "ERROR") {
panic("ERROR")
}
if !strings.HasPrefix(l, "VALUE") {
result = append(result, l...)
result = append(result, '\n')
}
} else {
panic(err)
}
}
} else {
panic(err)
}
}
return result, err
}
func (mc *Memcache) set(key string, value []byte) (err error) {
_, err = mc.buffered.WriteString("set "+key+" 0 0 "+strconv.Itoa(len(value))+"\r\n")
if err == nil {
v := append(value,"\r\n"...)
_, err = mc.buffered.Write(v)
if err != nil {
panic(err)
}
err = mc.buffered.Flush()
if err == nil {
mc.buffered.ReadLine()
}
}
return err
}
func main() {
m, err := Mem("127.0.0.1:11211")
if err == nil {
err = m.set("foo",[]byte("unko"))
if err == nil {
res,merr := m.get("foo")
if merr == nil {
fmt.Printf("%s", res)
}
}
}
defer m.conn.Close()
}
进行基准测试。
BenchmarkMemOrigin是我自己编写的Memcache连接程序的结果。
package main
import(
"testing"
"github.com/bradfitz/gomemcache/memcache"
memcache2 "github.com/rainycape/memcache"
)
func BenchmarkMemLib(b *testing.B){
b.ReportAllocs()
b.ResetTimer()
mc := memcache.New("127.0.0.1:11211")
for i:= 0; i < b.N; i++{
mc.Set(&memcache.Item{Key:"foo", Value:[]byte("unko")})
mc.Get("foo")
}
}
func BenchmarkMemLib2(b *testing.B){
b.ReportAllocs()
b.ResetTimer()
mc,_ := memcache2.New("127.0.0.1:11211")
for i:= 0; i < b.N; i++{
mc.Set(&memcache2.Item{Key:"foo", Value:[]byte("unko")})
mc.Get("foo")
}
}
func BenchmarkMemOrigin(b *testing.B){
b.ReportAllocs()
b.ResetTimer()
m, _ := Mem("127.0.0.1:11211")
defer m.conn.Close()
for i:= 0; i < b.N; i++{
m.set("foo",[]byte("unko"))
m.get("foo")
}
}
运行结果
$ go test mem.go mem_test.go -bench=Bench
testing: warning: no tests to run
PASS
BenchmarkMemLib 20000 62850 ns/op 3169 B/op 60 allocs/op
BenchmarkMemLib2 20000 64663 ns/op 208 B/op 7 allocs/op
BenchmarkMemOrigin 50000 33910 ns/op 96 B/op 6 allocs/op
ok command-line-arguments 5.879s
处理速度大约减慢了一半左右。
我认为不一定必须自己制作,因为包含了错误处理和类型转换等安全措施,可以安全地使用。但是,对于像memcache的Set和Get这样的处理,自己制作可能在性能方面更好。
因为时间不够,我无法跟进每个包裹的瓶颈问题,所以将在以后进行调查(;´Д`)