基于Consul的分布式信号量实现

Boolean b = consulClient.setKVValue(contenderKey, “”, putParams).getValue();

if(!b) {

logger.error("Failed to add contender entry : " + contenderKey + ", " + sessionId);

throw new RuntimeException("Failed to add contender entry : " + contenderKey + ", " + sessionId);

}

while(true) {

// try to take the semaphore

String lockKey = keyPath + “/.lock”;

String lockKeyValue;

GetValue lockKeyContent = consulClient.getKVValue(lockKey).getValue();

if (lockKeyContent != null) {

// lock值转换

lockKeyValue = lockKeyContent.getValue();

BASE64Decoder decoder = new BASE64Decoder();

byte[] v = decoder.decodeBuffer(lockKeyValue);

String lockKeyValueDecode = new String(v);

logger.debug(“lockKey=” + lockKey + “, lockKeyValueDecode=” + lockKeyValueDecode);

Gson gson = new Gson();

ContenderValue contenderValue = gson.fromJson(lockKeyValueDecode, ContenderValue.class);

// 当前信号量已满

if(contenderValue.getLimit() == contenderValue.getHolders().size()) {

logger.debug("Semaphore limited " + contenderValue.getLimit() + “, waiting…”);

if(block) {

// 如果是阻塞模式,再尝试

try {

Thread.sleep(100L);

} catch (InterruptedException e) {

}

continue;

}

// 非阻塞模式,直接返回没有获取到信号量

return false;

}

// 信号量增加

contenderValue.getHolders().add(sessionId);

putParams = new PutParams();

putParams.setCas(lockKeyContent.getModifyIndex());

boolean c = consulClient.setKVValue(lockKey, contenderValue.toString(), putParams).getValue();

if© {

acquired = true;

return true;

}

else

continue;

} else {

// 当前信号量还没有,所以创建一个,并马上抢占一个资源

ContenderValue contenderValue = new ContenderValue();

contenderValue.setLimit(limit);

contenderValue.getHolders().add(sessionId);

putParams = new PutParams();

putParams.setCas(0L);

boolean c = consulClient.setKVValue(lockKey, contenderValue.toString(), putParams).getValue();

if © {

acquired = true;

return true;

}

continue;

}

}

}

/**

  • 创建sessionId

  • @param sessionName

  • @return

*/

public String createSessionId(String sessionName) {

NewSession newSession = new NewSession();

newSession.setName(sessionName);

return consulClient.sessionCreate(newSession, null).getValue();

}

/**

  • 释放session、并从lock中移除当前的sessionId

  • @throws IOException

*/

public void release() throws IOException {

if(this.acquired) {

// remove session from lock

while(true) {

String contenderKey = keyPath + “/” + sessionId;

String lockKey = keyPath + “/.lock”;

String lockKeyValue;

GetValue lockKeyContent = consulClient.getKVValue(lockKey).getValue();

if (lockKeyContent != null) {

// lock值转换

lockKeyValue = lockKeyContent.getValue();

BASE64Decoder decoder = new BASE64Decoder();

byte[] v = decoder.decodeBuffer(lockKeyValue);

String lockKeyValueDecode = new String(v);

Gson gson = new Gson();

ContenderValue contenderValue = gson.fromJson(lockKeyValueDecode, ContenderValue.class);

contenderValue.getHolders().remove(sessionId);

PutParams putParams = new PutParams();

putParams.setCas(lockKeyContent.getModifyIndex());

consulClient.deleteKVValue(contenderKey);

boolean c = consulClient.setKVValue(lockKey, contenderValue.toString(), putParams).getValue();

if© {

break;

}

}

}

// remove session key

}

this.acquired = false;

clearSession();

}

public void clearSession() {

if(sessionId != null) {

consulClient.sessionDestroy(sessionId, null);

sessionId = null;

}

}

class ContenderValue implements Serializable {

private Integer limit;

private List holders = new ArrayList<>();

public Integer getLimit() {

return limit;

}

public void setLimit(Integer limit) {

this.limit = limit;

}

public List getHolders() {

return holders;

}

public void setHolders(List holders) {

this.holders = holders;

}

@Override

public String toString() {

return new Gson().toJson(this);

}

}

}

单元测试


下面单元测试的逻辑:通过线程的方式来模拟不同的分布式服务来获取信号量执行业务逻辑。由于信号量与简单的分布式互斥锁有所不同,它不是只限定一个线程可以操作,而是可以控制多个线程的并发,所以通过下面的单元测试,我们设置信号量为3,然后同时启动15个线程来竞争的情况,来观察分布式信号量实现的结果如何。

public class TestLock {

private Logger logger = Logger.getLogger(getClass());

@Test

public void testSemaphore() throws Exception {

new Thread(new SemaphoreRunner(1)).start();

new Thread(new SemaphoreRunner(2)).start();

new Thread(new SemaphoreRunner(3)).start();

new Thread(new SemaphoreRunner(4)).start();

new Thread(new SemaphoreRunner(5)).start();

new Thread(new SemaphoreRunner(6)).start();

new Thread(new SemaphoreRunner(7)).start();

new Thread(new SemaphoreRunner(8)).start();

new Thread(new SemaphoreRunner(9)).start();

new Thread(new SemaphoreRunner(10)).start();

Thread.sleep(1000000L);

}

}

public class SemaphoreRunner implements Runnable {

private Logger logger = Logger.getLogger(getClass());

private int flag;

public SemaphoreRunner(int flag) {

this.flag = flag;

}

@Override

public void run() {

Semaphore semaphore = new Semaphore(new ConsulClient(), 3, “mg-init”);

try {

if (semaphore.acquired(true)) {

// 获取到信号量,执行业务逻辑

logger.info(“Thread " + flag + " start!”);

Thread.sleep(new Random().nextInt(10000));

logger.info(“Thread " + flag + " end!”);

}

} catch (Exception e) {

e.printStackTrace();

} finally {

try {

// 信号量释放、Session锁释放、Session删除

semaphore.release();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

INFO [Thread-6] SemaphoreRunner - Thread 7 start!

INFO [Thread-2] SemaphoreRunner - Thread 3 start!

INFO [Thread-7] SemaphoreRunner - Thread 8 start!

INFO [Thread-2] SemaphoreRunner - Thread 3 end!

INFO [Thread-5] SemaphoreRunner - Thread 6 start!

INFO [Thread-6] SemaphoreRunner - Thread 7 end!

INFO [Thread-9] SemaphoreRunner - Thread 10 start!

INFO [Thread-5] SemaphoreRunner - Thread 6 end!

INFO [Thread-1] SemaphoreRunner - Thread 2 start!

INFO [Thread-7] SemaphoreRunner - Thread 8 end!

INFO [Thread-10] SemaphoreRunner - Thread 11 start!

INFO [Thread-10] SemaphoreRunner - Thread 11 end!

INFO [Thread-12] SemaphoreRunner - Thread 13 start!

INFO [Thread-1] SemaphoreRunner - Thread 2 end!

INFO [Thread-3] SemaphoreRunner - Thread 4 start!

INFO [Thread-9] SemaphoreRunner - Thread 10 end!

INFO [Thread-0] SemaphoreRunner - Thread 1 start!

INFO [Thread-3] SemaphoreRunner - Thread 4 end!

INFO [Thread-14] SemaphoreRunner - Thread 15 start!

INFO [Thread-12] SemaphoreRunner - Thread 13 end!

INFO [Thread-0] SemaphoreRunner - Thread 1 end!

INFO [Thread-13] SemaphoreRunner - Thread 14 start!

INFO [Thread-11] SemaphoreRunner - Thread 12 start!

INFO [Thread-13] SemaphoreRunner - Thread 14 end!

INFO [Thread-4] SemaphoreRunner - Thread 5 start!

INFO [Thread-4] SemaphoreRunner - Thread 5 end!

INFO [Thread-8] SemaphoreRunner - Thread 9 start!

INFO [Thread-11] SemaphoreRunner - Thread 12 end!

INFO [Thread-14] SemaphoreRunner - Thread 15 end!

INFO [Thread-8] SemaphoreRunner - Thread 9 end!

从测试结果,我们可以发现当信号量持有者数量达到信号量上限3的时候,其他竞争者就开始进行等待了,只有当某个持有者释放信号量之后,才会有新的线程变成持有者,从而开始执行自己的业务逻辑。所以,分布式信号量可以帮助我们有效的控制同时操作某个共享资源的并发数。

优化建议


同前文一样,这里只是做了简单的实现。线上应用还必须加入TTL的session清理以及对.lock资源中的无效holder进行清理的机制。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

技术学习总结

学习技术一定要制定一个明确的学习路线,这样才能高效的学习,不必要做无效功,既浪费时间又得不到什么效率,大家不妨按照我这份路线来学习。

最后面试分享

大家不妨直接在牛客和力扣上多刷题,同时,我也拿了一些面试题跟大家分享,也是从一些大佬那里获得的,大家不妨多刷刷题,为金九银十冲一波!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
" alt=“img” style=“zoom: 33%;” />

技术学习总结

学习技术一定要制定一个明确的学习路线,这样才能高效的学习,不必要做无效功,既浪费时间又得不到什么效率,大家不妨按照我这份路线来学习。

[外链图片转存中…(img-HnJ2wMxR-1713606523885)]

[外链图片转存中…(img-OYKvCHPl-1713606523886)]

[外链图片转存中…(img-yFo30rgT-1713606523886)]

最后面试分享

大家不妨直接在牛客和力扣上多刷题,同时,我也拿了一些面试题跟大家分享,也是从一些大佬那里获得的,大家不妨多刷刷题,为金九银十冲一波!

[外链图片转存中…(img-5pCHWUg3-1713606523886)]

[外链图片转存中…(img-EzXrV9Tc-1713606523887)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值