From 29ad12512ba5e109e78a3c8384ae10747e1eabec Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Thu, 30 Jun 2022 09:12:01 -0700 Subject: Device lookup throttling --- .../org/traccar/database/DeviceLookupService.java | 83 ++++++++++++++++++++-- 1 file changed, 77 insertions(+), 6 deletions(-) (limited to 'src/main/java/org/traccar/database/DeviceLookupService.java') diff --git a/src/main/java/org/traccar/database/DeviceLookupService.java b/src/main/java/org/traccar/database/DeviceLookupService.java index f0a4e7bf0..9cf0899ee 100644 --- a/src/main/java/org/traccar/database/DeviceLookupService.java +++ b/src/main/java/org/traccar/database/DeviceLookupService.java @@ -15,40 +15,111 @@ */ package org.traccar.database; +import io.netty.util.Timeout; +import io.netty.util.Timer; +import io.netty.util.TimerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.model.Device; import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; import org.traccar.storage.query.Columns; import org.traccar.storage.query.Condition; import org.traccar.storage.query.Request; import javax.inject.Inject; import javax.inject.Singleton; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; @Singleton public class DeviceLookupService { private static final Logger LOGGER = LoggerFactory.getLogger(DeviceLookupService.class); + private static final long INFO_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(60); + private static final long THROTTLE_MIN_MS = TimeUnit.MINUTES.toMillis(1); + private static final long THROTTLE_MAX_MS = TimeUnit.MINUTES.toMillis(30); + private final Storage storage; + private final Timer timer; + + private static class IdentifierInfo { + private long lastQuery; + private long delay; + private Timeout timeout; + } + + private final class IdentifierTask implements TimerTask { + private final String uniqueId; + + private IdentifierTask(String uniqueId) { + this.uniqueId = uniqueId; + } + + @Override + public void run(Timeout timeout) { + LOGGER.debug("Device lookup expired {}", uniqueId); + synchronized (DeviceLookupService.this) { + identifierMap.remove(uniqueId); + } + } + } + + private final Map identifierMap = new ConcurrentHashMap<>(); @Inject - public DeviceLookupService(Storage storage) { + public DeviceLookupService(Storage storage, Timer timer) { this.storage = storage; + this.timer = timer; + } + + private synchronized boolean isThrottled(String uniqueId) { + IdentifierInfo info = identifierMap.get(uniqueId); + return info != null && System.currentTimeMillis() < info.lastQuery + info.delay; + } + + private synchronized void lookupSucceeded(String uniqueId) { + IdentifierInfo info = identifierMap.remove(uniqueId); + if (info != null) { + info.timeout.cancel(); + } + } + + private synchronized void lookupFailed(String uniqueId) { + IdentifierInfo info = identifierMap.get(uniqueId); + if (info != null) { + info.timeout.cancel(); + info.delay = Math.min(info.delay * 2, THROTTLE_MAX_MS); + } else { + info = new IdentifierInfo(); + identifierMap.put(uniqueId, info); + info.delay = THROTTLE_MIN_MS; + } + info.lastQuery = System.currentTimeMillis(); + info.timeout = timer.newTimeout(new IdentifierTask(uniqueId), INFO_TIMEOUT_MS, TimeUnit.MILLISECONDS); + LOGGER.debug("Device lookup {} throttled for {} ms", uniqueId, info.delay); } public Device lookup(String[] uniqueIds) { Device device = null; try { for (String uniqueId : uniqueIds) { - device = storage.getObject(Device.class, new Request( - new Columns.All(), new Condition.Equals("uniqueId", "uniqueId", uniqueId))); - if (device != null) { - break; + if (!isThrottled(uniqueId)) { + device = storage.getObject(Device.class, new Request( + new Columns.All(), new Condition.Equals("uniqueId", "uniqueId", uniqueId))); + if (device != null) { + lookupSucceeded(uniqueId); + break; + } else { + lookupFailed(uniqueId); + } + } else { + LOGGER.debug("Device lookup throttled {}", uniqueId); } } - } catch (Exception e) { + } catch (StorageException e) { LOGGER.warn("Find device error", e); } return device; -- cgit v1.2.3