fix: Reload 동시성 수정 - volatile ConcurrentHashMap + 원자적 맵 교체
1. 맵 5개를 volatile + ConcurrentHashMap으로 변경 - 비동기 읽기(isGroupPass 등) 중 HashMap 구조 변경으로 발생하던 race condition 제거 2. initAdapter/Interface/Group()에서 빌드-완성 후 원자적 교체 패턴 적용 - 신규 맵을 완전히 구성한 뒤 this.field = newMap으로 한 번에 교체 - 구→신 전환 중 불완전 상태가 외부에 노출되던 문제 제거 3. removeAdapter/Interface/Group()에 synchronized 추가 - 락 없이 맵을 수정하던 메서드와 reload 사이의 race condition 방지 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ 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;
|
||||
@@ -39,11 +40,11 @@ public class InflowControlManager implements Lifecycle, Bucket {
|
||||
public static final String QUOTA_TIMEUNIT_DAY = "DAY";
|
||||
public static final String QUOTA_TIMEUNIT_MONTH = "MON";
|
||||
|
||||
private Map<String, CustomBucket> adapterBucketList = new HashMap<>();
|
||||
private Map<String, CustomBucket> interfaceBucketList = new HashMap<>();
|
||||
private Map<String, CustomGroupBucket> groupBucketList = new HashMap<>();
|
||||
private Map<String, String> interfaceToGroupMap = new HashMap<>();
|
||||
private Map<String, InflowGroupVO> groupVoMap = new HashMap<>();
|
||||
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);
|
||||
|
||||
@@ -97,37 +98,39 @@ public class InflowControlManager implements Lifecycle, Bucket {
|
||||
}
|
||||
|
||||
private void initAdapter() throws Exception {
|
||||
adapterBucketList = new HashMap<>();
|
||||
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) {
|
||||
adapterBucketList.put(inflowVo.getName(), bucket);
|
||||
newMap.put(inflowVo.getName(), bucket);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.adapterBucketList = newMap;
|
||||
}
|
||||
|
||||
private void initInterface() throws Exception {
|
||||
interfaceBucketList = new HashMap<>();
|
||||
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) {
|
||||
interfaceBucketList.put(inflowVo.getName(), bucket);
|
||||
newMap.put(inflowVo.getName(), bucket);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.interfaceBucketList = newMap;
|
||||
}
|
||||
|
||||
private void initGroup() throws Exception {
|
||||
groupBucketList = new HashMap<>();
|
||||
interfaceToGroupMap = new HashMap<>();
|
||||
groupVoMap = new HashMap<>();
|
||||
Map<String, CustomGroupBucket> newGroupBucketList = new HashMap<>();
|
||||
Map<String, String> newInterfaceToGroupMap = new HashMap<>();
|
||||
Map<String, InflowGroupVO> newGroupVoMap = new HashMap<>();
|
||||
|
||||
List<InflowGroupVO> inflowGroupVoList = inflowControlDAO.getInflowGroupList();
|
||||
|
||||
@@ -135,20 +138,24 @@ public class InflowControlManager implements Lifecycle, Bucket {
|
||||
if (isInflowGroupTarget(groupVo)) {
|
||||
CustomGroupBucket bucket = makeGroupBucket(groupVo);
|
||||
if (bucket != null) {
|
||||
groupBucketList.put(groupVo.getGroupId(), bucket);
|
||||
groupVoMap.put(groupVo.getGroupId(), groupVo);
|
||||
newGroupBucketList.put(groupVo.getGroupId(), bucket);
|
||||
newGroupVoMap.put(groupVo.getGroupId(), groupVo);
|
||||
// 인터페이스 → 그룹 매핑 등록
|
||||
for (String interfaceId : groupVo.getInterfaceList()) {
|
||||
interfaceToGroupMap.put(interfaceId, groupVo.getGroupId());
|
||||
newInterfaceToGroupMap.put(interfaceId, groupVo.getGroupId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.isInfo()) {
|
||||
logger.info("InflowControlManager] initGroup completed. groups=" + groupBucketList.size()
|
||||
+ ", mappings=" + interfaceToGroupMap.size());
|
||||
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 {
|
||||
@@ -293,11 +300,11 @@ public class InflowControlManager implements Lifecycle, Bucket {
|
||||
// Notify our interested LifecycleListeners
|
||||
lifecycle.fireLifecycleEvent(STOPING_EVENT, this);
|
||||
|
||||
adapterBucketList.clear();
|
||||
interfaceBucketList.clear();
|
||||
groupBucketList.clear();
|
||||
interfaceToGroupMap.clear();
|
||||
groupVoMap.clear();
|
||||
adapterBucketList = new ConcurrentHashMap<>();
|
||||
interfaceBucketList = new ConcurrentHashMap<>();
|
||||
groupBucketList = new ConcurrentHashMap<>();
|
||||
interfaceToGroupMap = new ConcurrentHashMap<>();
|
||||
groupVoMap = new ConcurrentHashMap<>();
|
||||
|
||||
started = false;
|
||||
// Notify our interested LifecycleListeners
|
||||
@@ -340,15 +347,15 @@ public class InflowControlManager implements Lifecycle, Bucket {
|
||||
return svcCd;
|
||||
}
|
||||
|
||||
public void removeAdapter(String adapter) {
|
||||
public synchronized void removeAdapter(String adapter) {
|
||||
adapterBucketList.remove(adapter);
|
||||
}
|
||||
|
||||
public void removeInterface(String inter) {
|
||||
public synchronized void removeInterface(String inter) {
|
||||
interfaceBucketList.remove(inter);
|
||||
}
|
||||
|
||||
public void removeGroup(String groupId) {
|
||||
public synchronized void removeGroup(String groupId) {
|
||||
groupBucketList.remove(groupId);
|
||||
groupVoMap.remove(groupId);
|
||||
// 해당 그룹에 속한 인터페이스 매핑도 제거
|
||||
|
||||
Reference in New Issue
Block a user