在游戏中排行榜是很常见的需求,之前一直使用的是redis的sortedset,刚好看到一个lua的实现,于是就移植到golang版本,github地址:https://github.com/Skycrab/go-zset,使用方法和redis 的cli命令基本一致。
使用了cgo封装,skiplist的实现基本和redis的一致,虽然cgo用起来很方便,但涉及到C中回调go时比较费事,
感觉还不如lua来得清爽。
go-zet并没有提供持久化的方法,我在https://github.com/Skycrab/cham/blob/master/lib/zset/zset.go中提供了持久化。主要是没有好的持久化方案,不像redis使用rdb格式。在cham中,和redis一样,fork后写文件,
只不过我使用的是json格式。
//use fork snapshot to save func (z *zset) BgStore(name string) error { f,err := helper.LockFile(name,true) if err != nil { return err } pid,errno := helper.Fork() if errno != 0 { return errors.New("fork error," + errno.Error()) } //child if pid == 0 { buf := bufio.NewWriter(f) encoder := json.NewEncoder(buf) encoder.Encode(z.tbl) buf.Flush() f.Close() os.Exit(0) } return nil }
helper代码如下:
package helper import ( "os" "runtime" "syscall" ) func Fork() (int,syscall.Errno) { r1,r2,err := syscall.RawSyscall(syscall.SYS_FORK,0) if err != 0 { return 0,err } if runtime.GOOS == "darwin" && r2 == 1 { r1 = 1 } return int(r1),0 } // referer http://ikarishinjieva.github.io/blog/blog/2014/03/20/go-file-lock/ func LockFile(name string,truncate bool) (*os.File,error) { flag := os.O_RDWR | os.O_CREATE if truncate { flag |= os.O_TRUNC } f,err := os.OpenFile(name,flag,0666) if err != nil { return nil,err } if err := syscall.Flock(int(f.Fd()),syscall.LOCK_EX|syscall.LOCK_NB); err != nil { f.Close() return nil,err } return f,nil }
看看test基本就知道用法了
package zset import ( "fmt" "testing" "time" ) func equal(a []string,b []string) bool { if len(a) != len(b) { return false } for i := 0; i < len(a); i++ { if a[i] != b[i] { return false } } return true } func assert(t *testing.T,ok bool,s string) { if !ok { t.Error(s) } } func TestBase(t *testing.T) { z := New() assert(t,z.Count() == 0,"empty Count error") z.Add(1,"12") z.Add(1,"32") assert(t,z.Count() == 2,"not empty Count error") var score float64 var ex bool score,ex = z.score("12") assert(t,score == 1,"score error") z.Add(2,"12") assert(t,"after add duplicate Count error") score,score == 2,"after add score error") z.Rem("12") assert(t,z.Count() == 1,"after rem Count error") score,ex == false,"not exist score error") fmt.Println("") } func TestRangeByscore(t *testing.T) { z := New() z.Add(2,"22") z.Add(1,"11") z.Add(3,"33") s := "TestRangeByscore error" assert(t,equal(z.RangeByscore(2,3),[]string{"22","33"}),s) assert(t,equal(z.RangeByscore(0,5),[]string{"11","22",equal(z.RangeByscore(10,[]string{}),0),[]string{"33","11"}),s) } func TestRange(t *testing.T) { z := New() z.Add(100.1,"1") z.Add(100.9,"9") z.Add(100.5,"5") assert(t,equal(z.Range(1,[]string{"1","5","9"}),"Range1 error") assert(t,equal(z.Range(3,1),[]string{"9","1"}),"Range2 error") assert(t,equal(z.RevRange(1,2),"5"}),"RevRange1 error") assert(t,equal(z.RevRange(3,"RevRange2 error") } func TestRank(t *testing.T) { z := New() assert(t,z.Rank("kehan") == 0,"Rank empty error") z.Add(1111.1111,"kehan") assert(t,z.Rank("kehan") == 1,"Rank error") z.Add(222.2222,"lwy") assert(t,z.Rank("kehan") == 2,"Rank 2 error") assert(t,z.RevRank("kehan") == 1,"RevRank error") } func TestLimit(t *testing.T) { z := New() z.Add(1,"1") z.Add(2,"2") z.Add(3,"3") z.Limit(1) assert(t,"Limit error") assert(t,z.Rank("3") == 0,"Limit Rank error") z.Add(4.4,"4") z.Add(5.5,"5") z.Add(0.5,"0.5") z.Dump() assert(t,z.RevLimit(4) == 0,"RevLimit error") assert(t,z.RevLimit(0) == 4,"RevLimit2 error") } func TestStore(t *testing.T) { z := New() z.Add(1,"3") z.BgStore("zdb.json") go func() { time.Sleep(time.Second * 2) fmt.Println("continue add") z.Add(4,"4") z.Add(5,"5") }() } func TestReStore(t *testing.T) { z := New() assert(t,len(z.tbl) == 0,"restore error") z.ReStore("zdb.json") fmt.Println(z.tbl) assert(t,len(z.tbl) != 0,"restore2 error") assert(t,z.Count() == len(z.tbl),"restore3 error") }