Как добавить имя хоста в черный список после последовательных сбоев в многопоточном приложении?
Я использую Callable в своем коде, который будет вызываться несколькими потоками, как показано ниже. На данный момент, когда-либо RestClientException
брошен, то я добавляю hostname
заблокировать.
public class Task implements Callable<DataResponse> {
private DataKey key;
private RestTemplate restTemplate;
public Task(DataKey key, RestTemplate restTemplate) {
this.key = key;
this.restTemplate = restTemplate;
}
@Override
public DataResponse call() {
ResponseEntity<String> response = null;
// construct what are the hostnames I can call basis on user id
List<String> hostnames = some_code_here;
for (String hostname : hostnames) {
// If host name is null or host name is in block list, skip sending request to this host
if (DataUtils.isEmpty(hostname) || DataMapping.isBlocked(hostname)) {
continue;
}
try {
String url = createURL(hostname);
response = restTemplate.exchange(url, HttpMethod.GET, key.getEntity(), String.class);
// some code here to return the response if successful
} catch (HttpClientErrorException ex) {
// log exception
return new DataResponse(errorMessage, error, DataStatusEnum.ERROR);
} catch (HttpServerErrorException ex) {
// log exception
return new DataResponse(errorMessage, error, DataStatusEnum.ERROR);
} catch (RestClientException ex) {
// I don't want to add it to block list instantly.
// If same hostname as failed five times consecutively, then only add it
DataMapping.blockHost(hostname);
}
}
return new DataResponse(DataErrorEnum.SERVER_UNAVAILABLE, DataStatusEnum.ERROR);
}
}
Ниже то, что я имею в DataMapping
учебный класс:
private static final AtomicReference<ConcurrentHashMap<String, String>> blockedHosts =
new AtomicReference<ConcurrentHashMap<String, String>>(new ConcurrentHashMap<String, String>());
public static boolean isBlocked(String hostName) {
return blockedHosts.get().containsKey(hostName);
}
public static void blockHost(String hostName) {
blockedHosts.get().put(hostName, hostName);
}
Постановка задачи:-
Теперь, как вы можете видеть в call
метод, я блокирую hostname
как только он бросает RestClientException
что может быть не правильно. Мне нужно посмотреть, если конкретный hostname
бросил RestClientException
пять раз подряд, затем только добавить это hostname
в blockList, вызвав эту строку DataMapping.blockHost(hostname);
в противном случае не добавляйте его в блок-лист.
Какой самый эффективный и лучший способ сделать это? Максимум, у меня будет 70-100 уникальных машин.
В этом случае мой метод вызова будет вызываться из нескольких потоков, поэтому мне нужно убедиться, что я правильно храню счетчик для каждого hostname
в случае, если они бросают RestClientException
,
РЕДАКТИРОВАТЬ:
У меня также есть метод ниже DataMapping
класс также:
У меня есть фоновый поток, который запускается каждые 2 минуты и заменяет весь набор, поскольку мой сервис предоставляет реальные данные независимо от того, заблокировано ли какое-либо имя хоста или нет. И я думаю, мне нужно atomic reference
когда я заменю весь набор.
Я также добавляю функцию блокировки локально в код, так как я могу знать, какая машина заблокирована через 2 минуты, поэтому лучше узнать ее заранее, если это возможно.
// this is being updated from my background thread which runs every 2 minutes
public static void replaceBlockedHosts(List<String> hostNames) {
ConcurrentHashMap<String, String> newBlockedHosts = new ConcurrentHashMap<>();
for (String hostName : hostNames) {
newBlockedHosts.put(hostName, hostName);
}
blockedHosts.set(newBlockedHosts);
}
2 ответа
Я бы связал каждого хоста с AtomicInteger
который увеличивается на каждом RestClientException
, Это целое число будет установлено равным нулю при успешном вызове, чтобы применить ограничение "пять последовательных раз". Код будет выглядеть примерно так.
private final ConcurrentHashMap<String, AtomicInteger> failedCallCount = new ConcurrentHashMap<>();
void call() {
try {
String url = createURL(host);
// make rest call
resetFailedCallCount(host);
// ...
} catch (RestClientException ex) {
registerFailedCall(host);
if (shouldBeBlocked(host)) {
DataMapping.blockHost(host);
}
}
}
private boolean shouldBeBlocked(String hostName) {
AtomicInteger count = failedCallCount.getOrDefault(hostName, new AtomicInteger());
return count.get() >= 5;
}
private void registerFailedCall(String hostName) {
AtomicInteger newValue = new AtomicInteger();
AtomicInteger val = failedCallCount.putIfAbsent(hostName, newValue);
if (val == null) {
val = newValue;
}
if (val.get() < 5) {
val.incrementAndGet();
}
}
private void resetFailedCallCount(String hostName) {
AtomicInteger count = failedCallCount.get(hostName);
if (count != null) {
count.set(0);
}
}
Это без блокировки (по крайней мере, в нашем собственном коде) и очень эффективно. Однако это уязвимо для некоторых условий гонки. Наиболее примечательно, что счетчик может стать больше 5. Однако это не должно быть проблемой, так как хост все равно заблокирован, и счет не используется ни для чего другого.
Поддерживать статический регистр, как - public static ConcurrentHashMap<String, Integer> toBeBlockedHostName = new ConcurrentHashMap<String, Integer>();
в вашем DataMapping
учебный класс. А затем используйте это как ваш цикл FOR:
for (String hostname : hostnames) {
// .. some code here
//After ensuring everything is success and no RestClientException, i.e. can be last line of your TRY block...
DataMapping.toBeBlockedHostName.remove("stackru6361");
catch (RestClientException ex) {
if(DataMapping.toBeBlockedHostName.get("stackru6361") == null){
DataMapping.toBeBlockedHostName.put("stackru6361", new Integer(1));
} else{
if(DataMapping.toBeBlockedHostName.get("stackru6361") == 5){ //Don't hard code 5, have it from some property file after defining as retryThreshold...
System.out.println("Blocking threshold reached, block the hostname...");
DataMapping.blockHost(hostname);
} else{
DataMapping.toBeBlockedHostName.put("stackru6361", (toBeBlockedHostName.get("stackru6361") + 1));
}
}
}
Пожалуйста, обратите внимание:: для ConcurrentHashMap
даже если все операции являются поточно-ориентированными, операции поиска не влекут за собой блокировку.
Обратите внимание, что после 5 неудачных попыток подряд вы заблокируете имя хоста, но если вы снова разблокируете его, вам следует очистить регистр.
PS: Иметь соответствующий геттер и сеттер для HashMap.