首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
宝塔
V2EX  ›  Java

如果以用户为单位上锁,你们会怎么实现这个功能?

  •  
  •   abcbuzhiming · 294 天前 · 3169 次点击
    这是一个创建于 294 天前的主题,其中的信息可能已经有所发展或是发生改变。
    有这么个场景。用户日程安排,要保证时间段设定的唯一性(时间段不能冲突),所以没法在数据库层面上用唯一约束解决问题。一定得想办法在应用层解决。但是同时,每个用户的日程彼此独立,所以不会发生冲突。因此,这个场景是 一个用户可能会和自己发生资源争抢(当压力比较大的时候,用户可能提交两个一样的时间段数据到数据库去),但是用户和用户之间不会发生资源争抢的问题。因此,如果单独在方法阶段加锁,对性能损失就很大——因为它对应的其实是用户争抢资源问题。而我希望的是,是针对用户本身进行加锁,也就是 A 用户如果已经提交了一次设定时间段的请求,在此次提交还没处理完前,因为种种原因 A 用户再次提交了请求,那么这个请求只能等待上一次请求处理完,才能进行处理。同时,其它用户的请求不受影响
    32 回复  |  直到 2019-02-12 19:27:12 +08:00
        1
    wccc   294 天前
    redisson 针对用户 时间 加锁 怎么样
    没有分布式 那就 ConcurrentMap 存一个写锁
        2
    lhx2008   294 天前 via Android
    一般用数据库版本号 update set xxx, version=5 where vrrsion=4
    或者用 redis 锁,用 setnx 和 del 的脚本
        3
    jimrok   294 天前
    Actor 模式下不用加锁,但我怕你去看 Akka 后,就逃掉了。
        4
    abcbuzhiming   294 天前
    @wccc 没有看懂你的方案,如何针对时间加锁呢?
    @lhx2008 我这个问题里不光有 update,insert 也是大头,insert 时就不好用版本号解决了
    @jimrok Actor 本质是用单线程消息队列躲开了锁的问题。我不太喜好这个方案
        5
    wccc   294 天前
    @abcbuzhiming #4 我有一个问题 时间段大小是固定的吗?
        6
    lihongjie0209   294 天前
    使用锁, 多个线程拿到锁再操作 >>>>> 临界区单线程操作

    使用队列, 多线程写入队列作为生产者, 单线程操作数据库作为消费者 >>>>>>>> 临界区单线程操作






    剩下的就是查 API 喽
        7
    TomVista   294 天前
    开始时间 结束时间

    用户选择开始时间,传到服务器上,这之后,用户没有选对应结束时间之前,产生了其他日程时间,

    如果新的日程包含第一个日程的开始时间,不允许用户操作.如果新的日程在第一个日程的开始时间之后,并且未接收到结束时间,提示用户去操作完第一个日程.

    另外用数据库约束实现这个需求,本身极不明智.
        8
    abcbuzhiming   294 天前
    @wccc 当然不是固定的,固定的话就好解决多了
        9
    abcbuzhiming   294 天前
    @TomVista 额,你可能理解错了,开始时间和结束时间是在客户端选好后一起提交服务器的,服务器要判断用户选的时间段是否和已经有的日程发生冲突。另外,主要问题是需要考虑高负载下由于后端不能很快的响应客户端,造成客户端多次提交问题
    你会用什么方式来约束呢?
        10
    wccc   294 天前
    @abcbuzhiming #8 我这个是针对用户级别加锁 让提需求的人去死.......
        11
    abcbuzhiming   294 天前
    @wccc 如何加锁,我感兴趣的是这个
        12
    timsims   294 天前
    我的理解是,LZ 意思是用户会在同时多次提交相同(或不同)的日程时间段,所以现在 LZ 要解决的是时间段冲突的问题?

    那锁的粒度就是用户 id , 把所有日程提交的操作全都串行来处理可以吗?
        13
    TomVista   294 天前
    抱歉,想不出来,坐等答案,顺便找一个前端仔祭天
        14
    abcbuzhiming   294 天前
    @timsims 串行就是用队列,问题是不可能每个用户都给一个队列,明显是有浪费的。用队列就涉及到用几个的问题,我觉得挺烦这个,所以才想到针对用户 id 进行加锁的策略
        15
    wccc   294 天前
    redis 分布式锁 相关的框架 例如 Rlock
    或者本地加锁 用一个 ConcurrentMap 存一下锁 ...
        16
    fashy   294 天前
    12306 行程冲突问题
        17
    pabupa   294 天前 via Android
    用户提交请求>查询数据库,验证是否冲突>根据验证结果,返回响应。
    不就是这么个流程吗?我理解你的意思是如何避免用户重复请求,和锁没关系吧。
    那:
    让前段在没有相应之前禁用按钮;后台在查询数据库这一步做缓存。
    是这么个道理吧……⊙﹏⊙
        18
    wccc   294 天前
    @pabupa #17 验证冲突的时候,需要加锁,
    后端还是要做验证的 无论前端做不做验证
        19
    timsims   294 天前
    @abcbuzhiming 不用上队列,拿不到锁的操作可以等待,等待超时失败就让它失败咯,让用户重新提交就好

    另外用队列的方案,怎么会想到每个用户给一个队列,所有用户公用一个队列也可以啊, 如果你有多队列的话,同一个用户都分配在相同的队列也能保证串行
        20
    zy445566   294 天前
    你这个没必要加锁,想办法搞用队列成顺序执行吧,如果是 nodejs 队列都能剩了,这样性能消耗会小很多
        21
    wccc   294 天前
    @timsims #19 单个队列 串行的化 还不如直接一个全局的锁吧.....
        22
    Raymon111111   294 天前
    用户 id 维度上 redis 的锁?

    应该是我没看懂题目, 不知道问题的难点在哪
        23
    pabupa   294 天前 via Android
    @wccc (⊙o⊙)哦,,我的
        24
    prolic   294 天前
    hash 分桶,打到队列或者机器内,然后串行就行了
        25
    guyeu   294 天前   ♥ 1
    这个显然应该是串行化队列来处理,当然没必要每个用户一个队列。了解一下一致性哈希,把同一用户的所有操作扔到同一个队列去处理就行了。
        26
    limuyan44   294 天前 via Android
    怎么感觉是防重复提交的问题
        27
    fishfisher   293 天前
    @fashy12306 也有这个问题,我同时用多个抢票软件去提交订单,应该就是并发了,抢到了两张时间冲突的票,
    用一个软件就只有行程冲突的提示。。。
        28
    fishfisher   293 天前
    @fanshy ,,,12306 也有这个问题,我同时用多个抢票软件去提交订单,应该就是并发了,抢到了两张时间冲突的票,
    用一个软件就只有行程冲突的提示。。。
        29
    fishfisher   293 天前
    @fashy 晕,手残老是 @不到正确的人
        30
    deming   293 天前
    锁的粒度控制在用户身上就可以,多个用户同时进来,互不影响,因为锁的粒度在 userId 身上。
    利用工具将 一个 userId 并发进来的时候,因为锁的存在也是互斥的。伪代码:
    ```
    addTask(param...){
    long userId = getUserId();

    //get lock
    lockValue=genLockValue();
    if(!getLock(userId,lockValue)){
    return "some message";
    }
    //do business
    ...
    releseLock(userId,lockValue);

    }


    getLock(userId,lockValue){
    int retryTime=5;
    do{
    retryTime--;
    redis.setNx(genKey(userId),lockValue);
    }while(retryTime>0);
    }
    releseLock(userId,lockValue){
    释放锁的时候判断 key 是这个 key,lockValue 也是这个我的 lockValue 才释放。
    否则会错误释放。
    }
    ```
        31
    abcbuzhiming   292 天前
    @deming 你的想法就是我的想法。不过,一定非得上 redis 才能实现这个功能? Java 自己的锁搞不定?
        32
    PazuLee   280 天前
    拆分成两个问题:
    1. 重复提交:增加 token 机制解决,每次提交校验 token 是否相同;前段增加等待的提示,实现方式比较多。
    2. 1 之后,剩下问题是如何判断历史 N 条数据与当前新插入数据是否重叠,如果存储只有 MySQL,则必须取出来吧?如果记录数较大,DB 扛不住,可以考虑在缓存中维护一份 user 级别的已提交时间的数据,保存条记录中的 start_time & end_time

    以上~应该能解决了吧
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1133 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 26ms · UTC 23:13 · PVG 07:13 · LAX 15:13 · JFK 18:13
    ♥ Do have faith in what you're doing.