MinIO对象读写中的分布式锁

一、背景

      minio在对象读写和删除的操作中,可以指定是否需要加锁,用于保证对象读写的互斥逻辑。minio默认打开对象读写锁。本文不会详细讨论锁具体的实现细节,只是从流程上梳理锁的初始化和使用。

二、分布式锁接口

2.1 接口定义

        minio用一个抽象接口来定义锁的行为。接口定义在minio的internal/dsync/locker.go文件中。具体接口定义如下。方法的用途和详细注释,请参考minio的源代码。

type NetLocker interface {
	RLock(ctx context.Context, args LockArgs) (bool, error)
	Lock(ctx context.Context, args LockArgs) (bool, error)
	RUnlock(ctx context.Context, args LockArgs) (bool, error)
	Unlock(ctx context.Context, args LockArgs) (bool, error)
	Refresh(ctx context.Context, args LockArgs) (bool, error)
	ForceUnlock(ctx context.Context, args LockArgs) (bool, error)
	String() string
	Close() error
	IsOnline() bool
	IsLocal() bool
}

2.2 接口实现类

        虽然minio在所有节点都可以进行对象读写操作,但对单个对象的读写操作只能通过一个节点进行。例如对象A要写入一个3节点的集群,只能通过其中一个节点对A对象进行纠删处理,再将对象数据(含纠删数据)切割为多份后并行写入到各个节点。假设A对象通过3节点集群的1号节点进行写入,对A对象来说,1号节点就是local节点,2、3号节点就是remote节点。因此minio用本地和远程两个结构体来实现分布式锁的接口。

编号结构体说明
1localLocker用于保存本地对象锁信息
2lockRESTClient用于保存远程对象锁服务接口的client

三、相关结构体

3.1 erasureSets

        erasureSets结构体用于保存单个pool中所有set的信息。结构体中的erasureLockers字段是一个二维的slice,按set编号保存每个set的所有的locker。erasureLockers字段在newErasureSets函数中被初始化。具体代码如下。

// Initialize the erasure sets instance.
s := &erasureSets{
	sets:               make([]*erasureObjects, setCount),
	erasureDisks:       make([][]StorageAPI, setCount),
	erasureLockers:     make([][]dsync.NetLocker, setCount),
	erasureLockOwner:   globalLocalNodeName,
	endpoints:          endpoints,
	endpointStrings:    endpointStrings,
	setCount:           setCount,
	setDriveCount:      setDriveCount,
	defaultParityCount: defaultParityCount,
	format:             format,
	setReconnectEvent:  make(chan int),
	distributionAlgo:   format.Erasure.DistributionAlgo,
	deploymentID:       uuid.MustParse(format.ID),
	poolIndex:          poolIdx,
}

        erasureSets结构体中的sets字段是一个slice,按set编号保存了erasureObjects结构体实例的指针。erasureObjects结构体实例为指定的set提供对象操作(读写删等)的能力。

        erasureSets结构体提供了GetLockers方法,用于获取指定set的所有locker。该方法将set所保有的locker的指针拷贝到新的切片里面并返回该切片。具体代码如下。

// GetLockers 传入set索引,通过copy方式返回指定set的所保有的locker的切片
//  set跨几个节点就有几个locker,locker分local和dist两种。
func (s *erasureSets) GetLockers(setIndex int) func() ([]dsync.NetLocker, string) {
	return func() ([]dsync.NetLocker, string) {
		lockers := make([]dsync.NetLocker, len(s.erasureLockers[setIndex]))
		copy(lockers, s.erasureLockers[setIndex])
		return lockers, s.erasureLockOwner
	}
}

3.2 erasureObjects

        erasureObjects结构体实例提供了操作对象的能力。erasureObjects在实例化的时候,将erasureSets的GetLockers方法赋值给构体中getLockers字段。在对对象进行操作时,通过执行getLockers方法获取这个set的所有locker。erasureObjects结构体在newErasureSets函数中进行初始化,具体代码如下。

// Initialize erasure objects for a given set.
// 设置单个set的信息,包括set的索引,set所在pool的索引,set中的磁盘数量,
// getDisks获取所有磁盘的StorageAPI接口的实现
// getLockers获取当前set所有locker
s.sets[i] = &erasureObjects{
	setIndex:              i,
	poolIndex:             poolIdx,
	setDriveCount:         setDriveCount,
	defaultParityCount:    defaultParityCount,
	getDisks:              s.GetDisks(i),
	getLockers:            s.GetLockers(i),
	getEndpoints:          s.GetEndpoints(i),
	deletedCleanupSleeper: newDynamicSleeper(10, 2*time.Second),
	nsMutex:               mutex,
	bp:                    bp,
	bpOld:                 bpOld,
}

        erasureObjects结构体的nsMutex字段用于保存nsLockMap结构体实例的指针。在newErasureSets函数中通过newNSLock函数创建。newNSLock函数创建nsLockMap结构体实例并返回实例的指针。也就是说每个set的erasureObjects实例中的nsMutex字段都指向同一个nsLockMap结构体实例。

3.3 nsLockMap

        nsLockMap结构体的提供了NewNSLock方法,该方法在分布式集群的场景下返回distLockInstance结构体实例的指针。distLockInstance结构体实现了RWLocker接口。RWLocker接口和distLockInstance结构体具体代码如下。

// RWLocker - locker interface to introduce GetRLock, RUnlock.
type RWLocker interface {
	GetLock(ctx context.Context, timeout *dynamicTimeout) (lkCtx LockContext, timedOutErr error)
	Unlock(cancel context.CancelFunc)
	GetRLock(ctx context.Context, timeout *dynamicTimeout) (lkCtx LockContext, timedOutErr error)
	RUnlock(cancel context.CancelFunc)
}
type distLockInstance struct {
	rwMutex *dsync.DRWMutex
	opsID   string
}

        调用NewNSLock方法时会随机生成一个UUID并将这个UUID赋值给opsID字段。

        rwMutex字段保存的是dsync.DRWMutex结构体的指针,dsync.NewDRWMutex函数生成。

3.4 DRWMutex & Dsync

        DRWMutex是一个用于分布式互斥锁的结构体。具体代码如下。

type DRWMutex struct {
	Names         []string
	writeLocks    []string // Array of nodes that granted a write lock
	readLocks     []string // Array of array of nodes that granted reader locks
	rng           *rand.Rand
	m             sync.Mutex // Mutex to prevent multiple simultaneous locks from this node
	clnt          *Dsync
	cancelRefresh context.CancelFunc
}

// Dsync represents dsync client object which is initialized with
// authenticated clients, used to initiate lock REST calls.
type Dsync struct {
	// List of rest client objects, one per lock server.
	GetLockers func() ([]NetLocker, string)
}

        DRWMutex结构体最重要的两个字段,Names字段保存对象的路径,clnt字段保存Dsync结构体的指针。

        Dsync结构体只有一个方法类型的字段GetLokers。在实例化Dsync结构体时,实际会传入erasureSets的GetLocker方法。

四、锁相关操作

4.1 锁的初始化

        在minio服务启动时,会调用newErasureSets函数对锁进行初始化。具体如下代码。

erasureLockers := map[string]dsync.NetLocker{}
for _, endpoint := range endpoints.Endpoints {
	if _, ok := erasureLockers[endpoint.Host]; !ok {
		erasureLockers[endpoint.Host] = newLockAPI(endpoint)
	}
}

        通过遍历单个pool的所有endpoint来生成所有节点的locker。注意是一个节点一个locker,而不是一个endpoint一个locker。临时变量erasureLockers保存了所有节点的locker实例的指针。

        newLockAPI函数根据endpoint中的是否是本地节点标识来生成对应的locker对象并返回对象的指针。代码如下。

func newLockAPI(endpoint Endpoint) dsync.NetLocker {
	if endpoint.IsLocal {
		return globalLockServer
	}
	return newlockRESTClient(endpoint)
}

        globalLockServer是在minio服务启动过程中注册路由的过程中创建的,保存的是localLocker结构体对象的指针。newlockRESTClient函数根据endpoint中的host创建lockRESTClient结构体对象并返回对象的指针。

        在获取了所有节点的locker后,再通过下面的代码按set保存对应的locker。

for i := 0; i < setCount; i++ {
		lockerEpSet := set.NewStringSet()
		for j := 0; j < setDriveCount; j++ {
			endpoint := endpoints.Endpoints[i*setDriveCount+j]
			// Only add lockers only one per endpoint and per erasure set.
			if locker, ok := erasureLockers[endpoint.Host]; ok && !lockerEpSet.Contains(endpoint.Host) {
				lockerEpSet.Add(endpoint.Host)
				s.erasureLockers[i] = append(s.erasureLockers[i], locker)
			}
		}
	}

        上述代码中,s是erasureSets结构体实例的指针,erasureLockers数据项是一个二维的slice,按set保存对应的locker的指针。erasureLockers数据项和上面的erasureLockers临时变量具有相同的名字,请务必不要混淆。

4.2 创建分布式锁

        在进行对象操作时,调用erasureObjects结构体的NewNSLock方法来创建分布式锁。实际就是创建了一个distLockInstance结构体的实例并返回指针。由于distLockInstance结构体实现了RWLocker接口,所以可以加解锁。

      erasureObjects结构体的NewNSLock方法实际调用了erasureObjects结构体字段nsMutex的NewNSLock方法,并传入了erasureObjects结构体的getLockers字段。实际就是传入了erasureSets的GetLockers方法。通过erasureSets的GetLockers方法可以获取当前set中所有节点的locker(locakLocker或lockRESTClient结构体实例的指针)。

4.3 加解分布式锁

        创建distLockInstance结构体的实例后,可以调用distLockInstance结构体的GetLock或GetRLock实现加锁。在GetLock和GetRLock方法内部,调用的distLockInstance结构体的rwMutex字段的GetLock和GetRLock方法。rwMutex字段的GetLock和GetRLock方法内部又调用来DRWMutex结构体的lockBlocking方法来进行加锁,lockBlocking方法中调用的lock函数就是最终控制加锁的函数。

        lock函数根据传入的locker并发的调用NetLocker接口的方法。如果是当前节点的locker,实际调用localLocker的Lock或RLock方法。非当前节点的locker,实际调用lockRESTClient的Lock或RLock方法。lockRESTClient的Lock或RLock方法通过rest api接口,调用的也是指定节点的localLocker的Lock或RLock方法。

        解分布式锁的处理流程和加锁的流程一致。

详细的代码注释:

GitHub - luo2pei4/minio: High Performance, Kubernetes Native Object Storage

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MinIO是一个开源的分布式对象存储服务,它可以在私有云环境提供高性能和高可用性的存储解决方案。下面是关于MinIO的一些基本概念和使用方法: 1. 分布式存储:MinIO使用分布式架构,可以将数据存储在多个节点上,实现数据的冗余和高可用性。每个节点都可以独立地提供存储服务,并且可以通过添加更多的节点来扩展存储容量和吞吐量。 2. 对象存储:MinIO对象的形式存储数据,每个对象都有一个唯一的键(Key)和对应的值(Value)。对象可以是任意类型的文件,例如图片、视频、文档等。通过使用对象存储,MinIO可以提供高效的数据访问和管理。 3. 数据分片:MinIO将每个对象分成多个数据片段(Data Shard),并将它们分布在不同的节点上。这种数据分片的方式可以提高数据的可靠性和可用性,同时也可以提高数据的读写性能。 4. 冗余备份:MinIO使用纠删码(Erasure Code)技术来实现数据的冗余备份。纠删码可以将数据分成多个片段,并将这些片段分布在不同的节点上。即使某个节点发生故障,系统仍然可以通过其他节点上的数据片段进行数据恢复。 5. 客户端接口:MinIO提供了丰富的客户端接口,可以方便地与MinIO进行交互。你可以使用MinIO的命令行工具、API接口或者各种编程语言的SDK来管理和操作MinIO存储。 下面是一个使用MinIO Python SDK上传文件的例子: ```python from minio import Minio # 创建MinIO客户端 client = Minio('play.min.io', access_key='YOUR_ACCESS_KEY', secret_key='YOUR_SECRET_KEY', secure=True) # 上传文件 client.fput_object('mybucket', 'myobject', 'path/to/local/file.jpg') # 关闭客户端连接 client.close() ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值