golang sortedset 实现(基于redis skiplist)

在游戏中排行榜是很常见的需求,之前一直使用的是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, 0, 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, z.Count() == 2, "after add duplicate Count error")
	score, ex = z.Score("12")
	assert(t, score == 2, "after add Score error")
	z.Rem("12")
	assert(t, z.Count() == 1, "after rem Count error")
	score, ex = z.Score("12")
	assert(t, 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", "33"}), s)
	assert(t, equal(z.RangeByScore(10, 5), []string{}), s)
	assert(t, equal(z.RangeByScore(10, 0), []string{"33", "22", "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, 3), []string{"1", "5", "9"}), "Range1 error")
	assert(t, equal(z.Range(3, 1), []string{"9", "5", "1"}), "Range2 error")
	assert(t, equal(z.RevRange(1, 2), []string{"9", "5"}), "RevRange1 error")
	assert(t, equal(z.RevRange(3, 2), []string{"1", "5"}), "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, z.Count() == 1, "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, "1")
	z.Add(2, "2")
	z.Add(3, "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")
}
时间: 2024-12-28 01:48:17

golang sortedset 实现(基于redis skiplist)的相关文章

基于 Redis 构建数据服务

今天我们来聊聊如何基于redis数据库扩展数据服务,如何实现分片(sharding)以及高可用(high availability). 分布式系统不存在完美的设计,处处都体现了trade off. 因此我们在开始正文前,需要确定后续的讨论原则,仍然以分布式系统设计中的CAP原则为例.由于主角是redis,那性能表现肯定是最高设计目标,之后讨论过程中的所有抉择,都会优先考虑CAP中的AP性质. 两个点按顺序来,先看分片. 何谓分片?简单来说,就是对单机redis做水平扩展. 当然,做游戏的同学可能

基于Redis的三种分布式爬虫策略

前言: 爬虫是偏IO型的任务,分布式爬虫的实现难度比分布式计算和分布式存储简单得多. 个人以为分布式爬虫需要考虑的点主要有以下几个: 爬虫任务的统一调度 爬虫任务的统一去重 存储问题 速度问题 足够"健壮"的情况下实现起来越简单/方便越好 最好支持"断点续爬"功能 Python分布式爬虫比较常用的应该是scrapy框架加上Redis内存数据库,中间的调度任务等用scrapy-redis模块实现. 此处简单介绍一下基于Redis的三种分布式策略,其实它们之间还是很相似

基于redis AE的异步网络框架

最近一直在研究redis的源码,redis的高效率令人佩服. 在我们的linux机器上,cpu型号为, Intel(R) Pentium(R) CPU G630 @ 2.70GHz Intel(R) Pentium(R) CPU G630 @ 2.70GHz 上 set,get 都能达到每秒钟15W的请求处理量,真是佩服这代码的效率. 前几篇文章,主要是介绍了基本的代码,比如字符串处理,链表处理,hash等.这篇文章介绍网络的核心,基于事件反映的异步网络框架. 异步网络处理,是基于epoll的.

基于Redis的分布式锁到底安全吗(上)?

网上有关Redis分布式锁的文章可谓多如牛毛了,不信的话你可以拿关键词"Redis 分布式锁"随便到哪个搜索引擎上去搜索一下就知道了.这些文章的思路大体相近,给出的实现算法也看似合乎逻辑,但当我们着手去实现它们的时候,却发现如果你越是仔细推敲,疑虑也就越来越多. 实际上,大概在一年以前,关于Redis分布式锁的安全性问题,在分布式系统专家Martin Kleppmann和Redis的作者antirez之间就发生过一场争论.由于对这个问题一直以来比较关注,所以我前些日子仔细阅读了与这场争

基于redis分布式缓存实现(新浪微博案例)

第一:Redis 是什么? Redis是基于内存.可持久化的日志型.Key-Value数据库 高性能存储系统,并提供多种语言的API. 第二:出现背景 数据结构(Data Structure)需求越来越多, 但memcache中没有, 影响开发效率 性能需求, 随着读操作的量的上升需要解决,经历的过程有: 数据库读写分离(M/S)–>数据库使用多个Slave–>增加Cache (memcache)–>转到Redis 解决写的问题: 水平拆分,对表的拆分,将有的用户放在这个表,有的用户放在

基于Redis实现分布式消息队列(1)

1.为什么需要消息队列? 当系统中出现"生产"和"消费"的速度或稳定性等因素不一致的时候,就需要消息队列,作为抽象层,弥合双方的差异. 举个例子:业务系统触发短信发送申请,但短信发送模块速度跟不上,需要将来不及处理的消息暂存一下,缓冲压力. 再举个例子:调远程系统下订单成本较高,且因为网络等因素,不稳定,攒一批一起发送. 再举个栗子,交互模块5:00到24:00和电商系统联通,和内部ERP断开.1:00到4:00和ERP联通,和电商系统断开. 再举个例子,服务员点菜

基于Redis的在线用户列表解决方案

前言: 由于项目需求,需要在集群环境下实现在线用户列表的功能,并依靠在线列表实现用户单一登陆(同一账户只能一处登陆)功能: 在单机环境下,在线列表的实现方案可以采用SessionListener来完成,当有Session创建和销毁的时候做相应的操作即可完成功能及将相应的Session的引用存放于内存中,由于持有了所有的Session的引用,故可以方便的实现用户单一登陆的功能(比如在第二次登陆的时候使之前登陆的账户所在的Session失效). 而在集群环境下,由于用户的请求可能分布在不同的Web服

基于redis 内存数据库简单使用

在ecplise中使用内存数据的客端户,前提要准备要下载两个jar包 commons-pool2-2.0.jar jedis-2.4.2.jar 前提准备做好了,那我们就开启redis的服务,打开一个命令窗口输入如下命令:redis-server  或redis-server  redis根目\redis.conf 服务器已经开启了,注意端号是6377 2.在eclipse 创建一个项目,把redist需要的包导入项目中 3.写一个Jedis工具类 public class JedisUtil 

基于redis缓存数据库实现lnmp架构高速访问

how-缓存加速 使用nosql数据库: 如redis,mongodb,memcache what-redis redis 是一个高性能的 key-value 数据库. 1) redis 的出现,很大程度弥补了memcached 这类 key-value 存储的不足(只能存入内存). 2)它支持的数据类型比memcache多,包括了 Python,Ruby,Erlang,PHP 客户端... 3)Redis 的所有数据都是保存在内存中,两种同步模式 A>半持久化模式:RDB(全量同步) i>R