From 89c8958b1dbda2a7e2b7a31f816061a7b2432c17 Mon Sep 17 00:00:00 2001 From: zhuxiujia Date: Sat, 21 Mar 2020 22:53:11 +0800 Subject: [PATCH] add ids package --- example/Example_test.go | 8 +- ids/SnowFlake.go | 365 ++++++++++++++++++++++++++++++++++++++++ ids/SnowFlake_test.go | 37 ++++ ids/readme.md | 5 + utils/UUID_test.go | 9 + 5 files changed, 423 insertions(+), 1 deletion(-) create mode 100644 ids/SnowFlake.go create mode 100644 ids/SnowFlake_test.go create mode 100644 ids/readme.md diff --git a/example/Example_test.go b/example/Example_test.go index 30865b5..86446c6 100644 --- a/example/Example_test.go +++ b/example/Example_test.go @@ -6,6 +6,7 @@ import ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/zhuxiujia/GoMybatis" + "github.com/zhuxiujia/GoMybatis/ids" "io/ioutil" "reflect" "testing" @@ -58,6 +59,9 @@ type TestService struct { UpdateRemark func(id string, remark string) error `tx:"" rollback:"error"` } +//推荐使用snowflake雪花算法 代替uuid防止ID碰撞 +var SnowflakeNode, e = ids.NewNode(0) + func init() { if MysqlUri == "*" { println("GoMybatisEngine not init! because MysqlUri is * or MysqlUri is ''") @@ -109,8 +113,10 @@ func Test_inset(t *testing.T) { fmt.Println("no database url define in Example_config.go , you must set the mysql link!") return } + //推荐使用snowflake雪花算法 代替uuid防止ID碰撞 + var id = SnowflakeNode.Generate() //使用mapper - var result, err = exampleActivityMapper.Insert(Activity{Id: "171", Name: "test_insret", CreateTime: time.Now(), DeleteFlag: 1}) + var result, err = exampleActivityMapper.Insert(Activity{Id: id.String(), Name: "test_insert", CreateTime: time.Now(), DeleteFlag: 1}) if err != nil { panic(err) } diff --git a/ids/SnowFlake.go b/ids/SnowFlake.go new file mode 100644 index 0000000..437d341 --- /dev/null +++ b/ids/SnowFlake.go @@ -0,0 +1,365 @@ +// Package snowflake provides a very simple Twitter snowflake generator and parser. +package ids + +import ( + "encoding/base64" + "encoding/binary" + "errors" + "fmt" + "strconv" + "sync" + "time" +) + +var ( + // Epoch is set to the twitter snowflake epoch of Nov 04 2010 01:42:54 UTC in milliseconds + // You may customize this to set a different epoch for your application. + Epoch int64 = 1288834974657 + + // NodeBits holds the number of bits to use for Node + // Remember, you have a total 22 bits to share between Node/Step + NodeBits uint8 = 10 + + // StepBits holds the number of bits to use for Step + // Remember, you have a total 22 bits to share between Node/Step + StepBits uint8 = 12 + + // DEPRECATED: the below four variables will be removed in a future release. + mu sync.Mutex + nodeMax int64 = -1 ^ (-1 << NodeBits) + nodeMask = nodeMax << StepBits + stepMask int64 = -1 ^ (-1 << StepBits) + timeShift = NodeBits + StepBits + nodeShift = StepBits +) + +const encodeBase32Map = "ybndrfg8ejkmcpqxot1uwisza345h769" + +var decodeBase32Map [256]byte + +const encodeBase58Map = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ" + +var decodeBase58Map [256]byte + +// A JSONSyntaxError is returned from UnmarshalJSON if an invalid ID is provided. +type JSONSyntaxError struct{ original []byte } + +func (j JSONSyntaxError) Error() string { + return fmt.Sprintf("invalid snowflake ID %q", string(j.original)) +} + +// ErrInvalidBase58 is returned by ParseBase58 when given an invalid []byte +var ErrInvalidBase58 = errors.New("invalid base58") + +// ErrInvalidBase32 is returned by ParseBase32 when given an invalid []byte +var ErrInvalidBase32 = errors.New("invalid base32") + +// Create maps for decoding Base58/Base32. +// This speeds up the process tremendously. +func init() { + + for i := 0; i < len(encodeBase58Map); i++ { + decodeBase58Map[i] = 0xFF + } + + for i := 0; i < len(encodeBase58Map); i++ { + decodeBase58Map[encodeBase58Map[i]] = byte(i) + } + + for i := 0; i < len(encodeBase32Map); i++ { + decodeBase32Map[i] = 0xFF + } + + for i := 0; i < len(encodeBase32Map); i++ { + decodeBase32Map[encodeBase32Map[i]] = byte(i) + } +} + +// A Node struct holds the basic information needed for a snowflake generator +// node +type Node struct { + mu sync.Mutex + epoch time.Time + time int64 + node int64 + step int64 + + nodeMax int64 + nodeMask int64 + stepMask int64 + timeShift uint8 + nodeShift uint8 +} + +// An ID is a custom type used for a snowflake ID. This is used so we can +// attach methods onto the ID. +type ID int64 + +// NewNode returns a new snowflake node that can be used to generate snowflake +// IDs +func NewNode(node int64) (*Node, error) { + + // re-calc in case custom NodeBits or StepBits were set + // DEPRECATED: the below block will be removed in a future release. + mu.Lock() + nodeMax = -1 ^ (-1 << NodeBits) + nodeMask = nodeMax << StepBits + stepMask = -1 ^ (-1 << StepBits) + timeShift = NodeBits + StepBits + nodeShift = StepBits + mu.Unlock() + + n := Node{} + n.node = node + n.nodeMax = -1 ^ (-1 << NodeBits) + n.nodeMask = n.nodeMax << StepBits + n.stepMask = -1 ^ (-1 << StepBits) + n.timeShift = NodeBits + StepBits + n.nodeShift = StepBits + + if n.node < 0 || n.node > n.nodeMax { + return nil, errors.New("Node number must be between 0 and " + strconv.FormatInt(n.nodeMax, 10)) + } + + var curTime = time.Now() + // add time.Duration to curTime to make sure we use the monotonic clock if available + n.epoch = curTime.Add(time.Unix(Epoch/1000, (Epoch%1000)*1000000).Sub(curTime)) + + return &n, nil +} + +// Generate creates and returns a unique snowflake ID +// To help guarantee uniqueness +// - Make sure your system is keeping accurate system time +// - Make sure you never have multiple nodes running with the same node ID +func (n *Node) Generate() ID { + + n.mu.Lock() + + now := time.Since(n.epoch).Nanoseconds() / 1000000 + + if now == n.time { + n.step = (n.step + 1) & n.stepMask + + if n.step == 0 { + for now <= n.time { + now = time.Since(n.epoch).Nanoseconds() / 1000000 + } + } + } else { + n.step = 0 + } + + n.time = now + + r := ID((now)<= 32 { + b = append(b, encodeBase32Map[f%32]) + f /= 32 + } + b = append(b, encodeBase32Map[f]) + + for x, y := 0, len(b)-1; x < y; x, y = x+1, y-1 { + b[x], b[y] = b[y], b[x] + } + + return string(b) +} + +// ParseBase32 parses a base32 []byte into a snowflake ID +// NOTE: There are many different base32 implementations so becareful when +// doing any interoperation. +func ParseBase32(b []byte) (ID, error) { + + var id int64 + + for i := range b { + if decodeBase32Map[b[i]] == 0xFF { + return -1, ErrInvalidBase32 + } + id = id*32 + int64(decodeBase32Map[b[i]]) + } + + return ID(id), nil +} + +// Base36 returns a base36 string of the snowflake ID +func (f ID) Base36() string { + return strconv.FormatInt(int64(f), 36) +} + +// ParseBase36 converts a Base36 string into a snowflake ID +func ParseBase36(id string) (ID, error) { + i, err := strconv.ParseInt(id, 36, 64) + return ID(i), err +} + +// Base58 returns a base58 string of the snowflake ID +func (f ID) Base58() string { + + if f < 58 { + return string(encodeBase58Map[f]) + } + + b := make([]byte, 0, 11) + for f >= 58 { + b = append(b, encodeBase58Map[f%58]) + f /= 58 + } + b = append(b, encodeBase58Map[f]) + + for x, y := 0, len(b)-1; x < y; x, y = x+1, y-1 { + b[x], b[y] = b[y], b[x] + } + + return string(b) +} + +// ParseBase58 parses a base58 []byte into a snowflake ID +func ParseBase58(b []byte) (ID, error) { + + var id int64 + + for i := range b { + if decodeBase58Map[b[i]] == 0xFF { + return -1, ErrInvalidBase58 + } + id = id*58 + int64(decodeBase58Map[b[i]]) + } + + return ID(id), nil +} + +// Base64 returns a base64 string of the snowflake ID +func (f ID) Base64() string { + return base64.StdEncoding.EncodeToString(f.Bytes()) +} + +// ParseBase64 converts a base64 string into a snowflake ID +func ParseBase64(id string) (ID, error) { + b, err := base64.StdEncoding.DecodeString(id) + if err != nil { + return -1, err + } + return ParseBytes(b) + +} + +// Bytes returns a byte slice of the snowflake ID +func (f ID) Bytes() []byte { + return []byte(f.String()) +} + +// ParseBytes converts a byte slice into a snowflake ID +func ParseBytes(id []byte) (ID, error) { + i, err := strconv.ParseInt(string(id), 10, 64) + return ID(i), err +} + +// IntBytes returns an array of bytes of the snowflake ID, encoded as a +// big endian integer. +func (f ID) IntBytes() [8]byte { + var b [8]byte + binary.BigEndian.PutUint64(b[:], uint64(f)) + return b +} + +// ParseIntBytes converts an array of bytes encoded as big endian integer as +// a snowflake ID +func ParseIntBytes(id [8]byte) ID { + return ID(int64(binary.BigEndian.Uint64(id[:]))) +} + +// Time returns an int64 unix timestamp in milliseconds of the snowflake ID time +// DEPRECATED: the below function will be removed in a future release. +func (f ID) Time() int64 { + return (int64(f) >> timeShift) + Epoch +} + +// Node returns an int64 of the snowflake ID node number +// DEPRECATED: the below function will be removed in a future release. +func (f ID) Node() int64 { + return int64(f) & nodeMask >> nodeShift +} + +// Step returns an int64 of the snowflake step (or sequence) number +// DEPRECATED: the below function will be removed in a future release. +func (f ID) Step() int64 { + return int64(f) & stepMask +} + +// MarshalJSON returns a json byte array string of the snowflake ID. +func (f ID) MarshalJSON() ([]byte, error) { + buff := make([]byte, 0, 22) + buff = append(buff, '"') + buff = strconv.AppendInt(buff, int64(f), 10) + buff = append(buff, '"') + return buff, nil +} + +// UnmarshalJSON converts a json byte array of a snowflake ID into an ID type. +func (f *ID) UnmarshalJSON(b []byte) error { + if len(b) < 3 || b[0] != '"' || b[len(b)-1] != '"' { + return JSONSyntaxError{b} + } + + i, err := strconv.ParseInt(string(b[1:len(b)-1]), 10, 64) + if err != nil { + return err + } + + *f = ID(i) + return nil +} diff --git a/ids/SnowFlake_test.go b/ids/SnowFlake_test.go new file mode 100644 index 0000000..db73ee6 --- /dev/null +++ b/ids/SnowFlake_test.go @@ -0,0 +1,37 @@ +package ids + +import ( + "fmt" + "runtime" + "testing" +) + +func TestGetSnowflakeId(t *testing.T) { + n, _ := NewNode(0) + id := n.Generate() + fmt.Println(id) +} + +func TestSnowflakeDataRace(t *testing.T) { + var total = 100000 + n, _ := NewNode(0) + for i := 0; i < runtime.NumCPU()*2; i++ { + go func() { + for i := 0; i < total; i++ { + n.Generate() + } + }() + } + for i := 0; i < total; i++ { + n.Generate() + } +} + +func BenchmarkGetSnowflakeId(b *testing.B) { + b.StopTimer() + n, _ := NewNode(0) + b.StartTimer() + for i := 0; i < b.N; i++ { + n.Generate() + } +} diff --git a/ids/readme.md b/ids/readme.md new file mode 100644 index 0000000..d1eb71f --- /dev/null +++ b/ids/readme.md @@ -0,0 +1,5 @@ + +# id package + +* snowflake clone from github https://github.com/bwmarrin/snowflake + diff --git a/utils/UUID_test.go b/utils/UUID_test.go index ab099d3..6b90a1f 100644 --- a/utils/UUID_test.go +++ b/utils/UUID_test.go @@ -12,3 +12,12 @@ func TestCreateUUID(t *testing.T) { t.Fatal("CreateUUID fail") } } + +func BenchmarkCreateUUID(b *testing.B) { + b.StopTimer() + + b.StartTimer() + for i := 0; i < b.N; i++ { + CreateUUID() + } +}