我正在探索etcd为分布式环境实现序列号生成器。我的要求是为同一应用程序的多个实例的每个请求生成不重复的序号。并且可能有n种这样的应用需求。我使用golang client packages
中提供的STM和互斥锁以多种方式对此进行了POC。在本地机器设置中,使用一个单节点etcd服务器(将是至少3个节点群集,RAFT才能在生产环境中工作),我编写了一个简单程序来生成500个goroutine中的id(数字)。每个例程每个获得10个ID,因此总共有5000个ID。使用时间统计信息,具有重试次数的STM的性能要优于互斥锁。除了这些方法之外,还有没有更好的选择来实现顺序编号生成?可以首先将etcd用于此目的吗?
PS:我附上代码示例仅供参考。我不希望它被审查。我关心的只是用etcd生成序列号的正确方法
package main
import (
"context"
"errors"
"strconv"
"sync"
"sync/atomic"
"time"
CONC "go.etcd.io/etcd/clientv3/concurrency"
"github.com/golang/glog"
ETCD "go.etcd.io/etcd/clientv3"
)
var client *ETCD.Client
var deadline = 200 * time.Second
func main() {
var err error
client, err = ETCD.New(ETCD.Config{
Endpoints: []string{"127.0.0.1:2379"},
})
if err != nil {
glog.Errorln("err:", err)
return
}
idGen := &SeqIDGenerator{key: "_id"}
err = func() error {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
_, err = client.Put(ctx, idGen.key, strconv.FormatInt(0, 10))
return err
}()
if err != nil {
glog.Errorln("err:", err)
return
}
id, err := idGen.nextWithMutex()
if err != nil {
glog.Errorln("err:", err)
return
}
glog.Errorln("done", id)
id, err = idGen.nextWithSTMSerialiazable()
if err != nil {
glog.Errorln("err:", err)
return
}
glog.Errorln("done", id)
// st := time.Now()
// stressSTMSerialiazableSeq(idGen)
// glog.Errorln(time.Since(st))
}
type SeqIDGenerator struct {
key string
}
func (idGen *SeqIDGenerator) nextWithSTMSerialiazable() (int64, error) {
var retrived int64
ctx, cancel := context.WithTimeout(context.Background(), deadline)
defer cancel()
var err error
retry := retry
for retry > 0 {
retry--
stmresp, err := CONC.NewSTMSerializable(ctx, client, func(s CONC.STM) error {
v := s.Get(idGen.key)
retrived, err = strconv.ParseInt(v, 10, 64)
if err != nil {
return err
}
retrived++
s.Put(idGen.key, strconv.FormatInt(retrived, 10))
return nil
})
if err != nil {
continue
} else if stmresp.Succeeded {
return retrived, nil
}
}
return 0, errors.New("ID gen failed. Retry exceeded")
}
func (idGen *SeqIDGenerator) nextWithMutex() (int64, error) {
s, err := CONC.NewSession(client) // explore options to pass
if err != nil {
return 0, err
}
m := CONC.NewMutex(s, idGen.key)
ctx, cancel := context.WithTimeout(context.Background(), deadline)
defer cancel()
m.Lock(ctx)
defer m.Unlock(ctx)
resp, err := client.Get(ctx, idGen.key)
if err != nil {
return 0, err
}
retrived, err := strconv.ParseInt(string(resp.OpResponse().Get().Kvs[0].Value), 10, 64)
if err != nil {
return 0, err
}
retrived++
_, err = client.Put(ctx, idGen.key, strconv.FormatInt(retrived, 10))
if err != nil {
return 0, err
}
return retrived, nil
}
func (idGen *SeqIDGenerator) nextWithSTMReapeatable() (int64, error) {
var retrived int64
ctx, cancel := context.WithTimeout(context.Background(), deadline)
defer cancel()
var err error
retry := retry
for retry > 0 {
retry--
stmresp, err := CONC.NewSTMRepeatable(ctx, client, func(s CONC.STM) error {
v := s.Get(idGen.key)
retrived, err = strconv.ParseInt(v, 10, 64)
if err != nil {
return err
}
retrived++
s.Put(idGen.key, strconv.FormatInt(retrived, 10))
return nil
})
if err != nil {
continue
} else if stmresp.Succeeded {
return retrived, nil
}
}
return 0, errors.New("ID gen failed. Retry exceeded")
}
var n int = 500
var retry int = 40 // move as conf
func stressMutex(idGen *SeqIDGenerator) {
wg := &sync.WaitGroup{}
wg.Add(n)
for i := 0; i < n; i++ {
go func(i int) {
defer wg.Done()
for j := 0; j < 10; j++ {
_, err := idGen.nextWithMutex()
if err != nil {
glog.Errorln("err:", err)
return
}
}
}(i)
}
wg.Wait()
}
func stressMutexSeq(idGen *SeqIDGenerator) {
for i := 0; i < n; i++ {
for j := 0; j < 10; j++ {
_, err := idGen.nextWithMutex()
if err != nil {
glog.Errorln("err:", err)
}
}
}
}
func stressSTMSerialiazableSeq(idGen *SeqIDGenerator) {
for i := 0; i < n; i++ {
for j := 0; j < 10; j++ {
_, err := idGen.nextWithSTMSerialiazable()
if err != nil {
glog.Errorln("err:", err)
}
}
}
}
func stressSTMReapeatableSeq(idGen *SeqIDGenerator) {
for i := 0; i < n; i++ {
for j := 0; j < 10; j++ {
_, err := idGen.nextWithSTMReapeatable()
if err != nil {
glog.Errorln("err:", err)
}
}
}
}
func stressSTMSerialiazable(idGen *SeqIDGenerator) {
wg := &sync.WaitGroup{}
wg.Add(n)
var success int64
for i := 0; i < n; i++ {
go func(i int) {
defer wg.Done()
for j := 0; j < 10; j++ {
_, err := idGen.nextWithSTMSerialiazable()
if err != nil {
glog.Errorln("err:", err)
} else {
atomic.AddInt64(&success, 1)
}
}
}(i)
}
wg.Wait()
glog.Errorln("success:", success)
}
func stressSTMReapeatable(idGen *SeqIDGenerator) {
wg := &sync.WaitGroup{}
wg.Add(n)
var success int64
for i := 0; i < n; i++ {
go func(i int) {
defer wg.Done()
for j := 0; j < 10; j++ {
_, err := idGen.nextWithSTMReapeatable()
if err != nil {
glog.Errorln("err:", err)
} else {
atomic.AddInt64(&success, 1)
}
}
}(i)
}
wg.Wait()
glog.Errorln("success:", success)
}