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

一、 前言

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

阿里巴巴长期招聘Java研发工程师p6,p7,p8等上不封顶级别,有意向的可以发简历给我,注明想去的部门和工作地点:[email protected]

二、 方案研究

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

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

2.1 使用乐观锁来控制

case_id count
1 0

如表每条记录case_id唯一,并且对应一个count字段用来维持进入庭审人员个数。
bo方法都有事务切面的。使用单个数据库。

  • 进入庭审
 prviate final int COUNT = 30
public 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 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();
        }

    }

}
public class SafeIntegerCount {

    //当前计数
    private int count = 0;

    //最大计数
    private int maxCount = 0;

    //公平独占锁
    private final ReentrantLock lock = new ReentrantLock(true);

    //构造函数设置最大值
    public SafeIntegerCount(int maxCount) {
        this.maxCount = maxCount;
    }

    //自增加一
    public Boolean inc() {

        lock.lock();
        try {
            if (count == maxCount) {
                return false;
            }
            ++count;

            return true;
        } finally {
            lock.unlock();
        }
    }

    //自减-
    public Boolean desc() {

        lock.lock();
        try {
            if(count == 0 ){
                return false;
            }
            --count;
            
            return true;
            
        } finally {
            lock.unlock();
        }
    }

}

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

2.4 总结

推荐使用悲观锁方式。

欢迎关注微信公众号:‘技术原始积累’ 获取更多技术干货__

受限访问量问题中锁的使用_第1张图片
image.png

你可能感兴趣的:(受限访问量问题中锁的使用)