这是我参与「第三届青训营 -后端场」笔记创作活动的第5篇笔记。
在抖音后端项目中实现注册功能时,常见的思路是先在数据库中查询是否已经注册,然后进行注册。实现关注功能时,会首先查询是否已创建关注关系,从而执行修改或者新建功能。
在不考虑并发的情况下,上述逻辑是正确的。但是考虑并发时可能会出现重复注册和重复关注数据。解决的方法有两种:第一种方法使用设置数据库相关字段唯一,将去重的任务交给数据库,这种方法通用性较低,不是所有的数据库都能使用,同时会消耗数据库性能。第二种方法通过锁确保并发时只有一个请求可以查询并创建数据。
在单机程序中,可以使用编程语言自带的锁实现功能,但是高并发情况下反复创建回收锁对性能会有影响,除此之外这种锁无法在分布式环境下使用。我们选择的解决方法是使用redis实现分布式锁。
redis是基于内存的kv数据库,分布式环境中也可以配置redis集群。我们通过redis的SET key value EX expiretime NX命令向redis中插入一条kv数据,为了防止程序出错没有释放锁,对锁设置了过期时间,NX参数确保在数据未被创建时才会成功,其它获得锁失败的程序可以在sleep一定时间后重试。释放锁时使用DEL key命令删除kv数据。
实际使用中,为了减少锁的粒度,设置key类似lock_user_{username},以达到尽量不阻塞其他程序的目的。一种特殊的情况是程序偶尔执行过慢,锁已经被过期销毁,这是一个新请求申请了这个锁,那么原请求最后可能会把这个锁释放。为了防止这种情况发生,在创建锁时同时创建一个随机字符串作为value,只有在锁的value与该随机字符串相同时才释放锁,避免释放其它请求的锁。我们实际使用了uuid生成随机字符串。
目前我们的实现存在一个潜在的问题,在释放锁时有查询和删除两个原子操作,但他们合起来不是原子操作,有概率删除的锁与刚刚查询的锁不是同一个,有待解决。