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...))
}