Skip to content

Commit 623fcf5

Browse files
author
Olivier Poitrey
committed
Initial Commit
0 parents  commit 623fcf5

File tree

7 files changed

+419
-0
lines changed

7 files changed

+419
-0
lines changed

.travis.yml

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
language: go
2+
go:
3+
- 1.5
4+
- tip
5+
matrix:
6+
allow_failures:
7+
- go: tip

LICENSE

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2015 Olivier Poitrey <[email protected]>
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is furnished
8+
to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

README.md

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Globally Unique ID Generator
2+
3+
[![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/xid) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/xid/master/LICENSE) [![Build Status](https://travis-ci.org/rs/xid.svg?branch=master)](https://travis-ci.org/rs/xid) [![Coverage](http://gocover.io/_badge/github.com/rs/xid)](http://gocover.io/github.com/rs/xid)
4+
5+
Package xid is a globally unique id generator suited for web scale
6+
7+
Xid is using Mongo Object ID algorithm to generate globally unique ids:
8+
https://docs.mongodb.org/manual/reference/object-id/
9+
10+
- 4-byte value representing the seconds since the Unix epoch,
11+
- 3-byte machine identifier,
12+
- 2-byte process id, and
13+
- 3-byte counter, starting with a random value.
14+
15+
The binary representation of the id is compatible with Mongo 12 bytes Object IDs.
16+
The string representation is using URL safe base64 for better space efficiency when
17+
stored in that form (16 bytes).
18+
19+
UUID is 16 bytes (128 bits), snowflake is 8 bytes (64 bits), xid stands in between
20+
with 12 bytes with a more compact string representation ready for the web and no
21+
required configuration or central generation server.
22+
23+
Features:
24+
25+
- Size: 12 bytes (96 bits), smaller than UUID, larger than snowflake
26+
- Base64 URL safe encoded by default (16 bytes storage when transported as printable string)
27+
- Non configured, you don't need set a unique machine and/or data center id
28+
- K-ordered
29+
- Embedded time with 1 second precision
30+
- Unicity guaranted for 16,777,216 (24 bits) unique ids per second and per host/process
31+
32+
Best used with [xlog](https://github.com/rs/xlog)'s
33+
[RequestIDHandler](https://godoc.org/github.com/rs/xlog#RequestIDHandler).
34+
35+
References:
36+
37+
- http://www.slideshare.net/davegardnerisme/unique-id-generation-in-distributed-systems
38+
- https://en.wikipedia.org/wiki/Universally_unique_identifier
39+
- https://blog.twitter.com/2010/announcing-snowflake
40+
41+
## Install
42+
43+
go get github.com/rs/xid
44+
45+
## Usage
46+
47+
```go
48+
guid := xid.New()
49+
50+
println(guid.String())
51+
// Output: TYjhW2D0huQoQS3J
52+
```
53+
54+
Store an `xid` into a `net/context`:
55+
56+
```go
57+
ctx = xid.NewContext(ctx, guid)
58+
```
59+
60+
Retrieve an `xid` from a `net/context` if any:
61+
62+
```go
63+
guid, ok := xid.FromContext(ctx)
64+
```
65+
66+
Get `xid` embedded info:
67+
68+
```go
69+
guid.Machine()
70+
guid.Pid()
71+
guid.Time()
72+
guid.Counter()
73+
```
74+
75+
## Licenses
76+
77+
All source code is licensed under the [MIT License](https://raw.github.com/rs/xid/master/LICENSE).

context.go

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package xid
2+
3+
import "golang.org/x/net/context"
4+
5+
type key int
6+
7+
const idKey key = 0
8+
9+
// FromContext returns a uniq id associated to the request if any
10+
func FromContext(ctx context.Context) (id ID, ok bool) {
11+
if ctx != nil {
12+
id, ok = ctx.Value(idKey).(ID)
13+
}
14+
return
15+
}
16+
17+
// NewContext returns a copy of the parent context and associates it with passed xid.
18+
func NewContext(ctx context.Context, id ID) context.Context {
19+
return context.WithValue(ctx, idKey, id)
20+
}

context_test.go

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package xid
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"golang.org/x/net/context"
8+
)
9+
10+
func TestFromContext(t *testing.T) {
11+
id, ok := FromContext(nil)
12+
assert.False(t, ok)
13+
assert.Equal(t, ID{}, id)
14+
id, ok = FromContext(context.Background())
15+
assert.False(t, ok)
16+
assert.Equal(t, ID{}, id)
17+
id2 := New()
18+
ctx := NewContext(context.Background(), id2)
19+
id, ok = FromContext(ctx)
20+
assert.True(t, ok)
21+
assert.Equal(t, id2, id)
22+
}

id.go

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// Package xid is a globally unique id generator suited for web scale
2+
//
3+
// Xid is using Mongo Object ID algorithm to generate globally unique ids:
4+
// https://docs.mongodb.org/manual/reference/object-id/
5+
//
6+
// - 4-byte value representing the seconds since the Unix epoch,
7+
// - 3-byte machine identifier,
8+
// - 2-byte process id, and
9+
// - 3-byte counter, starting with a random value.
10+
//
11+
// The binary representation of the id is compatible with Mongo 12 bytes Object IDs.
12+
// The string representation is using URL safe base64 for better space efficiency when
13+
// stored in that form (16 bytes).
14+
//
15+
// UUID is 16 bytes (128 bits), snowflake is 8 bytes (64 bits), xid stands in between
16+
// with 12 bytes with a more compact string representation ready for the web and no
17+
// required configuration or central generation server.
18+
//
19+
// Features:
20+
//
21+
// - Size: 12 bytes (96 bits), smaller than UUID, larger than snowflake
22+
// - Base64 URL safe encoded by default (16 bytes storage when transported as printable string)
23+
// - Non configured, you don't need set a unique machine and/or data center id
24+
// - K-ordered
25+
// - Embedded time with 1 second precision
26+
// - Unicity guaranted for 16,777,216 (24 bits) unique ids per second and per host/process
27+
//
28+
// Best used with xlog's RequestIDHandler (https://godoc.org/github.com/rs/xlog#RequestIDHandler).
29+
//
30+
// References:
31+
//
32+
// - http://www.slideshare.net/davegardnerisme/unique-id-generation-in-distributed-systems
33+
// - https://en.wikipedia.org/wiki/Universally_unique_identifier
34+
// - https://blog.twitter.com/2010/announcing-snowflake
35+
package xid
36+
37+
import (
38+
"crypto/md5"
39+
"crypto/rand"
40+
"encoding/base64"
41+
"encoding/binary"
42+
"errors"
43+
"fmt"
44+
"io"
45+
"os"
46+
"sync/atomic"
47+
"time"
48+
)
49+
50+
// Code inspired from mgo/bson ObjectId
51+
52+
// ID represents a unique request id
53+
type ID [rawLen]byte
54+
55+
const (
56+
encodedLen = 16
57+
rawLen = 12
58+
)
59+
60+
// ErrInvalidID is returned when trying to unmarshal an invalid ID
61+
var ErrInvalidID = errors.New("invalid ID")
62+
63+
// objectIDCounter is atomically incremented when generating a new ObjectId
64+
// using NewObjectId() function. It's used as a counter part of an id.
65+
var objectIDCounter uint32
66+
67+
// machineId stores machine id generated once and used in subsequent calls
68+
// to NewObjectId function.
69+
var machineID = readMachineID()
70+
71+
// readMachineId generates machine id and puts it into the machineId global
72+
// variable. If this function fails to get the hostname, it will cause
73+
// a runtime error.
74+
func readMachineID() []byte {
75+
id := make([]byte, 3)
76+
hostname, err1 := os.Hostname()
77+
if err1 != nil {
78+
// Fallback to rand number if machine id can't be gathered
79+
_, err2 := io.ReadFull(rand.Reader, id)
80+
if err2 != nil {
81+
panic(fmt.Errorf("cannot get hostname: %v; %v", err1, err2))
82+
}
83+
return id
84+
}
85+
hw := md5.New()
86+
hw.Write([]byte(hostname))
87+
copy(id, hw.Sum(nil))
88+
return id
89+
}
90+
91+
// New generates a globaly unique ID
92+
func New() ID {
93+
var id ID
94+
// Timestamp, 4 bytes, big endian
95+
binary.BigEndian.PutUint32(id[:], uint32(time.Now().Unix()))
96+
// Machine, first 3 bytes of md5(hostname)
97+
id[4] = machineID[0]
98+
id[5] = machineID[1]
99+
id[6] = machineID[2]
100+
// Pid, 2 bytes, specs don't specify endianness, but we use big endian.
101+
pid := os.Getpid()
102+
id[7] = byte(pid >> 8)
103+
id[8] = byte(pid)
104+
// Increment, 3 bytes, big endian
105+
i := atomic.AddUint32(&objectIDCounter, 1)
106+
id[9] = byte(i >> 16)
107+
id[10] = byte(i >> 8)
108+
id[11] = byte(i)
109+
return id
110+
}
111+
112+
// String returns a base64 URL safe representation of the id
113+
func (id ID) String() string {
114+
return base64.URLEncoding.EncodeToString(id[:])
115+
}
116+
117+
// MarshalText implements encoding/text TextMarshaler interface
118+
func (id ID) MarshalText() (text []byte, err error) {
119+
text = make([]byte, encodedLen)
120+
base64.URLEncoding.Encode(text, id[:])
121+
return
122+
}
123+
124+
// UnmarshalText implements encoding/text TextUnmarshaler interface
125+
func (id *ID) UnmarshalText(text []byte) error {
126+
if len(text) != encodedLen {
127+
return ErrInvalidID
128+
}
129+
b := make([]byte, rawLen)
130+
_, err := base64.URLEncoding.Decode(b, text)
131+
for i, c := range b {
132+
id[i] = c
133+
}
134+
return err
135+
}
136+
137+
// Time returns the timestamp part of the id.
138+
// It's a runtime error to call this method with an invalid id.
139+
func (id ID) Time() time.Time {
140+
// First 4 bytes of ObjectId is 32-bit big-endian seconds from epoch.
141+
secs := int64(binary.BigEndian.Uint32(id[0:4]))
142+
return time.Unix(secs, 0)
143+
}
144+
145+
// Machine returns the 3-byte machine id part of the id.
146+
// It's a runtime error to call this method with an invalid id.
147+
func (id ID) Machine() []byte {
148+
return id[4:7]
149+
}
150+
151+
// Pid returns the process id part of the id.
152+
// It's a runtime error to call this method with an invalid id.
153+
func (id ID) Pid() uint16 {
154+
return binary.BigEndian.Uint16(id[7:9])
155+
}
156+
157+
// Counter returns the incrementing value part of the id.
158+
// It's a runtime error to call this method with an invalid id.
159+
func (id ID) Counter() int32 {
160+
b := id[9:12]
161+
// Counter is stored as big-endian 3-byte value
162+
return int32(uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2]))
163+
}

0 commit comments

Comments
 (0)