https://github.com/study-only/go-locks

Redis分布式锁

SET
Redis 2.6.12SET
SET key value [EX seconds] [PX milliseconds] [NX|XX]

可选参数

EX secondssecondsSET key value EX secondsSETEX key seconds valuePX millisecondsmillisecondsSET key value PX millisecondsPSETEX key milliseconds valueNXSET key value NXSETNX key valueXX

返回值

Redis 2.6.12SETRedis 2.6.12SETOKNXXX

Redis分布式锁实现原理

SETSET key value EX seconds NX
import (
    "errors"
    "time"
    "github.com/go-redis/redis"
)

var redisClient *redis.Client

type redisLock struct {
    name    string
    expiry  time.Duration
}

func (l *redisLock) TryLock() error {
    if ok, _ := redisClient.SetNX(l.name, 1, l.expiry).Result(); !ok {
        return errors.New("redis lock: already locked")
    }

    return nil
}

func (l *redisLock) Unlock() error {
    return redisClient.Del(l.name).Err()
}

MySQL分布式锁

NXSET
import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
    "time"
)

const (
    createTableSql = `
CREATE TABLE IF NOT EXISTS %s (
  id         int          NOT NULL AUTO_INCREMENT,
  name       varchar(255) NOT NULL,
  expire_at  timestamp    NOT NULL,
  created_at timestamp    NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uk_name (name) USING HASH,
  KEY idx_expire_at (expire_at) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
`
    insertRowSql = `
INSERT INTO %s (name, expire_at, created_at) VALUES (?, ?, ?)
`
    deleteRowSql = `
DELETE FROM %s WHERE name=? LIMIT 1
`
)

var lockDb *sql.DB
var lockTableName string

type mysqlLock struct {
    name   string
    expiry time.Duration
}

func (l *mysqlLock) TryLock() error {
    createdAt := time.Now()
    expireAt := createdAt.Add(l.expiry)
    return insertRow(l.name, expireAt, createdAt)
}

func (l *mysqlLock) Unlock() error {
    return deleteRow(l.name)
}

func createTable() error {
    query := fmt.Sprintf(createTableSql, lockTableName)
    _, err := lockDb.Exec(query)
    return err
}

func insertRow(name string, expireAt, createdAt time.Time) error {
    query := fmt.Sprintf(insertRowSql, lockTableName)
    _, err := lockDb.Exec(query, name, expireAt, createdAt)
    return err
}

func deleteRow(name string) error {
    query := fmt.Sprintf(deleteRowSql, lockTableName)
    _, err := lockDb.Exec(query, name)
    return err
}

自旋锁

MySQLRedis
import (
    "errors"
    "fmt"
    "time"
)

type TryLocker interface {
    TryLock() error
    Unlock() error
}

func NewSpinLock(lock TryLocker, spinTries int, spinInterval time.Duration) *spinLock {
    return &spinLock{
        lock:         lock,
        spinTries:    spinTries,
        spinInterval: spinInterval,
    }
}

type spinLock struct {
    lock         TryLocker
    spinTries    int
    spinInterval time.Duration
}

func (l *spinLock) Lock() error {
    for i := 0; i < l.spinTries; i++ {
        if err := l.lock.TryLock(); err == nil {
            return nil
        }

        time.Sleep(l.spinInterval)
    }

    return errorf("spin lock: failed after %f seconds", float64(l.spinTries)*l.spinInterval.Seconds())
}

func (l *spinLock) Unlock() error {
    return l.lock.Unlock()
}

func errorf(format string, args ...interface{}) error {
    return errors.New(fmt.Sprintf(format, args...))
}