V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
supuwoerc
V2EX  ›  程序员

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

  •  
  •   supuwoerc · 116 天前 · 1390 次点击
    这是一个创建于 116 天前的主题,其中的信息可能已经有所发展或是发生改变。

    场景是这样的,我想要用 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
    
    4 条回复    2024-08-29 14:21:16 +08:00
    fengYH8080
        1
    fengYH8080  
       116 天前   ❤️ 1
    这种场景我不去搞数据库持久化层,我弄分布式锁去锁线程,一般用 Redlock 。
    你这个功能锁的 key 会定义为 uid_type_date ,然后网络服务中请求没有抢到锁且超过重试次数后就拒绝这次请求,返回系统繁忙之类的。而你这个场景的差别就是必须要重试到成功为止,可以加个 redis 做缓存层去搞重试,先记录到缓存中,保证缓存 get set 原子性就可以不用去在意重试多少次,失败的同时又有请求过来就一直往缓存加次数就好,成功就清 0 ,甚至数据实时性不高还可以定时处理,减轻数据库负担。
    原理就是这样,里面一下细节就需要自己斟酌了,例如缓存 key 的定义,时间交叉点的问题。
    csys
        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 ),订阅消息进行统计

    你不可能保证更新绝对不丢失
    MoYi123
        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 就找出来修一下, 一般就是服务器重启的时候丢的.
    wangliran1121
        4
    wangliran1121  
       115 天前   ❤️ 1
    我倾向于 @csys 的方案 3 ,本身你这个业务场景要上锁就很难理解。。。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2753 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 20ms · UTC 08:39 · PVG 16:39 · LAX 00:39 · JFK 03:39
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.