Go语言 KV商店的钥匙是如何上锁的?

hrirmatl  于 2022-12-16  发布在  Go
关注(0)|答案(1)|浏览(109)

我正在构建一个分布式KV存储,只是为了了解更多关于分布式系统和并发性的知识。我正在构建的KV存储的实现完全是事务性的,具有内存中的事务日志。为了简单起见,存储也完全是内存中的。API公开了GETINSERTUPDATEREMOVE。注意,所有端点都在单个密钥上操作,而不是在密钥范围上操作。
我是通过锁来管理并发的,但是我有一个全局锁来锁定整个数据存储,这听起来效率非常低,因为如果我想在更新K2时读取K1的值,我必须等待k2完成更新,尽管它是不相关的。
我知道有些数据库使用更细粒度的锁。例如,在MySQL服务器中有行级锁。如何实现键级锁?
我有

type Storage struct {
  store map[string]int32
}

我应该加上这样的东西吗?:

type Storage struct {
  store map[string]int32
  locks map[string]mutex.Lock
}

这样做的问题是locks必须与store保持同步,另一个选择是合并两个Map,但即使这样,我也会遇到在持有锁的情况下删除Map中的条目的问题,如果REMOVE请求在GET之前到来。

mcdcgff0

mcdcgff01#

概念部分

交易
首先,事务日志不需要强一致性,事务日志对于维护ACID属性非常有用。
事务也不是数据库中强一致性所必需的,但在许多情况下,它们是确保一致性的有用工具。
强一致性指的是这样一种属性,它可以确保数据库的所有读取操作都将返回最近的写入操作,而不管读取操作是在何处执行的。换句话说,强一致性保证所有客户机都将看到相同的数据,并且数据在整个系统中是最新的和一致的。
您可以使用一致性算法,如Paxos或Raft,以确保强一致性。当存储数据时,您可以使用版本存储数据,并将其用作Paxos中的ID。

锁定KV存储

在键-值(KV)存储中,键通常使用某种锁定机制来锁定,如互斥锁或读写器锁(如@paulsm4所建议的),这允许多个线程或进程并发访问和修改KV存储中的数据,同时仍然确保数据保持一致和正确。
例如,当线程或进程想要读取或修改KV存储中的特定密钥时,它可以获取该密钥的锁。这可以防止其他线程或进程同时修改同一密钥,这可能导致争用条件和其他问题。一旦线程或进程完成阅读或修改密钥,它可以释放锁,允许其他线程或进程访问该密钥。
如何在KV存储中锁定键的具体详细信息可能因KV存储的实现而异。某些KV存储可能使用全局锁(正如您已经使用的,这有时效率很低)来锁定整个数据存储,而其他KV存储可能使用更细粒度的锁定机制(如行级或键级锁)来允许对数据的更多并发访问。
所以从概念上来说,你是对的,魔鬼存在于锁实现的细节中。

编码

要严格回答关于锁的问题,我们可以考虑@paulsm4建议的Readers-writers locks,在golang中,类似的锁是RWMutex,它用在sync.Map中。
下面是一个简短的例子:

type Storage struct {
  store sync.Map // a concurrent map
}

// GET retrieves the value for the given key.
func (s *Storage) GET(key string) (int32, error) {
  // Acquire a read lock for the key.
  v, ok := s.store.Load(key)
  if !ok {
    return 0, fmt.Errorf("key not found: %s", key)
  }

  // Return the value.
  return v.(int32), nil
}

// INSERT inserts the given key-value pair into the data store.
func (s *Storage) INSERT(key string, value int32) error {
  // Acquire a write lock for the key.
  s.store.Store(key, value)
  return nil
}

// UPDATE updates the value for the given key.
func (s *Storage) UPDATE(key string, value int32) error {
  // Acquire a write lock for the key.
  s.store.Store(key, value)
  return nil
}

// REMOVE removes the key-value pair for the given key from the data store.
func (s *Storage) REMOVE(key string) error {
  // Acquire a write lock for the key.
  s.store.Delete(key)
  return nil
}

在此基础上,您需要Paxos来确保跨副本的一致性。

相关问题