转载自:etcd实现分布式锁
当并发的访问共享资源的时候,如果没有加锁的话,无法保证共享资源安全性和正确性。这个时候就需要用到锁
1、需要具备的特性
- 需要保证互斥访问(分布式环境需要保证不同节点、不同线程的互斥访问)
- 需要有超时机制,防止锁意外未释放,其他节点无法获取到锁;也要保证任务能够正常执行完成,不能超时了任务还没结束,导致任务执行一般被释放锁
- 需要有阻塞和非阻塞两种请求锁的接口
2、本地锁
当业务执行在同一个线程内,也就是我初始化一个本地锁,其他请求也认这把锁。一般是服务部署在单机环境下。
我们可以看下下面的例子,开1000个goroutine并发的给Counter做自增操作,结果会是什么样的呢?
package main
import (
"fmt"
"sync"
)
var sg sync.WaitGroup
type Counter struct {
count int
}
// 自增操作
func (m *Counter) Incr() {
m.count++
}
// 获取总数
func (m *Counter) Count() int {
return m.count
}
func main() {
c := &Counter{}
for i := 0; i < 1000; i++ {
sg.Add(1)
// 模拟并发请求
go func() {
c.Incr()
sg.Done()
}()
}
sg.Wait()
fmt.Println(c.Count())
}
结果是count的数量并不是预想中的1000,而是下面这样,每次打印出的结果都不一样,但是接近1000
user@userdeMacBook-Pro ~/go/src/go-demo/mutex go run main.go
953
user@userdeMacBook-Pro ~/go/src/go-demo/mutex go run main.go
982
user@userdeMacBook-Pro ~/go/src/go-demo/mutex go run main.go
984
出现这个问题的原因就是没有给自增操作加锁
下面我们修改代码如下,在Incr中加上go的mutex互斥锁
package main
import (
"fmt"
"sync"
)
var sg sync.WaitGroup
type Counter struct {
count int
mu sync.Mutex
}
func (m *Counter) Incr() {
// 每次写之前先加锁,写完之后释放锁
m.mu.Lock()
defer m.mu.Unlock()
m.count++
}
func (m *Counter) Count() int {
return m.count
}
func main() {
c := &Counter{}
for i := 0; i < 1000; i++ {
sg.Add(1)
go func() {
c.Incr()
sg.Done()
}()
}
sg.Wait()
fmt.Println(c.Count())
}
可以看到现在count正常输出1000了
user@userdeMacBook-Pro ~/go/src/go-demo/mutex go run main.go
1000
user@userdeMacBook-Pro ~/go/src/go-demo/mutex go run main.go
1000
user@userdeMacBook-Pro ~/go/src/go-demo/mutex go run main.go
1000
3、etcd分布式锁
简单部署一个etcd集群
├── docker-compose.yml
├── etcd
│ └── Dockerfile
Dockerfile文件内容
FROM bitnami/etcd:latest
LABEL maintainer="liuyuede123 <liufutianoppo@163.com>"
Docker-compose.yml内容
version: '3.5'
# 网络配置
networks:
backend:
driver: bridge
# 服务容器配置
services:
etcd1: # 自定义容器名称
build:
context: etcd # 指定构建使用的 Dockerfile 文件
environment:
- TZ=Asia/Shanghai
- ALLOW_NONE_AUTHENTICATION=yes
- ETCD_NAME=etcd1
- ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd1:2380
- ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
- ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
- ETCD_ADVERTISE_CLIENT_URLS=http://etcd1:2379
- ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster
- ETCD_INITIAL_CLUSTER=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
- ETCD_INITIAL_CLUSTER_STATE=new
ports: # 设置端口映射
- "12379:2379"
- "12380:2380"
networks:
- backend
restart: always
etcd2: # 自定义容器名称
build:
context: etcd # 指定构建使用的 Dockerfile 文件
environment:
- TZ=Asia/Shanghai
- ALLOW_NONE_AUTHENTICATION=yes
- ETCD_NAME=etcd2
- ETCD_INITIAL_ADVERTISE_PEER_URLS=http://etcd2:2380
- ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
- ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
- ETCD_ADVERTISE_CLIENT_URLS=http://etcd2:2379
- ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster
- ETCD_INITIAL_CLUSTER=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380
- ETCD_INITIAL_CLUSTER_STATE=new
ports: # 设置端口映射
- "22379:2379"
- "22380:2380&q