1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.hadoop.hbase.client;
17
18 import com.google.common.annotations.VisibleForTesting;
19 import java.io.Closeable;
20 import org.apache.commons.logging.Log;
21 import org.apache.commons.logging.LogFactory;
22 import org.apache.hadoop.conf.Configuration;
23 import org.apache.hadoop.hbase.HConstants;
24 import org.apache.hadoop.hbase.TableName;
25 import org.apache.hadoop.hbase.classification.InterfaceAudience;
26 import org.apache.hadoop.hbase.classification.InterfaceStability;
27 import org.apache.hadoop.hbase.ipc.RpcControllerFactory;
28
29 import java.io.IOException;
30 import java.io.InterruptedIOException;
31 import java.util.Arrays;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.NoSuchElementException;
35 import java.util.concurrent.ConcurrentLinkedQueue;
36 import java.util.concurrent.ExecutorService;
37 import java.util.concurrent.TimeUnit;
38 import java.util.concurrent.atomic.AtomicInteger;
39 import java.util.concurrent.atomic.AtomicLong;
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 @InterfaceAudience.Private
59 @InterfaceStability.Evolving
60 public class BufferedMutatorImpl implements BufferedMutator {
61
62 private static final Log LOG = LogFactory.getLog(BufferedMutatorImpl.class);
63
64 private final ExceptionListener listener;
65
66 protected ClusterConnection connection;
67 private final TableName tableName;
68 private volatile Configuration conf;
69 @VisibleForTesting
70 final ConcurrentLinkedQueue<Mutation> writeAsyncBuffer = new ConcurrentLinkedQueue<Mutation>();
71 @VisibleForTesting
72 AtomicLong currentWriteBufferSize = new AtomicLong(0);
73
74
75
76
77
78 @VisibleForTesting
79 AtomicInteger undealtMutationCount = new AtomicInteger(0);
80 private long writeBufferSize;
81 private final int maxKeyValueSize;
82 private boolean closed = false;
83 private final ExecutorService pool;
84 private int writeRpcTimeout;
85 private int operationTimeout;
86
87 @VisibleForTesting
88 protected AsyncProcess ap;
89
90 BufferedMutatorImpl(ClusterConnection conn, RpcRetryingCallerFactory rpcCallerFactory,
91 RpcControllerFactory rpcFactory, BufferedMutatorParams params) {
92 if (conn == null || conn.isClosed()) {
93 throw new IllegalArgumentException("Connection is null or closed.");
94 }
95
96 this.tableName = params.getTableName();
97 this.connection = conn;
98 this.conf = connection.getConfiguration();
99 this.pool = params.getPool();
100 this.listener = params.getListener();
101
102 ConnectionConfiguration tableConf = new ConnectionConfiguration(conf);
103 this.writeBufferSize = params.getWriteBufferSize() != BufferedMutatorParams.UNSET ?
104 params.getWriteBufferSize() : tableConf.getWriteBufferSize();
105 this.maxKeyValueSize = params.getMaxKeyValueSize() != BufferedMutatorParams.UNSET ?
106 params.getMaxKeyValueSize() : tableConf.getMaxKeyValueSize();
107
108 this.writeRpcTimeout = conn.getConfiguration().getInt(HConstants.HBASE_RPC_WRITE_TIMEOUT_KEY,
109 conn.getConfiguration().getInt(HConstants.HBASE_RPC_TIMEOUT_KEY,
110 HConstants.DEFAULT_HBASE_RPC_TIMEOUT));
111 this.operationTimeout = conn.getConfiguration().getInt(
112 HConstants.HBASE_CLIENT_OPERATION_TIMEOUT,
113 HConstants.DEFAULT_HBASE_CLIENT_OPERATION_TIMEOUT);
114
115 ap = new AsyncProcess(connection, conf, pool, rpcCallerFactory, true, rpcFactory, writeRpcTimeout);
116 }
117
118 @Override
119 public TableName getName() {
120 return tableName;
121 }
122
123 @Override
124 public Configuration getConfiguration() {
125 return conf;
126 }
127
128 @Override
129 public void mutate(Mutation m) throws InterruptedIOException,
130 RetriesExhaustedWithDetailsException {
131 mutate(Arrays.asList(m));
132 }
133
134 @Override
135 public void mutate(List<? extends Mutation> ms) throws InterruptedIOException,
136 RetriesExhaustedWithDetailsException {
137
138 if (closed) {
139 throw new IllegalStateException("Cannot put when the BufferedMutator is closed.");
140 }
141
142 long toAddSize = 0;
143 int toAddCount = 0;
144 for (Mutation m : ms) {
145 if (m instanceof Put) {
146 validatePut((Put) m);
147 }
148 toAddSize += m.heapSize();
149 ++toAddCount;
150 }
151
152
153
154
155 if (ap.hasError()) {
156 currentWriteBufferSize.addAndGet(toAddSize);
157 writeAsyncBuffer.addAll(ms);
158 undealtMutationCount.addAndGet(toAddCount);
159 backgroundFlushCommits(true);
160 } else {
161 currentWriteBufferSize.addAndGet(toAddSize);
162 writeAsyncBuffer.addAll(ms);
163 undealtMutationCount.addAndGet(toAddCount);
164 }
165
166
167 while (undealtMutationCount.get() != 0
168 && currentWriteBufferSize.get() > writeBufferSize) {
169 backgroundFlushCommits(false);
170 }
171 }
172
173
174 public void validatePut(final Put put) throws IllegalArgumentException {
175 HTable.validatePut(put, maxKeyValueSize);
176 }
177
178 @Override
179 public synchronized void close() throws IOException {
180 try {
181 if (this.closed) {
182 return;
183 }
184
185
186 backgroundFlushCommits(true);
187 this.pool.shutdown();
188 boolean terminated;
189 int loopCnt = 0;
190 do {
191
192 terminated = this.pool.awaitTermination(60, TimeUnit.SECONDS);
193 loopCnt += 1;
194 if (loopCnt >= 10) {
195 LOG.warn("close() failed to terminate pool after 10 minutes. Abandoning pool.");
196 break;
197 }
198 } while (!terminated);
199
200 } catch (InterruptedException e) {
201 LOG.warn("waitForTermination interrupted");
202
203 } finally {
204 this.closed = true;
205 }
206 }
207
208 @Override
209 public synchronized void flush() throws InterruptedIOException,
210 RetriesExhaustedWithDetailsException {
211
212
213 backgroundFlushCommits(true);
214 }
215
216
217
218
219
220
221
222
223
224 private void backgroundFlushCommits(boolean synchronous) throws
225 InterruptedIOException,
226 RetriesExhaustedWithDetailsException {
227 if (!synchronous && writeAsyncBuffer.isEmpty()) {
228 return;
229 }
230
231 if (!synchronous) {
232 try (QueueRowAccess taker = createQueueRowAccess()){
233 ap.submit(tableName, taker, true, null, false);
234 if (ap.hasError()) {
235 LOG.debug(tableName + ": One or more of the operations have failed -"
236 + " waiting for all operation in progress to finish (successfully or not)");
237 }
238 }
239 }
240 if (synchronous || ap.hasError()) {
241 while (true) {
242 try (QueueRowAccess taker = createQueueRowAccess()){
243 if (taker.isEmpty()) {
244 break;
245 }
246 ap.submit(tableName, taker, true, null, false);
247 }
248 }
249
250 RetriesExhaustedWithDetailsException error =
251 ap.waitForAllPreviousOpsAndReset(null, tableName.getNameAsString());
252 if (error != null) {
253 if (listener == null) {
254 throw error;
255 } else {
256 this.listener.onException(error, this);
257 }
258 }
259 }
260 }
261
262
263
264
265
266
267 @Deprecated
268 public void setWriteBufferSize(long writeBufferSize) throws RetriesExhaustedWithDetailsException,
269 InterruptedIOException {
270 this.writeBufferSize = writeBufferSize;
271 if (currentWriteBufferSize.get() > writeBufferSize) {
272 flush();
273 }
274 }
275
276
277
278
279 @Override
280 public long getWriteBufferSize() {
281 return this.writeBufferSize;
282 }
283
284 public void setRpcTimeout(int writeRpcTimeout) {
285 this.writeRpcTimeout = writeRpcTimeout;
286 this.ap.setRpcTimeout(writeRpcTimeout);
287 }
288
289 public void setOperationTimeout(int operationTimeout) {
290 this.operationTimeout = operationTimeout;
291 this.ap.setOperationTimeout(operationTimeout);
292 }
293
294
295
296
297
298
299 @Deprecated
300 public List<Row> getWriteBuffer() {
301 return Arrays.asList(writeAsyncBuffer.toArray(new Row[0]));
302 }
303
304 @VisibleForTesting
305 QueueRowAccess createQueueRowAccess() {
306 return new QueueRowAccess();
307 }
308
309 @VisibleForTesting
310 class QueueRowAccess implements RowAccess<Row>, Closeable {
311 private int remainder = undealtMutationCount.getAndSet(0);
312 private Mutation last = null;
313
314 @Override
315 public Iterator<Row> iterator() {
316 return new Iterator<Row>() {
317 private int countDown = remainder;
318 @Override
319 public boolean hasNext() {
320 return countDown > 0;
321 }
322 @Override
323 public Row next() {
324 restoreLastMutation();
325 if (!hasNext()) {
326 throw new NoSuchElementException();
327 }
328 last = writeAsyncBuffer.poll();
329 if (last == null) {
330 throw new NoSuchElementException();
331 }
332 currentWriteBufferSize.addAndGet(-last.heapSize());
333 --countDown;
334 return last;
335 }
336 @Override
337 public void remove() {
338 if (last == null) {
339 throw new IllegalStateException();
340 }
341 --remainder;
342 last = null;
343 }
344 };
345 }
346
347 private void restoreLastMutation() {
348
349 if (last != null) {
350 writeAsyncBuffer.add(last);
351 currentWriteBufferSize.addAndGet(last.heapSize());
352 last = null;
353 }
354 }
355
356 @Override
357 public int size() {
358 return remainder;
359 }
360
361 @Override
362 public boolean isEmpty() {
363 return remainder <= 0;
364 }
365 @Override
366 public void close() {
367 restoreLastMutation();
368 if (remainder > 0) {
369 undealtMutationCount.addAndGet(remainder);
370 remainder = 0;
371 }
372 }
373 }
374 }