1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.hadoop.hbase.client;
21
22 import com.google.common.annotations.VisibleForTesting;
23 import com.google.common.util.concurrent.ThreadFactoryBuilder;
24
25 import java.io.IOException;
26 import java.util.AbstractMap.SimpleEntry;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.concurrent.ConcurrentHashMap;
33 import java.util.concurrent.ExecutorService;
34 import java.util.concurrent.Executors;
35 import java.util.concurrent.LinkedBlockingQueue;
36 import java.util.concurrent.ScheduledExecutorService;
37 import java.util.concurrent.TimeUnit;
38 import java.util.concurrent.atomic.AtomicInteger;
39 import java.util.concurrent.atomic.AtomicLong;
40
41 import org.apache.commons.logging.Log;
42 import org.apache.commons.logging.LogFactory;
43 import org.apache.hadoop.conf.Configuration;
44 import org.apache.hadoop.hbase.HBaseConfiguration;
45 import org.apache.hadoop.hbase.HConstants;
46 import org.apache.hadoop.hbase.HRegionInfo;
47 import org.apache.hadoop.hbase.HRegionLocation;
48 import org.apache.hadoop.hbase.ServerName;
49 import org.apache.hadoop.hbase.TableName;
50 import org.apache.hadoop.hbase.classification.InterfaceAudience;
51 import org.apache.hadoop.hbase.classification.InterfaceStability;
52 import org.apache.hadoop.hbase.client.AsyncProcess.AsyncRequestFuture;
53 import org.apache.hadoop.hbase.ipc.RpcControllerFactory;
54 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 @InterfaceAudience.Public
71 @InterfaceStability.Evolving
72 public class HTableMultiplexer {
73 private static final Log LOG = LogFactory.getLog(HTableMultiplexer.class.getName());
74
75 public static final String TABLE_MULTIPLEXER_FLUSH_PERIOD_MS =
76 "hbase.tablemultiplexer.flush.period.ms";
77 public static final String TABLE_MULTIPLEXER_INIT_THREADS = "hbase.tablemultiplexer.init.threads";
78 public static final String TABLE_MULTIPLEXER_MAX_RETRIES_IN_QUEUE =
79 "hbase.client.max.retries.in.queue";
80
81
82 private final Map<HRegionLocation, FlushWorker> serverToFlushWorkerMap =
83 new ConcurrentHashMap<>();
84
85 private final Configuration workerConf;
86 private final ClusterConnection conn;
87 private final ExecutorService pool;
88 private final int retryNum;
89 private final int perRegionServerBufferQueueSize;
90 private final int maxKeyValueSize;
91 private final ScheduledExecutorService executor;
92 private final long flushPeriod;
93
94
95
96
97
98
99 public HTableMultiplexer(Configuration conf, int perRegionServerBufferQueueSize)
100 throws IOException {
101 this(ConnectionFactory.createConnection(conf), conf, perRegionServerBufferQueueSize);
102 }
103
104
105
106
107
108
109
110 public HTableMultiplexer(Connection conn, Configuration conf,
111 int perRegionServerBufferQueueSize) {
112 this.conn = (ClusterConnection) conn;
113 this.pool = HTable.getDefaultExecutor(conf);
114 this.retryNum = conf.getInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER,
115 HConstants.DEFAULT_HBASE_CLIENT_RETRIES_NUMBER);
116 this.perRegionServerBufferQueueSize = perRegionServerBufferQueueSize;
117 this.maxKeyValueSize = HTable.getMaxKeyValueSize(conf);
118 this.flushPeriod = conf.getLong(TABLE_MULTIPLEXER_FLUSH_PERIOD_MS, 100);
119 int initThreads = conf.getInt(TABLE_MULTIPLEXER_INIT_THREADS, 10);
120 this.executor =
121 Executors.newScheduledThreadPool(initThreads,
122 new ThreadFactoryBuilder().setDaemon(true).setNameFormat("HTableFlushWorker-%d").build());
123
124 this.workerConf = HBaseConfiguration.create(conf);
125
126
127 this.workerConf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 0);
128 }
129
130
131
132
133
134
135 @SuppressWarnings("deprecation")
136 public synchronized void close() throws IOException {
137 if (!getConnection().isClosed()) {
138 getConnection().close();
139 }
140 }
141
142
143
144
145
146
147
148
149 public boolean put(TableName tableName, final Put put) {
150 return put(tableName, put, this.retryNum);
151 }
152
153
154
155
156
157
158
159
160 public List<Put> put(TableName tableName, final List<Put> puts) {
161 if (puts == null)
162 return null;
163
164 List <Put> failedPuts = null;
165 boolean result;
166 for (Put put : puts) {
167 result = put(tableName, put, this.retryNum);
168 if (result == false) {
169
170
171 if (failedPuts == null) {
172 failedPuts = new ArrayList<Put>();
173 }
174
175 failedPuts.add(put);
176 }
177 }
178 return failedPuts;
179 }
180
181
182
183
184 @Deprecated
185 public List<Put> put(byte[] tableName, final List<Put> puts) {
186 return put(TableName.valueOf(tableName), puts);
187 }
188
189
190
191
192
193
194
195 public boolean put(final TableName tableName, final Put put, int retry) {
196 return _put(tableName, put, retry, false);
197 }
198
199
200
201
202
203
204
205
206
207
208 boolean _put(final TableName tableName, final Put put, int retry, boolean reloadCache) {
209 if (retry <= 0) {
210 return false;
211 }
212
213 try {
214 HTable.validatePut(put, maxKeyValueSize);
215
216 ClusterConnection conn = (ClusterConnection) getConnection();
217 HRegionLocation loc = conn.getRegionLocation(tableName, put.getRow(), reloadCache);
218 if (loc != null) {
219
220 LinkedBlockingQueue<PutStatus> queue = getQueue(loc);
221
222
223 PutStatus s = new PutStatus(loc.getRegionInfo(), put, retry);
224
225 return queue.offer(s);
226 }
227 } catch (IOException e) {
228 LOG.debug("Cannot process the put " + put, e);
229 }
230 return false;
231 }
232
233
234
235
236 @Deprecated
237 public boolean put(final byte[] tableName, final Put put, int retry) {
238 return put(TableName.valueOf(tableName), put, retry);
239 }
240
241
242
243
244 @Deprecated
245 public boolean put(final byte[] tableName, Put put) {
246 return put(TableName.valueOf(tableName), put);
247 }
248
249
250
251
252 public HTableMultiplexerStatus getHTableMultiplexerStatus() {
253 return new HTableMultiplexerStatus(serverToFlushWorkerMap);
254 }
255
256 @VisibleForTesting
257 LinkedBlockingQueue<PutStatus> getQueue(HRegionLocation addr) {
258 FlushWorker worker = serverToFlushWorkerMap.get(addr);
259 if (worker == null) {
260 synchronized (this.serverToFlushWorkerMap) {
261 worker = serverToFlushWorkerMap.get(addr);
262 if (worker == null) {
263
264 worker = new FlushWorker(workerConf, this.conn, addr, this, perRegionServerBufferQueueSize,
265 pool, executor);
266 this.serverToFlushWorkerMap.put(addr, worker);
267 executor.scheduleAtFixedRate(worker, flushPeriod, flushPeriod, TimeUnit.MILLISECONDS);
268 }
269 }
270 }
271 return worker.getQueue();
272 }
273
274 @VisibleForTesting
275 ClusterConnection getConnection() {
276 return this.conn;
277 }
278
279
280
281
282
283
284 @InterfaceAudience.Public
285 @InterfaceStability.Evolving
286 public static class HTableMultiplexerStatus {
287 private long totalFailedPutCounter;
288 private long totalBufferedPutCounter;
289 private long maxLatency;
290 private long overallAverageLatency;
291 private Map<String, Long> serverToFailedCounterMap;
292 private Map<String, Long> serverToBufferedCounterMap;
293 private Map<String, Long> serverToAverageLatencyMap;
294 private Map<String, Long> serverToMaxLatencyMap;
295
296 public HTableMultiplexerStatus(
297 Map<HRegionLocation, FlushWorker> serverToFlushWorkerMap) {
298 this.totalBufferedPutCounter = 0;
299 this.totalFailedPutCounter = 0;
300 this.maxLatency = 0;
301 this.overallAverageLatency = 0;
302 this.serverToBufferedCounterMap = new HashMap<String, Long>();
303 this.serverToFailedCounterMap = new HashMap<String, Long>();
304 this.serverToAverageLatencyMap = new HashMap<String, Long>();
305 this.serverToMaxLatencyMap = new HashMap<String, Long>();
306 this.initialize(serverToFlushWorkerMap);
307 }
308
309 private void initialize(
310 Map<HRegionLocation, FlushWorker> serverToFlushWorkerMap) {
311 if (serverToFlushWorkerMap == null) {
312 return;
313 }
314
315 long averageCalcSum = 0;
316 int averageCalcCount = 0;
317 for (Map.Entry<HRegionLocation, FlushWorker> entry : serverToFlushWorkerMap
318 .entrySet()) {
319 HRegionLocation addr = entry.getKey();
320 FlushWorker worker = entry.getValue();
321
322 long bufferedCounter = worker.getTotalBufferedCount();
323 long failedCounter = worker.getTotalFailedCount();
324 long serverMaxLatency = worker.getMaxLatency();
325 AtomicAverageCounter averageCounter = worker.getAverageLatencyCounter();
326
327 SimpleEntry<Long, Integer> averageComponents = averageCounter
328 .getComponents();
329 long serverAvgLatency = averageCounter.getAndReset();
330
331 this.totalBufferedPutCounter += bufferedCounter;
332 this.totalFailedPutCounter += failedCounter;
333 if (serverMaxLatency > this.maxLatency) {
334 this.maxLatency = serverMaxLatency;
335 }
336 averageCalcSum += averageComponents.getKey();
337 averageCalcCount += averageComponents.getValue();
338
339 this.serverToBufferedCounterMap.put(addr.getHostnamePort(),
340 bufferedCounter);
341 this.serverToFailedCounterMap
342 .put(addr.getHostnamePort(),
343 failedCounter);
344 this.serverToAverageLatencyMap.put(addr.getHostnamePort(),
345 serverAvgLatency);
346 this.serverToMaxLatencyMap
347 .put(addr.getHostnamePort(),
348 serverMaxLatency);
349 }
350 this.overallAverageLatency = averageCalcCount != 0 ? averageCalcSum
351 / averageCalcCount : 0;
352 }
353
354 public long getTotalBufferedCounter() {
355 return this.totalBufferedPutCounter;
356 }
357
358 public long getTotalFailedCounter() {
359 return this.totalFailedPutCounter;
360 }
361
362 public long getMaxLatency() {
363 return this.maxLatency;
364 }
365
366 public long getOverallAverageLatency() {
367 return this.overallAverageLatency;
368 }
369
370 public Map<String, Long> getBufferedCounterForEachRegionServer() {
371 return this.serverToBufferedCounterMap;
372 }
373
374 public Map<String, Long> getFailedCounterForEachRegionServer() {
375 return this.serverToFailedCounterMap;
376 }
377
378 public Map<String, Long> getMaxLatencyForEachRegionServer() {
379 return this.serverToMaxLatencyMap;
380 }
381
382 public Map<String, Long> getAverageLatencyForEachRegionServer() {
383 return this.serverToAverageLatencyMap;
384 }
385 }
386
387 @VisibleForTesting
388 static class PutStatus {
389 public final HRegionInfo regionInfo;
390 public final Put put;
391 public final int retryCount;
392
393 public PutStatus(HRegionInfo regionInfo, Put put, int retryCount) {
394 this.regionInfo = regionInfo;
395 this.put = put;
396 this.retryCount = retryCount;
397 }
398 }
399
400
401
402
403 private static class AtomicAverageCounter {
404 private long sum;
405 private int count;
406
407 public AtomicAverageCounter() {
408 this.sum = 0L;
409 this.count = 0;
410 }
411
412 public synchronized long getAndReset() {
413 long result = this.get();
414 this.reset();
415 return result;
416 }
417
418 public synchronized long get() {
419 if (this.count == 0) {
420 return 0;
421 }
422 return this.sum / this.count;
423 }
424
425 public synchronized SimpleEntry<Long, Integer> getComponents() {
426 return new SimpleEntry<Long, Integer>(sum, count);
427 }
428
429 public synchronized void reset() {
430 this.sum = 0l;
431 this.count = 0;
432 }
433
434 public synchronized void add(long value) {
435 this.sum += value;
436 this.count++;
437 }
438 }
439
440 @VisibleForTesting
441 static class FlushWorker implements Runnable {
442 private final HRegionLocation addr;
443 private final LinkedBlockingQueue<PutStatus> queue;
444 private final HTableMultiplexer multiplexer;
445 private final AtomicLong totalFailedPutCount = new AtomicLong(0);
446 private final AtomicInteger currentProcessingCount = new AtomicInteger(0);
447 private final AtomicAverageCounter averageLatency = new AtomicAverageCounter();
448 private final AtomicLong maxLatency = new AtomicLong(0);
449
450 private final AsyncProcess ap;
451 private final List<PutStatus> processingList = new ArrayList<>();
452 private final ScheduledExecutorService executor;
453 private final int maxRetryInQueue;
454 private final AtomicInteger retryInQueue = new AtomicInteger(0);
455
456 public FlushWorker(Configuration conf, ClusterConnection conn, HRegionLocation addr,
457 HTableMultiplexer htableMultiplexer, int perRegionServerBufferQueueSize,
458 ExecutorService pool, ScheduledExecutorService executor) {
459 this.addr = addr;
460 this.multiplexer = htableMultiplexer;
461 this.queue = new LinkedBlockingQueue<>(perRegionServerBufferQueueSize);
462 RpcRetryingCallerFactory rpcCallerFactory = RpcRetryingCallerFactory.instantiate(conf);
463 RpcControllerFactory rpcControllerFactory = RpcControllerFactory.instantiate(conf);
464 this.ap = new AsyncProcess(conn, conf, pool, rpcCallerFactory, false, rpcControllerFactory);
465 this.executor = executor;
466 this.maxRetryInQueue = conf.getInt(TABLE_MULTIPLEXER_MAX_RETRIES_IN_QUEUE, 10000);
467 }
468
469 protected LinkedBlockingQueue<PutStatus> getQueue() {
470 return this.queue;
471 }
472
473 public long getTotalFailedCount() {
474 return totalFailedPutCount.get();
475 }
476
477 public long getTotalBufferedCount() {
478 return queue.size() + currentProcessingCount.get();
479 }
480
481 public AtomicAverageCounter getAverageLatencyCounter() {
482 return this.averageLatency;
483 }
484
485 public long getMaxLatency() {
486 return this.maxLatency.getAndSet(0);
487 }
488
489 boolean resubmitFailedPut(PutStatus ps, HRegionLocation oldLoc) throws IOException {
490
491 final int retryCount = ps.retryCount - 1;
492
493 if (retryCount <= 0) {
494
495 return false;
496 }
497
498 int cnt = getRetryInQueue().incrementAndGet();
499 if (cnt > getMaxRetryInQueue()) {
500
501 getRetryInQueue().decrementAndGet();
502 return false;
503 }
504
505 final Put failedPut = ps.put;
506
507 final TableName tableName = ps.regionInfo.getTable();
508
509 long delayMs = getNextDelay(retryCount);
510 if (LOG.isDebugEnabled()) {
511 LOG.debug("resubmitting after " + delayMs + "ms: " + retryCount);
512 }
513
514 getExecutor().schedule(new Runnable() {
515 @Override
516 public void run() {
517 boolean succ = false;
518 try {
519 succ = FlushWorker.this.getMultiplexer()._put(tableName, failedPut, retryCount, true);
520 } finally {
521 FlushWorker.this.getRetryInQueue().decrementAndGet();
522 if (!succ) {
523 FlushWorker.this.getTotalFailedPutCount().incrementAndGet();
524 }
525 }
526 }
527 }, delayMs, TimeUnit.MILLISECONDS);
528 return true;
529 }
530
531 @VisibleForTesting
532 long getNextDelay(int retryCount) {
533 return ConnectionUtils.getPauseTime(multiplexer.flushPeriod,
534 multiplexer.retryNum - retryCount - 1);
535 }
536
537 @VisibleForTesting
538 AtomicInteger getRetryInQueue() {
539 return this.retryInQueue;
540 }
541
542 @VisibleForTesting
543 int getMaxRetryInQueue() {
544 return this.maxRetryInQueue;
545 }
546
547 @VisibleForTesting
548 AtomicLong getTotalFailedPutCount() {
549 return this.totalFailedPutCount;
550 }
551
552 @VisibleForTesting
553 HTableMultiplexer getMultiplexer() {
554 return this.multiplexer;
555 }
556
557 @VisibleForTesting
558 ScheduledExecutorService getExecutor() {
559 return this.executor;
560 }
561
562 @Override
563 public void run() {
564 int failedCount = 0;
565 try {
566 long start = EnvironmentEdgeManager.currentTime();
567
568
569 processingList.clear();
570 queue.drainTo(processingList);
571 if (processingList.size() == 0) {
572
573 return;
574 }
575
576 currentProcessingCount.set(processingList.size());
577
578 failedCount = processingList.size();
579
580 List<Action<Row>> retainedActions = new ArrayList<>(processingList.size());
581 MultiAction<Row> actions = new MultiAction<>();
582 for (int i = 0; i < processingList.size(); i++) {
583 PutStatus putStatus = processingList.get(i);
584 Action<Row> action = new Action<Row>(putStatus.put, i);
585 actions.add(putStatus.regionInfo.getRegionName(), action);
586 retainedActions.add(action);
587 }
588
589
590 List<PutStatus> failed = null;
591 Object[] results = new Object[actions.size()];
592 ServerName server = addr.getServerName();
593 Map<ServerName, MultiAction<Row>> actionsByServer =
594 Collections.singletonMap(server, actions);
595 try {
596 AsyncRequestFuture arf =
597 ap.submitMultiActions(null, retainedActions, 0L, null, results, true, null,
598 null, actionsByServer, null);
599 arf.waitUntilDone();
600 if (arf.hasError()) {
601
602 LOG.debug("Caught some exceptions when flushing puts to region server "
603 + addr.getHostnamePort(), arf.getErrors());
604 }
605 } finally {
606 for (int i = 0; i < results.length; i++) {
607 if (results[i] instanceof Result) {
608 failedCount--;
609 } else {
610 if (failed == null) {
611 failed = new ArrayList<PutStatus>();
612 }
613 failed.add(processingList.get(i));
614 }
615 }
616 }
617
618 if (failed != null) {
619
620 for (PutStatus putStatus : failed) {
621 if (resubmitFailedPut(putStatus, this.addr)) {
622 failedCount--;
623 }
624 }
625 }
626
627 long elapsed = EnvironmentEdgeManager.currentTime() - start;
628
629 averageLatency.add(elapsed);
630 if (elapsed > maxLatency.get()) {
631 maxLatency.set(elapsed);
632 }
633
634
635 if (LOG.isDebugEnabled()) {
636 LOG.debug("Processed " + currentProcessingCount + " put requests for "
637 + addr.getHostnamePort() + " and " + failedCount + " failed"
638 + ", latency for this send: " + elapsed);
639 }
640
641
642 currentProcessingCount.set(0);
643 } catch (RuntimeException e) {
644
645
646 LOG.debug(
647 "Caught some exceptions " + e + " when flushing puts to region server "
648 + addr.getHostnamePort(), e);
649 } catch (Exception e) {
650 if (e instanceof InterruptedException) {
651 Thread.currentThread().interrupt();
652 }
653
654 LOG.debug(
655 "Caught some exceptions " + e + " when flushing puts to region server "
656 + addr.getHostnamePort(), e);
657 } finally {
658
659 this.totalFailedPutCount.addAndGet(failedCount);
660 }
661 }
662 }
663 }