nsq的目录锁,源码分析

文章目录

        前言

        nsqd启动加锁流程及源码分析

        总结


前言

前面几篇博客我们讲了nsq是什么,nsq的安装等,大家想过下面这样的问题没有,就是

问题:一个目录下能启动多个nsqd进程吗?

答案:不能

问题:为什么不能呢?它是怎么实现的呢?

答案:nsqd需要保存topic,channel等数据到文件中,文件名都是nsqd.dat。如果启动多个nsqd进程,这些进程保存的文件会冲突或相互覆盖。所以一个nsqd进程启动时会对启动目录加锁,其他nsqd进程若再加锁该目录就会失败,最终启动失败

问题:一个目录下能启动多个nsqlookupd,nsqadmin吗?

答案:可以,因为这些进程没有读取,保存文件的需求,各进程绑定的端口不一致即可

这一篇博客我们就讲nsqd进程启动时加锁目录的实现原理

nsqd启动加锁流程及源码分析

先看下nsqd进程启动的步骤,nsqd启动的main函数在文件 nsq\apps\nsqd\main.go,main函数源码如下(已加注释)

// nsqd的启动入口
func main() {
	prg := &program{}

	// Run内部会调用Init(),Start(),监听到这两个信号时会调用Stop()
	if err := svc.Run(prg, syscall.SIGINT, syscall.SIGTERM); err != nil {
		logFatal("%s", err)
	}
}

要注意的是:nsqdnsqlookupdnsqadmin等所有进程都是使用了go-svc包,这个包很简单,就是三个函数 Init(), Start(), Stop(),各个使用者实现了这三个接口就行

上面的main()里面调用了svc.Run(),这个函数内部会调用实现者的Init()函数

nsqd实现的Init()源码如下(已加注释)

// nsqd的初始化,主要是配置及检测相关,最后会创建nsqd实例
func (p *program) Init(env svc.Environment) error {
	// 生成nsqd的默认配置
	opts := nsqd.NewOptions()
    ......
	// 重点:根据opts新建一个nsqd实例
	nsqd, err := nsqd.New(opts)
    ......
}

省略了一些不重要的代码,nsqd的Init函数主要就是生成配置opts,然后使用opts来创建nsqd实例

重点来了:nsqd.New(opts),源码如下(已加注释)

// 根据配置opts新建一个nsqd对象
func New(opts *Options) (*NSQD, error) {
	var err error

	// 没有指定运行路径,就使用当前目录
	dataPath := opts.DataPath
	if opts.DataPath == "" {
		cwd, _ := os.Getwd()
		dataPath = cwd
	}
    ......
	n := &NSQD{
		startTime:            time.Now(),
		topicMap:             make(map[string]*Topic),
		exitChan:             make(chan int),
		notifyChan:           make(chan interface{}),
		optsNotificationChan: make(chan struct{}, 1),
		dl:                   dirlock.New(dataPath),	// 创建目录锁
	}
    ......
	// 加锁运行的目录
	err = n.dl.Lock()
	if err != nil {
		return nil, fmt.Errorf("failed to lock data-path: %v", err)
	}
    ......
}

上面的代码,我们主要关注的几个点

1. 如果启动配置中没有指定data目录,nsqd会以当前目录为运行目录,赋给dataPath

2. 创建nsqd实例时,会以dataPth来创建一个目录锁dl,即dl : dirlock.New(dataPath)

3. 创建nsqd实例后,会调用dl.Lock()对目录进行加锁,加锁失败就返回了,也就是nsqd进程启动失败

核心:dl.Lock()这里真正实现了目录的加锁

有意思的是,dirlock包对不同的系统实现的加锁方式也不同,但是只有linux下dirlock.go真正有实现,另外的illumos.go,dirlock_windows.go的实现都是空的。

dirlock包位置在nsq/internal/dirlock,如下图

 我们只看linux下dirlock.go即可,代码如下(已加注释)

// 目录锁(内部靠文件锁来实现)
type DirLock struct {
	dir 	string		// 要锁定的目录(全路径),锁定时会把这个目录当成文件来锁定
	f   	*os.File	// 以该目录创建的文件对象
}
// 加锁
func (l *DirLock) Lock() error {
	f, err := os.Open(l.dir) // linux下目录也是文件,可以像文件一样打开
	if err != nil {
		return err
	}
	l.f = f

	// 锁定文件(LOCK_EX表放置互斥锁, LOCK_NB表非阻塞锁)
	err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
	if err != nil {
		return fmt.Errorf("cannot flock directory %s - %s (possibly in use by another instance of nsqd)", l.dir, err)
	}
	return nil
}
// 解锁
func (l *DirLock) Unlock() error {
	defer l.f.Close() // 关闭文件
	return syscall.Flock(int(l.f.Fd()), syscall.LOCK_UN) // LOCK_UN表解锁
}

上面代码,主要就两个函数

Lock() 加锁内部调用syscall.Flock(fd, syscall.LOCK_EX|syscall.LOCK_NB)

fd是文件打开后的fd

LOCK_EX :加锁标记。只有一个进程能加锁成功,其他进程再尝试加锁时会阻塞,等同于我们常用的写锁

LOCK_NB :不阻塞标记。如果其他进程已加锁成功,自己去尝试加锁时就不再阻塞,而是直接返回错误

Unlock() 解锁内部调用 syscall.Flock(int(l.f.Fd()), syscall.LOCK_UN)

LOCK_UN :解锁标记。如果自己已经加锁成功,可以用此标记去解锁

nsqd进程在关闭的时候,会调用Stop()函数,函数内部会调用dl.Unlock()对目录进行解锁,代码如下(已加注释)

func (n *NSQD) Exit() {
    ......
	// 解锁目录
    n.dl.Unlock()
	n.logf(LOG_INFO, "NSQ: bye")
	n.ctxCancel()
}

总结

1. nsqd进程之所以要对目录加锁是因为nsqd需要保存topic,channel等数据到文件,且文件名字固定为nsqd.dat。nsqd进程启动时需要加载文件以恢复数据。不加锁的话,多个nsqd进程会导致文件冲突,相互覆盖

2. nsqd进程在启动时,创建nsqd对象后就进行了加锁目录,加锁失败则进程启动失败

3. nsqd进程在退出时,会解锁目录

4. nsqd进程加锁,解锁目录用的是syscall包,最终使用的是函数syscall.Flock(),其中LOCK_EX标记用来加锁,LOCK_UN标记用来解锁

如果大家想对go语言的syscall包有更详细地了解,可以参考我的这篇博客

golang文件锁,目录锁,syscall包的使用_YZF_Kevin的博客-优快云博客

<think>好的,我现在需要回答用户的问题:“优秀的go并发项目参考”。用户之前已经询问过关于Go并发优秀源码的问题,现在他们可能希望了解一些具体的开源项目,这些项目在实际中如何应用Go的并发特性。我需要确保回答的结构清晰,分层次,并且提供有用的信息。 首先,回顾之前的回答,用户已经得到了一些标准库和开源项目的例子,比如Etcd和Docker。现在用户可能想要更多的项目参考,或者更详细的项目列表,以便进一步学习。我需要扩展这些例子,并添加更多不同类型的项目,展示Go并发在不同场景下的应用。 接下来,我需要考虑用户的潜在需求。他们可能是开发者,想通过研究实际项目来提升自己的并发编程能力。因此,项目应具有代表性,代码结构清晰,且广泛被认可。同时,项目应涵盖不同领域,如分布式系统、网络服务、数据库等,以展示Go并发的多样性。 我需要收集一些知名的Go项目,特别是那些以并发处理著称的。例如: 1. **Kubernetes**:作为容器编排系统,大量使用Go的并发模型处理集群管理。 2. **CockroachDB**:分布式SQL数据库,利用Go的并发处理分布式事务和一致性。 3. **Tyk**:API网关,处理高并发请求。 4. **InfluxDB**:时间序列数据库,高效处理并发写入和查询。 5. **NSQ**:实时分布式消息平台,使用Go的channel进行消息传递。 6. **Jaeger**:分布式追踪系统,处理并发数据收集和查询。 7. **Go-kit**:微服务工具包,提供并发模式的最佳实践。 8. **Gorilla WebSocket**:处理WebSocket连接的并发管理。 9. **Traefik**:反向代理和负载均衡器,处理高并发连接。 10. **Prometheus**:监控系统,并发抓取和处理指标数据。 每个项目需要简要介绍其并发应用场景和核心机制,例如使用goroutines、channels、sync包的原语等。同时,可以指出源码中的关键文件或模块,帮助用户快速定位学习重点。 还需要注意避免重复之前提到的项目,如Etcd和Docker,但可以补充更多细节。此外,提供学习建议,如如何有效阅读源码,使用工具调试并发问题,以及推荐的资源,帮助用户深入学习。 在结构上,可以按项目类型分类,如分布式系统、数据库、消息队列、网络服务等,每个类别下列举几个项目,并说明其并发实现的特点。这样用户可以根据兴趣或需求选择适合的项目进行研究。 需要确保信息准确,项目确实使用Go并发特性,并且是活跃的开源项目。另外,每个项目的描述要简明扼要,突出其并发方面的亮点,例如如何利用channels进行通信,使用goroutines处理任务,或者如何优化并发性能。 最后,总结部分可以强调通过研究这些项目,用户能学到哪些并发模式或最佳实践,如工作池、扇入扇出、并行处理管道等,并推荐进一步的学习资源或方法。</think>以下是一些优秀的Go并发项目参考,这些项目在并发模型设计和实现上具有示范意义,适合深入学习Go的高并发处理机制: --- ### 一、分布式系统与协调 1. **Kubernetes (k8s)** - **核心并发场景**:协调百万级容器的状态同步、事件处理 - **关键实现**: - `client-go` 中的 `Informer` 机制(`tools/cache/shared_informer.go`) 使用 `Reflector` + `Delta FIFO` 队列监听资源变更,通过 `goroutine` 异步处理事件 - 控制器模式:多个控制器并发运行,通过 `WorkQueue` 实现任务分发 - **源码重点**:`staging/src/k8s.io/client-go/tools/cache/` 2. **CockroachDB** (分布式SQL数据库) - **并发亮点**: - 使用`Raft`共识算法实现多副本一致性(`pkg/storage/raft_transport.go`) - 分布式事务的并发控制(`pkg/sql/execinfra/` 中的并行查询执行) - **关键技术**: - 无数据结构优化读写冲突 - `context` 包实现操作级联取消 --- ### 二、高性能网络服务 3. **NSQ** (实时分布式消息平台) - **并发模型**: - 每个消息通道(`Channel`)独立运行处理协程(`nsqd/channel.go`) - 使用 `sync.Pool` 重用内存降低GC压力 - **核心文件**: - `nsqd/protocol_v2.go`:处理TCP连接的读写协程 - `internal/protocol/http.go`:HTTP API的并发请求处理 4. **Traefik** (云原生反向代理) - **并发机制**: - 中间件链的并行处理(`pkg/middlewares/chain.go`) - 动态配置的热加载(`pkg/provider/configuration.go` 中的原子操作替换配置) - **设计模式**: - 使用 `channel` 传递请求上下文 - `sync.Once` 确保单次初始化 --- ### 三、存储与数据处理 5. **InfluxDB** (时间序列数据库) - **并发优化**: - TSM引擎的并行数据压缩(`tsdb/engine/tsm1/compact.go`) - 使用 `mmap` 实现零拷贝并发读取 - **关键技术**: - `RWMutex` 控制分片(Shard)的读写访问 - `go-dedup` 算法优化重复数据删除 6. **Dgraph** (分布式图数据库) - **亮点实现**: - 基于 `Badger` KV存储的并发事务处理(`dgraph/x/`) - 使用 `gRPC` 流式接口实现多节点并行查询 - **源码重点**: - `worker/palimir.go`:分布式查询的并发执行计划 --- ### 四、工具与框架 7. **Go-kit** (微服务工具包) - **并发实践**: - 中间件的并发安全设计(`transport/http/server.go`) - 断路器模式(`circuitbreaker` 包中的状态机原子操作) - **学习重点**: - `endpoint` 链式调用的并发控制 8. **Gorilla WebSocket** - **并发处理**: - 每个WebSocket连接独立读写协程(`conn.go` 中的 `readLoop`/`writeLoop`) - 使用 `sync.Mutex` 保护共享缓冲区 - **抗并发问题**: - 通过 `CloseHandler` 处理并发关闭连接 --- ### 五、学习建议 1. **源码阅读技巧**: - 使用 `go doc` 查看接口定义(如 `go doc sync.Map`) - 结合 `pprof` 分析goroutine运行状态 ```bash go tool pprof -http=:8080 http://localhost:6060/debug/pprof/goroutine ``` 2. **调试工具**: - **Goroutine泄漏检测**:使用 `go.uber.org/goleak` - **竞态检测**:`go build -race` - **调度跟踪**:`GODEBUG=scheddetail=1,schedtrace=1000` 3. **推荐学习路径**: ``` 基础并发原语 → Channel高级模式 → 并发安全设计 → 分布式系统应用 ``` 这些项目展示了Go并发在实践中的典型应用模式: - **通信顺序进程(CSP)**:通过Channel解耦组件 - **工作窃取**:优化任务分配效率 - **无编程**:原子操作替代传统 - **并发控制**:Context传播与超时管理 通过研究这些项目的源码,可以深入理解如何在高并发场景下平衡性能与正确性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值