package main
import (
"context"
"errors"
"fmt"
"log"
"time"
"github.com/redis/go-redis/v9"
)
type redisClient struct {
client *redis.Client
key string
value string
ctx context.Context
}
// 集群redis
func connRedisCluster(address []string, password string) *redis.ClusterClient {
conf := redis.ClusterOptions{
Addrs: address,
Password: password,
}
return redis.NewClusterClient(&conf)
}
// 单机redis
func connRedisSingle(addr, password string) *redis.Client {
conf := redis.Options{
Addr: addr,
Password: password,
}
return redis.NewClient(&conf)
}
func (r *redisClient) lock() (bool, error) {
ret := r.client.SetNX(r.ctx, r.key, r.value, time.Second*10)
if err := ret.Err(); err != nil {
return false, fmt.Errorf("redis: key: %s, value %s, set nx error: %v", r.key, r.value, err)
}
return ret.Val(), nil
}
func (r *redisClient) unlock() error {
ret := r.client.Del(r.ctx, r.key)
if err := ret.Err(); err != nil {
return fmt.Errorf("redis: key: %s, value %s, unlock error: %v", r.key, r.value, err)
}
return nil
}
func (r *redisClient) retryLock() error {
ok := false
for !ok {
t, err := r.getTTL()
if err != nil {
return fmt.Errorf("redis client: get ttl error: %v", err)
}
if t > 0 {
log.Printf("lock occupied, retrying in %f seconds...\n", (t / 10).Seconds())
time.Sleep(t / 10)
}
ok, err = r.lock()
if err != nil {
return fmt.Errorf("redis client: lock error: %v", err)
}
}
return nil
}
func (r *redisClient) getLock() (string, error) {
ret := r.client.Get(r.ctx, r.key)
if err := ret.Err(); err != nil {
return "", err
}
rt, _ := ret.Bytes()
return string(rt), nil
}
// Get the remaining TTL of the lock
func (r *redisClient) getTTL() (time.Duration, error) {
ret := r.client.TTL(r.ctx, r.key)
if err := ret.Err(); err != nil {
return 0, fmt.Errorf("redis: key: %s, value: %s, get ttl error: %s", r.key, r.value, err)
}
return ret.Val(), nil
}
func (r *redisClient) threadLock(threadId string) error {
for {
_, err := r.getLock()
if err != nil && errors.Is(err, redis.Nil) {
// No value retrieved, indicating no one holds the lock currently
log.Printf("thread %s starting to lock", threadId)
ok, err := r.lock()
if err != nil { // 加锁出错
return fmt.Errorf("redis client: lock error: %s", err)
}
if !ok { // 加锁失败
err := r.retryLock() // 加锁重试
if err != nil {
return fmt.Errorf("threadId: %s, redis client: retry lock error: %s", threadId, err)
}
}
log.Printf("thread %s has locked", threadId)
// Perform actions after acquiring the lock
time.Sleep(25 * time.Second)
// Release the lock
err = r.unlock()
if err != nil {
return fmt.Errorf("threadId: %s, redis client: unlock error: %s", threadId, err)
}
log.Printf("thread %s has released the lock", threadId)
return nil
} else if err != nil {
return fmt.Errorf("redis client: get lock error: %s", err)
}
t, err := r.getTTL()
if err != nil {
return fmt.Errorf("redis client: get ttl error: %s", err)
}
if t > 0 {
log.Printf("thread %s lock occupied, retrying in %f seconds\n", threadId, (t / 10).Seconds())
time.Sleep(t / 10)
}
}
}
func main() {
ctx := context.Background()
address := "127.0.0.1:6379"
cl := connRedisSingle(address, "123456")
defer cl.Close()
// Create redisClient instances, specifying the key and value
r1 := redisClient{client: cl, key: "lock_key_1", value: "lock_value_1", ctx: ctx}
// Thread 1 acquires the lock
go func() {
err := r1.threadLock("1")
if err != nil {
log.Fatal(err)
}
}()
// Thread 2 acquires the lock
go func() {
err := r1.threadLock("2")
if err != nil {
log.Fatal(err)
}
}()
// Thread 2 acquires the lock
go func() {
err := r1.threadLock("3")
if err != nil {
log.Fatal(err)
}
}()
select {}
}
go实现redis分布式锁
于 2024-06-28 17:59:39 首次发布