V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
abcbuzhiming
V2EX  ›  Java

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

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

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






    剩下的就是查 API 喽
    TomVista
        7
    TomVista  
       Jan 29, 2019
    开始时间 结束时间

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

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

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

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

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

    应该是我没看懂题目, 不知道问题的难点在哪
    pabupa
        23
    pabupa  
       Jan 29, 2019 via Android
    @wccc (⊙o⊙)哦,,我的
    prolic
        24
    prolic  
       Jan 29, 2019
    hash 分桶,打到队列或者机器内,然后串行就行了
    guyeu
        25
    guyeu  
       Jan 29, 2019   ❤️ 1
    这个显然应该是串行化队列来处理,当然没必要每个用户一个队列。了解一下一致性哈希,把同一用户的所有操作扔到同一个队列去处理就行了。
    limuyan44
        26
    limuyan44  
       Jan 29, 2019 via Android
    怎么感觉是防重复提交的问题
    fishfisher
        27
    fishfisher  
       Jan 30, 2019
    @fashy12306 也有这个问题,我同时用多个抢票软件去提交订单,应该就是并发了,抢到了两张时间冲突的票,
    用一个软件就只有行程冲突的提示。。。
    fishfisher
        28
    fishfisher  
       Jan 30, 2019
    @fanshy ,,,12306 也有这个问题,我同时用多个抢票软件去提交订单,应该就是并发了,抢到了两张时间冲突的票,
    用一个软件就只有行程冲突的提示。。。
    fishfisher
        29
    fishfisher  
       Jan 30, 2019
    @fashy 晕,手残老是 @不到正确的人
    deming
        30
    deming  
       Jan 30, 2019
    锁的粒度控制在用户身上就可以,多个用户同时进来,互不影响,因为锁的粒度在 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 才释放。
    否则会错误释放。
    }
    ```
    abcbuzhiming
        31
    abcbuzhiming  
    OP
       Jan 31, 2019
    @deming 你的想法就是我的想法。不过,一定非得上 redis 才能实现这个功能? Java 自己的锁搞不定?
    PazuLee
        32
    PazuLee  
       Feb 12, 2019
    拆分成两个问题:
    1. 重复提交:增加 token 机制解决,每次提交校验 token 是否相同;前段增加等待的提示,实现方式比较多。
    2. 1 之后,剩下问题是如何判断历史 N 条数据与当前新插入数据是否重叠,如果存储只有 MySQL,则必须取出来吧?如果记录数较大,DB 扛不住,可以考虑在缓存中维护一份 user 级别的已提交时间的数据,保存条记录中的 start_time & end_time

    以上~应该能解决了吧
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   2698 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 88ms · UTC 13:42 · PVG 21:42 · LAX 06:42 · JFK 09:42
    ♥ Do have faith in what you're doing.