Golang 基于etcd的分布式锁 - 公平锁和非公平锁

Golang 基于etcd的分布式锁 - 公平锁和非公平锁

Golang 学习笔记

目的

首先,要确认好希望实现的东西

  1. 希望实现一个分布式锁
  2. 可以实现公平锁和非公平锁
  3. 使用起来不复杂

设想

  1. Key :钥匙
    package qtlock
    // Key 钥匙
    type Key string
    
  2. Lock :锁
    package qtlock
    // Lock 锁
    type Lock interface {
    	// Unlock 可以直接调用锁的方法来释放锁
    	Unlock()
    }
    
  3. Locker :负责上锁和解锁,区分公平和非公平
    package qtlock
    // Locker 
    type Locker interface {
    	// TryLock 尝试获取锁,不会等待,直接返回获取结果
    	// 如果成功,返回 lock,nil
    	// 如果失败,返回 nil,nil
    	// 如果出错,返回 nil,err
    	TryLock(key Key) (lock Lock, err error)
    	// Unlock 释放锁
    	Unlock(lock Lock)
    	// Lock 获取锁
    	// 等待获取锁或者ctx结束
    	Lock(ctx context.Context, key Key) (lock Lock, err error)
    }
    // GetLocker 获取一个Locker
    // Locker会根据 fair 的值去实现公平或者非公平的抢占锁行为
    func GetLocker(fair bool) Locker
    
  4. 调用方式:
    func main() {
    	wg := &sync.WaitGroup{}
    	wg.Add(4)
    	startTime := time.Now()
    	locker := qtlock.GetLocker(true)
    	for i := range []int{1, 2, 3, 4} {
    		go func(i int) {
    			defer wg.Done()
    			//如果10秒钟还没有获取锁,则放弃
    			ctx, cf := context.WithTimeout(context.Background(), 10*time.Second)
    			defer cf()
    			if lock, err := locker.Lock(ctx, "my-key"); err != nil {
    				fmt.Println(i, "err", err)
    			} else if lock != nil {
    				defer func() {
    					lock.Unlock()
    					fmt.Println(i, "释放锁")
    				}()
    				fmt.Println(i, "获取锁", time.Now().Sub(startTime).Seconds())
    				time.Sleep(4 * time.Second)
    			} else {
    				fmt.Println(i, "没有获取锁", time.Now().Sub(startTime).Seconds())
    			}
    		}(i)
    	}
    	wg.Wait()
    	fmt.Println("所有工作都完成啦", time.Now().Sub(startTime).Seconds())
    }
    

具体实现

1. 首先把接口都定义好

package qtlock

import "context"

// Key 钥匙
type Key string

// Lock 锁
type Lock interface {
	// Unlock 可以直接调用锁的方法来释放锁
	Unlock()
}

// Locker 锁柜
type Locker interface {
	// TryLock 尝试获取锁,不会等待,直接返回获取结果
	// 如果成功,返回 lock,nil
	// 如果失败,返回 nil,nil
	// 如果出错,返回 nil,err
	TryLock(key Key) (lock Lock, err error)
	// Unlock 释放锁
	Unlock(lock Lock)
	// Lock 获取锁
	// 等待获取锁或者ctx结束
	Lock(ctx context.Context, key Key) (lock Lock, err error)
}

// LockerProvider Locker的提供者
type LockerProvider func(fair bool) Locker

var lockProvider LockerProvider

// GetLocker 获取一个锁
func GetLocker(fair bool) Locker {
	if lockProvider == nil {
		return nil
	}
	return lockProvider(fair)
}

// RegisterLockerProvider 注册一个LockerProvider
func RegisterLockerProvider(provider LockerProvider) {
	lockProvider = provider
}

2. etcd 实现

1. 存储的KV方案

我们需要将key存入etcd中,表示当前正锁住了

在公平锁的模式下,我们还需要将抢占者以队列的方式保存在etcd中
以 key :test-key 为例子

抢占的锁的Key:Value:

/qt/lock/test-key : id0

抢占者队列Key:Value:

/qt/lock/test-key/_OWNERS_/idA : idA
/qt/lock/test-key/_OWNERS_/idB : idB

这样,我们可以通过查询
前缀=/qt/lock/test-key/_OWNERS_/
排序=CreateRevision 升序
来查到最先的等待者,以此来判断自己是否可以进行抢占锁了

2. 租约方案

对于 Key:

  • 我们需要租约的方式,给key一个过期时间,防止程序或者机器意外,导致key无法清除,即锁无法释放
  • 在锁没有被持有期间,需要不停的进行续租,防止key过期后删除,导致锁被别人拿了
  • 在锁被释放后,需要马上废除租约,并且关闭续租。使得其他人可以立即开始抢占锁

对于等待者队列的OwnerKey

  • 需要租约的方式,防止程序或机器意外后,无法清楚key,使得后续排队的Owner一直排队下去
  • 排队期间需要保持续租,防止掉队
  • 持有期间也可保持续租
  • 锁被释放后,需要马上废除租约,并且关闭续租。使得队列后面的人立即开始抢占
3. TryLock 非公平

非公平锁的模式下,抢占锁无需考虑自己之前是否已经有人在等待了,直接抢的就完事了
由于无需阻塞,如果没有抢到,直接返回 nil 就行了

func (l *Locker) tryLock(key qtlock.Key, uuid uuid.UUID) (lock qtlock.Lock, err error) {
	newLock := &Lock{
		locker: l,
		key:    genKey(key), // /qt/lock/key
		uuid:   uuid,
	}
	
	// 创建自动续约的租约
	cxt, cancelLeaseFun := context.WithCancel(context.Background())
	defer func() {
		//如果返回的lock=nil说明创建没成功,调下取消续租的方法
		if lock == nil {
			cancelLeaseFun()
		}
	}()
	if lease, err := createKeepAliveLease(cxt, 10, l.client); err != nil {
		return nil, err
	} else {
		//保存租约和取消租约的方法
		newLock.lease = lease
		newLock.leaseCancelFun = cancelLeaseFun
	}

	//尝试抢锁
	txn := l.client.Txn(context.Background())
	// 如果没有锁则抢占锁,不然就获取当前的锁
	txn.If(clientv3.Compare(clientv3.CreateRevision(newLock.key), "=", 0)).
		Then(clientv3.OpPut(newLock.key, newLock.uuid.String(), clientv3.WithLease(newLock.lease))).
		Else(clientv3.OpGet(newLock.key))
	if resp, err := txn.Commit(); err != nil {
		return nil, err
	} else {
		if resp.Succeeded {
			return newLock, nil
		} else {
			return nil, nil
		}
	}
}

2. TryLock 公平

TryLock下,其实不需要进行等待,只需要关心目前队列中是否有在等待的抢占者就行,如果有就不去抢占
所以只需要在上面非公平的模式下添加一个前置判断

func (l *Locker) tryLock(key qtlock.Key, uuid uuid.UUID) (lock qtlock.Lock, err error) {
	newLock := &Lock{
		locker: l,
		key:    genKey(key), // /qt/lock/key
		uuid:   uuid,
	}
	
	if l.fair {
		//获取锁下的owner节点数量,有就返回
		if response, err := l.client.Get(context.Background(),
			newLock.key+"/_OWNERS_/", clientv3.WithPrefix(),
			clientv3.WithSort(clientv3.SortByCreateRevision, clientv3.SortAscend),
			clientv3.WithLimit(1),
		); err != nil {
			return nil, err
		} else {
			if len(response.Kvs) == 1 && string(response.Kvs[0].Value) != newLock.uuid.String() {
				return nil, nil
			}
		}
	}
	... 
}
3. Lock 非公平

Lock阻塞模式下,需要对Key进行watch,如果发现Key被删除了,说明锁被释放了,然后进行抢占

func (l *Locker) Lock(ctx context.Context, key qtlock.Key) (lock qtlock.Lock, err error) {
	id := uuid.New()
	//创建watch
	cancelCtx, cancelFun := context.WithCancel(context.TODO())
	//方法结束说明不需要再watch了
	defer cancelFun()
	//watch Key的事件
	watch := l.client.Watch(cancelCtx, genKey(key))
	//首先尝试一波是否可以抢占,可以就返回lock
	if lock, err := l.tryLock(key, id); err != nil {
		return nil, err
	} else if lock != nil {
		return lock, nil
	}
	//select模式,如果超时了就不干了
	for {
		select {
		case resp := <-watch:
			for _, ev := range resp.Events {
				if ev.Type == clientv3.EventTypeDelete {
					if lock, err := l.tryLock(key, id); err != nil {
						return nil, err
					} else if lock != nil {
						return lock, nil
					}
				}
			}
		case <-ctx.Done():
			return nil, ctx.Err()
		}
	}
}
4. Lock 公平

Lock的公平模式下,还需要在队列里面添加抢占者,同时watch的时候需要watch抢占者列表。
如果抢占者被删除了,我们也需要再尝试去抢占
对于非公平的方法进行修改,就可以实现了

func (l *Locker) Lock(ctx context.Context, key qtlock.Key) (lock qtlock.Lock, err error) {
	id := uuid.New()
	
	var ownerLease clientv3.LeaseID
	var ownerCancelFunc func()
	defer func() {
		if lock != nil {
			etcdLock := lock.(*Lock)
			etcdLock.ownerCancelFun = ownerCancelFunc
			etcdLock.ownerLease = ownerLease
		} else {
			if ownerCancelFunc != nil {
				ownerCancelFunc()
			}
			if ownerLease != 0 {
				l.client.Lease.Revoke(context.Background(), ownerLease)
			}
		}
	}()
	if l.fair {
		cancelCtx, cancelFun := context.WithCancel(context.Background())
		if leaseID, err := createKeepAliveLease(cancelCtx, 10, l.client); err != nil {
			cancelFun()
			return nil, err
		} else {
			ownerLease = leaseID
			ownerCancelFunc = cancelFun
		}

		if _, err := l.client.Put(context.Background(), genKey(key)+"/_OWNERS_/"+id.String(), id.String(), clientv3.WithLease(ownerLease)); err != nil {
			return nil, err
		}
	}

	//创建watch
	cancelCtx, cancelFun := context.WithCancel(context.Background())
	//方法结束说明不需要再watch了
	defer cancelFun()
	watch := l.client.Watch(cancelCtx, genKey(key), clientv3.WithPrefix())
	//首先尝试一波是否可以抢占,可以就返回lock
	if lock, err := l.tryLock(key, id); err != nil {
		return nil, err
	} else if lock != nil {
		return lock, nil
	}
	//select模式,如果超时了就不干了
	for {
		select {
		case resp := <-watch:
			for _, ev := range resp.Events {
				if ev.Type == clientv3.EventTypeDelete {
					if lock, err := l.tryLock(key, id); err != nil {
						return nil, err
					} else if lock != nil {
						return lock, nil
					}
				}
			}
		case <-ctx.Done():
			return nil, ctx.Err()
		}
	}
}
5. 最终etcd实现代码
package qtlock_etcd

import (
	"context"
	"github.com/google/uuid"
	clientv3 "go.etcd.io/etcd/client/v3"
	"qtjob/qtlock"
)

var defaultClient *clientv3.Client

func init() {
	var err error
	if defaultClient, err = clientv3.New(clientv3.Config{Endpoints: []string{"127.0.0.1:2379"}}); err != nil {
		panic(err)
	}
	qtlock.RegisterLockerProvider(getLocker)
}

// getLocker 获取Locker
func getLocker(fair bool) qtlock.Locker {
	return &Locker{
		client: defaultClient,
		fair:   fair,
	}
}

//创建自动续租的租约
func createKeepAliveLease(ctx context.Context, ttl int64, client *clientv3.Client) (leaseId clientv3.LeaseID, err error) {
	//创建租约
	if grant, err := client.Lease.Grant(context.Background(), ttl); err != nil {
		return 0, err
	} else {
		leaseId = grant.ID
	}
	//自动续租
	if err := keepLeaseAlive(ctx, leaseId, client); err != nil {
		return 0, err
	}
	return leaseId, nil
}

func keepLeaseAlive(ctx context.Context, leaseId clientv3.LeaseID, client *clientv3.Client) error {
	if _, err := client.Lease.KeepAlive(ctx, leaseId); err != nil {
		return err
	} else {
		return nil
	}
}

type Lock struct {
	locker *Locker
	key    string
	uuid   uuid.UUID

	lease          clientv3.LeaseID
	leaseCancelFun func()

	ownerLease     clientv3.LeaseID
	ownerCancelFun func()
}

func (l *Lock) Unlock() {
	l.locker.Unlock(l)
}

type Owner struct {
}

type Locker struct {
	fair   bool             //是否公平
	client *clientv3.Client //etcd锁的客户端
}

func (l *Locker) TryLock(key qtlock.Key) (lock qtlock.Lock, err error) {
	return l.tryLock(key, uuid.New())
}

func (l *Locker) tryLock(key qtlock.Key, uuid uuid.UUID) (lock qtlock.Lock, err error) {
	newLock := &Lock{
		locker: l,
		key:    genKey(key),
		uuid:   uuid,
	}

	if l.fair {
		//获取锁下的owner节点数量,有就返回
		if response, err := l.client.Get(context.Background(),
			newLock.key+"/_OWNERS_/", clientv3.WithPrefix(),
			clientv3.WithSort(clientv3.SortByCreateRevision, clientv3.SortAscend),
			clientv3.WithLimit(1),
		); err != nil {
			return nil, err
		} else {
			if len(response.Kvs) == 1 && string(response.Kvs[0].Value) != newLock.uuid.String() {
				return nil, nil
			}
		}
	}
	// 创建自动续约的租约
	cxt, cancelLeaseFun := context.WithCancel(context.Background())
	defer func() {
		//如果返回的lock=nil说明创建没成功,调下取消续租的方法
		if lock == nil {
			cancelLeaseFun()
		}
	}()
	if lease, err := createKeepAliveLease(cxt, 10, l.client); err != nil {
		return nil, err
	} else {
		//保存租约和取消租约的方法
		newLock.lease = lease
		newLock.leaseCancelFun = cancelLeaseFun
	}

	//尝试抢锁
	txn := l.client.Txn(context.Background())
	// 如果没有锁则抢占锁,不然就获取当前的锁
	txn.If(clientv3.Compare(clientv3.CreateRevision(newLock.key), "=", 0)).
		Then(clientv3.OpPut(newLock.key, newLock.uuid.String(), clientv3.WithLease(newLock.lease))).
		Else(clientv3.OpGet(newLock.key))
	if resp, err := txn.Commit(); err != nil {
		return nil, err
	} else {
		if resp.Succeeded {
			return newLock, nil
		} else {
			return nil, nil
		}
	}
}

func (l *Locker) Unlock(lock qtlock.Lock) {
	if etcdLock, ok := lock.(*Lock); ok {
		if l.fair {
			if etcdLock.ownerCancelFun != nil {
				etcdLock.ownerCancelFun()
			}
			if etcdLock.ownerLease != 0 {
				l.client.Lease.Revoke(context.Background(), etcdLock.ownerLease)
			}
		}

		etcdLock.leaseCancelFun()
		l.client.Lease.Revoke(context.Background(), etcdLock.lease)
	}
}

func (l *Locker) Lock(ctx context.Context, key qtlock.Key) (lock qtlock.Lock, err error) {
	id := uuid.New()

	var ownerLease clientv3.LeaseID
	var ownerCancelFunc func()
	defer func() {
		if lock != nil {
			etcdLock := lock.(*Lock)
			etcdLock.ownerCancelFun = ownerCancelFunc
			etcdLock.ownerLease = ownerLease
		} else {
			if ownerCancelFunc != nil {
				ownerCancelFunc()
			}
			if ownerLease != 0 {
				l.client.Lease.Revoke(context.Background(), ownerLease)
			}
		}
	}()
	if l.fair {
		cancelCtx, cancelFun := context.WithCancel(context.Background())
		if leaseID, err := createKeepAliveLease(cancelCtx, 10, l.client); err != nil {
			cancelFun()
			return nil, err
		} else {
			ownerLease = leaseID
			ownerCancelFunc = cancelFun
		}

		if _, err := l.client.Put(context.Background(), genKey(key)+"/_OWNERS_/"+id.String(), id.String(), clientv3.WithLease(ownerLease)); err != nil {
			return nil, err
		}
	}

	//创建watch
	cancelCtx, cancelFun := context.WithCancel(context.Background())
	//方法结束说明不需要再watch了
	defer cancelFun()
	watch := l.client.Watch(cancelCtx, genKey(key), clientv3.WithPrefix())
	//首先尝试一波是否可以抢占,可以就返回lock
	if lock, err := l.tryLock(key, id); err != nil {
		return nil, err
	} else if lock != nil {
		return lock, nil
	}
	//select模式,如果超时了就不干了
	for {
		select {
		case resp := <-watch:
			for _, ev := range resp.Events {
				if ev.Type == clientv3.EventTypeDelete {
					if lock, err := l.tryLock(key, id); err != nil {
						return nil, err
					} else if lock != nil {
						return lock, nil
					}
				}
			}
		case <-ctx.Done():
			return nil, ctx.Err()
		}
	}
}

func genKey(key qtlock.Key) string {
	return "/qt/lock/" + string(key)
}

运行结果

公平锁

package main

import (
	"context"
	"fmt"
	"qtjob/qtlock"
	_ "qtjob/qtlock-etcd"
	"sync"
	"time"
)

func main() {
	wg := &sync.WaitGroup{}
	wg.Add(10)
	startTime := time.Now()
	locker := qtlock.GetLocker(true)
	for i := 0; i < 10; i++ {
		time.Sleep(100 * time.Millisecond)
		go func(i int) {
			defer wg.Done()
			if lock, err := locker.Lock(context.Background(), "my-key"); err != nil {
				fmt.Println(i, "err", err, time.Now())
			} else if lock != nil {
				defer func() {
					lock.Unlock()
					fmt.Println(i, "释放锁", time.Now())
				}()
				fmt.Println(i, "获取锁", time.Now())
				time.Sleep(1 * time.Second)
			} else {
				fmt.Println(i, "没有获取锁", time.Now().Sub(startTime).Seconds())
			}
		}(i)
	}
	wg.Wait()
	fmt.Println("所有工作都完成啦", time.Now().Sub(startTime).Seconds())
}

以依次运行3个main

  • 第一个
0 获取锁 2021-06-27 20:19:27.550466 +0800 CST m=+0.127847385
0 释放锁 2021-06-27 20:19:28.557069 +0800 CST m=+1.134474677
1 获取锁 2021-06-27 20:19:28.5642 +0800 CST m=+1.141605853
1 释放锁 2021-06-27 20:19:29.57077 +0800 CST m=+2.148199317
2 获取锁 2021-06-27 20:19:29.577877 +0800 CST m=+2.155306443
2 释放锁 2021-06-27 20:19:30.5882 +0800 CST m=+3.165653668
3 获取锁 2021-06-27 20:19:30.595688 +0800 CST m=+3.173141675
3 释放锁 2021-06-27 20:19:31.605569 +0800 CST m=+4.183047291
4 获取锁 2021-06-27 20:19:31.613068 +0800 CST m=+4.190545893
4 释放锁 2021-06-27 20:19:32.61865 +0800 CST m=+5.196151610
5 获取锁 2021-06-27 20:19:32.623703 +0800 CST m=+5.201205418
5 释放锁 2021-06-27 20:19:33.635073 +0800 CST m=+6.212598991
6 获取锁 2021-06-27 20:19:33.642475 +0800 CST m=+6.220001909
6 释放锁 2021-06-27 20:19:34.65735 +0800 CST m=+7.234900445
7 获取锁 2021-06-27 20:19:34.671907 +0800 CST m=+7.249458452
7 释放锁 2021-06-27 20:19:35.686387 +0800 CST m=+8.263961758
8 获取锁 2021-06-27 20:19:35.695422 +0800 CST m=+8.272997592
8 释放锁 2021-06-27 20:19:36.709496 +0800 CST m=+9.287095249
9 获取锁 2021-06-27 20:19:36.719634 +0800 CST m=+9.297233253
9 释放锁 2021-06-27 20:19:37.725698 +0800 CST m=+10.303321779
所有工作都完成啦 10.299433566
  • 第二个
0 获取锁 2021-06-27 20:19:37.737731 +0800 CST m=+7.568569649
0 释放锁 2021-06-27 20:19:38.754212 +0800 CST m=+8.585075088
1 获取锁 2021-06-27 20:19:38.762839 +0800 CST m=+8.593702185
1 释放锁 2021-06-27 20:19:39.771861 +0800 CST m=+9.602748940
2 获取锁 2021-06-27 20:19:39.779315 +0800 CST m=+9.610202705
2 释放锁 2021-06-27 20:19:40.788968 +0800 CST m=+10.619879770
3 获取锁 2021-06-27 20:19:40.797028 +0800 CST m=+10.627939922
3 释放锁 2021-06-27 20:19:41.804775 +0800 CST m=+11.635711081
4 获取锁 2021-06-27 20:19:41.815311 +0800 CST m=+11.646246703
4 释放锁 2021-06-27 20:19:42.826904 +0800 CST m=+12.657865757
5 获取锁 2021-06-27 20:19:42.833435 +0800 CST m=+12.664394949
5 释放锁 2021-06-27 20:19:43.839824 +0800 CST m=+13.670808299
6 获取锁 2021-06-27 20:19:43.85154 +0800 CST m=+13.682524092
6 释放锁 2021-06-27 20:19:44.860329 +0800 CST m=+14.691337585
7 获取锁 2021-06-27 20:19:44.8655 +0800 CST m=+14.696508425
7 释放锁 2021-06-27 20:19:45.874083 +0800 CST m=+15.705116168
8 获取锁 2021-06-27 20:19:45.883744 +0800 CST m=+15.714776664
8 释放锁 2021-06-27 20:19:46.893219 +0800 CST m=+16.724276235
9 获取锁 2021-06-27 20:19:46.897397 +0800 CST m=+16.728454358
9 释放锁 2021-06-27 20:19:47.904127 +0800 CST m=+17.735208096
所有工作都完成啦 17.731764593
  • 第三个
0 获取锁 2021-06-27 20:19:47.913892 +0800 CST m=+14.753784641
0 释放锁 2021-06-27 20:19:48.920926 +0800 CST m=+15.760842312
1 获取锁 2021-06-27 20:19:48.929371 +0800 CST m=+15.769287127
1 释放锁 2021-06-27 20:19:49.939623 +0800 CST m=+16.779564043
2 获取锁 2021-06-27 20:19:49.947469 +0800 CST m=+16.787409354
2 释放锁 2021-06-27 20:19:50.957638 +0800 CST m=+17.797602940
3 获取锁 2021-06-27 20:19:50.964732 +0800 CST m=+17.804697111
3 释放锁 2021-06-27 20:19:51.978776 +0800 CST m=+18.818765511
4 获取锁 2021-06-27 20:19:51.987461 +0800 CST m=+18.827450408
4 释放锁 2021-06-27 20:19:52.995747 +0800 CST m=+19.835760735
5 获取锁 2021-06-27 20:19:53.002322 +0800 CST m=+19.842335471
5 释放锁 2021-06-27 20:19:54.012092 +0800 CST m=+20.852129302
6 获取锁 2021-06-27 20:19:54.018343 +0800 CST m=+20.858380657
6 释放锁 2021-06-27 20:19:55.02701 +0800 CST m=+21.867072232
7 获取锁 2021-06-27 20:19:55.033416 +0800 CST m=+21.873477902
7 释放锁 2021-06-27 20:19:56.046365 +0800 CST m=+22.886451162
8 获取锁 2021-06-27 20:19:56.052991 +0800 CST m=+22.893077955
8 释放锁 2021-06-27 20:19:57.062726 +0800 CST m=+23.902836457
9 获取锁 2021-06-27 20:19:57.068247 +0800 CST m=+23.908357798
9 释放锁 2021-06-27 20:19:58.077912 +0800 CST m=+24.918046010
所有工作都完成啦 24.914203824

非公平锁

运行3个main

  • 第一个
0 获取锁 2021-06-27 20:18:05.838694 +0800 CST m=+0.121275535
0 释放锁 2021-06-27 20:18:06.844003 +0800 CST m=+1.126608017
5 获取锁 2021-06-27 20:18:06.851627 +0800 CST m=+1.134232908
5 释放锁 2021-06-27 20:18:07.854577 +0800 CST m=+2.137206217
7 获取锁 2021-06-27 20:18:07.862737 +0800 CST m=+2.145366240
7 释放锁 2021-06-27 20:18:08.869402 +0800 CST m=+3.152055847
4 获取锁 2021-06-27 20:18:08.879202 +0800 CST m=+3.161856030
4 释放锁 2021-06-27 20:18:09.886098 +0800 CST m=+4.168775718
2 获取锁 2021-06-27 20:18:10.914925 +0800 CST m=+5.197627433
2 释放锁 2021-06-27 20:18:11.92025 +0800 CST m=+6.202976608
6 获取锁 2021-06-27 20:18:11.929309 +0800 CST m=+6.212035431
6 释放锁 2021-06-27 20:18:12.938716 +0800 CST m=+7.221467034
8 获取锁 2021-06-27 20:18:12.956149 +0800 CST m=+7.238900413
8 释放锁 2021-06-27 20:18:13.959678 +0800 CST m=+8.242453029
9 获取锁 2021-06-27 20:18:17.030777 +0800 CST m=+11.313624917
9 释放锁 2021-06-27 20:18:18.036743 +0800 CST m=+12.319615356
3 获取锁 2021-06-27 20:18:22.121173 +0800 CST m=+16.404142281
3 释放锁 2021-06-27 20:18:23.13125 +0800 CST m=+17.414243048
1 获取锁 2021-06-27 20:18:27.218464 +0800 CST m=+21.501554198
1 释放锁 2021-06-27 20:18:28.224302 +0800 CST m=+22.507416502
所有工作都完成啦 22.503525374
  • 第二个
2 获取锁 2021-06-27 20:18:09.893969 +0800 CST m=+1.132471256
2 释放锁 2021-06-27 20:18:10.900968 +0800 CST m=+2.139494159
9 获取锁 2021-06-27 20:18:13.968598 +0800 CST m=+5.207197245
9 释放锁 2021-06-27 20:18:14.976034 +0800 CST m=+6.214657380
3 获取锁 2021-06-27 20:18:14.989242 +0800 CST m=+6.227865562
3 释放锁 2021-06-27 20:18:15.993106 +0800 CST m=+7.231752786
4 获取锁 2021-06-27 20:18:20.076725 +0800 CST m=+11.315469643
4 释放锁 2021-06-27 20:18:21.087402 +0800 CST m=+12.326171986
6 获取锁 2021-06-27 20:18:21.104958 +0800 CST m=+12.343726803
6 释放锁 2021-06-27 20:18:22.112438 +0800 CST m=+13.351231093
1 获取锁 2021-06-27 20:18:24.163994 +0800 CST m=+15.402835545
1 释放锁 2021-06-27 20:18:25.168774 +0800 CST m=+16.407639425
7 获取锁 2021-06-27 20:18:28.23352 +0800 CST m=+19.472458771
7 释放锁 2021-06-27 20:18:29.239593 +0800 CST m=+20.478557098
8 获取锁 2021-06-27 20:18:33.294351 +0800 CST m=+24.533410239
8 释放锁 2021-06-27 20:18:34.300123 +0800 CST m=+25.539205720
0 获取锁 2021-06-27 20:18:34.305126 +0800 CST m=+25.544209210
0 释放锁 2021-06-27 20:18:35.312746 +0800 CST m=+26.551853436
5 获取锁 2021-06-27 20:18:35.319791 +0800 CST m=+26.558898016
5 释放锁 2021-06-27 20:18:36.325765 +0800 CST m=+27.564896058
所有工作都完成啦 27.561429141
  • 第三个
4 获取锁 2021-06-27 20:18:16.006294 +0800 CST m=+3.738873366
4 释放锁 2021-06-27 20:18:17.01444 +0800 CST m=+4.747043499
3 获取锁 2021-06-27 20:18:18.047145 +0800 CST m=+5.779772816
3 释放锁 2021-06-27 20:18:19.052704 +0800 CST m=+6.785355106
5 获取锁 2021-06-27 20:18:19.062197 +0800 CST m=+6.794849085
5 释放锁 2021-06-27 20:18:20.069998 +0800 CST m=+7.802673572
2 获取锁 2021-06-27 20:18:23.144169 +0800 CST m=+10.876918442
2 释放锁 2021-06-27 20:18:24.152375 +0800 CST m=+11.885148298
7 获取锁 2021-06-27 20:18:25.181316 +0800 CST m=+12.914113627
7 释放锁 2021-06-27 20:18:26.186676 +0800 CST m=+13.919497141
8 获取锁 2021-06-27 20:18:26.193912 +0800 CST m=+13.926733805
8 释放锁 2021-06-27 20:18:27.201253 +0800 CST m=+14.934098797
9 获取锁 2021-06-27 20:18:29.249341 +0800 CST m=+16.982235762
9 释放锁 2021-06-27 20:18:30.253136 +0800 CST m=+17.986054265
0 获取锁 2021-06-27 20:18:30.25859 +0800 CST m=+17.991508093
0 释放锁 2021-06-27 20:18:31.267492 +0800 CST m=+19.000434755
1 获取锁 2021-06-27 20:18:31.275035 +0800 CST m=+19.007977583
1 释放锁 2021-06-27 20:18:32.279909 +0800 CST m=+20.012875533
6 获取锁 2021-06-27 20:18:32.28645 +0800 CST m=+20.019416445
6 释放锁 2021-06-27 20:18:33.289617 +0800 CST m=+21.022607274
所有工作都完成啦 21.019210949

还有很多需要改进的地方

还有很多优化的地方

  • key的命名规则
  • watch机制
  • 等等

等我学的更多吧~

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言基于Redis实现的分布式限流是一种常见的解决方案,可以有效地控制系统的并发访问流量,防止系统被过多的请求压垮。 首先,分布式限流需要使用Redis的计数器功能。通过对每个请求进行计数,并设置一个时间窗口,可以统计在该窗口内的请求次数。当请求次数超过某个阈值时,可以拒绝该请求或者进行降级处理。 其次,为了保证分布式限流的准确性和高效性,需要使用Redis的原子操作,例如INCR、EXPIRE等。INCR命令可以原子地将计数器的值加1,并返回加1后的结果,而EXPIRE命令可以设置计数器的过期时间。通过这些原子操作,可以在多个节点之间共享计数状态,并且保证计数器的同步和高效性。 此外,为了保证系统的稳定性和可靠性,需要考虑设置适当的限流阈值和时间窗口大小。根据系统的负载情况和性能需求,可以调整这些参数,实现对系统流量的合理控制。 在实际应用中,可以使用Go语言的Redis客户端连接Redis服务器,并通过相关命令操作计数器。同时,还可以结合其他的组件和技术,如分布式、消息队列等,增强系统的稳定性和可扩展性。 总之,Go语言基于Redis实现的分布式限流是一种可行且有效的解决方案,可以帮助我们应对大流量的并发请求,保证系统的稳定运行。通过合理设定限流参数和灵活运用Redis的功能,我们可以实现流量控制、降级和保护系统免受恶意请求的攻击。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值