go语言一些开源的package拾遗
Table of Contents
- 日志相关
- 解析命令行参数的工具 gopkg.in/alecthomas/kingpin.v2
- github.com/spf13/cobra 命令行相关
- 读取配置文件 相关(json ,yaml,环境变量,etcd,命令行参数)
- github.com/dgryski/go-jump
- github.com/pquerna/ffjson
- github.com/tools/godep
- github.com/gogo/protobuf/proto
- github.com/stretchr/testify/assert
- leveldb 的hashmap cache与LRU 实现可以参考使用
- leveldb 的memdb 实现分析
- 乱入之一个位操作的小技巧
- 乱入一段 UUID 生成算法(从fabric/common/util来)
- github.com/eapache/queue
- github.com/fsouza/go-dockerclient docker的go客户端
- github.com/gocraft/web 一个轻量http框架
- github.com/mitchellh/mapstructure
在看一些golang 的开源项目的时候 ,看到他们用到的一些第三方库,感觉还不错
记下来以备后用,看多少加多少
最近在看https://github.com/hyperledger/fabric
日志相关
github.com/cihub/seelog
github.com/op/go-logging
可以支持多种backend ,包括seelog 、文件等
可以实现按模块定制日志输出级别(通过正则匹配package名实现)
使用示例 github.com/hyperledger/fabric/common/flogging/logging.go
解析命令行参数的工具 gopkg.in/alecthomas/kingpin.v2
轻松实现以下格式的命令行参数
usage: main [<flags>] <command> [<args> …]
Flags:
–help Show context-sensitive help (also try –help-long and –help-man).
-d, –dir="." directory of data
Commands:
help [<command>…]
Show help.
accountlist [<n>]
get all account List.
short
get short info about our chainblock system.
account <name>
get account info.
blocklist
get all blocklist.
block <name>
get block info.
var ( dir = kingpin.Flag("dir", "directory of data").Short('d').Default(".").String() accountListCMD = kingpin.Command("accountlist", "get all account List.") accountListLimit = accountListCMD.Arg("n", "limit account count").Int() shortCMD = kingpin.Command("short", "get short info about our chainblock system.") accountCMD = kingpin.Command("account", "get account info.") accountName = accountCMD.Arg("name", "account name").Required().String() blockListCMD = kingpin.Command("blocklist", "get all blocklist.") blockCMD = kingpin.Command("block", "get block info.") blockName = blockCMD.Arg("name", "block name").Required().String() ) func main() { kingpin.Parse() switch kingpin.MustParse(kingpin.Parse(), nil) { // Register user case shortCMD.FullCommand(): printChain(*dir) return case accountListCMD.FullCommand(): printAccountList(*dir, *accountListLimit) return case blockListCMD.FullCommand(): printBlockList(*dir) return case blockCMD.FullCommand(): if blockName == nil || *blockName == "" { fmt.Printf("please give an account name like this:%s %s %s\n", os.Args[0], os.Args[1], "block{blockid}") return } printBlock(*dir, *blockName) return case accountCMD.FullCommand(): if accountName == nil || *accountName == "" { fmt.Printf("please give an account name like this:%s %s %s\n", os.Args[0], os.Args[1], "account{accountid}") return } printAccount(*dir, *accountName) return }
github.com/spf13/cobra 命令行相关
可以参考 https://studygolang.com/articles/7588
似乎是可以用这个工具帮你生成一部分代码,然后去填充每个具体的命令的实现
demo
package main import ( "fmt" "strings" "github.com/spf13/cobra" ) func main() { var echoTimes int var cmdPrint = &cobra.Command{ Use: "print [string to print]", Short: "Print anything to the screen", Long: `print is for printing anything back to the screen. For many years people have printed back to the screen. `, Run: func(cmd *cobra.Command, args []string) { fmt.Println("Print: " + strings.Join(args, " ")) }, } var cmdEcho = &cobra.Command{ Use: "echo [string to echo]", Short: "Echo anything to the screen", Long: `echo is for echoing anything back. Echo works a lot like print, except it has a child command. `, Run: func(cmd *cobra.Command, args []string) { fmt.Println("Print: " + strings.Join(args, " ")) }, } var cmdTimes = &cobra.Command{ Use: "times [# times] [string to echo]", Short: "Echo anything to the screen more times", Long: `echo things multiple times back to the user by providing a count and a string.`, Run: func(cmd *cobra.Command, args []string) { for i := 0; i < echoTimes; i++ { fmt.Println("Echo: " + strings.Join(args, " ")) } }, } cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input") var rootCmd = &cobra.Command{Use: "app"} rootCmd.AddCommand(cmdPrint, cmdEcho) cmdEcho.AddCommand(cmdTimes) rootCmd.Execute() }
读取配置文件 相关(json ,yaml,环境变量,etcd,命令行参数)
github.com/spf13/viper
可以设定默认值,以各种方式读取配置,各个配置可以相互覆盖,甚至可以通过e,tcd、Consul等方式进行读取
也自以可选的进行加密
github.com/dgryski/go-jump
golang 使用memcached 集群时有用到一致性哈希算法,这是一种实现
纯计算 内在占用少
github.com/pquerna/ffjson
json decode encode 加速
github.com/tools/godep
go 包版本管理工具,
github.com/gogo/protobuf/proto
protobuf decode encode 加速
可以实现为每条协议 生成 Marshaler UnMarshaler Size 等函数
生成减少一次内在copy ,并且序列化反序列化的代码在编译期就生成 ,速度提升
default: @mkdir -p ../pb/ @awk -v "n=line-number"\ -v 'line1=import "github.com/gogo/protobuf/gogoproto/gogo.proto"; '\ -v 'line2=option (gogoproto.marshaler_all) = true;'\ -v 'line3=option (gogoproto.sizer_all) = true;'\ -v 'line4=option (gogoproto.unmarshaler_all) = true;'\ '(NR==2) { print line1;print line2;print line3;print line4 } 1' base.proto>base2.proto protoc --proto_path=$$GOPATH/src/github.com/gogo/protobuf/protobuf:$$GOPATH/src/:. --gogo_out=. base2.proto @mv base2.pb.go ../pb/base.pb.go @rm -f base2.proto cp -f base.proto ../pb/base_bak.proto
github.com/stretchr/testify/assert
写测试用的工具 demo:
assert.True(t, true, "should be true")
leveldb 的hashmap cache与LRU 实现可以参考使用
https://github.com/syndtr/goleveldb/blob/master/leveldb/cache/cache.go
目前似乎key 只支持uint64,不支持string
支持namespace,即一个 namespace 和key 两个参数才惟一确定一条数据
hash 算法全用 murmur
hash 算法将namespace与key 与seed 算出一个hash值(uint32),
然后根据hash值的低位4字节 分配到一个桶内(bucket),后期随着桶内元素的增加,会增加桶的数量
重新进行hash,
type mNode struct { buckets []unsafe.Pointer // []*mBucket n个桶桶 mask uint32 pred unsafe.Pointer // *mNode resizeInProgess int32 overflow int32 growThreshold int32 shrinkThreshold int32 } type mBucket struct { mu sync.Mutex node []*Node frozen bool } // Node is a 'cache node'. type Node struct { r *Cache hash uint32 ns, key uint64 mu sync.Mutex size int value Value // 真正存数据 ref int32 onDel []func() CacheData unsafe.Pointer }
demo
c := cache.NewCache(nil) h := c.Get(1, 11, func() (int, cache.Value) { return 5, "hello" }) // namespace=1,key==11 fmt.Println(h.Value().(string)) h.Release() // 用完就得release() h = c.Get(1, 11, nil) fmt.Println(h.Value().(string)) h.Release() // 用完就得release()
var c *cache.Cache c = cache.NewCache(cache.NewLRU(20)) // 这种情况支持lru的hash ,当占用内存达到20时,将会采用lru算法清除最久没使用的数据 for i := 0; i < 1000; i++ { c.Get(1, uint64(i), func() (int, cache.Value) { return 4, "world" + strconv.Itoa(i) }).Release() }
如果不需要namespace ,即所有的key在同一个namespace下,
则以用NamespaceGetter包装一下,以后Get 只需要 传key 不需要传namespace
var c *cache.Cache c = cache.NewCache(cache.NewLRU(20)) // 这种情况支持lru的hash ,当占用内存达到20时,将会采用lru算法清除最久没使用的数据 geter:=cache.NamespaceGetter{Cache: c, NS: 0} geter.Get(11, func() (int, cache.Value) { return 5, "hello" }).Release() // namespace=1,key==11 geter.Get(11).Release()
leveldb 的memdb 实现分析
关于跳表可以参考 http://blog.sina.com.cn/s/blog_72995dcc01017w1t.html,%E4%B8%8D%E5%86%8D
跳表是一种随机化的数据结构,目前开源软件 Redis 和 LevelDB 都有用到它,
它的效率和红黑树以及 AVL 树不相上下O(logn),但跳表的原理相当简单,只要你能熟练操作链表,
就能轻松实现一个 SkipList
主要实现使用skiplist 跳转来实现
type DB struct { cmp comparer.BasicComparer rnd *rand.Rand mu sync.RWMutex kvData []byte // key and value 都 append到此,通过偏移量来定位key value nodeData []int // 记录key value 的偏移量等信息 prevNode [tMaxHeight]int maxHeight int n int kvSize int }
优点 ,内存连续,GC压力应该小一点,缺点似乎单从Put Get 来看,并不如golang 自带的map 快
当然leveldb可能有其他方面的考虑,如iterator等
这个benchmark中, 所有带2的都是RWMutex+map 的实现,其他则是goleveldb.memdb的
可以看出来,memdb比map 大概慢一个数量级,rwMutex+map能满足你需求的时候 就不要考虑memdb了
BenchmarkPut2 1000000 1059 ns/op
BenchmarkPutRandom2 20000000 81.0 ns/op
BenchmarkGet2 10000000 110 ns/op
BenchmarkGetRandom2 3000000 462 ns/op
BenchmarkPut 1000000 1584 ns/op
BenchmarkPutRandom 1000000 2969 ns/op
BenchmarkGet 1000000 1863 ns/op
BenchmarkGetRandom 1000000 3831 ns/op
乱入之一个位操作的小技巧
声明位常量的时候可以这样声明
type Strict uint const ( StrictManifest Strict = 1 << iota // 1 StrictJournalChecksum // 2 StrictJournal // 4 )
乱入一段 UUID 生成算法(从fabric/common/util来)
// GenerateBytesUUID returns a UUID based on RFC 4122 returning the generated bytes func GenerateBytesUUID() []byte { uuid := make([]byte, 16) _, err := io.ReadFull(rand.Reader, uuid) if err != nil { panic(fmt.Sprintf("Error generating UUID: %s", err)) } // variant bits; see section 4.1.1 uuid[8] = uuid[8]&^0xc0 | 0x80 // version 4 (pseudo-random); see section 4.1.3 uuid[6] = uuid[6]&^0xf0 | 0x40 return uuid } // GenerateIntUUID returns a UUID based on RFC 4122 returning a big.Int func GenerateIntUUID() *big.Int { uuid := GenerateBytesUUID() z := big.NewInt(0) return z.SetBytes(uuid) } // GenerateUUID returns a UUID based on RFC 4122 func GenerateUUID() string { uuid := GenerateBytesUUID() return idBytesToStr(uuid) } func idBytesToStr(id []byte) string { return fmt.Sprintf("%x-%x-%x-%x-%x", id[0:4], id[4:6], id[6:8], id[8:10], id[10:]) }
github.com/eapache/queue
一个简单的非并发安全的queue(非常简单)
github.com/fsouza/go-dockerclient docker的go客户端
demo:
import ( "fmt" "github.com/fsouza/go-dockerclient" ) func main() { endpoint := "unix:///var/run/docker.sock" client, _ := docker.NewClient(endpoint) imgs, _ := client.ListImages(docker.ListImagesOptions{All: false}) for _, img := range imgs { fmt.Println("ID: ", img.ID) fmt.Println("RepoTags: ", img.RepoTags) fmt.Println("Created: ", img.Created) fmt.Println("Size: ", img.Size) fmt.Println("VirtualSize: ", img.VirtualSize) fmt.Println("ParentId: ", img.ParentID) } }
github.com/gocraft/web 一个轻量http框架
package main import ( "github.com/gocraft/web" "fmt" "net/http" "strings" ) type Context struct { HelloCount int } func (c *Context) SetHelloCount(rw web.ResponseWriter, req *web.Request, next web.NextMiddlewareFunc) { c.HelloCount = 3 next(rw, req) } func (c *Context) SayHello(rw web.ResponseWriter, req *web.Request) { fmt.Fprint(rw, strings.Repeat("Hello ", c.HelloCount), "World!") } func main() { router := web.New(Context{}). // Create your router Middleware(web.LoggerMiddleware). // Use some included middleware Middleware(web.ShowErrorsMiddleware). // ... Middleware((*Context).SetHelloCount). // Your own middleware! Get("/", (*Context).SayHello) // Add a route http.ListenAndServe("localhost:3000", router) // Start the server! }
github.com/mitchellh/mapstructure
实现 了 将一个 map[string]interface{} 转成结构体的功能
比如遇到以下问题: 一串json ,在未解析json某些字段之前,并不知道具体还有哪些字段
则可以将json 先转成map[string]interface{},然后判断之后利用此包再转成具体的struct