DB

Golang使用FoundationDB计数器

Posted by 聪少 on 2018-04-27

工作中利用foundationDB实现了一套自己的IM服务,有针对于消息的统计计数,
官方对golang使用计数器的文档对负数的处理说的含糊不清,废话不多说直接上代码,坑都写在里面了,请欣赏:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

package main

import (
"encoding/binary"
"fmt"
"log"
"time"

"github.com/apple/foundationdb/bindings/go/src/fdb"
)

func main() {
fdb.MustAPIVersion(510)
db := fdb.MustOpenDefault()
start := time.Now()
_, err := db.Transact(func(tr fdb.Transaction) (ret interface{}, e error) {
for index := 0; index < 20; index++ {
value := make([]byte, binary.MaxVarintLen64)
var v int64 = -1
// golang 大小端接口都是无符号的,所以要进行强转(被坑了很久)
binary.LittleEndian.PutUint64(value, uint64(v))
// Add参数的value要求必须是小端存储
tr.Add(fdb.Key("hello"), value)
}
return
})
if err != nil {
log.Fatalf("Unable to set FDB database value (%v)", err)
}

log.Println("入库耗时", time.Now().Sub(start).Seconds())

// 查询
ret, err := db.Transact(func(tr fdb.Transaction) (ret interface{}, e error) {
ret = tr.Get(fdb.Key("hello")).MustGet()
return
})
if err != nil {
log.Fatalf("Unable to read FDB database value (%v)", err)
}

v := ret.([]byte)
// 记得小端和符号问题
fmt.Println("hello, ", int64(binary.LittleEndian.Uint64(v)))
}

作者给我的回复

I think it might be better to head post this on the FoundationDB forums in the "Using FDB" section. That's where we are trying to push customers who are interested in support for getting started or data modelling or what have you.

But to answer your question, the short answer is to use "encoding/binary" to encode a -1 (in little endian two's compliment). Adding a negative 1 is then the same as a 1. In two's complement, -1 is entirely 0xff bytes, so you can hard code that in, or you could do something like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func incrKey(tor fdb.Transactor, k fdb.Key) error {
_, e := tor.Transact(func (tr fdb.Transaction) (interface{}, error) {
buf := new(bytes.Buffer)
err := binary.Write(buf, binary.LittleEndian, int64(1))
if err != nil {
return nil, err
}
one := buf.Bytes()
tr.Add(k, one)
return nil, nil
})
return e
}

func decrKey(tor fdb.Transactor, k fdb.Key) error {
_, e := tor.Transact(func (tr fdb.Transaction) (interface{}, error) {
buf := new(bytes.Buffer)
err := binary.Write(buf, binary.LittleEndian, int64(-1))
if err != nil {
return nil, err
}
negativeOne := buf.Bytes()
tr.Add(k, negativeOne)
return nil, nil
})
return e
}
Those two samples are very similar, but in the first, we increment the key by 1, and in the second we decrement by 1 (i.e., increment by -1). If we looked at the bytes, we would see that the first is a 1 byte followed by 7 0 bytes, and then the second is 8 0xff bytes.

Here's a sample that reads the given key, as well as a little tester than tries to read the key and do some atomic updates:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
func getKey(tor fdb.Transactor, k fdb.Key) (int64, error) {
val, e := tor.Transact(func (tr fdb.Transaction) (interface{}, error) {
return tr.Get(k).Get()
})
if e != nil {
return 0, e
}
if val == nil {
return 0, nil
}
byteVal := val.([]byte)
var numVal int64
readE := binary.Read(bytes.NewReader(byteVal), binary.LittleEndian, &numVal)
if readE != nil {
return 0, readE
} else {
return numVal, nil
}
}

func run(db fdb.Database) {
var t tuple.Tuple
t = []tuple.TupleElement{"foo"}
var key fdb.Key
key = t.Pack()
db.Transact(func (tr fdb.Transaction) (interface{}, error) {
tr.Clear(key)
return nil, nil
})
v1, _ := getKey(db, key)
fmt.Printf("v1 = %d\n", v1)
incrKey(db, key)
v2, _ := getKey(db, key)
fmt.Printf("v2 = %d\n", v2)
decrKey(db, key)
v3, _ := getKey(db, key)
fmt.Printf("v3 = %d\n", v3)
incrKey(db, key)
v4, _ := getKey(db, key)
fmt.Printf("v1 = %d\n", v4)
incrKey(db, key)
v5, _ := getKey(db, key)
fmt.Printf("v2 = %d\n", v5)
decrKey(db, key)
}

If I run this (passing a database for my local instance), I get:

v1 = 0
v2 = 1
v3 = 0
v1 = 1
v2 = 2
which is the desired behavior.

OK! See you!