受限访问量问题中锁的使用

一、 前言

最近在做网上法庭的一个比较有意思的小需求,就是通过扫二维码方式允许最多30个人同时进入庭审,但是不限制进入的是是不是庭审人员,也就是说只要扫了这个二维码并且当前案件对应的参与人数不到30那么就可以进入,始终维持一个庭审案件里面最多有30人。

二、 方案研究

扫描二维码会调用后台的一个rpc,而Rpc会调用bo方法进行处理,那么下面就研究下bo里面里面怎么做。

由于需求是要控制一个庭审的人数,而扫码人肯定是并发的访问这个bo方法,首先会有两种思路使用数据库的锁或者在业务层面进行控制。

2.1 使用乐观锁来控制

case_idcount
10

如表每条记录case_id唯一,并且对应一个count字段用来维持进入庭审人员个数。

  • 进入庭审

 prviate final int COUNT = 30public boolean boEnterMethod(String enCaseid){

    第一步解密enCaseid获取真正caseId;
    第二步根据caseId获取记录(里面包含count)nowCount = count ;        if( nowCount == COUNT){             return false;
        }

    update 表 set count=nowCount+1 where count =nowCount and id = #id;
    Long rows = 更新语句返回行数;    if(rows == 1){
        处理业务              return true;
    }        return false;
}
  • 退出庭审

public boolean boExitMethod(String enCaseid){

    第一步解密enCaseid获取真正caseId;
    第二步根据caseId获取记录(里面包含count)nowCount = count;        if( nowCount == 0){             return false;
        }

    update 表 set count=nowCount-1 where count =nowCount and id = #id;
    Long rows = 更新语句返回行数;    if(rows == 1){
        处理业务        return true;

    }        return false}

这种方式好处是在执行时候才进行校验不需要提前对记录进行加锁,坏处是,假如两个人同时扫描二维码,获取的nowCount=1那么只有一个能真正进入庭审,另外一个会失败,结果是他进入不了庭审。

乐观锁下有咩有办法解决那?答案是肯定的,还记得AQS里面的trylock?第一次cas失败,那好吧,我再循环一次再试试。所以改进在于可以加个循环,如下:

  • 进入庭审

public boolean boEnterMethod(String enCaseid){

  第一步解密enCaseid获取真正caseId;  for(;;){
        第二步根据caseId获取记录(里面包含count)nowCount = count;        if( nowCount == COUNT){             return false;
        }

        update 表 set count=nowCount+1 where count =nowCount and id = #id;
        Long rows = 更新语句返回行数;        if(rows == 1){
            处理业务            return true;

        }
   }
}
  • 退出庭审

public boolean boExitMethod(String enCaseid){

    第一步解密enCaseid获取真正caseId;    for(;;){
        第二步根据caseId获取记录(里面包含count)nowCount = count;            if( nowCount == 0){                 return false;
            }

        update 表 set count=nowCount-1 where count =nowCount and id = #id;
        Long rows = 更新语句返回行数;        if(rows == 1){
            处理业务            return true;
        }
    }

}

加个循环目前是为了避免当访问量不足30时候由于乐观锁竞争导致的失败,这里当当前访问量为30的时候直接返回是为了避免大量请求线程空轮造成tomcat线程池满。但是问题是可能查询数据库的频率比较高。

2.2 使用悲观锁来控制

  • 乐观锁

    public boolean boEnterMethod(String enCaseid){
    
      第一步解密enCaseid获取真正caseId;
      第二步根据caseId获取记录(里面包含count)使用select * from 表 where .. for update 对本记录加锁
      nowCount = count;
      rowId = id;  if( nowCount == COUNT){       return false;
      }
    
      update 表 set count=nowCount+1 where  id = rowId;
      Long rows = 更新语句返回行数;  if(rows == 1){
          处理业务      return true;
    
      }         return false;
    }
  • 退出庭审

public boolean boExitMethod(String enCaseid){

    第二步根据caseId获取记录(里面包含count)使用select * from 表 where .. for update 对本记录加锁
    nowCount = count;
    rowId = id;    if( nowCount == 0){         return false;
    }

    update 表 set count=nowCount-1 where id = rowId;
    Long rows = 更新语句返回行数;    if(rows == 1){
        处理业务        return true;

    }        return false}

使用悲观锁方式是事先对记录加锁,其他事务访问时候需要等待,直到当前事务提交。

2.3 使用业务锁来控制

public class TestLock {    private final int COUNT_NUM = 30;    private final SafeIntegerCount count = new SafeIntegerCount(COUNT_NUM);    private final static ConcurrentHashMap<String, SafeIntegerCount> caseLockMap = new ConcurrentHashMap<>();    //初始化缓存
    public void init() {        //select所有caseid到list
        for (String caseId:list) {
            SafeIntegerCount count = new SafeIntegerCount(COUNT_NUM);
            caseLockMap.put(caseId, count);

        }
    }    public void boEnterMethod(String enCaseid) {

        对enCaseid进行解密得到caseId

        SafeIntegerCount count = caseLockMap.get(caseId);        // 进入
        if (null == count) {            return;
        }        try {            if (count.inc()) {                // 处理业务
            }
        } catch (Exception e) {
            count.desc();
        }

    }    public void boExitMethod(String enCaseid) {

        对enCaseid进行解密得到caseId

        SafeIntegerCount count = caseLockMap.get(caseId);        // 进入
        if (null == count) {            return;
        }        try {            if (count.desc()) {                // 处理业务
            }
        } catch (Exception e) {
            count.inc();
        }

    }

}

使用ReentrantLock实现了一个可以判断上下限的计数器。眨眼看可以解决问题,但是仅仅单台机器可以正常,多台机器下会有问题,另外案件量特别大时候缓存可能占用大量内存。

2.4 总结

推荐使用悲观锁方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值