Client 유량제어 관련 eapim-online으로 이동
This commit is contained in:
@@ -1,60 +0,0 @@
|
||||
package com.eactive.eai.agent.inflow;
|
||||
|
||||
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.ClientDualInflowControlManager;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
public class ReloadInflowClientControlCommand extends Command {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ReloadInflowClientControlCommand() {
|
||||
super("ReloadInflowClientControlCommand");
|
||||
}
|
||||
|
||||
public Object execute() throws CommandException {
|
||||
Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
|
||||
if (logger.isInfo())
|
||||
logger.info(this.name + " is executed");
|
||||
|
||||
if (!(args instanceof String)) {
|
||||
String rspErrorCode = "RECEAIMCM001";
|
||||
String msg = makeException(rspErrorCode, null);
|
||||
if (logger.isError())
|
||||
logger.error(msg);
|
||||
throw new CommandException(msg);
|
||||
}
|
||||
|
||||
String keyName = (String) args;
|
||||
try {
|
||||
AbstractInflowControlManager base = InflowControlUtil.getInflowControlManager();
|
||||
if (!(base instanceof ClientDualInflowControlManager)) {
|
||||
throw new IllegalStateException("ClientDualInflowControlManager 가 활성화되지 않았습니다.");
|
||||
}
|
||||
ClientDualInflowControlManager manager = (ClientDualInflowControlManager) base;
|
||||
|
||||
if (keyName != null) {
|
||||
if ("ALL".equals(keyName)) {
|
||||
manager.reloadClient();
|
||||
if (logger.isWarn())
|
||||
logger.warn(this.name + "] all rule Reload.");
|
||||
} else {
|
||||
manager.reloadClient(keyName);
|
||||
if (logger.isWarn())
|
||||
logger.warn(this.name + "] " + keyName + " Reload.");
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
String rspErrorCode = "RECEAIMCM002";
|
||||
String msg = makeException(rspErrorCode, e);
|
||||
if (logger.isError())
|
||||
logger.error(msg, e);
|
||||
throw new CommandException(msg);
|
||||
}
|
||||
return "success";
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package com.eactive.eai.agent.inflow;
|
||||
|
||||
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.ClientDualInflowControlManager;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
public class RemoveInflowClientControlCommand extends Command {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public RemoveInflowClientControlCommand() {
|
||||
super("RemoveInflowClientControlCommand");
|
||||
}
|
||||
|
||||
public Object execute() throws CommandException {
|
||||
Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
|
||||
if (logger.isInfo())
|
||||
logger.info(this.name + " is executed");
|
||||
|
||||
if (!(args instanceof String)) {
|
||||
String rspErrorCode = "RECEAIMCM001";
|
||||
String msg = makeException(rspErrorCode, null);
|
||||
if (logger.isError())
|
||||
logger.error(msg);
|
||||
throw new CommandException(msg);
|
||||
}
|
||||
|
||||
String key = (String) args;
|
||||
try {
|
||||
AbstractInflowControlManager base = InflowControlUtil.getInflowControlManager();
|
||||
if (!(base instanceof ClientDualInflowControlManager)) {
|
||||
throw new IllegalStateException("ClientDualInflowControlManager 가 활성화되지 않았습니다.");
|
||||
}
|
||||
ClientDualInflowControlManager manager = (ClientDualInflowControlManager) base;
|
||||
manager.removeClient(key);
|
||||
if (logger.isWarn())
|
||||
logger.warn(this.name + "] " + key + " removed.");
|
||||
} catch (Exception e) {
|
||||
String rspErrorCode = "RECEAIMCM002";
|
||||
String msg = makeException(rspErrorCode, e);
|
||||
if (logger.isError())
|
||||
logger.error(msg, e);
|
||||
throw new CommandException(msg);
|
||||
}
|
||||
return "success";
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
-190
@@ -1,190 +0,0 @@
|
||||
package com.eactive.eai.common.inflow.dual;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import com.eactive.eai.common.inflow.InflowTargetVO;
|
||||
|
||||
import io.github.bucket4j.Bandwidth;
|
||||
import io.github.bucket4j.Bucket4j;
|
||||
import io.github.bucket4j.local.LocalBucket;
|
||||
|
||||
/**
|
||||
* ClientDualInflowControlManager 클라이언트 메트릭 단위 테스트.
|
||||
*/
|
||||
class ClientDualInflowControlManagerMetricsTest {
|
||||
|
||||
private ClientDualInflowControlManager manager;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
manager = new ClientDualInflowControlManager();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private InflowTargetVO makeTargetVO(String name) {
|
||||
InflowTargetVO vo = new InflowTargetVO();
|
||||
vo.setName(name);
|
||||
vo.setThresholdPerSecond(100);
|
||||
vo.setThreshold(10000);
|
||||
vo.setThresholdTimeUnit("DAY");
|
||||
vo.setActivate(true);
|
||||
return vo;
|
||||
}
|
||||
|
||||
private LocalBucket stableBucket(long capacity) {
|
||||
return Bucket4j.builder()
|
||||
.addLimit(Bandwidth.simple(capacity, Duration.ofHours(1)))
|
||||
.build();
|
||||
}
|
||||
|
||||
private LocalBucket exhaustedBucket(long capacity) {
|
||||
LocalBucket b = stableBucket(capacity);
|
||||
b.tryConsume(capacity);
|
||||
return b;
|
||||
}
|
||||
|
||||
private DualCustomBucket passDualBucket(String name) {
|
||||
return new DualCustomBucket(stableBucket(100), null, makeTargetVO(name));
|
||||
}
|
||||
|
||||
private DualCustomBucket blockedPerSecondDualBucket(String name) {
|
||||
return new DualCustomBucket(exhaustedBucket(1), null, makeTargetVO(name));
|
||||
}
|
||||
|
||||
private DualCustomBucket blockedThresholdDualBucket(String name) {
|
||||
return new DualCustomBucket(stableBucket(100), exhaustedBucket(1), makeTargetVO(name));
|
||||
}
|
||||
|
||||
private void injectClientMap(Map<String, DualCustomBucket> map) {
|
||||
ReflectionTestUtils.setField(manager, "clientBucketMap", map);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// isClientPassDetail() — 카운터 증가
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void isClientPassDetail_bucketNotRegistered_noMetricsCreated() {
|
||||
manager.isClientPassDetail("UNKNOWN");
|
||||
|
||||
assertNull(manager.getClientMetrics("UNKNOWN"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isClientPassDetail_pass_incrementsAllowed() {
|
||||
Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("C1", passDualBucket("C1"));
|
||||
injectClientMap(map);
|
||||
|
||||
manager.isClientPassDetail("C1");
|
||||
|
||||
assertEquals(1, manager.getClientMetrics("C1").allowed.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void isClientPassDetail_blockedThreshold_incrementsRejectedThreshold() {
|
||||
Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("C1", blockedThresholdDualBucket("C1"));
|
||||
injectClientMap(map);
|
||||
|
||||
manager.isClientPassDetail("C1");
|
||||
|
||||
assertEquals(1, manager.getClientMetrics("C1").rejectedThreshold.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void isClientPassDetail_blockedPerSecond_incrementsRejectedPerSecond() {
|
||||
Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("C1", blockedPerSecondDualBucket("C1"));
|
||||
injectClientMap(map);
|
||||
|
||||
manager.isClientPassDetail("C1");
|
||||
|
||||
assertEquals(1, manager.getClientMetrics("C1").rejectedPerSecond.get());
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// getAllClientMetrics — 조회 및 불변성
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void getAllClientMetrics_empty_returnsEmptyMap() {
|
||||
assertTrue(manager.getAllClientMetrics().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAllClientMetrics_afterCalls_containsEntries() {
|
||||
Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("C1", passDualBucket("C1"));
|
||||
map.put("C2", passDualBucket("C2"));
|
||||
injectClientMap(map);
|
||||
|
||||
manager.isClientPassDetail("C1");
|
||||
manager.isClientPassDetail("C2");
|
||||
|
||||
Map<String, DualInflowControlManager.TargetMetrics> all = manager.getAllClientMetrics();
|
||||
assertEquals(2, all.size());
|
||||
assertTrue(all.containsKey("C1"));
|
||||
assertTrue(all.containsKey("C2"));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// resetClientMetrics / resetAllClientMetrics
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void resetClientMetrics_resetsTargetClient() {
|
||||
Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("C1", passDualBucket("C1"));
|
||||
injectClientMap(map);
|
||||
|
||||
manager.isClientPassDetail("C1");
|
||||
manager.resetClientMetrics("C1");
|
||||
|
||||
assertEquals(0, manager.getClientMetrics("C1").allowed.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void resetAllClientMetrics_resetsAll() {
|
||||
Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("C1", passDualBucket("C1"));
|
||||
map.put("C2", passDualBucket("C2"));
|
||||
injectClientMap(map);
|
||||
|
||||
manager.isClientPassDetail("C1");
|
||||
manager.isClientPassDetail("C2");
|
||||
manager.resetAllClientMetrics();
|
||||
|
||||
assertEquals(0, manager.getClientMetrics("C1").allowed.get());
|
||||
assertEquals(0, manager.getClientMetrics("C2").allowed.get());
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// removeClient — 메트릭 자동 삭제
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void removeClient_cleansUpMetrics() {
|
||||
Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("C1", passDualBucket("C1"));
|
||||
injectClientMap(map);
|
||||
|
||||
manager.isClientPassDetail("C1");
|
||||
assertNotNull(manager.getClientMetrics("C1"));
|
||||
|
||||
manager.removeClient("C1");
|
||||
|
||||
assertNull(manager.getClientMetrics("C1"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user