BulkProcessor 是ES客户端提供的ES请求处理器, 表现的是一种异步(非阻塞)、批量、有通知回调的请求处理器模式示例。让我们学习如何构建一段优雅的代码
构建一个Builder类, 设置参数返回builder对象本身,可以持续的调用设置参数方法。并提供合适的默认值减少设置。
BulkProcessor.Builder的参数设置方法如下:
public BulkProcessor.Builder setConcurrentRequests(int concurrentRequests) { this.concurrentRequests = concurrentRequests; return this; } |
private Builder(BiConsumer this.concurrentRequests = 1; this.bulkActions = 1000; this.bulkSize = new ByteSizeValue(5L, ByteSizeUnit.MB); this.flushInterval = null; this.backoffPolicy = BackoffPolicy.exponentialBackoff(); this.consumer = consumer; this.listener = listener; this.scheduler = scheduler; this.onClose = onClose; } |
Builder的使用代码示例:
return BulkProcessor.builder(bulkConsumer, new BulkProcessor.Listener() { public void beforeBulk(long executionId, BulkRequest request) { } public void afterBulk(long executionId, BulkRequest request, BulkResponse response) { } public void afterBulk(long executionId, BulkRequest request, Throwable failure) { } }).setBulkActions(this.get_bulkActions()).setBulkSize(new ByteSizeValue(this.get_bulkSize(), ByteSizeUnit.MB)).setFlushInterval(TimeValue.timeValueSeconds(this.get_flushInterval())).setConcurrentRequests(this.get_concurrentRequests()).build(); |
这一点是优秀设计最重要的判断条件, 保证功能健壮性和可扩展性的设计方法。
要处理任务任务,通常来说会设计一个接受任务的方法, 同时在任务处理模块中建立列表等各种数据结构来保存。BuikProcessor类设计上将请求的执行和请求的管理区别开,核心实现请求的执行,而将请求的管理(产生)通过函数式编程的Supplier接口交由调用者负责提供。
private final Supplier |
private void execute() { BulkRequest bulkRequest = this.bulkRequest; long executionId = this.executionIdGen.incrementAndGet(); this.bulkRequest = (BulkRequest)this.bulkRequestSupplier.get(); this.bulkRequestHandler.execute(bulkRequest, executionId); } |
BiConsumer
BiConsumer RestClientHelper.getClient().bulkAsync(request, RequestOptions.DEFAULT, bulkListener); }; |
BulkProcessor.Listener: 提供执行回调的扩展。
通过向使用者要求产生请求的功能而不是直接接受请求,将请求控制由无法掌控的外界转为自己张控制(推送转为拉)
通过定时任务定时拉取处理。
private Cancellable startFlushTask(TimeValue flushInterval, Scheduler scheduler) { if (flushInterval == null) { return new Cancellable() { public boolean cancel() { return false; } public boolean isCancelled() { return true; } }; } else { Runnable flushRunnable = scheduler.preserveContext(new BulkProcessor.Flush()); return scheduler.scheduleWithFixedDelay(flushRunnable, flushInterval, "generic"); } } |
class Flush implements Runnable { Flush() { } public void run() { synchronized(BulkProcessor.this) { if (!BulkProcessor.this.closed) { if (BulkProcessor.this.bulkRequest.numberOfActions() != 0) { BulkProcessor.this.execute(); } } } } } |
在进行创新的拉取控制,提供传统直接添加请求的方法
public BulkProcessor add(IndexRequest request) { return this.add((DocWriteRequest)request); } public BulkProcessor add(DeleteRequest request) { return this.add((DocWriteRequest)request); } |
调度器Scheduler: 负责构建底层执行引擎(线程池);实现可取消、延迟处理、
线程池
static ScheduledThreadPoolExecutor initScheduler(Settings settings) { ScheduledThreadPoolExecutor scheduler = new Scheduler.SafeScheduledThreadPoolExecutor(1, EsExecutors.daemonThreadFactory(settings, "scheduler"), new EsAbortPolicy()); scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); scheduler.setContinueExistingPeriodicTasksAfterShutdownPolicy(false); scheduler.setRemoveOnCancelPolicy(true); return scheduler; } |
任务可延迟、可取消。 提供帮助类将普通任务封装为可取消、可延迟的任务。
class ScheduledCancellableAdapter implements ScheduledCancellable { private final ScheduledFuture scheduledFuture; ScheduledCancellableAdapter(ScheduledFuture scheduledFuture) { assert scheduledFuture != null; this.scheduledFuture = scheduledFuture; } public long getDelay(TimeUnit unit) { return this.scheduledFuture.getDelay(unit); } public int compareTo(Delayed other) { return -other.compareTo(this.scheduledFuture); } public boolean cancel() { return FutureUtils.cancel(this.scheduledFuture); } public boolean isCancelled() { return this.scheduledFuture.isCancelled(); } } |
static Scheduler.ScheduledCancellable wrapAsScheduledCancellable(ScheduledFuture scheduledFuture) { return new ScheduledCancellableAdapter(scheduledFuture); } |
BulkRequestHandler 负责处理具体任务:负责任务的执行、重试、调用实际的请求处理器、调用执行回调等。
执行入口(BulkProcessor调用):
private void execute() { BulkRequest bulkRequest = this.bulkRequest; long executionId = this.executionIdGen.incrementAndGet(); this.bulkRequest = (BulkRequest)this.bulkRequestSupplier.get(); this.bulkRequestHandler.execute(bulkRequest, executionId); } |
BulkRequestHandler是一个线程安全类。执行代码解析:
public void execute(final BulkRequest bulkRequest, final long executionId) { Runnable toRelease = () -> { }; boolean bulkRequestSetupSuccessful = false; try { //调用监听器的前置处理 this.listener.beforeBulk(executionId, bulkRequest); //信号量并发控制 this.semaphore.acquire(); Semaphore var10000 = this.semaphore; Objects.requireNonNull(var10000); toRelease = var10000::release; //CountDownLatch并发通知 final CountDownLatch latch = new CountDownLatch(1); //retry异步执行,执行后进行信号量释放、CountDownLatch释放通知 this.retry.withBackoff(this.consumer, bulkRequest, new ActionListener public void onResponse(BulkResponse response) { try { BulkRequestHandler.this.listener.afterBulk(executionId, bulkRequest, response); } finally { BulkRequestHandler.this.semaphore.release(); latch.countDown(); } } public void onFailure(Exception e) { try { BulkRequestHandler.this.listener.afterBulk(executionId, bulkRequest, e); } finally { BulkRequestHandler.this.semaphore.release(); latch.countDown(); } } }); bulkRequestSetupSuccessful = true; //当并发量达上线,等待释放 if (this.concurrentRequests == 0) { latch.await(); } } catch (InterruptedException var11) { Thread.currentThread().interrupt(); this.logger.info(() -> { return new ParameterizedMessage("Bulk request {} has been cancelled.", executionId); }, var11); this.listener.afterBulk(executionId, bulkRequest, var11); } catch (Exception var12) { this.logger.warn(() -> { return new ParameterizedMessage("Failed to execute bulk request {}.", executionId); }, var12); //异常回调 this.listener.afterBulk(executionId, bulkRequest, var12); } finally { if (!bulkRequestSetupSuccessful) { toRelease.run(); } } } |
通过Iteratble接口的抽象,以hasNext 和 Next来确定能否重试及重试的延迟时间。
public abstract class BackoffPolicy implements Iterable |
ConstantBackoff: 固定频率+固定时间的重试
NoBackOff: 无重试
ExponentialBackOff: 指数型重试。
private static class ExponentialBackoffIterator implements Iterator private final int numberOfElements; private final int start; private int currentlyConsumed; private ExponentialBackoffIterator(int start, int numberOfElements) { this.start = start; this.numberOfElements = numberOfElements; } public boolean hasNext() { return this.currentlyConsumed < this.numberOfElements; } public TimeValue next() { if (!this.hasNext()) { throw new NoSuchElementException("Only up to " + this.numberOfElements + " elements"); } else { int result = this.start + 10 * ((int)Math.exp(0.8D * (double)this.currentlyConsumed) - 1); ++this.currentlyConsumed; return TimeValue.timeValueMillis((long)result); } } } |
private void retry(BulkRequest bulkRequestForRetry) { assert this.backoff.hasNext(); TimeValue next = (TimeValue)this.backoff.next(); logger.trace("Retry of bulk request scheduled in {} ms.", next.millis()); Runnable command = this.scheduler.preserveContext(() -> { this.execute(bulkRequestForRetry); }); this.retryCancellable = this.scheduler.schedule(command, next, "same"); } |
RetryHandler implements ActionListener
public void execute(BulkRequest bulkRequest) { this.currentBulkRequest = bulkRequest; this.consumer.accept(bulkRequest, this); } |
public void onResponse(BulkResponse bulkItemResponses) { if (!bulkItemResponses.hasFailures()) { this.addResponses(bulkItemResponses, (r) -> { return true; }); this.finishHim(); } else if (this.canRetry(bulkItemResponses)) { this.addResponses(bulkItemResponses, (r) -> { return !r.isFailed(); }); this.retry(this.createBulkRequestForRetry(bulkItemResponses)); } else { this.addResponses(bulkItemResponses, (r) -> { return true; }); this.finishHim(); } } |
其他包括onFailure、finishHim等方法负责执行对应的回调行为(同时业务回调接口)。
批处理包括请求缓存、批量处理。
this.bulkRequest.add(request); this.executeIfNeeded(); |
private void executeIfNeeded() { this.ensureOpen(); if (this.isOverTheLimit()) { this.execute(); } } |
private boolean isOverTheLimit() { if (this.bulkActions != -1 && this.bulkRequest.numberOfActions() >= this.bulkActions) { return true; } else { return this.bulkSize != -1L && this.bulkRequest.estimatedSizeInBytes() >= this.bulkSize; } } |
private final AtomicLong executionIdGen = new AtomicLong();
long executionId = this.executionIdGen.incrementAndGet();
public synchronized boolean awaitClose(long timeout, TimeUnit unit) throws InterruptedException { if (this.closed) { return true; } else { this.closed = true; this.cancellableFlushTask.cancel(); if (this.bulkRequest.numberOfActions() > 0) { this.execute(); } boolean var4; try { var4 = this.bulkRequestHandler.awaitClose(timeout, unit); } finally { this.onClose.run(); } return var4; } } |
请求添加方法需要考虑安全性, 这里使用synchronized
public synchronized BulkProcessor add |
优秀的代码设计是的产品高效、健壮和强扩展性。BulkProcessor ES请求处理器的时间体现了以下优秀的设计实践: