redis分布式锁校验网段重叠或重复

需求分析

在分布式系统中,在一个网段内已经存在部分子网,新创建的子网需要先校验是否属于该网段,再校验是否与已存在的网段有包含关系,考虑到并发的问题,这个过程需要加redis分布式锁

锁的选择

1、setnx
①:有多个协程同时抢到锁,但只有一个协程拿到锁
②:有过期时间,超过过期时间锁自动释放

2、redislock
①:有多个协程同时抢到锁,但只有一个协程拿到锁
②:有过期时间,超过过期时间锁自动释放
③:通过Lua脚本判断准备释放的锁中的值是否为自己设置的

通过以上对比可以发现单纯的使用redis自带的setNx方法在遇到多个server使用一个redis时,因没有校验机制会出现serverA加的锁被serverB释放的情况,所以setNx多用于校验单个server是否重复提交请求,而分布式锁需要参考github.com/bsm/redislock的实现

package main
import (
	"context"
	"fmt"
	"math/big"
	"net"
	"time"

	"github.com/bsm/redislock"
	"github.com/go-redis/redis"
	"github.com/sirupsen/logrus"
)

var locker *redislock.Client

func InitRedisLock() (*redislock.Client){
	// 连接redis
	client := redis.NewClient(&redis.Options{
		Network: "tcp",
		Addr:    "127.0.0.1:6379",
	})
	defer client.Close()

	// 根据client创建锁
	locker := redislock.New(client)
	return locker
}

func verifyTTL(ctx context.Context, lock *redislock.Lock) error  {
	// 校验TTL
	if ttl, err := lock.TTL(ctx); err != nil {
		return err
	} else if ttl > 0 {
		fmt.Println("I still have time to use my lock!")
	}else if ttl == 0 {
		fmt.Println("Now, my lock has expired!")
	}

	// 延长锁的使用时间为新的ttl
	if err := lock.Refresh(ctx, 200*time.Millisecond, nil); err != nil {
		return err
	}
	return nil
}

func getLocker(ctx context.Context,key string)(*redislock.Lock, error){
// redislock v0.3.0,RetryCount阻塞后重试次数,RetryBackoff阻塞后重试时间间隔
	option := redislock.Options{
		RetryCount:   1,
		RetryBackoff: 100*time.Millisecond,
		Metadata:     "",
		Context:      nil,
	}	
// redislock 最新版,Retry every 100ms, for up-to 3x
	backoff := redislock.LimitRetry(redislock.LinearBackoff(100*time.Millisecond), 3)

	option := redislock.Options{
		RetryStrategy: backoff,
	}
	// 第一个参数指定key,相同的key之间是互斥关系,第二个是获取锁的最大时间,第三个是option可选项redislock.Options里面支持配置参数
	lock, err := locker.Obtain(ctx, key, time.Second, &option)
	if err == redislock.ErrNotObtained {
		fmt.Println("Could not obtain lock!")
	} else if err != nil {
		logrus.Error(err)
		return nil,err
	}
	return lock,nil
}

func VerifySubnetCIDR(subnetCIDR ,networkCIDR string, subnetCIDRs []string) error {
	var (
		err     error
		errInfo string
	)

	// 校验新加入的subnetCIDR是否符合规范
	_, subnet, err := net.ParseCIDR(subnetCIDR)
	if err != nil {
		errInfo = fmt.Sprintf("subnet cidr: %s error: %v", subnetCIDR, err)
		return fmt.Errorf(errInfo)
	}

	// 校验已存在的networkCIDR是否符合规范
	_, network, err := net.ParseCIDR(networkCIDR)
	if err != nil {
		errInfo = fmt.Sprintf("network cidr: %s error: %v", networkCIDR, err)
		return fmt.Errorf(errInfo)
	}

	// 校验已存在的多个subnets是否符合规范
	subnets := []*net.IPNet{}
	for _, s := range subnetCIDRs {
		_, ss, err := net.ParseCIDR(s)
		if err != nil {
			errInfo = fmt.Sprintf("existed subnet cidr: %v error: %v", s, err)
			return fmt.Errorf(errInfo)
		}
		subnets = append(subnets, ss)
	}

	if err = VerifyOverlap(subnet, subnets, network); err != nil {
		return err
	}
	return nil
}

func VerifyOverlap(subnet *net.IPNet, subnets []*net.IPNet, network *net.IPNet) error {
	//校验新的subnet是否超出network范围
	if err := VerifyCIDRInNet(subnet, network); err != nil {
		return err
	}

	//校验新加入的subnet和已存在的subnet是否重叠
	if err := VerifyCIDRInSubnets(subnet, subnets); err != nil {
		return err
	}
	return nil
}

func VerifyCIDRInNet(subnetCidr, netCidr *net.IPNet) error {
	subFirst, subLast := IPRange(subnetCidr)
	if !netCidr.Contains(subFirst) || !netCidr.Contains(subLast) {
		return fmt.Errorf("%s does not fully contain %s", netCidr.String(), subnetCidr.String())
	}
	return nil
}

func VerifyCIDRInSubnets(subnet *net.IPNet, subnetIPnets []*net.IPNet) error {
	// Contains方法是net.IPNet类型自带的方法,确认网段是否包含ip
	for _, s := range subnetIPnets {
		if s.Contains(subnet.IP) || subnet.Contains(s.IP) {
			return fmt.Errorf("%s overlaps with %s", subnet.String(), s.String())
		}
	}
	return nil
}

func IPRange(network *net.IPNet) (net.IP, net.IP) {
	firstIP := network.IP
	fmt.Printf("firstIP=%v",firstIP)
	//firstIP=10.1.0.0,获取网段的第一个ip

	prefixLen, bits := network.Mask.Size()
	fmt.Printf("prefixLen=%v,bits=%v",prefixLen,bits)
	//prefixLen=16,bits=32,prefixLen是子网掩码,bits是ipv4标准掩码
	if prefixLen == bits {
		// 如果是ipv4类型且传递的网段是32位,则第一个ip和最后一个ip相同
		lastIP := make([]byte, len(firstIP))
		copy(lastIP, firstIP)
		return firstIP, lastIP
	}
	// 将ip转换位int类型,ipToInt方法实现过程有很多,这里不写了
	firstIPInt, bits := ipToInt(firstIP)
	fmt.Printf("firstIPInt=%v,bits=%v",firstIPInt,bits)
	//firstIPInt=167837696,bits=32

	// 计算主机位32-网络位
	hostLen := uint(bits) - uint(prefixLen)

	lastIPInt := big.NewInt(1)
	fmt.Printf("lastIPInt=%v",lastIPInt)
	// lastIPInt=1
	// LSH代表1向左移动16位(主机位)
	lastIPInt.Lsh(lastIPInt, hostLen)

	// 1向左移动16位的值-1
	lastIPInt.Sub(lastIPInt, big.NewInt(1))
	lastIPInt.Or(lastIPInt, firstIPInt)

	// 返回ip格式的数据,intToIP方法实现过程有很多,这里不写了
	return firstIP, intToIP(lastIPInt, bits)
}

func main()  {
	logctx := logrus.WithFields(logrus.Fields{"func":  VerifySubnetCIDR})
	ctx := context.WithValue(context.Background(), "logger", logctx)

	InitRedisLock()
	key := "redisKey"
	locker,err := getLocker(ctx,key)
	if err != nil {
		return
	}
	err = verifyTTL(ctx,locker)
	defer locker.Release(ctx)

	subnetCIDR := "10.7.0.0/16"
	networkCIDR := "10.0.0.0/8"
	subnetCIDRs := []string{"10.1.0.0/16","10.2.0.0/16"}
	err = VerifySubnetCIDR(subnetCIDR,networkCIDR,subnetCIDRs)
	if err != nil {
		return
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值