前提
项目业务开发已完成,开始着手准备多节点拓展方案。
项目使用go语言开发,gin框架。
项目中使用了go原生的进程锁 sync.Mutex ,用于防止高并发下,数据可能遭到重复修改的问题。
但是用了进程锁就无法支持集群部署架构,集群中每个单点都是一个进程,只能锁住自己的一部分,而操作的又是同一个DB,高并发下就会出现数据重复修改的问题。
因此需要引入分布式锁,用来保证多节点部署架构下,数据正常。
方案选择
有多种分布式锁的方案,ZooKeeper,基于数据库实现分布式锁(for update),redis等。
鉴于项目本身面对的用户量并不多,首期预计2000人,多节点方案也只是plan B,避免上线当天出现炸服的情况,有个方案可以及时拓展节点,提高承载量,
因此最终选用了redis,简单易用,而且项目本身也部署了redis,也不影响现有的架构,毕竟多引入一个中间件,就多了其他的风险。
redis分布式锁应用实例
redis分布式锁,用的是其Setnx(SET if Not Exists)函数, 指定的 key 不存在时,为 key 设置指定的值。
因为redis是单线程的,因此Setnx 100%是串行执行命令, 当多个请求进来时,即使同时调用Setnx,最终是按顺序返回值,达到了锁的效果。
项目中使用示例:
func main() {
key := consts.USER_UPDATE + id
op := ExistKeyOrSet(key, consts.DB_OP_EXPIRES_TIME)
if !op {
// 已存在key,表示上一次操作未结束
appG.Response(http.StatusBadRequest, e.FREQUENT_OPERATION, nil)
return
}
// 程序执行结束后删除 redis 存储数据
defer rdb.DelKey(key)
}
func ExistKeyOrSet(key string, expiresTime time.Duration) bool {
timestamp := time.Now().UnixNano()
// redis的setnx方法 key存在返回false, 不存在则设置锁并返回true
return Client.SetNX(ctx, key, timestamp, expiresTime).Val()
}