Merge branch 'feature/inflow-control-improvement' into master
This commit is contained in:
@@ -4,7 +4,7 @@ import com.eactive.eai.agent.command.Command;
|
||||
import com.eactive.eai.agent.command.CommandException;
|
||||
import com.eactive.eai.common.inflow.AbstractInflowControlManager;
|
||||
import com.eactive.eai.common.inflow.InflowControlUtil;
|
||||
import com.eactive.eai.common.inflow.dual.DualInflowControlManager;
|
||||
import com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
public class ReloadInflowClientControlCommand extends Command {
|
||||
@@ -31,10 +31,10 @@ public class ReloadInflowClientControlCommand extends Command {
|
||||
String keyName = (String) args;
|
||||
try {
|
||||
AbstractInflowControlManager base = InflowControlUtil.getInflowControlManager();
|
||||
if (!(base instanceof DualInflowControlManager)) {
|
||||
throw new IllegalStateException("DualInflowControlManager 가 활성화되지 않았습니다.");
|
||||
if (!(base instanceof ClientDualInflowControlManager)) {
|
||||
throw new IllegalStateException("ClientDualInflowControlManager 가 활성화되지 않았습니다.");
|
||||
}
|
||||
DualInflowControlManager manager = (DualInflowControlManager) base;
|
||||
ClientDualInflowControlManager manager = (ClientDualInflowControlManager) base;
|
||||
|
||||
if (keyName != null) {
|
||||
if ("ALL".equals(keyName)) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.eactive.eai.agent.command.Command;
|
||||
import com.eactive.eai.agent.command.CommandException;
|
||||
import com.eactive.eai.common.inflow.AbstractInflowControlManager;
|
||||
import com.eactive.eai.common.inflow.InflowControlUtil;
|
||||
import com.eactive.eai.common.inflow.dual.DualInflowControlManager;
|
||||
import com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
public class RemoveInflowClientControlCommand extends Command {
|
||||
@@ -31,10 +31,10 @@ public class RemoveInflowClientControlCommand extends Command {
|
||||
String key = (String) args;
|
||||
try {
|
||||
AbstractInflowControlManager base = InflowControlUtil.getInflowControlManager();
|
||||
if (!(base instanceof DualInflowControlManager)) {
|
||||
throw new IllegalStateException("DualInflowControlManager 가 활성화되지 않았습니다.");
|
||||
if (!(base instanceof ClientDualInflowControlManager)) {
|
||||
throw new IllegalStateException("ClientDualInflowControlManager 가 활성화되지 않았습니다.");
|
||||
}
|
||||
DualInflowControlManager manager = (DualInflowControlManager) base;
|
||||
ClientDualInflowControlManager manager = (ClientDualInflowControlManager) base;
|
||||
manager.removeClient(key);
|
||||
if (logger.isWarn())
|
||||
logger.warn(this.name + "] " + key + " removed.");
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.eactive.eai.common.util.Logger;
|
||||
|
||||
import io.github.bucket4j.Bandwidth;
|
||||
import io.github.bucket4j.Bucket4j;
|
||||
import io.github.bucket4j.Refill;
|
||||
import io.github.bucket4j.local.LocalBucket;
|
||||
import io.github.bucket4j.local.LocalBucketBuilder;
|
||||
|
||||
@@ -345,12 +346,14 @@ public abstract class AbstractInflowControlManager implements Lifecycle, Bucket
|
||||
if (AbstractInflowControlManager.isInflowTarget(inflowTarget)) {
|
||||
LocalBucketBuilder builder = Bucket4j.builder();
|
||||
if (inflowTarget.getThresholdPerSecond() > 0) {
|
||||
builder.addLimit(Bandwidth.simple(inflowTarget.getThresholdPerSecond(), Duration.ofSeconds(1)));
|
||||
builder.addLimit(Bandwidth.classic(inflowTarget.getThresholdPerSecond(),
|
||||
Refill.greedy(inflowTarget.getThresholdPerSecond(), Duration.ofSeconds(1))));
|
||||
}
|
||||
|
||||
if (inflowTarget.getThreshold() > 0 && StringUtils.isNotBlank(inflowTarget.getThresholdTimeUnit())) {
|
||||
builder.addLimit(Bandwidth.simple(inflowTarget.getThreshold(),
|
||||
AbstractInflowControlManager.getPeriod(inflowTarget.getThresholdTimeUnit())));
|
||||
builder.addLimit(Bandwidth.classic(inflowTarget.getThreshold(),
|
||||
Refill.intervally(inflowTarget.getThreshold(),
|
||||
AbstractInflowControlManager.getPeriod(inflowTarget.getThresholdTimeUnit()))));
|
||||
}
|
||||
|
||||
return new CustomBucket(builder.build(), inflowTarget);
|
||||
@@ -369,14 +372,16 @@ public abstract class AbstractInflowControlManager implements Lifecycle, Bucket
|
||||
|
||||
if (groupVo.getThresholdPerSecond() > 0) {
|
||||
perSecondBucket = Bucket4j.builder()
|
||||
.addLimit(Bandwidth.simple(groupVo.getThresholdPerSecond(), Duration.ofSeconds(1)))
|
||||
.addLimit(Bandwidth.classic(groupVo.getThresholdPerSecond(),
|
||||
Refill.greedy(groupVo.getThresholdPerSecond(), Duration.ofSeconds(1))))
|
||||
.build();
|
||||
}
|
||||
|
||||
if (groupVo.getThreshold() > 0 && StringUtils.isNotBlank(groupVo.getThresholdTimeUnit())) {
|
||||
thresholdBucket = Bucket4j.builder()
|
||||
.addLimit(Bandwidth.simple(groupVo.getThreshold(),
|
||||
AbstractInflowControlManager.getPeriod(groupVo.getThresholdTimeUnit())))
|
||||
.addLimit(Bandwidth.classic(groupVo.getThreshold(),
|
||||
Refill.intervally(groupVo.getThreshold(),
|
||||
AbstractInflowControlManager.getPeriod(groupVo.getThresholdTimeUnit()))))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.eactive.eai.common.inflow.dual;
|
||||
|
||||
import com.eactive.eai.common.inflow.InflowTargetVO;
|
||||
|
||||
/**
|
||||
* DualBucket에 클라이언트 유량제어 메서드를 추가한 인터페이스.
|
||||
* {@link ClientDualInflowControlManager}가 구현한다.
|
||||
*/
|
||||
public interface ClientDualBucket extends DualBucket {
|
||||
|
||||
/**
|
||||
* 클라이언트 유량제어 체크 — 차단 원인 포함.
|
||||
* @return null이면 통과, "PER_SECOND" 또는 "THRESHOLD"이면 해당 버킷에서 차단
|
||||
*/
|
||||
String isClientPassDetail(String clientId);
|
||||
|
||||
/**
|
||||
* 클라이언트 유량제어 설정 조회 — 에러 메시지 생성용.
|
||||
* @return 등록된 설정이 없으면 null
|
||||
*/
|
||||
InflowTargetVO getClientInflowThreshold(String clientId);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.eactive.eai.common.inflow.dual;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.eactive.eai.common.inflow.Bucket;
|
||||
import com.eactive.eai.common.inflow.InflowTargetVO;
|
||||
import com.eactive.eai.common.inflow.InflowType;
|
||||
import com.eactive.eai.common.lifecycle.LifecycleException;
|
||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
/**
|
||||
* 어댑터/인터페이스 이중 버킷(DualInflowControlManager)에 클라이언트 유량제어를 추가한 관리자.
|
||||
*
|
||||
* <p>적용 방법: INFLOW.properties에 아래 한 줄 추가.
|
||||
* <pre>inflow.control.bucket.className=com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager</pre>
|
||||
*/
|
||||
@Component
|
||||
public class ClientDualInflowControlManager extends DualInflowControlManager implements ClientDualBucket {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
|
||||
|
||||
public static synchronized Bucket getBucket() {
|
||||
return ApplicationContextProvider.getContext().getBean(ClientDualInflowControlManager.class);
|
||||
}
|
||||
|
||||
private volatile Map<String, DualCustomBucket> clientBucketMap = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, TargetMetrics> clientMetricsMap = new ConcurrentHashMap<>();
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Lifecycle
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void start() throws LifecycleException {
|
||||
super.start();
|
||||
try {
|
||||
initClients();
|
||||
} catch (Exception e) {
|
||||
throw new LifecycleException("ClientDualInflowControlManager init failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 초기화 / 리로드
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void initClients() throws Exception {
|
||||
this.clientBucketMap = loadBucketMap(InflowType.CLIENT);
|
||||
if (logger.isInfo()) logger.info("ClientDualInflowControlManager] initClients: " + clientBucketMap.size() + " buckets");
|
||||
}
|
||||
|
||||
public synchronized void reloadClient() throws Exception {
|
||||
initClients();
|
||||
clientMetricsMap.clear();
|
||||
}
|
||||
|
||||
public synchronized void reloadClient(String clientId) throws Exception {
|
||||
reloadBucketMap(clientBucketMap, InflowType.CLIENT, clientId);
|
||||
TargetMetrics m = clientMetricsMap.get(clientId);
|
||||
if (m != null) m.reset();
|
||||
}
|
||||
|
||||
public synchronized void removeClient(String clientId) {
|
||||
clientBucketMap.remove(clientId);
|
||||
clientMetricsMap.remove(clientId);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 클라이언트 메트릭 접근자
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public TargetMetrics getClientMetrics(String clientId) { return clientMetricsMap.get(clientId); }
|
||||
public Map<String, TargetMetrics> getAllClientMetrics() { return Collections.unmodifiableMap(clientMetricsMap); }
|
||||
public void resetClientMetrics(String clientId) { TargetMetrics m = clientMetricsMap.get(clientId); if (m != null) m.reset(); }
|
||||
public void resetAllClientMetrics() { clientMetricsMap.values().forEach(TargetMetrics::reset); }
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// ClientDualBucket — 클라이언트 유량제어
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public String isClientPassDetail(String clientId) {
|
||||
DualCustomBucket b = clientBucketMap.get(clientId);
|
||||
if (b == null) return DualCustomBucket.RESULT_PASS;
|
||||
String result = b.tryConsume();
|
||||
recordMetrics(clientMetricsMap, clientId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InflowTargetVO getClientInflowThreshold(String clientId) {
|
||||
DualCustomBucket b = clientBucketMap.get(clientId);
|
||||
return (b == null) ? null : b.getInflowTargetVo();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 모니터링용 접근자
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public DualCustomBucket getClientBucket(String clientId) {
|
||||
return clientBucketMap.get(clientId);
|
||||
}
|
||||
|
||||
public Map<String, DualCustomBucket> getClientBucketMap() {
|
||||
return clientBucketMap;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
package com.eactive.eai.common.inflow.dual;
|
||||
|
||||
import com.eactive.eai.common.inflow.Bucket;
|
||||
import com.eactive.eai.common.inflow.InflowTargetVO;
|
||||
|
||||
/**
|
||||
* Bucket 인터페이스 확장.
|
||||
* 기존 isAdapterPass/isInterfacePass(boolean)에 더해
|
||||
* 어느 버킷(PER_SECOND/THRESHOLD)에서 차단됐는지 반환하는 메서드 추가.
|
||||
* 클라이언트별 유량제어 메서드도 포함.
|
||||
*
|
||||
* <p>클라이언트 유량제어는 {@link ClientDualBucket}으로 분리됨.
|
||||
*/
|
||||
public interface DualBucket extends Bucket {
|
||||
|
||||
@@ -22,16 +22,4 @@ public interface DualBucket extends Bucket {
|
||||
* @return null이면 통과, "PER_SECOND" 또는 "THRESHOLD"이면 해당 버킷에서 차단
|
||||
*/
|
||||
String isInterfacePassDetail(String inter);
|
||||
|
||||
/**
|
||||
* 클라이언트 유량제어 체크 — 차단 원인 포함.
|
||||
* @return null이면 통과, "PER_SECOND" 또는 "THRESHOLD"이면 해당 버킷에서 차단
|
||||
*/
|
||||
String isClientPassDetail(String clientId);
|
||||
|
||||
/**
|
||||
* 클라이언트 유량제어 설정 조회 — 에러 메시지 생성용.
|
||||
* @return 등록된 설정이 없으면 null
|
||||
*/
|
||||
InflowTargetVO getClientInflowThreshold(String clientId);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.eactive.eai.common.inflow.AbstractInflowControlManager;
|
||||
import com.eactive.eai.common.inflow.Bucket;
|
||||
@@ -22,21 +21,17 @@ import com.eactive.eai.common.util.Logger;
|
||||
|
||||
import io.github.bucket4j.Bandwidth;
|
||||
import io.github.bucket4j.Bucket4j;
|
||||
import io.github.bucket4j.Refill;
|
||||
import io.github.bucket4j.local.LocalBucket;
|
||||
|
||||
/**
|
||||
* 어댑터/인터페이스/클라이언트 버킷을 이중 구조(perSecond + threshold)로 관리하는 유량제어 관리자.
|
||||
* 어댑터/인터페이스 버킷을 이중 구조(perSecond + threshold)로 관리하는 유량제어 관리자.
|
||||
*
|
||||
* <p>기존 InflowControlManager는 어댑터/인터페이스에 단일 LocalBucket(복수 Bandwidth)을 사용하여
|
||||
* 차단 시 어느 한도(초당/기간)를 초과했는지 알 수 없었다.
|
||||
* 이 클래스는 그룹 버킷(CustomGroupBucket)과 동일하게 두 버킷을 분리하고
|
||||
* {@link DualBucket#isAdapterPassDetail}/{@link DualBucket#isInterfacePassDetail}로
|
||||
* 차단 원인을 반환한다. 그룹 버킷 통과 여부는 {@link TargetMetrics}로 카운팅된다.
|
||||
* <p>클라이언트 유량제어는 {@link ClientDualInflowControlManager}로 분리됨.
|
||||
*
|
||||
* <p>적용 방법: INFLOW.properties에 아래 한 줄 추가.
|
||||
* <pre>inflow.control.bucket.className=com.eactive.eai.common.inflow.dual.DualInflowControlManager</pre>
|
||||
* <pre>inflow.control.bucket.className=com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager</pre>
|
||||
*/
|
||||
@Component
|
||||
public class DualInflowControlManager extends AbstractInflowControlManager implements DualBucket {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
|
||||
@@ -48,11 +43,9 @@ public class DualInflowControlManager extends AbstractInflowControlManager imple
|
||||
|
||||
private volatile Map<String, DualCustomBucket> adapterBucketMap = new ConcurrentHashMap<>();
|
||||
private volatile Map<String, DualCustomBucket> interfaceBucketMap = new ConcurrentHashMap<>();
|
||||
private volatile Map<String, DualCustomBucket> clientBucketMap = new ConcurrentHashMap<>();
|
||||
|
||||
private final ConcurrentHashMap<String, TargetMetrics> adapterMetricsMap = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, TargetMetrics> interfaceMetricsMap = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, TargetMetrics> clientMetricsMap = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, TargetMetrics> groupMetricsMap = new ConcurrentHashMap<>();
|
||||
|
||||
public static class TargetMetrics {
|
||||
@@ -79,7 +72,6 @@ public class DualInflowControlManager extends AbstractInflowControlManager imple
|
||||
try {
|
||||
initAdapters();
|
||||
initInterfaces();
|
||||
initClients();
|
||||
} catch (Exception e) {
|
||||
throw new LifecycleException("DualInflowControlManager init failed: " + e.getMessage());
|
||||
}
|
||||
@@ -99,12 +91,7 @@ public class DualInflowControlManager extends AbstractInflowControlManager imple
|
||||
if (logger.isInfo()) logger.info("DualInflowControlManager] initInterfaces: " + interfaceBucketMap.size() + " buckets");
|
||||
}
|
||||
|
||||
private void initClients() throws Exception {
|
||||
this.clientBucketMap = loadBucketMap(InflowType.CLIENT);
|
||||
if (logger.isInfo()) logger.info("DualInflowControlManager] initClients: " + clientBucketMap.size() + " buckets");
|
||||
}
|
||||
|
||||
private Map<String, DualCustomBucket> loadBucketMap(InflowType type) throws Exception {
|
||||
protected Map<String, DualCustomBucket> loadBucketMap(InflowType type) throws Exception {
|
||||
Map<String, DualCustomBucket> newMap = new HashMap<>();
|
||||
for (InflowTargetVO vo : inflowControlDAO.getInflowList(type)) {
|
||||
DualCustomBucket bucket = makeBucket(vo);
|
||||
@@ -139,22 +126,6 @@ public class DualInflowControlManager extends AbstractInflowControlManager imple
|
||||
if (m != null) m.reset();
|
||||
}
|
||||
|
||||
public synchronized void reloadClient() throws Exception {
|
||||
initClients();
|
||||
clientMetricsMap.clear();
|
||||
}
|
||||
|
||||
public synchronized void reloadClient(String clientId) throws Exception {
|
||||
reloadBucketMap(clientBucketMap, InflowType.CLIENT, clientId);
|
||||
TargetMetrics m = clientMetricsMap.get(clientId);
|
||||
if (m != null) m.reset();
|
||||
}
|
||||
|
||||
public synchronized void removeClient(String clientId) {
|
||||
clientBucketMap.remove(clientId);
|
||||
clientMetricsMap.remove(clientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reloadGroup() throws Exception {
|
||||
super.reloadGroup();
|
||||
@@ -168,7 +139,7 @@ public class DualInflowControlManager extends AbstractInflowControlManager imple
|
||||
if (m != null) m.reset();
|
||||
}
|
||||
|
||||
private void reloadBucketMap(Map<String, DualCustomBucket> targetMap, InflowType type, String name) throws Exception {
|
||||
protected void reloadBucketMap(Map<String, DualCustomBucket> targetMap, InflowType type, String name) throws Exception {
|
||||
Map<String, InflowTargetVO> updated = inflowControlDAO.getInflowMap(type, name);
|
||||
if (updated == null) return;
|
||||
for (InflowTargetVO vo : updated.values()) {
|
||||
@@ -179,24 +150,20 @@ public class DualInflowControlManager extends AbstractInflowControlManager imple
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 어댑터/인터페이스/클라이언트 메트릭 접근자
|
||||
// 어댑터/인터페이스 메트릭 접근자
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public TargetMetrics getAdapterMetrics(String adapter) { return adapterMetricsMap.get(adapter); }
|
||||
public TargetMetrics getInterfaceMetrics(String inter) { return interfaceMetricsMap.get(inter); }
|
||||
public TargetMetrics getClientMetrics(String clientId) { return clientMetricsMap.get(clientId); }
|
||||
|
||||
public Map<String, TargetMetrics> getAllAdapterMetrics() { return Collections.unmodifiableMap(adapterMetricsMap); }
|
||||
public Map<String, TargetMetrics> getAllInterfaceMetrics() { return Collections.unmodifiableMap(interfaceMetricsMap); }
|
||||
public Map<String, TargetMetrics> getAllClientMetrics() { return Collections.unmodifiableMap(clientMetricsMap); }
|
||||
|
||||
public void resetAdapterMetrics(String adapter) { TargetMetrics m = adapterMetricsMap.get(adapter); if (m != null) m.reset(); }
|
||||
public void resetInterfaceMetrics(String inter) { TargetMetrics m = interfaceMetricsMap.get(inter); if (m != null) m.reset(); }
|
||||
public void resetClientMetrics(String clientId) { TargetMetrics m = clientMetricsMap.get(clientId); if (m != null) m.reset(); }
|
||||
|
||||
public void resetAllAdapterMetrics() { adapterMetricsMap.values().forEach(TargetMetrics::reset); }
|
||||
public void resetAllInterfaceMetrics() { interfaceMetricsMap.values().forEach(TargetMetrics::reset); }
|
||||
public void resetAllClientMetrics() { clientMetricsMap.values().forEach(TargetMetrics::reset); }
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 그룹 메트릭 — isGroupPass() 카운팅 및 접근자
|
||||
@@ -255,32 +222,15 @@ public class DualInflowControlManager extends AbstractInflowControlManager imple
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String isClientPassDetail(String clientId) {
|
||||
DualCustomBucket b = clientBucketMap.get(clientId);
|
||||
if (b == null) return DualCustomBucket.RESULT_PASS;
|
||||
String result = b.tryConsume();
|
||||
recordMetrics(clientMetricsMap, clientId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void recordMetrics(ConcurrentHashMap<String, TargetMetrics> metricsMap, String key, String result) {
|
||||
protected void recordMetrics(ConcurrentHashMap<String, TargetMetrics> metricsMap, String key, String result) {
|
||||
TargetMetrics m = metricsMap.computeIfAbsent(key, k -> new TargetMetrics());
|
||||
if (result == null) m.allowed.incrementAndGet();
|
||||
else if (DualCustomBucket.RESULT_BLOCKED_PER_SECOND.equals(result)) m.rejectedPerSecond.incrementAndGet();
|
||||
else m.rejectedThreshold.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InflowTargetVO getClientInflowThreshold(String clientId) {
|
||||
DualCustomBucket b = clientBucketMap.get(clientId);
|
||||
return (b == null) ? null : b.getInflowTargetVo();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Bucket 인터페이스 오버라이드 — 이중 버킷으로 교체 (boolean 반환 유지)
|
||||
// isAdapterPassDetail/isInterfacePassDetail이 토큰을 소비하므로
|
||||
// 두 메서드를 동일 요청에서 중복 호출하면 토큰이 이중 소비됨에 주의.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
@@ -295,7 +245,6 @@ public class DualInflowControlManager extends AbstractInflowControlManager imple
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 어댑터/인터페이스 키·VO·삭제 — Dual 맵 기준으로 오버라이드
|
||||
// (base의 adapterBucketList / interfaceBucketList 는 읽지 않아 DAO 이중 호출 제거)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
@@ -356,19 +305,16 @@ public class DualInflowControlManager extends AbstractInflowControlManager imple
|
||||
return interfaceBucketMap;
|
||||
}
|
||||
|
||||
public DualCustomBucket getClientBucket(String clientId) {
|
||||
return clientBucketMap.get(clientId);
|
||||
}
|
||||
|
||||
public Map<String, DualCustomBucket> getClientBucketMap() {
|
||||
return clientBucketMap;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 버킷 팩토리
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private DualCustomBucket makeBucket(InflowTargetVO vo) {
|
||||
/**
|
||||
* perSecond 버킷: greedy refill — 초당 N개 요청을 균등하게 허용.
|
||||
* threshold 버킷: intervally refill — 기간(시/일/월 등) 단위 쿼터를 기간 시작 시 일괄 충전.
|
||||
* Bandwidth.simple은 두 버킷 모두 greedy로 생성되어 시간 단위 구분이 없어지므로 classic으로 교체.
|
||||
*/
|
||||
protected DualCustomBucket makeBucket(InflowTargetVO vo) {
|
||||
if (!AbstractInflowControlManager.isInflowTarget(vo)) {
|
||||
return null;
|
||||
}
|
||||
@@ -378,14 +324,16 @@ public class DualInflowControlManager extends AbstractInflowControlManager imple
|
||||
|
||||
if (vo.getThresholdPerSecond() > 0) {
|
||||
perSecondBucket = Bucket4j.builder()
|
||||
.addLimit(Bandwidth.simple(vo.getThresholdPerSecond(), Duration.ofSeconds(1)))
|
||||
.addLimit(Bandwidth.classic(vo.getThresholdPerSecond(),
|
||||
Refill.greedy(vo.getThresholdPerSecond(), Duration.ofSeconds(1))))
|
||||
.build();
|
||||
}
|
||||
|
||||
if (vo.getThreshold() > 0 && StringUtils.isNotBlank(vo.getThresholdTimeUnit())) {
|
||||
Duration period = AbstractInflowControlManager.getPeriod(vo.getThresholdTimeUnit());
|
||||
thresholdBucket = Bucket4j.builder()
|
||||
.addLimit(Bandwidth.simple(vo.getThreshold(),
|
||||
AbstractInflowControlManager.getPeriod(vo.getThresholdTimeUnit())))
|
||||
.addLimit(Bandwidth.classic(vo.getThreshold(),
|
||||
Refill.intervally(vo.getThreshold(), period)))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
package com.eactive.eai.inbound.processor;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.common.inflow.Bucket;
|
||||
import com.eactive.eai.common.inflow.InflowTargetVO;
|
||||
import com.eactive.eai.common.inflow.dual.DualBucket;
|
||||
import com.eactive.eai.common.inflow.dual.DualCustomBucket;
|
||||
|
||||
/**
|
||||
* 어댑터/인터페이스 유량제어 차단 시 어느 버킷(PER_SECOND/THRESHOLD)에서 차단됐는지
|
||||
* 에러 메시지에 포함하는 RequestProcessor 확장.
|
||||
*
|
||||
* <p>DualInflowControlManager와 함께 사용:
|
||||
* <pre>
|
||||
* inflow.control.bucket.className=com.eactive.eai.common.inflow.dual.DualInflowControlManager
|
||||
* </pre>
|
||||
*
|
||||
* <p>Spring Bean 등록 후 기존 RequestProcessor 대신 이 클래스를 어댑터에 설정한다.
|
||||
*/
|
||||
public class DualRequestProcessor extends RequestProcessor {
|
||||
|
||||
@Override
|
||||
protected String checkClientInflow(Bucket bucket, String clientId) {
|
||||
if (StringUtils.isBlank(clientId)) return null;
|
||||
if (bucket instanceof DualBucket) {
|
||||
return ((DualBucket) bucket).isClientPassDetail(clientId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InflowTargetVO resolveClientInflowThreshold(Bucket bucket, String clientId) {
|
||||
if (bucket instanceof DualBucket) {
|
||||
return ((DualBucket) bucket).getClientInflowThreshold(clientId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String checkAdapterInflow(Bucket bucket, String adapterGroupName) {
|
||||
if (bucket instanceof DualBucket) {
|
||||
return ((DualBucket) bucket).isAdapterPassDetail(adapterGroupName);
|
||||
}
|
||||
return super.checkAdapterInflow(bucket, adapterGroupName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String checkInterfaceInflow(Bucket bucket, String eaiSvcCd) {
|
||||
if (bucket instanceof DualBucket) {
|
||||
return ((DualBucket) bucket).isInterfacePassDetail(eaiSvcCd);
|
||||
}
|
||||
return super.checkInterfaceInflow(bucket, eaiSvcCd);
|
||||
}
|
||||
|
||||
/**
|
||||
* 차단 원인에 따라 에러 메시지에 한도 정보를 포함.
|
||||
* <ul>
|
||||
* <li>PER_SECOND: "API 호출 한도 초과 [name: Nreq/sec]"</li>
|
||||
* <li>THRESHOLD: "API 호출 한도 초과 [name: Nreq/UNIT]"</li>
|
||||
* <li>그 외: "API 호출 한도 초과 [name]" (기존 동작)</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Override
|
||||
protected String buildInflowTargetErrorMsg(InflowTargetVO inflowTargetVO, String blockedType) {
|
||||
if (DualCustomBucket.RESULT_BLOCKED_PER_SECOND.equals(blockedType)) {
|
||||
return String.format("API 호출 한도 초과 [%s: %dreq/sec]",
|
||||
inflowTargetVO.getName(),
|
||||
inflowTargetVO.getThresholdPerSecond());
|
||||
}
|
||||
if (DualCustomBucket.RESULT_BLOCKED_THRESHOLD.equals(blockedType)) {
|
||||
return String.format("API 호출 한도 초과 [%s: %dreq/%s]",
|
||||
inflowTargetVO.getName(),
|
||||
inflowTargetVO.getThreshold(),
|
||||
inflowTargetVO.getThresholdTimeUnit());
|
||||
}
|
||||
return super.buildInflowTargetErrorMsg(inflowTargetVO, blockedType);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user