浅谈分布式锁

golang操作zookeeper 阅读详情

为什么需要锁

单机程序,在多线程并发情况下,操作同一资源时,需要对其进行加锁等同步措施来保证原子性。举一个多线程自增的例子:

package main
import (
   "sync"
)
// 全局变量
var counter int
func main() {
   var wg sync.WaitGroup
   for i := 0; i < 100; i++ {
       wg.Add(1)
       go func() {
            counter++
            wg.Done()
       }()
   }
   wg.Wait()
   println(counter)
}

多次运行会得到不同的结果:

> go run test.go
98
> go run test.go
99
> go run test.go
100

显然这个结果不能让人满意,充满了不可预知。想要得到正确结果,就需要对计数自增加锁

package main

import (
    "sync"
)

// 全局变量
var counter int
var mtx sync.Mutex

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func() {
            mtx.Lock()
            counter++
            mtx.Unlock()
            wg.Done()
        }()
    }
    wg.Wait()
    println(counter)
}

多次运行后得到的结果:

> go run test.go
100
> go run test.go
100
> go run test.go
100
一、基于Redis的setnx

在分布式场景下,我们也需要这种"抢占"的逻辑,这时候怎么办?我们可以使用Redis提供的setnx命令:

package main

import (
    "fmt"
    "strconv"
    "sync"
    "time"

    "gopkg.in/redis.v5"
)

var rds = redis.NewFailoverClient(&redis.FailoverOptions{
    MasterName:    "mymaster",
    SentinelAddrs: []string{"127.0.0.1:26379"},
})

// 全局变量
func incrby() error {

    lockkey := "count_key"
    counterkey := "counter"

    succ, err := rds.SetNX(lockkey, 1, time.Second*time.Duration(5)).Result()
    if err != nil || !succ {
        fmt.Println(err, " lock result:", succ)
        return err
    }

    defer func() {
        succ, err := rds.Del(lockkey).Result()
        if err == nil && succ > 0 {
            fmt.Println("unlock sucess")
        } else {
            fmt.Println("unlock failed, err=", err)
        }
    }()

    resp, err := rds.Get(counterkey).Result()
    if err != nil && err != redis.Nil {
        fmt.Println("get count failed, err=", err)
        return err
    }

    var cnt int64
    if err == nil {
        cnt, err = strconv.ParseInt(resp, 10, 64)
        if err != nil {
            fmt.Println("parse string failed, s=", resp)
            return err
        }
    }
    fmt.Println("curr cnt:", cnt)
    cnt++
    _, err = rds.Set(counterkey, cnt, 0).Result()
    if err != nil {
        fmt.Println("set value fialed,err=", err)
        return err
    }

    return nil
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            incrby()
        }()
    }
    wg.Wait()
}

运行结果:

> go run test.go
curr cnt: 0
<nil>  lock result: false
unlock sucess
<nil>  lock result: false
curr cnt: 1
<nil>  lock result: false
unlock sucess
curr cnt: 2
<nil>  lock result: false
unlock sucess
curr cnt: 3
<nil>  lock result: false
<nil>  lock result: false
unlock sucess

远程调用setnx运行流程上和单机的trylock非常相似,如果获取锁失败,那么相关的任务逻辑就不会继续向下执行。
setnx很适合在高并发场景下,来争抢一些唯一的资源。

二、基于zookeeper
package main 
import (
    "fmt"
    "sync"
    "time"

    "github.com/samuel/go-zookeeper/zk"
)

var zkconn *zk.Conn
var count int64

func incrby() {
    lock := zk.NewLock(zkconn, "/lock", zk.WorldACL(zk.PermAll))
    err := lock.Lock()
    if err != nil {
        panic(err)
    }
    count++
    lock.Unlock()
}

func main() {
    c, _, err := zk.Connect([]string{"127.0.0.1"}, time.Second)
    if err != nil {
        fmt.Println("connect zookeeper failed, err=", err)
        return
    }
    zkconn = c
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            incrby()
        }()
    }
    wg.Wait()
    fmt.Println(" cnt :", count)
}

运行结果:

$ > go run test.go 
Connected to 127.0.0.1:2181
authenticated: id=72138376348368897, timeout=4000
re-submitting `0` credentials after reconnect
cnt : 10

基于ZooKeeper的锁与基于Redis锁不同之处在于lock成功之前会一直阻塞,这与sync.Mutex的Lock方法类似。
其原理是基于临时Sequence节点和watch API,例如我们这里使用的是/lock节点。Lock会在该节点下的节点列中插入自己的值,只要节点下的子节点发生变化,就会通知所有watch该节点的程序。这时候程序会检查当前节点下最小的子节点的id是否与自己的一致,一致则说明加锁成功了。
这种分布式的阻塞锁比较适合分布式任务调度场景,但不适合高频次持锁时间短的抢锁场景。按照Google的Chubby论文里的阐述,基于强一致协议的锁适用于 粗粒度的加锁操作。这里的粗粒度指锁占用时间较长。我们在使用时也应思考在自己的业务场景中使用是否合适

三、基于etcd

这个etcd的包"github.com/zieckey/etcdsync"拉取go mod会出现两次问题

#第一次
/etcd imports
        github.com/coreos/etcd/clientv3 tested by
        github.com/coreos/etcd/clientv3.test imports
        github.com/coreos/etcd/auth imports
        github.com/coreos/etcd/mvcc/backend imports
        github.com/coreos/bbolt: github.com/coreos/bbolt@v1.3.5: parsing go.mod:
        module declares its path as: go.etcd.io/bbolt
                but was required as: github.com/coreos/bbolt
#第二次
imports
        google.golang.org/grpc/naming: module google.golang.org/grpc@latest found (v1.32.0), but does not contain package google.golang.org/grpc/naming

需要在go.mod中加上

replace (
    github.com/coreos/bbolt v1.3.4 => go.etcd.io/bbolt v1.3.4
    go.etcd.io/bbolt v1.3.4 => github.com/coreos/bbolt v1.3.4
    google.golang.org/grpc => google.golang.org/grpc v1.26.0
)
import (
    "log"

    "github.com/zieckey/etcdsync"
)

func main() {
    m, err := etcdsync.New("/lock", 10, []string{"http://127.0.0.1:2379"})
    if m == nil || err != nil {
        log.Printf("etcdsync.New failed")
        return
    }
    err = m.Lock()
    if err != nil {
        log.Println("etcdsync.Lock failed, err=", err)
        return
    }
    log.Printf("etcdsync.Lock OK")
    log.Printf("Get the lock. Do something here.")
    err = m.Unlock()
    if err != nil {
        log.Println("etcdsync.Unlock failed, err=", err)
    } else {
        log.Printf("etcdsync.Unlock OK")
    }
}

etcd中没有像ZooKeeper那样的Sequence节点。所以其锁实现和基于ZooKeeper实现的有所不同。在上述示例代码中使用的etcdsync的Lock流程是:

  • 1、先检查/lock路径下是否有值,如果有值,说明锁已经被别人抢了
  • 2、如果没有值,那么写入自己的值。写入成功返回,说明加锁成功。写入时如果节点被其他节点写入过了,那么会导致加锁失败。
  • 3、watch /lock下的事件,此时陷入阻塞
  • 4、当/lock路径下发生事件时,当前进程被唤醒。检查发生的事件是否是删除事件(说明锁被持有者主动unlock),或者过期事件(锁过期失效),如果是的话,回到1,走抢锁流程。
如何选择合适的锁
单机量级

业务还在单机就可以搞定的量级下,那么按照需求使用任意的单机锁方案就可以。

分布式量级
  • 量级较低
    如果发展到分布式服务阶段,但业务规模不大,QPS很小的情况下,使用哪种锁的方案都差不多。如果公司内部有可以使用的ZooKeeper、etcd或者Redis集群,那么尽量不引入新的技术栈。
  • 量级较高
    如果锁是在任务恶劣的条件下都不允许数据丢失,那么就不能用Redis的setnx的简单锁。
    对锁数据的可靠性要求极高的话,那只能使用etcd或者ZooKeeper这种通过一致性协议保证数据可靠性的锁方案。(但可靠的背后往往都是较低的吞吐量和较高的延迟。需要根据业务的量级对其进行压力测试,以确保分布式锁所使用的的etcd和ZooKeeper集群可以承受得住实际的业务请求压力。
采用离散模型搭建永磁同步电机控制系统 永磁同步电机发电系统分为控制器和发电机,通过simulink仿真软件进行发电系统的离散系统仿真。离散搭建仿真,可以更方便的自动代码生成。通过实践经验离散模块搭建,更能加深对整个控制系统的理解。离散系统在矢量控制中经常涉及到上个开关周期的角度,电压,或者预测下个周期的角度,电流,一般都是通过延时模块实现。PWM比较值寄存器被改写后都是使能了预装载,采样后触发计算到比较值生效可以延时0.5个开关周期Ts,也可以延时1个开关周期Ts,可以通过离散系统进行仿真,大大缩短开发周期。 阅读详情

相关推荐

通达信浮筹多空波段抓牛擒龙指标公式源码

VAR33:=VA3>=REF(VA4,1) AND C<REF(C,1) AND VAR19=60 AND VAR18=0 AND 活跃线>=REF(活跃线,1) AND VA12<REF(VA13,1);DRAWTEXT(VAR28 AND COUNT(VAR28,30)=1,65,' ★超涨注意风险'),COLOR0000FF;DRAWTEXT(VAR26 AND COUNT(VAR26,30)=1,90,' ←继续等'),COLOR00FFFF;浮筹线:VA3,COLORFF00FF;

2401_86043146的博客 5256

open-falcon kafka-consumer 部署

作用 官方文档中没有说明 发现生产环境中使用到该服务 后续需要源代码分析再更新其他信息 创建目录 mkdir /apps/svr/falcon-plus/kafka_consumer/{bin,config,logs} -p 配置文件 /apps/svr/falcon-plus/kafka_consumer/config/cfg.json { "log_level": "info", "http": { "enabled": true, "listen"

Terry Tsang 388

win10自动修复-无限循环【通用完美】超简单解决办法

2020.02.14今个我的笔记本电脑(Thinkpad Carbon 7 X1)(win10)早晨起来发现蓝屏,上面显示自动修复失败,重启后依然自动修复失败,不断手动重启,始终是修复失败。然后上网开始搜集资料找解决办法。 前边介绍关闭自动修复,如何找出错文件的方法;如果想看最终解决办法,直接看第三部分 上网就会发现这种现象很常见,同时是电脑自动安装系统更新,或者安装某些驱动出现。解决办法也大...

y15941300574的博客 19万+

api 创建zookeeper客户端_Etcd和ZooKeeper,究竟谁在watch的功能表现更好?

ZooKeeper和Etcd的主要异同可以参考这篇文章,此外,Etcd的官网上也有对比表格(https://coreos.com/etcd/docs/latest/learning/why.html),本文不加赘述。本文主要关注这两者在watch上的功能差异。ZooKeeper和Etcd都可以对某个key进行watch,并在当这个key发生改变(比如有更新值,或删除key的操作发生)时触发。Zoo...

weixin_39740272的博客 216

QT绘制饼图+柱状图+折线图

使用qt绘制,折线图,饼图,柱状图,多柱状图

梅山 3877

VCS 2018 环境下,手把手教你用 UVMC 搞定 SystemC 和 SystemVerilog 混合仿真(附完整代码)

本文详细介绍了在VCS 2018环境下使用UVMC实现SystemC和SystemVerilog混合仿真的完整指南。从环境配置、工具链设置到UVMC通信机制解析,提供了实战案例和高级调试技巧,帮助开发者高效搭建复杂验证环境。特别适合需要处理跨语言仿真的芯片验证工程师。

weixin_30952535的博客 285

golang zk大量disconnected event

背景 在容器平台上我们提供了zk做白名单功能,Pod启动时 sidecar会自动注册zk。昨天遇到zk server抖动,sidecar容器输出大量StateDisconnected事件,zk正常后仍无法恢复,由于大量日志造成sidecar容器 cpu占用过高,进而引发dockerdcpu占用过高,严重时影响dockerd正常调用。 问题分析 问题复现 正常情况下,sidecar启动后会去注册zk: # docker logs -f 01a1a4a74785 I0302 15:04:05.476463

Qinng的博客 638

2025年12月11日 Coherent收发器下沉到数据中心:调研

技术可行性已基本具备。随着3nm DSP技术的成熟和Coherent-Lite等低功耗方案的推出,Coherent技术与IMDD的功耗差距在800G及以上速率节点快速缩小。特别是在1.6T及更高速率,两种技术的功耗开始趋于收敛。同时,通过系统级优化,如将DSP功能从可插拔模块转移到ASIC芯片,可实现30%以上的功耗降低。在成本方面,硅光技术的进步使得单通道硅光引擎BOM成本降至18美元,较2020年下降62%,逼近InP方案的经济临界点。光学系统机会丰富多样。

weixin_51184047的博客 1167

golang通过zookeeper实现master选举

golang通过zookeeper实现master选举应用程序中有一个定时任务模块,之前是单机部署的,在实现多实例部署之后就对应用程序有了如下要求: 一个定时任务只能在一个实例上运行 一个应用实例故障后,原本由该实例执行的定时任务需要在其它实例上继续运行 Zookeeper 能够很容易实现这样的集群管理功能,它能够维护当前的集群中机器的服务状态,而且能够选出一个“总管”,让这个总管来管理集群

Dobbin 8407

牛耕式全覆盖算法-matlab代码

牛耕式路径全覆盖算法,也称为牛耕算法或者蚂蚁算法,是一种用于解决路径规划问题的启发式算法。这个算法的灵感来自于牛在耕田时的行为。在这个算法中,假设有一块田地需要耕作,牛从田地的某个角落开始行走,走过的路径会被标记。牛会优先选择尚未经过的路径,当所有的路径都走过后,算法停止。在路径规划问题中,田地可以看作是一个由节点和边构成的图。每个节点代表一个位置,每条边代表两个节点之间的连接。路径全覆盖问题是找出一组路径,使得每个节点都被至少一条路径经过。牛耕式路径全覆盖算法的基本思想是以启发式的方式逐步构建路径。

m0_69611489的博客 1689

华为路由器 VRRP主备配置

3、R3 出口路由器。

一直被模仿,从未被超越 5584

浅谈对分布式锁的认识

Zookeeper每次进行锁操作前都要创建若干节点,完成后要释放节点,会浪费很多时间;而Redis只是简单的数据操作,没有这个问题。

王小二(海阔天空) 1383

Qt + windows + Linux+QtInstallerFramework打包教程

本文介绍了使用Qt Installer Framework在Windows和Linux系统下打包Qt应用程序的完整教程。Windows部分详细说明了从下载安装框架、配置环境变量、生成release版本,到创建安装包目录结构和修改配置文件的完整流程。Linux部分则重点讲解了环境变量设置、桌面应用配置(.desktop文件)以及两种打包方案:一种使用linuxdeployqt生成AppImage文件,另一种采用类似Windows的安装包方式。教程提供了关键配置文件(config.xml、package.xml

Qt历险记,专注Qt技术,三年医疗设备研发经验,1年机器视觉研发经验,半年柔性传感研发经验。 516

浅谈Redis分布式锁

浅谈Redis分布式锁 文章目录浅谈Redis分布式锁前言(为什么需要分布式锁) 前言(为什么需要分布式锁) 在聊分布式锁之前,有必要先解释一下,为什么需要分布式锁。 与分布式锁相对就的是单机锁,我们在写多线程程序时,避免同时操作一个共享变量产生数据问题,通常会使用一把锁来互斥以保证共享变量的正确性,其使用范围是在同一个进程中。如果换做是多个进程,需要同时操作一个共享资源,如何互斥呢?现在的业务应用通常是微服务架构,这也意味着一个应用会部署多个进程,多个进程如果需要修改MySQL中的同一行记录,为了避免

m0_47679010的博客 567

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

浅谈分布式锁设计

在JVM中,在多线程并发的情况下,我们可以使用同步锁或Lock锁,保证在同一时间内,只能有一个线程修改共享变量或执行代码块。但现在我们的服务基本都是基于分布式集群来实现部署的,对于一些共享资源,例如我们之前讨论过的库存,在分布式环境下使用Java锁的方式就失去作用了。这时,我们就需要实现分布式锁来保证共享资源的原子性。

856

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

浅谈 Redis 的 setNX 分布式锁

Redis 中 setNX 分布式锁详解,有SpringBoot实现代码。

qq_17224327的博客 3131

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

分布式锁浅谈之redis锁

Redis 分布式锁 业务:用于库存扣减,因为是微服务架构所以 jvm 级别的锁都不满足需求。故考虑分布式锁 A:服务获取锁 修改库存 释放锁 背景: 在单机时代,虽然不存在分布式锁,但也会面临资源互斥的情况,只不过在单机的情况下,如果有多个线程要同时访问某个共享资源的时候,我们可以采用线程间加锁的机制,即当某个线程获取到这个资源后,就需要对这个资源进行加锁,当使用完资源之后,再解锁,其它线程就可...

哎幽的成长 1505
上一篇: Golang内存逃逸
下一篇: 浅谈流量限制
canger_
博客等级 码龄9年 46粉丝 99原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值