使用Golang利用ectd实现一个分布式锁
http://blog.codeg.cn/post/blog/2016-02-24-distrubute-lock-over-etcd/
By zieckey · 2016年02月24日 · 1205 Words · ~3min reading time | 编辑这个页面 | Tags: Golang etcd 分布式
etcd
是随着CoreOS
项目一起成长起来的,随着Golang和CoreOS等项目在开源社区日益火热, etcd
作为一个高可用、强一致性的分布式Key-Value存储系统被越来越多的开发人员关注和使用。
这篇文章全方位介绍了etcd的应用场景,这里简单摘要如下:
- 服务发现(Service Discovery)
- 消息发布与订阅
- 负载均衡
- 分布式通知与协调
- 分布式锁
- 分布式队列
- 集群监控与Leader竞选
- 为什么用etcd而不用ZooKeeper
本文重点介绍如何利用ectd
实现一个分布式锁。 锁的概念大家都熟悉,当我们希望某一事件在同一时间点只有一个线程(goroutine)在做,或者某一个资源在同一时间点只有一个服务能访问,这个时候我们就需要用到锁。 例如我们要实现一个分布式的id生成器,多台服务器之间的协调就非常麻烦。分布式锁就正好派上用场。
其基本实现原理为:
- 在ectd系统里创建一个key
- 如果创建失败,key存在,则监听该key的变化事件,直到该key被删除,回到1
- 如果创建成功,则认为我获得了锁
具体代码如下:
package etcdsync
import (
"fmt"
"io"
"os"
"sync"
"time"
"github.com/coreos/etcd/client"
"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
)
const (
defaultTTL = 60
defaultTry = 3
deleteAction = "delete"
expireAction = "expire"
)
// A Mutex is a mutual exclusion lock which is distributed across a cluster.
type Mutex struct {
key string
id string // The identity of the caller
client client.Client
kapi client.KeysAPI
ctx context.Context
ttl time.Duration
mutex *sync.Mutex
logger io.Writer
}
// New creates a Mutex with the given key which must be the same
// across the cluster nodes.
// machines are the ectd cluster addresses
func New(key string, ttl int, machines []string) *Mutex {
cfg := client.Config{
Endpoints: machines,
Transport: client.DefaultTransport,
HeaderTimeoutPerRequest: time.Second,
}
c, err := client.New(cfg)
if err != nil {
return nil
}
hostname, err := os.Hostname()
if err != nil {
return nil
}
if len(key) == 0 || len(machines) == 0 {
return nil
}
if key[0] != '/' {
key = "/" + key
}
if ttl < 1 {
ttl = defaultTTL
}
return &Mutex{
key: key,
id: fmt.Sprintf("%v-%v-%v", hostname, os.Getpid(), time.Now().Format("20060102-15:04:05.999999999")),
client: c,
kapi: client.NewKeysAPI(c),
ctx: context.TODO(),
ttl: time.Second * time.Duration(ttl),
mutex: new(sync.Mutex),
}
}
// Lock locks m.
// If the lock is already in use, the calling goroutine
// blocks until the mutex is available.
func (m *Mutex) Lock() (err error) {
m.mutex.Lock()
for try := 1; try <= defaultTry; try++ {
if m.lock() == nil {
return nil
}
m.debug("Lock node %v ERROR %v", m.key, err)
if try < defaultTry {
m.debug("Try to lock node %v again", m.key, err)
}
}
return err
}
func (m *Mutex) lock() (err error) {
m.debug("Trying to create a node : key=%v", m.key)
setOptions := &client.SetOptions{
PrevExist:client.PrevNoExist,
TTL: m.ttl,
}
resp, err := m.kapi.Set(m.ctx, m.key, m.id, setOptions)
if err == nil {
m.debug("Create node %v OK [%q]", m.key, resp)
return nil
}
m.debug("Create node %v failed [%v]", m.key, err)
e, ok := err.(client.Error)
if !ok {
return err
}
if e.Code != client.ErrorCodeNodeExist {
return err
}
// Get the already node's value.
resp, err = m.kapi.Get(m.ctx, m.key, nil)
if err != nil {
return err
}
m.debug("Get node %v OK", m.key)
watcherOptions := &client.WatcherOptions{
AfterIndex : resp.Index,
Recursive:false,
}
watcher := m.kapi.Watcher(m.key, watcherOptions)
for {
m.debug("Watching %v ...", m.key)
resp, err = watcher.Next(m.ctx)
if err != nil {
return err
}
m.debug("Received an event : %q", resp)
if resp.Action == deleteAction || resp.Action == expireAction {
return nil
}
}
}
// Unlock unlocks m.
// It is a run-time error if m is not locked on entry to Unlock.
//
// A locked Mutex is not associated with a particular goroutine.
// It is allowed for one goroutine to lock a Mutex and then
// arrange for another goroutine to unlock it.
func (m *Mutex) Unlock() (err error) {
defer m.mutex.Unlock()
for i := 1; i <= defaultTry; i++ {
var resp *client.Response
resp, err = m.kapi.Delete(m.ctx, m.key, nil)
if err == nil {
m.debug("Delete %v OK", m.key)
return nil
}
m.debug("Delete %v falied: %q", m.key, resp)
e, ok := err.(client.Error)
if ok && e.Code == client.ErrorCodeKeyNotFound {
return nil
}
}
return err
}
func (m *Mutex) debug(format string, v ...interface{}) {
if m.logger != nil {
m.logger.Write([]byte(m.id))
m.logger.Write([]byte(" "))
m.logger.Write([]byte(fmt.Sprintf(format, v...)))
m.logger.Write([]byte("\n"))
}
}
func (m *Mutex) SetDebugLogger(w io.Writer) {
m.logger = w
}
其实类似的实现有很多,但目前都已经过时,使用的都是被官方标记为deprecated
的项目。且大部分接口都不如上述代码简单。 使用上,跟Golang官方sync包的Mutex接口非常类似,先New()
,然后调用Lock()
,使用完后调用Unlock()
,就三个接口,就是这么简单。示例代码如下:
package main
import (
"github.com/zieckey/etcdsync"
"log"
)
func main() {
//etcdsync.SetDebug(true)
log.SetFlags(log.Ldate|log.Ltime|log.Lshortfile)
m := etcdsync.New("/etcdsync", "123", []string{"http://127.0.0.1:2379"})
if m == nil {
log.Printf("etcdsync.NewMutex failed")
}
err := m.Lock()
if err != nil {
log.Printf("etcdsync.Lock failed")
} else {
log.Printf("etcdsync.Lock OK")
}
log.Printf("Get the lock. Do something here.")
err = m.Unlock()
if err != nil {
log.Printf("etcdsync.Unlock failed")
} else {
log.Printf("etcdsync.Unlock OK")
}
}
参考
使用Golang利用ectd实现一个分布式锁的更多相关文章
- 利用ZooKeeper简单实现分布式锁
1.分布式锁的由来: 在程序开发过程中不得不考虑的就是并发问题.在java中对于同一个jvm而言,jdk已经提供了lock和同步等.但是在分布式情况下,往往存在多个进程对一些资源产生竞争关系,而这些进 ...
- 利用Python+Redis实现分布式锁
class MyDLock(object): def __init__(self, lockID,timeout): self.connection = redis.Redis(host=cfg.RE ...
- zookeeper分布式锁和服务优化配置
转自:https://www.jianshu.com/p/02eeaee4357f?utm_campaign=maleskine&utm_content=note&utm_medium ...
- [翻译]利用REDIS来搭建可靠分布式锁的提议
本系列都是翻译REDIS作者的博文 另外加上我自己的一点点理解 希望有问题大家一起讨论 http://antirez.com/news/77 原文地址 在利用REDIS做分布式锁时基本持有2种观点 ...
- golang基于etcd实现分布式锁(转)
下面描述使用 Etcd 实现分布式锁的业务流程,假设对某个共享资源设置的锁名为:/lock/mylock 步骤 1: 准备 客户端连接 Etcd,以 /lock/mylock 为前缀创建全局唯一的 k ...
- spring boot 利用redisson实现redis的分布式锁
原文:http://liaoke0123.iteye.com/blog/2375469 利用redis实现分布式锁,网上搜索的大部分是使用java jedis实现的. redis官方推荐的分布式锁实现 ...
- ETCD分布式锁实现选主机制(Golang实现)
ETCD分布式锁实现选主机制(Golang) 为什么要写这篇文章 做架构的时候,涉及到系统的一个功能,有一个服务必须在指定的节点执行,并且需要有个节点来做任务分发,想了半天,那就搞个主节点做这事呗,所 ...
- 【Redis】利用 Redis 实现分布式锁
技术背景 首先我们需要先来了解下什么是分布式锁,以及为什么需要分布式锁. 对于这个问题,我们可以简单将锁分为两种--内存级锁以及分布式锁,内存级锁即我们在 Java 中的 synchronized 关 ...
- 利用Redisson实现分布式锁及其底层原理解析
Redis介绍 参考地址:https://blog.csdn.net/turbo_zone/article/details/83422215 redis是一个key-value存储系统.和Memcac ...
随机推荐
- 【LeetCode】Balanced Binary Tree(平衡二叉树)
这道题是LeetCode里的第110道题. 题目要求: 给定一个二叉树,判断它是否是高度平衡的二叉树. 本题中,一棵高度平衡二叉树定义为: 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1. ...
- 【转载】用OCTAVE实现一元线性回归的梯度下降算法
原文地址:http://www.cnblogs.com/KID-XiaoYuan/p/7247481.html STEP1 PLOTTING THE DATA 在处理数据之前,我们通常要了解数据,对于 ...
- API生命周期第三阶段:API实施模式,以及结合swagger和项目现状的最佳模式
这篇博客,主要是宏观介绍一下开发模式,尤其是针对于目前公司前后分离的项目! 一.API实施模式概述 API实施模式,主要是三个,其中API-First又是作为一种指导思想的一种,所以,简单来说事实实施 ...
- Luogu【P1725】琪露诺(单调队列,DP)
本文是笔者第二篇解题报告.从现在开始,会将练的一些题发到博客上并归类到"解题报告"标签中. 琪露诺是这样一道题 这道题可以用纯DP做,但是据说会超时.(为什么?看起来过河这题比它数 ...
- [POI2006] KRA-The Disks (贪心)
题目描述 For his birthday present little Johnny has received from his parents a new plaything which cons ...
- 使用UltraEdit 替换解决---文字中含有逗号的文件,如何把逗号自动转换成为:回车换行呢?
实际工作中有时经常遇到一个问题: 一行文字中含有逗号,如何把逗号自动转换成为:回车换行呢? 普遍存在的问题,用Ultredit中^r^n(回车换行)也可以完成.提供大家参考. 王乐,李宏宇,张志鹏,刘 ...
- 【CF666B】World Tour(贪心,最短路)
题意:给你一张有向图,叫你给出四个点的序列a,b,c,d,使得这四个点依次间的最短路之和最大.(4 ≤ n ≤ 3000, 3 ≤ m ≤ 5000) 思路:O(n4)可用来对拍 我们需要O(n2)级 ...
- ObjectDataSource配置数据源的时候,选择业务对象下拉菜单没有任何东西
原文发布时间为:2008-08-03 -- 来源于本人的百度文章 [由搬家工具导入] 问题:在App_Code这个文件夹中添加了一个类,然后保存,但是在ObjectDataSource配置数据源的时候 ...
- 【Eclipse】Eclipse中tomcat的Server配置(解决修改代码不断的重启服务器)以及设置tomcat文件发布位置与JSP编译位置查看
Eclipse有时候修改一点JS或者JSP都会自动重启,有时候修改完JS或者JSP之后必须重启服务器才生效,下面研究了server的一些选项之后彻底解决了这些问题,下面做记录: 我的 Eclipse ...
- WebRTC VoiceEngine综合应用示例(一)——基本结构分析(转)
把自己这两天学习VoiceEngine的成果分享出来,供大家参考,有什么问题也欢迎大家指出,一起学习一起进步. 本文将对VoiceEngine的基本结构做一个分析,分析的方法是自底向上的:看一个音频编 ...