需求分析
在分布式系统中,在一个网段内已经存在部分子网,新创建的子网需要先校验是否属于该网段,再校验是否与已存在的网段有包含关系,考虑到并发的问题,这个过程需要加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
}
}