java中有信号量Semaphore控制特定资源的访问数量,在多进程甚至跨服务器跨网络的情况下,我们可以用reids来实现。
java的Semaphore,查看源码可知道通过设置state,每次被获取state-1,释放+1,等于0就等待,大于0就唤醒其他的线程。在redis中没有办法去唤醒其他的等待进程,所以可以用while循环来判断是否获取到信号量。
在while循环中,有以下几个步骤:
- 加锁,同一时间只能一个进程进行操作。
- 引用计数器,能否获取到信号量,取决于计数器的大小。
- 将计数器保存在有序集合中,通过有序集合的排序,来决定是否获取信号量。
- 将每个获取信号量元素另外保存其他有序集合,用于判断时间是否超时。
- 每次获取信号量之前,优先移除超时的元素。移除超时元素的同时,也要移除信号量有序集合对应的元素。
交集计算
上面的第五点,是两个集合的交集计算,先看看以下的例子。
并集
以下例子中,setkey1有三个元素,one、two、three,setkey2有两个元素,one、two,所以取他们的交集就是one和two,对应的score是相加的。
@Test
public void test() {
Transaction trans = JedisUtils.multi();
String dstkey = "dstkey:";
String setkey1 = "setkey1:";
String setkey2 = "setkey2:";
trans.zadd(setkey1, 1, "one");
trans.zadd(setkey1, 2, "two");
trans.zadd(setkey1, 3, "three");
trans.zadd(setkey2, 4, "one");
trans.zadd(setkey2, 5, "two");
// 1+4=5,2+5=7
trans.zinterstore(dstkey, setkey1, setkey2);
trans.exec();
Set<Tuple> tuples = JedisUtils.zrangeWithScores(dstkey, 0, -1);
System.out.println(tuples);
}
运行结果如下:
[[one,5.0], [two,7.0]]
权重
上面例子的权重很明显是1:1,如果我们想设置其他值,就需要引入ZParams的wight方法。下面的例子权重是7,8,所以计算规则为17+48=39,27+58=54。
@Test
public void testWeight() {
Transaction trans = JedisUtils.multi();
ZParams params = new ZParams();
params.weights(7, 8);
String dstkey = "dstkey:";
String setkey1 = "setkey1:";
String setkey2 = "setkey2:";
trans.zadd(setkey1, 1, "one");
trans.zadd(setkey1, 2, "two");
trans.zadd(setkey1, 3, "three");
trans.zadd(setkey2, 4, "one");
trans.zadd(setkey2, 5, "two");
// 1*7+4*8=39,2*7+5*8=54
trans.zinterstore(dstkey, params, setkey1, setkey2);
trans.exec();
Set<Tuple> tuples = JedisUtils.zrangeWithScores(dstkey, 0, -1);
System.out.println(tuples);
}
运行结果如下:
[[one,39.0], [two,54.0]]
最大值/最小值/平均值
除了权重也有最大值,最小值,平均值。这边只演示最大值。下面例子的计算规则为max(1,4)8=48=32,max(2,5)8=58=40。
@Test
public void testAggregate() {
Transaction trans = JedisUtils.multi();
ZParams params = new ZParams();
params.aggregate(ZParams.Aggregate.MAX);
params.weights(7, 8);
String dstkey = "dstkey:";
String setkey1 = "setkey1:";
String setkey2 = "setkey2:";
trans.zadd(setkey1, 1, "one");
trans.zadd(setkey1, 2, "two");
trans.zadd(setkey1, 3, "three");
trans.zadd(setkey2, 4, "one");
trans.zadd(setkey2, 5, "two");
// max(1,4)*8=4*8=32,max(2,5)*8=5*8=40
trans.zinterstore(dstkey, params, setkey1, setkey2);
trans.exec();
Set<Tuple> tuples = JedisUtils.zrangeWithScores(dstkey, 0, -1);
System.out.println(tuples);
}
运行结果如下:
[[one,32.0], [two,40.0]]
信号量
获取锁参考分布式锁的文章
static int cnt = 10;
static int timeOut = 1000;
static int limit = 2;
static CountDownLatch countDownLatch = new CountDownLatch(cnt);
static String count = "count:";
static String out = "out:";
static String semaphore = "semaphore:";
private static String acquire() {
Transaction trans = JedisUtils.multi();
// 移除过期的信号量
trans.zremrangeByScore(out, Long.MIN_VALUE, System.currentTimeMillis() - timeOut);
ZParams params = new ZParams();
params.weights(1, 0);
// 取交集,如果out的元素过期被移除了,则和semaphore交集后,再赋值给semaphore。
// 相对于semaphore对应的元素也被移除了。
// 如果没有过期,通过out权重为0,也不影响semaphore的分数
trans.zinterstore(semaphore, params, semaphore, out);
// 获取自增长的计数器
trans.incr(count);
List<Object> results = trans.exec();
// 获取自增长的值
int counter = ((Long) results.get(results.size() - 1)).intValue();
trans = JedisUtils.multi();
String uuid = UUID.randomUUID().toString();
// 添加到信号量集合
trans.zadd(semaphore, counter, uuid);
// 添加到超时集合
trans.zadd(out, System.currentTimeMillis(), uuid);
// 查看排名
trans.zrank(semaphore, uuid);
results = trans.exec();
// 获取当前排名
int result = ((Long) results.get(results.size() - 1)).intValue();
// 在限制的数量以内,则返回uuid
if (result < limit) {
return uuid;
}
// 没有在限制的数量以内,说明没获取到,则移除上面新增的值
release(uuid);
return null;
}
private static void release(String uuid) {
Transaction trans = JedisUtils.multi();
trans.zrem(semaphore, uuid);
trans.zrem(out, uuid);
trans.exec();
}
@Test
public void test() throws InterruptedException {
// 锁的时间,达到这个时间就释放
int lockTime = 1;
// 锁的超时时间,达到这个时间就放弃获取
long timeOut = 2500;
for (int i = 0; i < cnt; i++) {
new Thread(new LockThread(i, lockTime, timeOut)).start();
countDownLatch.countDown();
}
TimeUnit.SECONDS.sleep(10);
}
static class LockThread implements Runnable {
int lockTime;
long timeOut;
int idx;
public LockThread(int idx, int lockTime, long timeOut) {
this.idx = idx;
this.lockTime = lockTime;
this.timeOut = timeOut;
}
@Override
public void run() {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
while (true) {
// 获取锁
String lock = TestDistributedLocking.lock(TestDistributedLocking.lockName, lockTime, timeOut);
String uuid = acquire();
// 释放锁
JedisUtils.del(lock);
if (null != uuid) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(formatter.format(new Date()) + "---" + idx + ":获取到了信号量");
try {
TimeUnit.SECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
release(uuid);
break;
}
try {
// 休眠10毫秒
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。