我正在构建一个分布式KV存储,只是为了了解更多关于分布式系统和并发性的知识。我正在构建的KV存储的实现完全是事务性的,具有内存中的事务日志。为了简单起见,存储也完全是内存中的。API公开了GET
、INSERT
、UPDATE
REMOVE
。注意,所有端点都在单个密钥上操作,而不是在密钥范围上操作。
我是通过锁来管理并发的,但是我有一个全局锁来锁定整个数据存储,这听起来效率非常低,因为如果我想在更新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
之前到来。
1条答案
按热度按时间mcdcgff01#
概念部分
交易
首先,事务日志不需要强一致性,事务日志对于维护ACID属性非常有用。
事务也不是数据库中强一致性所必需的,但在许多情况下,它们是确保一致性的有用工具。
强一致性指的是这样一种属性,它可以确保数据库的所有读取操作都将返回最近的写入操作,而不管读取操作是在何处执行的。换句话说,强一致性保证所有客户机都将看到相同的数据,并且数据在整个系统中是最新的和一致的。
您可以使用一致性算法,如Paxos或Raft,以确保强一致性。当存储数据时,您可以使用版本存储数据,并将其用作Paxos中的ID。
锁定KV存储
在键-值(KV)存储中,键通常使用某种锁定机制来锁定,如互斥锁或读写器锁(如@paulsm4所建议的),这允许多个线程或进程并发访问和修改KV存储中的数据,同时仍然确保数据保持一致和正确。
例如,当线程或进程想要读取或修改KV存储中的特定密钥时,它可以获取该密钥的锁。这可以防止其他线程或进程同时修改同一密钥,这可能导致争用条件和其他问题。一旦线程或进程完成阅读或修改密钥,它可以释放锁,允许其他线程或进程访问该密钥。
如何在KV存储中锁定键的具体详细信息可能因KV存储的实现而异。某些KV存储可能使用全局锁(正如您已经使用的,这有时效率很低)来锁定整个数据存储,而其他KV存储可能使用更细粒度的锁定机制(如行级或键级锁)来允许对数据的更多并发访问。
所以从概念上来说,你是对的,魔鬼存在于锁实现的细节中。
编码
要严格回答关于锁的问题,我们可以考虑@paulsm4建议的Readers-writers locks,在golang中,类似的锁是
RWMutex
,它用在sync.Map
中。下面是一个简短的例子:
在此基础上,您需要Paxos来确保跨副本的一致性。