计数信号量是一种锁,可以让用户限制一项资源最多能够同时被多少个用户访问
计数信号量有如下几个特征:
1. 使用前必须要获取到资源,使用结束后必须要释放资源
2. 获取资源失败后立即将失败结果返回给用户
示例:
构建基本的计数信号量
使用有序集合来构建基本的计数信号量,使用当前时间戳作为分值,将标识值添加到有序集合里面,程序在将标识符添加到有序集合前,清理有序集合中时间戳大于超时数值的标识符
public String acquireSemaphore(Jedis conn,String semname,int limit,long timeout){
String identifier=UUID.randomUUID().toString();
long now=System.currentTimeMillis();
Pipeline pip=conn.pipelined();
pip.zremrangeByScore(semname, 0, now-timeout*1000);
pip.zadd(semname, now, identifier);
Response<Long>rank=pip.zrank(semname, identifier);
pip.syncAndReturnAll();
if ((long)rank.get()<limit) {
return identifier;
}
conn.zrem(semname, identifier);
System.out.println("remove:"+identifier);
return null;
}
释放信号量
public Long releaseSemaphore(Jedis conn,String semname,String identifier){
return conn.zrem(semname, identifier);
}
这个基本的计数信号量存在一定的问题,在集群或者分布式系统当中,每台计算机的时间都有细微的差异。比如说A机器比B机器慢了10毫秒,在A取走最后一个计数信号量后,B只要在10毫秒内就能取走A所持有的资源
公平信号量
为了消除时间造成的误差,我们需要另外两个数据结构,一个是全局计数器,另一个是记录获得获得资源的顺序sorted set,获取取得资源排名的时候不再利用时间作为分值排名而是利用全局计数器的到的值
一定要注意时间单位的换算,否则会导致超时数据漏删或者多删的情况
//获取资源
public String acquireFairSemaphore(Jedis conn,String semname,int limit,long timeout){
String identifier=UUID.randomUUID().toString();
//资源拥有者sorted set
String czset=semname+":owner";
//全局计数器
String ctr=semname+":counter";
long now=System.currentTimeMillis();
Transaction trans=conn.multi();
trans.zremrangeByScore(semname.getBytes(), "0".getBytes(), String.valueOf(now-timeout*1000).getBytes());
//trans.zremrangeByScore(semname, 0, now-timeout*1000);
ZParams params=new ZParams();
params.weights(1,0);
//czset和semname的交集计算
trans.zinterstore(czset, params, czset,semname);
trans.incr(ctr);
List<Object>results=trans.exec();
int counter=((Long)results.get(results.size()-1)).intValue();
trans=conn.multi();
trans.zadd(semname, now, identifier);
trans.zadd(czset, counter,identifier);
trans.zrank(czset, identifier);
results=trans.exec();
int result=((Long)results.get(results.size()-1)).intValue();
if (result<limit) {
return identifier;
}
trans=conn.multi();
trans.zrem(semname, identifier);
trans.zrem(czset, identifier);
trans.exec();
return null;
}
//释放资源
public boolean releaseFairSemaphore(Jedis conn,String semname,String identifier){
Transaction trans=conn.multi();
trans.zrem(semname, identifier);
trans.zrem(semname+":owner", identifier);
List<Object>results=trans.exec();
return (long)results.get(results.size()-1)==1;
}