package com.didispace.lock.consul; import com.ecwid.consul.v1.ConsulClient; import com.ecwid.consul.v1.kv.model.GetValue; import com.ecwid.consul.v1.kv.model.PutParams; import lombok.extern.slf4j.Slf4j; import java.io.IOException; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; /** * 基于Consul的信号量实现 * * @author 翟永超 * @create 2017/4/7. * @blog http://blog.didispace.com */ @Slf4j public class Semaphore extends BaseLock { private static final String prefix = "semaphore/"; // 信号量参数前缀 private int limit; private boolean acquired = false; /** * @param consulClient consul客户端实例 * @param limit 信号量上限值 * @param lockKey 信号量在consul中存储的参数路径 * @param checkTtl 对锁Session的TTL */ public Semaphore(ConsulClient consulClient, int limit, String lockKey, CheckTtl checkTtl) { super(consulClient, prefix + lockKey, checkTtl); this.limit = limit; } /** * acquired信号量 * * @param block 是否阻塞。如果为true,那么一直尝试,直到获取到该资源为止。 * @return * @throws IOException */ public Boolean acquired(boolean block) throws IOException { if (acquired) { log.error("{} - Already acquired", sessionId); throw new RuntimeException(sessionId + " - Already acquired"); } // destroy session and create new session destroySession(); this.sessionId = createSession("semaphore" + this.keyPath); log.debug("Create session : {}", sessionId); // add contender entry String contenderKey = keyPath + "/" + sessionId; log.debug("contenderKey : {}", contenderKey); PutParams putParams = new PutParams(); putParams.setAcquireSession(sessionId); Boolean b = consulClient.setKVValue(contenderKey, "", putParams).getValue(); if (!b) { log.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"; GetValue lockKeyContent = consulClient.getKVValue(lockKey).getValue(); if (lockKeyContent != null) { // get lock value ContenderValue contenderValue = ContenderValue.parse(lockKeyContent); // 当前信号量已满 if (contenderValue.getLimit() == contenderValue.getHolders().size()) { clearInvalidHolder(contenderValue); if (block) { // 如果是阻塞模式,再尝试 try { Thread.sleep(500L); } 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 (c) { acquired = true; return true; } 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 (c) { acquired = true; return true; } continue; } } } /** * 释放session、并从lock中移除当前的sessionId * * @throws IOException */ public void release() throws IOException { if (this.acquired) { // remove session int /.lock's holders list while (true) { String contenderKey = keyPath + "/" + sessionId; String lockKey = keyPath + "/.lock"; GetValue lockKeyContent = consulClient.getKVValue(lockKey).getValue(); if (lockKeyContent != null) { // lock值转换 ContenderValue contenderValue = ContenderValue.parse(lockKeyContent); 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 (c) { break; } } } } // remove session key this.acquired = false; clearSession(); } public void clearSession() { if (checkTtl != null) { checkTtl.stop(); } destroySession(); } public void clearInvalidHolder(ContenderValue contenderValue) throws IOException { log.debug("Semaphore limited {}, remove invalid session...", contenderValue.getLimit()); // 获取/semaphore/<key>/下的所有竞争者session Map<String, String> aliveSessionMap = new HashMap<>(); List<GetValue> sessionList = consulClient.getKVValues(keyPath).getValue(); for (GetValue value : sessionList) { String session = value.getSession(); if (session == null || value.getSession().isEmpty()) { continue; } aliveSessionMap.put(session, ""); } String lockKey = keyPath + "/.lock"; GetValue lockKeyContent = consulClient.getKVValue(lockKey).getValue(); if (lockKeyContent != null) { // 清理holders中存储的不在semaphore/<key>/<session>中的session(说明该session已经被释放了) List<String> removeList = new LinkedList<>(); for(int i = 0; i < contenderValue.getHolders().size(); i ++) { String holder = contenderValue.getHolders().get(i); if (!aliveSessionMap.containsKey(holder)) { // 该session已经失效,需要从holder中剔除 removeList.add(holder); } } if (removeList.size() > 0) { contenderValue.getHolders().removeAll(removeList); // 清理失效的holder PutParams putParams = new PutParams(); putParams.setCas(lockKeyContent.getModifyIndex()); consulClient.setKVValue(lockKey, contenderValue.toString(), putParams).getValue(); } } } }