Conceitos Preliminares:
Relógios Monotônicos (Monotonic Clocks) representam um tempo que avança continuamente a partir do início do processo, sendo insensível a ajustes manuais ou sincronizações de rede. Este tipo de temporização é crucial para a precisão na geração de identificadores.
Referência: Understand Time Struct in Go
O Snowflake é um algoritmo aberto pelo Twitter para a geração de IDs únicos em ambientes distribuídos.
Análise do Algoritmo Snowflake do Twitter
A estrutura padrão de um ID Snowflake é um inteiro de 64 bits, dividido da seguinte forma:
+--------------------------------------------------------------------------+
| 1 Bit Não Usado | 41 Bit Timestamp | 10 Bit NodeID | 12 Bit Sequence ID |
+--------------------------------------------------------------------------+
- O primeiro bit é reservado e padrão 0.
- 41 bits armazenam um timestamp em milissegundos.
- 10 bits identificam o nó (máx. 1024 nós).
- 12 bits formam uma sequência incremental (máx. 4096 IDs por nó, por milissegundo).
Com essa configuração, uma única máquina pode teoricamente gerar aproximadamente 4 milhões de IDs por segundo (4096 * 1000).
Exemplo de Uso
package main
import (
"fmt"
"github.com/bwmarrin/snowflake"
)
func main() {
// Inicializa um gerador com o número do nó igual a 1.
generator, err := snowflake.NewNode(1)
if err != nil {
panic(err)
}
// Gera um novo ID único.
uniqueID := generator.Generate()
// Apresenta o ID em diferentes representações.
fmt.Printf("ID Inteiro : %d\n", uniqueID)
fmt.Printf("ID String : %s\n", uniqueID)
fmt.Printf("ID Binário : %s\n", uniqueID.Base2())
fmt.Printf("ID Base64 : %s\n", uniqueID.Base64())
// Extrai as informações componentes do ID.
fmt.Printf("Timestamp : %d\n", uniqueID.Time())
fmt.Printf("Nó : %d\n", uniqueID.Node())
fmt.Printf("Sequência : %d\n", uniqueID.Step())
}
Saída esperada (exemplo):
ID Inteiro : 1283328784765816832
ID String : 1283328784765816832
ID Binário : 1000111001111010011001011101011111010000000000001000000000000
ID Base64 : MTI4MzMyODc4NDc2NTgxNjgzMg==
Timestamp : 1594804400041
Nó : 1
Sequência : 0
Formato Binário do ID Gerado
0 00100011100111101001100101110101111101000 0000000001 000000000000
Detalhes da Implementação em Go
Utilizando o pacote github.com/bwmarrin/snowflake:
var(
Epoch int64 = 1288834974657 // Epoch base em milissegundos
NodeBits uint8 = 10 // Bits dedicados ao identificador do nó
StepBits uint8 = 12 // Bits dedicados à sequência incremental
)
A estrutrua principal que gerencia a geração dos IDs é a Node:
type Node struct {
mu sync.Mutex
epoch time.Time
time int64
node int64
step int64
// Máscaras e deslocamentos calculados a partir das configurações
nodeMax int64
nodeMask int64
stepMask int64
timeShift uint8
nodeShift uint8
}
A função construtora NewNode inicializa a estrutura:
func NewNode(node int64) (*Node, error) {
// Recalcula as máscaras e deslocamentos com base nas constantes
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
// Configura os valores calculados na instância
n.nodeMax = -1 ^ (-1 << NodeBits)
n.nodeMask = n.nodeMax << StepBits
n.stepMask = -1 ^ (-1 << StepBits)
n.timeShift = NodeBits + StepBits
n.nodeShift = StepBits
// Valida o ID do nó fornecido
if n.node < 0 || n.node > n.nodeMax {
return nil, errors.New("O número do nó deve estar entre 0 e " + strconv.FormatInt(n.nodeMax, 10))
}
// Calcula o offset do epoch usando monotonic time
var now = time.Now()
n.epoch = now.Add(time.Unix(Epoch/1000, (Epoch%1000)*1000000).Sub(now))
return &n, nil
}
O método Generate é o coração da geração:
func (n *Node) Generate() ID {
n.mu.Lock()
// Obtém o tempo decorrido desde o epoch em milissegundos (monotônico)
currentTime := time.Since(n.epoch).Nanoseconds() / 1000000
if currentTime == n.time {
// Mesmo milissegundo: incrementa a sequência
n.step = (n.step + 1) & n.stepMask
if n.step == 0 {
// Sequência esgotou: espera até o próximo milissegundo
for currentTime <= n.time {
currentTime = time.Since(n.epoch).Nanoseconds() / 1000000
}
}
} else {
// Novo milissegundo: reinicia a sequência
n.step = 0
}
// Atualiza o timestamp armazenado
n.time = currentTime
// Compõe o ID final através de operações de deslocamento e OR
generatedID := ID((currentTime << n.timeShift) | (n.node << n.nodeShift) | n.step)
n.mu.Unlock()
return generatedID
}
Vantagens e Desvantagens do Algoritmo Original
O Snowflake depende fortemente do relógio do sistema. Alterações repentinas de tempo (clock skew) podem levar à geração de IDs duplicados. Estratégias comuns para mitigar isso incluem: desativar a sincronização de tempo (NTP) na máquina geradora; usar servidores NTP confiáveis (como os da Alibaba Cloud, que absorvem gradualmente "leap seconds"); ou implementar uma verificação que, ao detectar um atraso pequeno (e.g., 5ms), aguarde um momento antes de gerar o próximo ID.
Algoritmo Snowflake da Sony (Sonyflaek)
A Sony propôs uma variante com distribuição de bits diferente:
+--------------------------------------------------------------------------+
| 1 Bit Não Usado | 39 Bit Timestamp | 8 Bit Sequência | 16 Bit ID Máquina |
+--------------------------------------------------------------------------+
- 39 bits para um timestamp com granularidade de 10 milissegundos.
- 8 bits para a sequência (máx. 521 IDs por nó, por unidade de 10ms).
- 16 bits para o identificador da máquina ou thread (máx. 65.536).
Exemplo de Uso
func obterIDMaquina() (uint16, error) {
return uint16(1), nil // ID fixo para exemplo
}
func validarIDMaquina(id uint16) bool {
return true // Validação simplificada
}
func main() {
cfg := sonyflake.Settings{
StartTime: time.Unix(0, 0),
MachineID: obterIDMaquina,
CheckMachineID: validarIDMaquina,
}
sf := sonyflake.NewSonyflake(cfg)
if sf == nil {
panic("Falha ao inicializar o Sonyflake")
}
id, err := sf.NextID()
if err != nil {
panic(err)
}
fmt.Printf("ID Gerado: %d\n", id)
}
Implementação Interna do Sonyflake
A estrutura de configuração e o gerador:
type Settings struct {
StartTime time.Time
MachineID func() (uint16, error)
CheckMachineID func(uint16) bool
}
type Sonyflake struct {
mutex *sync.Mutex
startTime int64
elapsedTime int64 // Tempo da última geração
sequence uint16
machineID uint16
}
A função NewSonyflake inicializa o gerador:
func NewSonyflake(st Settings) *Sonyflake {
sf := new(Sonyflake)
sf.mutex = new(sync.Mutex)
sf.sequence = uint16(1<<bitlensequence a="" configura="" converte="" da="" de="" do="" e="" else="" err="" error="" func="" id="" if="" in="" inicial="" int64="" m="" nil="" o="" obt="" para="" return="" sequ="" sf="" sf.machineid="" sf.starttime="toSonyflakeTime(time.Date(2014," sonyflake="" sonyflaketimeunit="" st.machineid="=" st.starttime.after="" st.starttime.iszero="" t.utc="" tempo="" time.time="" time.utc="" tosonyflaketime="" um="" unidade="" valida="" var=""></bitlensequence>
A geração do ID com tratmaento de clock skew:
func (sf *Sonyflake) NextID() (uint64, error) {
const maskSequence = uint16(1<<bitlensequence :="currentElapsedTime(sf.startTime)" a="" alcan="" anterior="" avan="" calcula="" clock="" comp="" contra="" currentelapsed="" da="" de="" decorrido="" defer="defer" dentro="" dorme="" else="" em="" error="" estourou="" final="" func="" id="" if="" mant="" masksequence="" mesma="" necess="" normalmente:="" nova="" o="" overtime="" para="" pelo="" reseta="" return="" sequ="" sf.elapsedtime="" sf.mutex.lock="" sf.mutex.unlock="" sf.sequence="0" sf.toid="" skew="" tempo="" time.sleep="" toid="" total="" unidade="" unidades="">= 1<<bitlentime de="" errors.new="" excedido="" nil="" return="" tempo="" uint64=""></bitlentime></bitlensequence>
O Sonyflake lida com a regressão de relógio (clock skew) de forma direta: a variável elapsedTime nunca diminui. Se o relógio do sistema retroceder, o algoritmo continua usando o último elapsedTime válido, incrementando a sequência. Quando a sequência estoura, elapsedTime é incrementado artificialmente e a execução dorme para sincronizar, evitando colisões de ID.