refactor: AbstractInflowControlManager 상속 구조 리팩토링 및 커맨드 클래스 타입 변경

InflowControlManager에서 공통 로직을 AbstractInflowControlManager로 분리하고,
커맨드 클래스들이 AbstractInflowControlManager 타입을 사용하도록 변경.
InflowControlUtil에 cachedInflowControlManager 정적 캐시 도입.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curry772
2026-04-30 10:14:31 +09:00
parent 9589bc635a
commit 2bfc3d0bde
11 changed files with 592 additions and 571 deletions
@@ -2,7 +2,8 @@ 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.InflowControlManager;
import com.eactive.eai.common.inflow.AbstractInflowControlManager;
import com.eactive.eai.common.inflow.InflowControlUtil;
import com.eactive.eai.common.util.Logger;
public class ReloadInflowAdapterControlCommand extends Command {
@@ -27,7 +28,7 @@ public class ReloadInflowAdapterControlCommand extends Command {
String keyName = (String) args;
try {
InflowControlManager manager = InflowControlManager.getInstance();
AbstractInflowControlManager manager = InflowControlUtil.getInflowControlManager();
if (keyName != null) {
if ("ALL".equals(keyName)) {
@@ -2,7 +2,8 @@ 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.InflowControlManager;
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.util.Logger;
@@ -29,7 +30,7 @@ public class ReloadInflowClientControlCommand extends Command {
String keyName = (String) args;
try {
InflowControlManager base = InflowControlManager.getInstance();
AbstractInflowControlManager base = InflowControlUtil.getInflowControlManager();
if (!(base instanceof DualInflowControlManager)) {
throw new IllegalStateException("DualInflowControlManager 가 활성화되지 않았습니다.");
}
@@ -2,7 +2,8 @@ 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.InflowControlManager;
import com.eactive.eai.common.inflow.AbstractInflowControlManager;
import com.eactive.eai.common.inflow.InflowControlUtil;
import com.eactive.eai.common.util.Logger;
public class ReloadInflowGroupControlCommand extends Command {
@@ -28,7 +29,7 @@ public class ReloadInflowGroupControlCommand extends Command {
String keyName = (String) args;
try {
InflowControlManager manager = InflowControlManager.getInstance();
AbstractInflowControlManager manager = InflowControlUtil.getInflowControlManager();
if (keyName != null) {
if ("ALL".equals(keyName)) {
@@ -2,7 +2,8 @@ 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.InflowControlManager;
import com.eactive.eai.common.inflow.AbstractInflowControlManager;
import com.eactive.eai.common.inflow.InflowControlUtil;
import com.eactive.eai.common.util.Logger;
public class ReloadInflowInterfaceControlCommand extends Command {
@@ -28,7 +29,7 @@ public class ReloadInflowInterfaceControlCommand extends Command {
String keyName = (String) args;
try {
InflowControlManager manager = InflowControlManager.getInstance();
AbstractInflowControlManager manager = InflowControlUtil.getInflowControlManager();
if (keyName != null) {
if ("ALL".equals(keyName)) {
@@ -2,7 +2,8 @@ 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.InflowControlManager;
import com.eactive.eai.common.inflow.AbstractInflowControlManager;
import com.eactive.eai.common.inflow.InflowControlUtil;
import com.eactive.eai.common.util.Logger;
public class RemoveInflowAdapterControlCommand extends Command {
@@ -26,7 +27,7 @@ public class RemoveInflowAdapterControlCommand extends Command {
String key = (String) args;
try {
InflowControlManager manager = InflowControlManager.getInstance();
AbstractInflowControlManager manager = InflowControlUtil.getInflowControlManager();
manager.removeAdapter(key);
} catch (Exception e) {
String rspErrorCode = "RECEAIMCM002";
@@ -2,7 +2,8 @@ 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.InflowControlManager;
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.util.Logger;
@@ -29,7 +30,7 @@ public class RemoveInflowClientControlCommand extends Command {
String key = (String) args;
try {
InflowControlManager base = InflowControlManager.getInstance();
AbstractInflowControlManager base = InflowControlUtil.getInflowControlManager();
if (!(base instanceof DualInflowControlManager)) {
throw new IllegalStateException("DualInflowControlManager 가 활성화되지 않았습니다.");
}
@@ -2,7 +2,8 @@ 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.InflowControlManager;
import com.eactive.eai.common.inflow.AbstractInflowControlManager;
import com.eactive.eai.common.inflow.InflowControlUtil;
import com.eactive.eai.common.util.Logger;
public class RemoveInflowGroupControlCommand extends Command {
@@ -28,7 +29,7 @@ public class RemoveInflowGroupControlCommand extends Command {
String key = (String) args;
try {
InflowControlManager manager = InflowControlManager.getInstance();
AbstractInflowControlManager manager = InflowControlUtil.getInflowControlManager();
manager.removeGroup(key);
if (logger.isWarn())
logger.warn(this.name + "] group " + key + " removed.");
@@ -2,7 +2,8 @@ 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.InflowControlManager;
import com.eactive.eai.common.inflow.AbstractInflowControlManager;
import com.eactive.eai.common.inflow.InflowControlUtil;
import com.eactive.eai.common.util.Logger;
public class RemoveInflowInterfaceControlCommand extends Command {
@@ -25,7 +26,7 @@ public class RemoveInflowInterfaceControlCommand extends Command {
String key = (String) args;
try {
InflowControlManager manager = InflowControlManager.getInstance();
AbstractInflowControlManager manager = InflowControlUtil.getInflowControlManager();
manager.removeInterface(key);
} catch (Exception e) {
String rspErrorCode = "RECEAIMCM002";
@@ -0,0 +1,496 @@
package com.eactive.eai.common.inflow;
import java.time.Duration;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.lifecycle.Lifecycle;
import com.eactive.eai.common.lifecycle.LifecycleException;
import com.eactive.eai.common.lifecycle.LifecycleListener;
import com.eactive.eai.common.lifecycle.LifecycleSupport;
import com.eactive.eai.common.util.Logger;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket4j;
import io.github.bucket4j.local.LocalBucket;
import io.github.bucket4j.local.LocalBucketBuilder;
public abstract class AbstractInflowControlManager implements Lifecycle, Bucket {
static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
public static final String QUOTA_TIMEUNIT_SECOND = "SEC";
public static final String QUOTA_TIMEUNIT_MINUTE = "MIN";
public static final String QUOTA_TIMEUNIT_HOUR = "HOU";
public static final String QUOTA_TIMEUNIT_DAY = "DAY";
public static final String QUOTA_TIMEUNIT_MONTH = "MON";
private volatile Map<String, CustomBucket> adapterBucketList = new ConcurrentHashMap<>();
private volatile Map<String, CustomBucket> interfaceBucketList = new ConcurrentHashMap<>();
private volatile Map<String, CustomGroupBucket> groupBucketList = new ConcurrentHashMap<>();
private volatile Map<String, String> interfaceToGroupMap = new ConcurrentHashMap<>();
private volatile Map<String, InflowGroupVO> groupVoMap = new ConcurrentHashMap<>();
private boolean started;
private LifecycleSupport lifecycle = new LifecycleSupport(this);
@Autowired
protected InflowControlDAO inflowControlDAO;
public void start() throws LifecycleException {
if (started)
throw new LifecycleException("RECEAICMM201");
lifecycle.fireLifecycleEvent(STARTING_EVENT, this);
try {
init();
} catch (Exception e) {
throw new LifecycleException(ExceptionUtil.getErrorCode(e, "RECEAICMM202"));
}
started = true;
lifecycle.fireLifecycleEvent(STARTED_EVENT, this);
}
private void init() throws Exception {
initAdapter();
initInterface();
initGroup();
}
private void initAdapter() throws Exception {
Map<String, CustomBucket> newMap = new HashMap<>();
List<InflowTargetVO> inflowAdapterVoList = inflowControlDAO.getInflowAdapterList();
for (InflowTargetVO inflowVo : inflowAdapterVoList) {
if (AbstractInflowControlManager.isInflowTarget(inflowVo)) {
CustomBucket bucket = makeBucketUsingInflowVO(inflowVo);
if (bucket != null) {
newMap.put(inflowVo.getName(), bucket);
}
}
}
this.adapterBucketList = newMap;
}
private void initInterface() throws Exception {
Map<String, CustomBucket> newMap = new HashMap<>();
List<InflowTargetVO> inflowInterfaceVoList = inflowControlDAO.getInflowInterfaceList();
for (InflowTargetVO inflowVo : inflowInterfaceVoList) {
if (AbstractInflowControlManager.isInflowTarget(inflowVo)) {
CustomBucket bucket = makeBucketUsingInflowVO(inflowVo);
if (bucket != null) {
newMap.put(inflowVo.getName(), bucket);
}
}
}
this.interfaceBucketList = newMap;
}
private void initGroup() throws Exception {
Map<String, CustomGroupBucket> newGroupBucketList = new HashMap<>();
Map<String, String> newInterfaceToGroupMap = new HashMap<>();
Map<String, InflowGroupVO> newGroupVoMap = new HashMap<>();
List<InflowGroupVO> inflowGroupVoList = inflowControlDAO.getInflowGroupList();
for (InflowGroupVO groupVo : inflowGroupVoList) {
if (isInflowGroupTarget(groupVo)) {
CustomGroupBucket bucket = makeGroupBucket(groupVo);
if (bucket != null) {
newGroupBucketList.put(groupVo.getGroupId(), bucket);
newGroupVoMap.put(groupVo.getGroupId(), groupVo);
for (String interfaceId : groupVo.getInterfaceList()) {
newInterfaceToGroupMap.put(interfaceId, groupVo.getGroupId());
}
}
}
}
if (logger.isInfo()) {
logger.info("InflowControlManager] initGroup completed. groups=" + newGroupBucketList.size()
+ ", mappings=" + newInterfaceToGroupMap.size());
}
this.groupBucketList = newGroupBucketList;
this.interfaceToGroupMap = newInterfaceToGroupMap;
this.groupVoMap = newGroupVoMap;
}
public synchronized void reloadAdapter() throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload all Started ...");
}
initAdapter();
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload all finished ...");
}
}
public synchronized void reloadInterface() throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload all Started ...");
}
initInterface();
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload all finished ...");
}
}
public synchronized void reloadAdapter(String adapter) throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload Started ...");
}
Map<String, InflowTargetVO> map = inflowControlDAO.getInflowTargetByAdater(adapter);
if (map != null) {
Iterator<String> it = map.keySet().iterator();
String key = "";
while (it.hasNext()) {
key = it.next();
InflowTargetVO inflowTarget = map.get(key);
if (AbstractInflowControlManager.isInflowTarget(inflowTarget)) {
CustomBucket bucket = makeBucketUsingInflowVO(inflowTarget);
if (bucket != null) {
adapterBucketList.put(inflowTarget.getName(), bucket);
} else {
adapterBucketList.remove(inflowTarget.getName());
}
} else {
adapterBucketList.remove(inflowTarget.getName());
}
}
}
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload finished ...");
}
}
public synchronized void reloadInterface(String inter) throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload Started ...");
}
Map<String, InflowTargetVO> map = inflowControlDAO.getInflowTargetByInterface(inter);
if (map != null) {
Iterator<String> it = map.keySet().iterator();
String key = "";
while (it.hasNext()) {
key = it.next();
InflowTargetVO inflowTarget = map.get(key);
if (AbstractInflowControlManager.isInflowTarget(inflowTarget)) {
CustomBucket bucket = makeBucketUsingInflowVO(inflowTarget);
if (bucket != null) {
interfaceBucketList.put(inflowTarget.getName(), bucket);
} else {
interfaceBucketList.remove(inflowTarget.getName());
}
} else {
interfaceBucketList.remove(inflowTarget.getName());
}
}
}
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload finished ...");
}
}
public synchronized void reloadGroup() throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reloadGroup all Started ...");
}
initGroup();
if (logger.isWarn()) {
logger.warn("InflowControlManager] reloadGroup all finished ...");
}
}
public synchronized void reloadGroup(String groupId) throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reloadGroup Started ... groupId=" + groupId);
}
Map<String, InflowGroupVO> map = inflowControlDAO.getInflowTargetByGroup(groupId);
if (map != null) {
Iterator<Map.Entry<String, String>> mappingIt = interfaceToGroupMap.entrySet().iterator();
while (mappingIt.hasNext()) {
Map.Entry<String, String> entry = mappingIt.next();
if (groupId.equals(entry.getValue())) {
mappingIt.remove();
}
}
Iterator<String> it = map.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
InflowGroupVO groupVo = map.get(key);
if (isInflowGroupTarget(groupVo)) {
CustomGroupBucket bucket = makeGroupBucket(groupVo);
if (bucket != null) {
groupBucketList.put(groupVo.getGroupId(), bucket);
groupVoMap.put(groupVo.getGroupId(), groupVo);
for (String interfaceId : groupVo.getInterfaceList()) {
interfaceToGroupMap.put(interfaceId, groupVo.getGroupId());
}
} else {
groupBucketList.remove(groupVo.getGroupId());
groupVoMap.remove(groupVo.getGroupId());
}
} else {
groupBucketList.remove(groupVo.getGroupId());
groupVoMap.remove(groupVo.getGroupId());
}
}
}
if (logger.isWarn()) {
logger.warn("InflowControlManager] reloadGroup finished ...");
}
}
public void stop() throws LifecycleException {
if (!started)
throw new LifecycleException("RECEAICMM203");
lifecycle.fireLifecycleEvent(STOPING_EVENT, this);
adapterBucketList = new ConcurrentHashMap<>();
interfaceBucketList = new ConcurrentHashMap<>();
groupBucketList = new ConcurrentHashMap<>();
interfaceToGroupMap = new ConcurrentHashMap<>();
groupVoMap = new ConcurrentHashMap<>();
started = false;
lifecycle.fireLifecycleEvent(STOPPED_EVENT, this);
}
public void addLifecycleListener(LifecycleListener listener) {
lifecycle.addLifecycleListener(listener);
}
public LifecycleListener[] findLifecycleListeners() {
return lifecycle.findLifecycleListeners();
}
public void removeLifecycleListener(LifecycleListener listener) {
lifecycle.removeLifecycleListener(listener);
}
public boolean isStarted() {
return this.started;
}
public String[] getAdapterAllKeys() {
Iterator<String> it = this.adapterBucketList.keySet().iterator();
String[] svcCd = new String[this.adapterBucketList.size()];
for (int i = 0; it.hasNext(); i++) {
svcCd[i] = it.next();
}
Arrays.sort(svcCd);
return svcCd;
}
public String[] getInterfaceAllKeys() {
Iterator<String> it = this.interfaceBucketList.keySet().iterator();
String[] svcCd = new String[this.interfaceBucketList.size()];
for (int i = 0; it.hasNext(); i++) {
svcCd[i] = it.next();
}
Arrays.sort(svcCd);
return svcCd;
}
public synchronized void removeAdapter(String adapter) {
adapterBucketList.remove(adapter);
}
public synchronized void removeInterface(String inter) {
interfaceBucketList.remove(inter);
}
public synchronized void removeGroup(String groupId) {
groupBucketList.remove(groupId);
groupVoMap.remove(groupId);
Iterator<Map.Entry<String, String>> it = interfaceToGroupMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
if (groupId.equals(entry.getValue())) {
it.remove();
}
}
}
public String[] getGroupAllKeys() {
Iterator<String> it = this.groupBucketList.keySet().iterator();
String[] groupIds = new String[this.groupBucketList.size()];
for (int i = 0; it.hasNext(); i++) {
groupIds[i] = it.next();
}
Arrays.sort(groupIds);
return groupIds;
}
public CustomGroupBucket getGroupBucket(String groupId) {
return groupBucketList.get(groupId);
}
private CustomBucket makeBucketUsingInflowVO(InflowTargetVO inflowTarget) {
if (AbstractInflowControlManager.isInflowTarget(inflowTarget)) {
LocalBucketBuilder builder = Bucket4j.builder();
if (inflowTarget.getThresholdPerSecond() > 0) {
builder.addLimit(Bandwidth.simple(inflowTarget.getThresholdPerSecond(), Duration.ofSeconds(1)));
}
if (inflowTarget.getThreshold() > 0 && StringUtils.isNotBlank(inflowTarget.getThresholdTimeUnit())) {
builder.addLimit(Bandwidth.simple(inflowTarget.getThreshold(),
AbstractInflowControlManager.getPeriod(inflowTarget.getThresholdTimeUnit())));
}
return new CustomBucket(builder.build(), inflowTarget);
}
return null;
}
private CustomGroupBucket makeGroupBucket(InflowGroupVO groupVo) {
if (!isInflowGroupTarget(groupVo)) {
return null;
}
LocalBucket perSecondBucket = null;
LocalBucket thresholdBucket = null;
if (groupVo.getThresholdPerSecond() > 0) {
perSecondBucket = Bucket4j.builder()
.addLimit(Bandwidth.simple(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())))
.build();
}
return new CustomGroupBucket(perSecondBucket, thresholdBucket, groupVo);
}
@Override
public boolean isAdapterPass(String adapter) {
CustomBucket b = adapterBucketList.get(adapter);
if (b == null)
return true;
return b.getLocalBucket().tryConsume(1);
}
@Override
public boolean isInterfacePass(String inter) {
CustomBucket b = interfaceBucketList.get(inter);
if (b == null)
return true;
return b.getLocalBucket().tryConsume(1);
}
@Override
public InflowTargetVO getAdapterInflowThreashold(String adapter) {
CustomBucket b = adapterBucketList.get(adapter);
if (b == null)
return null;
return b.getInflowTargetVo();
}
@Override
public InflowTargetVO getInterfaceInflowThreashold(String inter) {
CustomBucket b = interfaceBucketList.get(inter);
if (b == null)
return null;
return b.getInflowTargetVo();
}
@Override
public InflowTargetVO getInflowThreashold(String inter) {
return null;
}
public InflowTargetVO getAdapterInflow(String adapter) {
return getAdapterInflowThreashold(adapter);
}
public InflowTargetVO getInterfaceInflow(String inter) {
return getInterfaceInflowThreashold(inter);
}
@Override
public String isGroupPass(String groupId) {
CustomGroupBucket b = groupBucketList.get(groupId);
if (b == null)
return null;
return b.tryConsume();
}
@Override
public InflowGroupVO getGroupInflowThreshold(String groupId) {
return groupVoMap.get(groupId);
}
@Override
public String getGroupIdByInterface(String interfaceId) {
return interfaceToGroupMap.get(interfaceId);
}
public InflowGroupVO getGroupInflow(String groupId) {
return getGroupInflowThreshold(groupId);
}
public static boolean isInflowTarget(InflowTargetVO inflowTarget) {
if (inflowTarget == null || !inflowTarget.isActivate()) {
return false;
}
return inflowTarget.getThresholdPerSecond() > 0 || (inflowTarget.getThreshold() > 0 && StringUtils
.equalsAnyIgnoreCase(inflowTarget.getThresholdTimeUnit(), QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_SECOND,
QUOTA_TIMEUNIT_MINUTE, QUOTA_TIMEUNIT_HOUR, QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_MONTH));
}
public static boolean isInflowGroupTarget(InflowGroupVO groupVo) {
if (groupVo == null || !groupVo.isActivate()) {
return false;
}
return groupVo.getThresholdPerSecond() > 0 || (groupVo.getThreshold() > 0 && StringUtils
.equalsAnyIgnoreCase(groupVo.getThresholdTimeUnit(), QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_SECOND,
QUOTA_TIMEUNIT_MINUTE, QUOTA_TIMEUNIT_HOUR, QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_MONTH));
}
public static Duration getPeriod(String timeUnit) {
if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_SECOND)) {
return Duration.ofSeconds(1);
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_MINUTE)) {
return Duration.ofMinutes(1);
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_HOUR)) {
return Duration.ofHours(1);
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_DAY)) {
return Duration.ofDays(1);
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_MONTH)) {
Calendar calendar = Calendar.getInstance();
return Duration.ofDays(calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
} else {
return null;
}
}
}
@@ -1,55 +1,11 @@
package com.eactive.eai.common.inflow;
import java.time.Duration;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.lifecycle.Lifecycle;
import com.eactive.eai.common.lifecycle.LifecycleException;
import com.eactive.eai.common.lifecycle.LifecycleListener;
import com.eactive.eai.common.lifecycle.LifecycleSupport;
import com.eactive.eai.common.message.EAIMessage;
import com.eactive.eai.common.server.EAIServerManager;
import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.common.util.MessageUtil;
import com.eactive.eai.message.service.InterfaceMapper;
import com.ext.eai.common.stdmessage.STDMessageKeys;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket4j;
import io.github.bucket4j.local.LocalBucket;
import io.github.bucket4j.local.LocalBucketBuilder;
@Component
public class InflowControlManager implements Lifecycle, Bucket {
static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
public static final String QUOTA_TIMEUNIT_SECOND = "SEC";
public static final String QUOTA_TIMEUNIT_MINUTE = "MIN";
public static final String QUOTA_TIMEUNIT_HOUR = "HOU";
public static final String QUOTA_TIMEUNIT_DAY = "DAY";
public static final String QUOTA_TIMEUNIT_MONTH = "MON";
private volatile Map<String, CustomBucket> adapterBucketList = new ConcurrentHashMap<>();
private volatile Map<String, CustomBucket> interfaceBucketList = new ConcurrentHashMap<>();
private volatile Map<String, CustomGroupBucket> groupBucketList = new ConcurrentHashMap<>();
private volatile Map<String, String> interfaceToGroupMap = new ConcurrentHashMap<>();
private volatile Map<String, InflowGroupVO> groupVoMap = new ConcurrentHashMap<>();
private boolean started;
private LifecycleSupport lifecycle = new LifecycleSupport(this);
@Autowired
private InflowControlDAO inflowControlDAO;
public class InflowControlManager extends AbstractInflowControlManager {
public static synchronized InflowControlManager getInstance() {
return ApplicationContextProvider.getContext().getBean(InflowControlManager.class);
@@ -58,485 +14,4 @@ public class InflowControlManager implements Lifecycle, Bucket {
public static synchronized Bucket getBucket() {
return ApplicationContextProvider.getContext().getBean(InflowControlManager.class);
}
/**
* 유량제어 대상인 거래인지 체크한다.(사이트에 맞게 구성)
*
* @param eaiServerManager
* @param eaiMessage
* @return
*/
public static Boolean isTargetOfInflowControl(EAIServerManager eaiServerManager, EAIMessage eaiMessage) {
InterfaceMapper mapper = eaiMessage.getMapper();
String returnType = mapper.getSendRecvDivision(eaiMessage.getStandardMessage()); // S|R
if (STDMessageKeys.SEND_RECV_CD_SEND.equals(returnType)) {
return Boolean.valueOf(true);
}
return Boolean.valueOf(false);
}
public void start() throws LifecycleException {
if (started)
throw new LifecycleException("RECEAICMM201");
lifecycle.fireLifecycleEvent(STARTING_EVENT, this);
try {
init();
} catch (Exception e) {
throw new LifecycleException(ExceptionUtil.getErrorCode(e, "RECEAICMM202"));
}
started = true;
lifecycle.fireLifecycleEvent(STARTED_EVENT, this);
}
private void init() throws Exception {
initAdapter();
initInterface();
initGroup();
}
private void initAdapter() throws Exception {
Map<String, CustomBucket> newMap = new HashMap<>();
List<InflowTargetVO> inflowAdapterVoList = inflowControlDAO.getInflowAdapterList();
for (InflowTargetVO inflowVo : inflowAdapterVoList) {
if (InflowControlManager.isInflowTarget(inflowVo)) {
CustomBucket bucket = makeBucketUsingInflowVO(inflowVo);
if (bucket != null) {
newMap.put(inflowVo.getName(), bucket);
}
}
}
this.adapterBucketList = newMap;
}
private void initInterface() throws Exception {
Map<String, CustomBucket> newMap = new HashMap<>();
List<InflowTargetVO> inflowInterfaceVoList = inflowControlDAO.getInflowInterfaceList();
for (InflowTargetVO inflowVo : inflowInterfaceVoList) {
if (InflowControlManager.isInflowTarget(inflowVo)) {
CustomBucket bucket = makeBucketUsingInflowVO(inflowVo);
if (bucket != null) {
newMap.put(inflowVo.getName(), bucket);
}
}
}
this.interfaceBucketList = newMap;
}
private void initGroup() throws Exception {
Map<String, CustomGroupBucket> newGroupBucketList = new HashMap<>();
Map<String, String> newInterfaceToGroupMap = new HashMap<>();
Map<String, InflowGroupVO> newGroupVoMap = new HashMap<>();
List<InflowGroupVO> inflowGroupVoList = inflowControlDAO.getInflowGroupList();
for (InflowGroupVO groupVo : inflowGroupVoList) {
if (isInflowGroupTarget(groupVo)) {
CustomGroupBucket bucket = makeGroupBucket(groupVo);
if (bucket != null) {
newGroupBucketList.put(groupVo.getGroupId(), bucket);
newGroupVoMap.put(groupVo.getGroupId(), groupVo);
// 인터페이스 → 그룹 매핑 등록
for (String interfaceId : groupVo.getInterfaceList()) {
newInterfaceToGroupMap.put(interfaceId, groupVo.getGroupId());
}
}
}
}
if (logger.isInfo()) {
logger.info("InflowControlManager] initGroup completed. groups=" + newGroupBucketList.size()
+ ", mappings=" + newInterfaceToGroupMap.size());
}
this.groupBucketList = newGroupBucketList;
this.interfaceToGroupMap = newInterfaceToGroupMap;
this.groupVoMap = newGroupVoMap;
}
public synchronized void reloadAdapter() throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload all Started ...");
}
initAdapter();
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload all finished ...");
}
}
public synchronized void reloadInterface() throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload all Started ...");
}
initInterface();
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload all finished ...");
}
}
public synchronized void reloadAdapter(String adapter) throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload Started ...");
}
Map<String, InflowTargetVO> map = inflowControlDAO.getInflowTargetByAdater(adapter);
if (map != null) {
Iterator<String> it = map.keySet().iterator();
String key = "";
while (it.hasNext()) {
key = it.next();
InflowTargetVO inflowTarget = map.get(key);
if (InflowControlManager.isInflowTarget(inflowTarget)) {
CustomBucket bucket = makeBucketUsingInflowVO(inflowTarget);
if (bucket != null) {
adapterBucketList.put(inflowTarget.getName(), bucket);
} else {
adapterBucketList.remove(inflowTarget.getName());
}
} else {
adapterBucketList.remove(inflowTarget.getName());
}
}
}
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload finished ...");
}
}
public synchronized void reloadInterface(String inter) throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload Started ...");
}
Map<String, InflowTargetVO> map = inflowControlDAO.getInflowTargetByInterface(inter);
if (map != null) {
Iterator<String> it = map.keySet().iterator();
String key = "";
while (it.hasNext()) {
key = it.next();
InflowTargetVO inflowTarget = map.get(key);
if (InflowControlManager.isInflowTarget(inflowTarget)) {
CustomBucket bucket = makeBucketUsingInflowVO(inflowTarget);
if (bucket != null) {
interfaceBucketList.put(inflowTarget.getName(), bucket);
} else {
interfaceBucketList.remove(inflowTarget.getName());
}
} else {
interfaceBucketList.remove(inflowTarget.getName());
}
}
}
if (logger.isWarn()) {
logger.warn("InflowControlManager] reload finished ...");
}
}
public synchronized void reloadGroup() throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reloadGroup all Started ...");
}
initGroup();
if (logger.isWarn()) {
logger.warn("InflowControlManager] reloadGroup all finished ...");
}
}
public synchronized void reloadGroup(String groupId) throws Exception {
if (logger.isWarn()) {
logger.warn("InflowControlManager] reloadGroup Started ... groupId=" + groupId);
}
Map<String, InflowGroupVO> map = inflowControlDAO.getInflowTargetByGroup(groupId);
if (map != null) {
// 기존 그룹에 속한 인터페이스 매핑 제거
Iterator<Map.Entry<String, String>> mappingIt = interfaceToGroupMap.entrySet().iterator();
while (mappingIt.hasNext()) {
Map.Entry<String, String> entry = mappingIt.next();
if (groupId.equals(entry.getValue())) {
mappingIt.remove();
}
}
Iterator<String> it = map.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
InflowGroupVO groupVo = map.get(key);
if (isInflowGroupTarget(groupVo)) {
CustomGroupBucket bucket = makeGroupBucket(groupVo);
if (bucket != null) {
groupBucketList.put(groupVo.getGroupId(), bucket);
groupVoMap.put(groupVo.getGroupId(), groupVo);
// 인터페이스 → 그룹 매핑 재등록
for (String interfaceId : groupVo.getInterfaceList()) {
interfaceToGroupMap.put(interfaceId, groupVo.getGroupId());
}
} else {
groupBucketList.remove(groupVo.getGroupId());
groupVoMap.remove(groupVo.getGroupId());
}
} else {
groupBucketList.remove(groupVo.getGroupId());
groupVoMap.remove(groupVo.getGroupId());
}
}
}
if (logger.isWarn()) {
logger.warn("InflowControlManager] reloadGroup finished ...");
}
}
public void stop() throws LifecycleException {
// Validate and update our current component state
if (!started)
throw new LifecycleException("RECEAICMM203");
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(STOPING_EVENT, this);
adapterBucketList = new ConcurrentHashMap<>();
interfaceBucketList = new ConcurrentHashMap<>();
groupBucketList = new ConcurrentHashMap<>();
interfaceToGroupMap = new ConcurrentHashMap<>();
groupVoMap = new ConcurrentHashMap<>();
started = false;
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(STOPPED_EVENT, this);
}
public void addLifecycleListener(LifecycleListener listener) {
lifecycle.addLifecycleListener(listener);
}
public LifecycleListener[] findLifecycleListeners() {
return lifecycle.findLifecycleListeners();
}
public void removeLifecycleListener(LifecycleListener listener) {
lifecycle.removeLifecycleListener(listener);
}
public boolean isStarted() {
return this.started;
}
public String[] getAdapterAllKeys() {
Iterator<String> it = this.adapterBucketList.keySet().iterator();
String[] svcCd = new String[this.adapterBucketList.size()];
for (int i = 0; it.hasNext(); i++) {
svcCd[i] = it.next();
}
Arrays.sort(svcCd);
return svcCd;
}
public String[] getInterfaceAllKeys() {
Iterator<String> it = this.interfaceBucketList.keySet().iterator();
String[] svcCd = new String[this.interfaceBucketList.size()];
for (int i = 0; it.hasNext(); i++) {
svcCd[i] = it.next();
}
Arrays.sort(svcCd);
return svcCd;
}
public synchronized void removeAdapter(String adapter) {
adapterBucketList.remove(adapter);
}
public synchronized void removeInterface(String inter) {
interfaceBucketList.remove(inter);
}
public synchronized void removeGroup(String groupId) {
groupBucketList.remove(groupId);
groupVoMap.remove(groupId);
// 해당 그룹에 속한 인터페이스 매핑도 제거
Iterator<Map.Entry<String, String>> it = interfaceToGroupMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
if (groupId.equals(entry.getValue())) {
it.remove();
}
}
}
public String[] getGroupAllKeys() {
Iterator<String> it = this.groupBucketList.keySet().iterator();
String[] groupIds = new String[this.groupBucketList.size()];
for (int i = 0; it.hasNext(); i++) {
groupIds[i] = it.next();
}
Arrays.sort(groupIds);
return groupIds;
}
public CustomGroupBucket getGroupBucket(String groupId) {
return groupBucketList.get(groupId);
}
private CustomBucket makeBucketUsingInflowVO(InflowTargetVO inflowTarget) {
if (InflowControlManager.isInflowTarget(inflowTarget)) {
LocalBucketBuilder builder = Bucket4j.builder();
if (inflowTarget.getThresholdPerSecond() > 0) {
builder.addLimit(Bandwidth.simple(inflowTarget.getThresholdPerSecond(), Duration.ofSeconds(1)));
}
if (inflowTarget.getThreshold() > 0 && StringUtils.isNotBlank(inflowTarget.getThresholdTimeUnit())) {
builder.addLimit(Bandwidth.simple(inflowTarget.getThreshold(),
InflowControlManager.getPeriod(inflowTarget.getThresholdTimeUnit())));
}
return new CustomBucket(builder.build(), inflowTarget);
}
return null;
}
/**
* 그룹용 버킷 생성 - 초당/추가 임계치를 별도 버킷으로 분리
*/
private CustomGroupBucket makeGroupBucket(InflowGroupVO groupVo) {
if (!isInflowGroupTarget(groupVo)) {
return null;
}
LocalBucket perSecondBucket = null;
LocalBucket thresholdBucket = null;
// 초당 임계치 버킷 (별도)
if (groupVo.getThresholdPerSecond() > 0) {
perSecondBucket = Bucket4j.builder()
.addLimit(Bandwidth.simple(groupVo.getThresholdPerSecond(), Duration.ofSeconds(1)))
.build();
}
// 추가 임계치 버킷 (별도)
if (groupVo.getThreshold() > 0 && StringUtils.isNotBlank(groupVo.getThresholdTimeUnit())) {
thresholdBucket = Bucket4j.builder()
.addLimit(Bandwidth.simple(groupVo.getThreshold(),
InflowControlManager.getPeriod(groupVo.getThresholdTimeUnit())))
.build();
}
return new CustomGroupBucket(perSecondBucket, thresholdBucket, groupVo);
}
@Override
public boolean isAdapterPass(String adapter) {
CustomBucket b = adapterBucketList.get(adapter);
if (b == null)
return true;
return b.getLocalBucket().tryConsume(1);
}
@Override
public boolean isInterfacePass(String inter) {
CustomBucket b = interfaceBucketList.get(inter);
if (b == null)
return true;
return b.getLocalBucket().tryConsume(1);
}
@Override
public InflowTargetVO getAdapterInflowThreashold(String adapter) {
CustomBucket b = adapterBucketList.get(adapter);
if (b == null)
return null;
return b.getInflowTargetVo();
}
@Override
public InflowTargetVO getInterfaceInflowThreashold(String inter) {
CustomBucket b = interfaceBucketList.get(inter);
if (b == null)
return null;
return b.getInflowTargetVo();
}
@Override
public InflowTargetVO getInflowThreashold(String inter) {
return null;
}
public InflowTargetVO getAdapterInflow(String adapter) {
return getAdapterInflowThreashold(adapter);
}
public InflowTargetVO getInterfaceInflow(String inter) {
return getInterfaceInflowThreashold(inter);
}
@Override
public String isGroupPass(String groupId) {
CustomGroupBucket b = groupBucketList.get(groupId);
if (b == null)
return null;
return b.tryConsume();
}
@Override
public InflowGroupVO getGroupInflowThreshold(String groupId) {
return groupVoMap.get(groupId);
}
@Override
public String getGroupIdByInterface(String interfaceId) {
return interfaceToGroupMap.get(interfaceId);
}
public InflowGroupVO getGroupInflow(String groupId) {
return getGroupInflowThreshold(groupId);
}
public static boolean isInflowTarget(InflowTargetVO inflowTarget) {
if (inflowTarget == null || !inflowTarget.isActivate()) {
return false;
}
return inflowTarget.getThresholdPerSecond() > 0 || (inflowTarget.getThreshold() > 0 && StringUtils
.equalsAnyIgnoreCase(inflowTarget.getThresholdTimeUnit(), QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_SECOND,
QUOTA_TIMEUNIT_MINUTE, QUOTA_TIMEUNIT_HOUR, QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_MONTH));
}
public static boolean isInflowGroupTarget(InflowGroupVO groupVo) {
if (groupVo == null || !groupVo.isActivate()) {
return false;
}
return groupVo.getThresholdPerSecond() > 0 || (groupVo.getThreshold() > 0 && StringUtils
.equalsAnyIgnoreCase(groupVo.getThresholdTimeUnit(), QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_SECOND,
QUOTA_TIMEUNIT_MINUTE, QUOTA_TIMEUNIT_HOUR, QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_MONTH));
}
public static Duration getPeriod(String timeUnit) {
if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_SECOND)) {
return Duration.ofSeconds(1);
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_MINUTE)) {
return Duration.ofMinutes(1);
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_HOUR)) {
return Duration.ofHours(1);
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_DAY)) {
return Duration.ofDays(1);
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_MONTH)) {
Calendar calendar = Calendar.getInstance();
return Duration.ofDays(calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
} else {
return null;
}
}
}
@@ -8,7 +8,10 @@ import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.common.message.EAIMessage;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.server.EAIServerManager;
import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.message.service.InterfaceMapper;
import com.ext.eai.common.stdmessage.STDMessageKeys;
public class InflowControlUtil {
private InflowControlUtil() {
@@ -16,28 +19,70 @@ public class InflowControlUtil {
static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
private static volatile AbstractInflowControlManager cachedInflowControlManager;
private static volatile Bucket cachedBucket;
/**
* INFLOW.properties의 inflow.control.bucket.className 설정에 따라
* 활성화된 InflowControlManager 구현체를 반환한다.
*
* getBean(InflowControlManager.class) 대신 설정된 구체 타입으로 조회하므로
* InflowControlManager와 DualInflowControlManager가 모두 Bean으로 등록된
* 환경에서도 NoUniqueBeanDefinitionException 없이 올바른 인스턴스를 반환한다.
*
* 최초 1회만 조회하고 이후에는 캐시된 인스턴스를 반환한다.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static Bucket getBucket() {
Properties inFlowProp = PropManager.getInstance().getProperties("INFLOW");
String className = inFlowProp.getProperty("inflow.control.bucket.className");
if (StringUtils.isBlank(className)
|| StringUtils.equals(className, "com.eactive.eai.common.inflow.InflowControlManager")) {
return InflowControlManager.getBucket();
} else {
try {
Class clazz = Class.forName(className);
Method method = clazz.getMethod("getBucket", (Class<?>[]) null);
return (Bucket) method.invoke(null, (Object[]) null);
} catch (Exception e) {
logger.error("Method call error class= {}, method= getBucket", className);
logger.error(e.getMessage());
public static AbstractInflowControlManager getInflowControlManager() {
if (cachedInflowControlManager == null) {
synchronized (InflowControlUtil.class) {
if (cachedInflowControlManager == null) {
Properties inFlowProp = PropManager.getInstance().getProperties("INFLOW");
String className = inFlowProp.getProperty("inflow.control.bucket.className");
if (StringUtils.isBlank(className)
|| StringUtils.equals(className, "com.eactive.eai.common.inflow.InflowControlManager")) {
cachedInflowControlManager = InflowControlManager.getInstance();
} else {
try {
Class clazz = Class.forName(className);
cachedInflowControlManager = (AbstractInflowControlManager) ApplicationContextProvider.getContext().getBean(clazz);
} catch (Exception e) {
logger.error("getInflowControlManager error class= {}", className);
logger.error(e.getMessage());
}
}
}
}
}
return null;
return cachedInflowControlManager;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public static Bucket getBucket() {
if (cachedBucket == null) {
synchronized (InflowControlUtil.class) {
if (cachedBucket == null) {
Properties inFlowProp = PropManager.getInstance().getProperties("INFLOW");
String className = inFlowProp.getProperty("inflow.control.bucket.className");
if (StringUtils.isBlank(className)
|| StringUtils.equals(className, "com.eactive.eai.common.inflow.InflowControlManager")) {
cachedBucket = InflowControlManager.getBucket();
} else {
try {
Class clazz = Class.forName(className);
Method method = clazz.getMethod("getBucket", (Class<?>[]) null);
cachedBucket = (Bucket) method.invoke(null, (Object[]) null);
} catch (Exception e) {
logger.error("Method call error class= {}, method= getBucket", className);
logger.error(e.getMessage());
}
}
}
}
}
return cachedBucket;
}
public static boolean isTargetOfInflowControl(EAIServerManager eaiServerManager, EAIMessage eaiMessage) {
Properties inFlowProp = PropManager.getInstance().getProperties("INFLOW");
String controlInflow = inFlowProp.getProperty("inflow.control.yn", "N");
@@ -45,21 +90,18 @@ public class InflowControlUtil {
if (!"Y".equalsIgnoreCase(controlInflow)) {
return false;
}
try {
InterfaceMapper mapper = eaiMessage.getMapper();
String returnType = mapper.getSendRecvDivision(eaiMessage.getStandardMessage()); // S|R
String className = inFlowProp.getProperty("inflow.control.bucket.className");
if (StringUtils.isBlank(className)
|| StringUtils.equals(className, "com.eactive.eai.common.inflow.InflowControlManager")) {
return InflowControlManager.isTargetOfInflowControl(eaiServerManager, eaiMessage).booleanValue();
} else {
try {
Class clazz = Class.forName(className);
Method method = clazz.getMethod("isTargetOfInflowControl", EAIServerManager.class, EAIMessage.class);
Boolean returnBoolean = (Boolean) method.invoke(null, eaiServerManager, eaiMessage);
return returnBoolean.booleanValue();
} catch (Exception e) {
logger.error("Method call error class= {}, method= isTargetOfInflowControl", className);
logger.error(e.getMessage());
if (STDMessageKeys.SEND_RECV_CD_SEND.equals(returnType)) {
return Boolean.valueOf(true);
}
return Boolean.valueOf(false);
} catch (Exception e) {
logger.error("isTargetOfInflowControl call error", e.getMessage());
}
return false;