diff --git a/hbase-rest/pom.xml b/hbase-rest/pom.xml index 0368e713bdc2..bb357d96abd8 100644 --- a/hbase-rest/pom.xml +++ b/hbase-rest/pom.xml @@ -139,6 +139,10 @@ <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> + <dependency> + <groupId>com.github.ben-manes.caffeine</groupId> + <artifactId>caffeine</artifactId> + </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/Constants.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/Constants.java index f0d1edc986af..e924be3d7fe8 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/Constants.java +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/Constants.java @@ -69,6 +69,12 @@ public interface Constants { String REST_DNS_NAMESERVER = "hbase.rest.dns.nameserver"; String REST_DNS_INTERFACE = "hbase.rest.dns.interface"; + String REST_SCANNERCACHE_SIZE = "hbase.rest.scannercache.size"; + final int DEFAULT_REST_SCANNERCACHE_SIZE = 10000; + + String REST_SCANNERCACHE_EXPIRE_TIME = "hbase.rest.scannercache.expire.time"; + final long DEFAULT_REST_SCANNERCACHE_EXPIRE_TIME_MS = 60 * 60 * 1000; + String FILTER_CLASSES = "hbase.rest.filter.classes"; String SCAN_START_ROW = "startrow"; String SCAN_END_ROW = "endrow"; diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/ScannerResource.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/ScannerResource.java index 49801b4f7d85..3c652ec6808b 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/ScannerResource.java +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/ScannerResource.java @@ -19,11 +19,14 @@ import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.RemovalCause; import java.io.IOException; import java.net.URI; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.TableNotFoundException; import org.apache.hadoop.hbase.filter.Filter; import org.apache.hadoop.hbase.rest.model.ScannerModel; @@ -46,9 +49,7 @@ public class ScannerResource extends ResourceBase { private static final Logger LOG = LoggerFactory.getLogger(ScannerResource.class); - static final Map<String, ScannerInstanceResource> scanners = - Collections.synchronizedMap(new HashMap<String, ScannerInstanceResource>()); - + private static final Cache<String, ScannerInstanceResource> scanners = setupScanners(); TableResource tableResource; /** @@ -59,8 +60,23 @@ public ScannerResource(TableResource tableResource) throws IOException { this.tableResource = tableResource; } + private static Cache<String, ScannerInstanceResource> setupScanners() { + final Configuration conf = HBaseConfiguration.create(); + + int size = conf.getInt(REST_SCANNERCACHE_SIZE, DEFAULT_REST_SCANNERCACHE_SIZE); + long evictTimeoutMs = conf.getTimeDuration(REST_SCANNERCACHE_EXPIRE_TIME, + DEFAULT_REST_SCANNERCACHE_EXPIRE_TIME_MS, TimeUnit.MILLISECONDS); + + Cache<String, ScannerInstanceResource> cache = + Caffeine.newBuilder().removalListener(ScannerResource::removalListener).maximumSize(size) + .expireAfterAccess(evictTimeoutMs, TimeUnit.MILLISECONDS) + .<String, ScannerInstanceResource> build(); + + return cache; + } + static boolean delete(final String id) { - ScannerInstanceResource instance = scanners.remove(id); + ScannerInstanceResource instance = scanners.asMap().remove(id); if (instance != null) { instance.generator.close(); return true; @@ -69,6 +85,12 @@ static boolean delete(final String id) { } } + static void removalListener(String key, ScannerInstanceResource value, RemovalCause cause) { + if (cause.wasEvicted()) { + delete(key); + } + } + Response update(final ScannerModel model, final boolean replace, final UriInfo uriInfo) { servlet.getMetrics().incrementRequests(1); if (servlet.isReadOnly()) { @@ -140,7 +162,7 @@ public Response post(final ScannerModel model, final @Context UriInfo uriInfo) { @Path("{scanner: .+}") public ScannerInstanceResource getScannerInstanceResource(final @PathParam("scanner") String id) throws IOException { - ScannerInstanceResource instance = scanners.get(id); + ScannerInstanceResource instance = scanners.getIfPresent(id); if (instance == null) { servlet.getMetrics().incrementFailedGetRequests(1); return new ScannerInstanceResource();