场景是这样的,我想要用 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
1
fengYH8080 116 天前 1
这种场景我不去搞数据库持久化层,我弄分布式锁去锁线程,一般用 Redlock 。
你这个功能锁的 key 会定义为 uid_type_date ,然后网络服务中请求没有抢到锁且超过重试次数后就拒绝这次请求,返回系统繁忙之类的。而你这个场景的差别就是必须要重试到成功为止,可以加个 redis 做缓存层去搞重试,先记录到缓存中,保证缓存 get set 原子性就可以不用去在意重试多少次,失败的同时又有请求过来就一直往缓存加次数就好,成功就清 0 ,甚至数据实时性不高还可以定时处理,减轻数据库负担。 原理就是这样,里面一下细节就需要自己斟酌了,例如缓存 key 的定义,时间交叉点的问题。 |
2
csys 116 天前 1
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 ),订阅消息进行统计 你不可能保证更新绝对不丢失 |
3
MoYi123 116 天前 1
一般是这样搞的.
1. 把完整的请求通过消息队列存到 OLAP 数据库里, 在离线的情况下用 count(*) group by 查. 2. 在线数据直接对数据库 update x = x + 1 where ... 3. 如果数据量太大, update 性能不行, 就本地或者 redis 里存一下, 攒个 n 次或者 5s 的定时器触发 一次性 update x = x + n 4. 因为有离线数据库, 可以比较在线库是不是漏数据了, 有 bug 就找出来修一下, 一般就是服务器重启的时候丢的. |
4
wangliran1121 115 天前 1
我倾向于 @csys 的方案 3 ,本身你这个业务场景要上锁就很难理解。。。
|