请教分布式下如何用锁确保更新不丢失?

24 天前
 supuwoerc

场景是这样的,我想要用 golang 的 NewSingleHostReverseProxy 来代理一个第三方系统的接口,然后在拿到接口响应的时候记录这次调用到数据库(按照用户每天每个资源类型来统计次数),程序部署到多个节点上,请求记录表大概是这样子,我需要确保更新不丢失,数据表大概是下面这样,因为我没做过高并发和分布式场景下的业务,我想的是表里扩展 version 来用乐观锁来处理,更新失败的话重试,但是重试也不知道要重试几次,重试失败应该怎么办,请教大家最好用那种方式?

更新/插入逻辑(伪代码):

	err := dao.DB.Where("`uid` = ? AND `date` = ? AND `type` = ?", uid, today, type).FirstOrInit(&record).Error
	if err != nil {
		return err
	}
	if record.RequestKey != "" {
		record.RequestCount++
	} else {
		record.RequestCount = 1
	}
	return dao.DB.Save(&record).Error

表大致的字段(伪代码):

CREATE TABLE `sys_request_record` (
      `uid` varchar(32) NOT NULL COMMENT '用户 uid',
      `type`int(11) NOT NULL COMMENT '请求资源类型',
      `date` date NOT NULL COMMENT '日期',
      `request_count` int(11) NOT NULL DEFAULT '0' COMMENT '请求次数',
      `created_at` bigint(20) NOT NULL DEFAULT '0' COMMENT '创建时间',
      `updated_at` bigint(20) NOT NULL DEFAULT '0' COMMENT '更新时间',
      `deleted_at` bigint(20) NOT NULL DEFAULT '0' COMMENT '删除时间',
      PRIMARY KEY (`uid`,`type`,`date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1214 次点击
所在节点    程序员
4 条回复
fengYH8080
24 天前
这种场景我不去搞数据库持久化层,我弄分布式锁去锁线程,一般用 Redlock 。
你这个功能锁的 key 会定义为 uid_type_date ,然后网络服务中请求没有抢到锁且超过重试次数后就拒绝这次请求,返回系统繁忙之类的。而你这个场景的差别就是必须要重试到成功为止,可以加个 redis 做缓存层去搞重试,先记录到缓存中,保证缓存 get set 原子性就可以不用去在意重试多少次,失败的同时又有请求过来就一直往缓存加次数就好,成功就清 0 ,甚至数据实时性不高还可以定时处理,减轻数据库负担。
原理就是这样,里面一下细节就需要自己斟酌了,例如缓存 key 的定义,时间交叉点的问题。
csys
24 天前
1. 不要用乐观锁去应对高并发场景
2. 尽量不要用锁去应对高并发场景
3. 尽量追加数据而非更新数据

几个方案:
1. SQL: Update `sys_request_record` SET `request_count`=`request_count` + 1;
2. redis: INCR sys_request_record:$uid:$type
3. 每次调用时发送可持久化的消息(如 kafka ),订阅消息进行统计

你不可能保证更新绝对不丢失
MoYi123
24 天前
一般是这样搞的.
1. 把完整的请求通过消息队列存到 OLAP 数据库里, 在离线的情况下用 count(*) group by 查.
2. 在线数据直接对数据库 update x = x + 1 where ...
3. 如果数据量太大, update 性能不行, 就本地或者 redis 里存一下, 攒个 n 次或者 5s 的定时器触发
一次性 update x = x + n
4. 因为有离线数据库, 可以比较在线库是不是漏数据了, 有 bug 就找出来修一下, 一般就是服务器重启的时候丢的.
wangliran1121
23 天前
我倾向于 @csys 的方案 3 ,本身你这个业务场景要上锁就很难理解。。。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1068403

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX