View Javadoc

1   /*
2    * Copyright 2010 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase.replication.regionserver;
21  
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  import org.apache.hadoop.conf.Configuration;
25  import org.apache.hadoop.hbase.HConstants;
26  import org.apache.hadoop.hbase.KeyValue;
27  import org.apache.hadoop.hbase.TableNotFoundException;
28  import org.apache.hadoop.hbase.client.Delete;
29  import org.apache.hadoop.hbase.client.HTableInterface;
30  import org.apache.hadoop.hbase.client.HTablePool;
31  import org.apache.hadoop.hbase.client.Put;
32  import org.apache.hadoop.hbase.regionserver.wal.HLog;
33  import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
34  import org.apache.hadoop.hbase.util.Bytes;
35  
36  import java.io.IOException;
37  import java.util.ArrayList;
38  import java.util.List;
39  import java.util.concurrent.atomic.AtomicBoolean;
40  
41  /**
42   * This class is responsible for replicating the edits coming
43   * from another cluster.
44   * <p/>
45   * This replication process is currently waiting for the edits to be applied
46   * before the method can return. This means that the replication of edits
47   * is synchronized (after reading from HLogs in ReplicationSource) and that a
48   * single region server cannot receive edits from two sources at the same time
49   * <p/>
50   * This class uses the native HBase client in order to replicate entries.
51   * <p/>
52   *
53   * TODO make this class more like ReplicationSource wrt log handling
54   */
55  public class ReplicationSink {
56  
57    private static final Log LOG = LogFactory.getLog(ReplicationSink.class);
58    // Name of the HDFS directory that contains the temporary rep logs
59    public static final String REPLICATION_LOG_DIR = ".replogs";
60    private final Configuration conf;
61    // Pool used to replicated
62    private final HTablePool pool;
63    // boolean coming from HRS to know when the process stops
64    private final AtomicBoolean stop;
65    private final ReplicationSinkMetrics metrics;
66  
67    /**
68     * Create a sink for replication
69     *
70     * @param conf                conf object
71     * @param stopper             boolean to tell this thread to stop
72     * @throws IOException thrown when HDFS goes bad or bad file name
73     */
74    public ReplicationSink(Configuration conf, AtomicBoolean stopper)
75        throws IOException {
76      this.conf = conf;
77      this.pool = new HTablePool(this.conf,
78          conf.getInt("replication.sink.htablepool.capacity", 10));
79      this.stop = stopper;
80      this.metrics = new ReplicationSinkMetrics();
81    }
82  
83    /**
84     * Replicate this array of entries directly into the local cluster
85     * using the native client.
86     *
87     * @param entries
88     * @throws IOException
89     */
90    public synchronized void replicateEntries(HLog.Entry[] entries)
91        throws IOException {
92      if (entries.length == 0) {
93        return;
94      }
95      // Very simple optimization where we batch sequences of rows going
96      // to the same table.
97      try {
98        long totalReplicated = 0;
99        byte[] lastTable = HConstants.EMPTY_BYTE_ARRAY;
100       List<Put> puts = new ArrayList<Put>();
101       for (HLog.Entry entry : entries) {
102         WALEdit edit = entry.getEdit();
103         List<KeyValue> kvs = edit.getKeyValues();
104         if (kvs.get(0).isDelete()) {
105           Delete delete = new Delete(kvs.get(0).getRow(),
106               kvs.get(0).getTimestamp(), null);
107           for (KeyValue kv : kvs) {
108             if (kv.isDeleteFamily()) {
109               delete.deleteFamily(kv.getFamily());
110             } else if (!kv.isEmptyColumn()) {
111               delete.deleteColumn(kv.getFamily(),
112                   kv.getQualifier());
113             }
114           }
115           delete(entry.getKey().getTablename(), delete);
116         } else {
117           // Switching table, flush
118           if (!Bytes.equals(lastTable, entry.getKey().getTablename())) {
119             put(lastTable, puts);
120           }
121           // With mini-batching, we need to expect multiple rows per edit
122           byte[] lastKey = kvs.get(0).getRow();
123           Put put = new Put(kvs.get(0).getRow(),
124               kvs.get(0).getTimestamp());
125           for (KeyValue kv : kvs) {
126             if (!Bytes.equals(lastKey, kv.getRow())) {
127               puts.add(put);
128               put = new Put(kv.getRow(), kv.getTimestamp());
129             }
130             put.add(kv.getFamily(), kv.getQualifier(), kv.getValue());
131             lastKey = kv.getRow();
132           }
133           puts.add(put);
134           lastTable = entry.getKey().getTablename();
135         }
136         totalReplicated++;
137       }
138       put(lastTable, puts);
139       this.metrics.setAgeOfLastAppliedOp(
140           entries[entries.length-1].getKey().getWriteTime());
141       this.metrics.appliedBatchesRate.inc(1);
142       LOG.info("Total replicated: " + totalReplicated);
143     } catch (IOException ex) {
144       if (ex.getCause() instanceof TableNotFoundException) {
145         LOG.warn("Losing edits because: ", ex);
146       } else {
147         // Should we log rejected edits in a file for replay?
148         LOG.error("Unable to accept edit because", ex);
149         this.stop.set(true);
150         throw ex;
151       }
152     } catch (RuntimeException re) {
153       if (re.getCause() instanceof TableNotFoundException) {
154         LOG.warn("Losing edits because: ", re);
155       } else {
156         this.stop.set(true);
157         throw re;
158       }
159     }
160   }
161 
162   /**
163    * Do the puts and handle the pool
164    * @param tableName table to insert into
165    * @param puts list of puts
166    * @throws IOException
167    */
168   private void put(byte[] tableName, List<Put> puts) throws IOException {
169     if (puts.isEmpty()) {
170       return;
171     }
172     HTableInterface table = null;
173     try {
174       table = this.pool.getTable(tableName);
175       table.put(puts);
176       this.metrics.appliedOpsRate.inc(puts.size());
177       this.pool.putTable(table);
178       puts.clear();
179     } finally {
180       if (table != null) {
181         this.pool.putTable(table);
182       }
183     }
184   }
185 
186   /**
187    * Do the delete and handle the pool
188    * @param tableName table to delete in
189    * @param delete the delete to use
190    * @throws IOException
191    */
192   private void delete(byte[] tableName, Delete delete) throws IOException {
193     HTableInterface table = null;
194     try {
195       table = this.pool.getTable(tableName);
196       table.delete(delete);
197       this.metrics.appliedOpsRate.inc(1);
198       this.pool.putTable(table);
199     } finally {
200       if (table != null) {
201         this.pool.putTable(table);
202       }
203     }
204   }
205 }