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  
21  package org.apache.hadoop.hbase.ipc;
22  
23  import java.lang.reflect.Proxy;
24  import java.lang.reflect.Method;
25  import java.lang.reflect.Array;
26  import java.lang.reflect.InvocationHandler;
27  import java.lang.reflect.InvocationTargetException;
28  
29  import java.net.InetSocketAddress;
30  import java.io.*;
31  import java.util.Map;
32  import java.util.HashMap;
33  
34  import javax.net.SocketFactory;
35  
36  import org.apache.commons.logging.*;
37  
38  import org.apache.hadoop.hbase.HRegionInfo;
39  import org.apache.hadoop.hbase.client.Operation;
40  import org.apache.hadoop.hbase.io.HbaseObjectWritable;
41  import org.apache.hadoop.hbase.monitoring.MonitoredRPCHandler;
42  import org.apache.hadoop.hbase.regionserver.HRegionServer;
43  import org.apache.hadoop.hbase.util.Bytes;
44  import org.apache.hadoop.hbase.util.Objects;
45  import org.apache.hadoop.io.*;
46  import org.apache.hadoop.ipc.RPC;
47  import org.apache.hadoop.hbase.ipc.VersionedProtocol;
48  import org.apache.hadoop.hbase.security.User;
49  import org.apache.hadoop.security.authorize.ServiceAuthorizationManager;
50  import org.apache.hadoop.conf.*;
51  
52  import org.codehaus.jackson.map.ObjectMapper;
53  
54  /** An RpcEngine implementation for Writable data. */
55  class WritableRpcEngine implements RpcEngine {
56    // LOG is NOT in hbase subpackage intentionally so that the default HBase
57    // DEBUG log level does NOT emit RPC-level logging. 
58    private static final Log LOG = LogFactory.getLog("org.apache.hadoop.ipc.RPCEngine");
59  
60    private static class Invoker implements InvocationHandler {
61      private Class<? extends VersionedProtocol> protocol;
62      private InetSocketAddress address;
63      private User ticket;
64      private HBaseClient client;
65      final private int rpcTimeout;
66  
67      public Invoker(HBaseClient client,
68                     Class<? extends VersionedProtocol> protocol,
69                     InetSocketAddress address, User ticket,
70                     Configuration conf, int rpcTimeout) {
71        this.protocol = protocol;
72        this.address = address;
73        this.ticket = ticket;
74        this.client = client;
75        this.rpcTimeout = rpcTimeout;
76      }
77  
78      public Object invoke(Object proxy, Method method, Object[] args)
79          throws Throwable {
80        final boolean logDebug = LOG.isDebugEnabled();
81        long startTime = 0;
82        if (logDebug) {
83          startTime = System.currentTimeMillis();
84        }
85  
86        HbaseObjectWritable value = (HbaseObjectWritable)
87          client.call(new Invocation(method, protocol, args), address,
88                      protocol, ticket, rpcTimeout);
89        if (logDebug) {
90          // FIGURE HOW TO TURN THIS OFF!
91          long callTime = System.currentTimeMillis() - startTime;
92          LOG.debug("Call: " + method.getName() + " " + callTime);
93        }
94        return value.get();
95      }
96    }
97  
98    private Configuration conf;
99    private HBaseClient client;
100 
101   @Override
102   public void setConf(Configuration config) {
103     this.conf = config;
104     // check for an already created client
105     if (this.client != null) {
106       this.client.stop();
107     }
108     this.client = new HBaseClient(HbaseObjectWritable.class, conf);
109   }
110 
111   @Override
112   public Configuration getConf() {
113     return conf;
114   }
115 
116   /** Construct a client-side proxy object that implements the named protocol,
117    * talking to a server at the named address. */
118   @Override
119   public <T extends VersionedProtocol> T getProxy(
120       Class<T> protocol, long clientVersion,
121       InetSocketAddress addr, Configuration conf, int rpcTimeout)
122     throws IOException {
123     if (this.client == null) {
124       throw new IOException("Client must be initialized by calling setConf(Configuration)");
125     }
126 
127     T proxy =
128           (T) Proxy.newProxyInstance(
129               protocol.getClassLoader(), new Class[] { protocol },
130               new Invoker(client, protocol, addr, User.getCurrent(), conf,
131                   HBaseRPC.getRpcTimeout(rpcTimeout)));
132 
133     /*
134      * TODO: checking protocol version only needs to be done once when we setup a new
135      * HBaseClient.Connection.  Doing it every time we retrieve a proxy instance is resulting
136      * in unnecessary RPC traffic.
137      */
138     long serverVersion = ((VersionedProtocol)proxy)
139       .getProtocolVersion(protocol.getName(), clientVersion);
140     if (serverVersion != clientVersion) {
141       throw new HBaseRPC.VersionMismatch(protocol.getName(), clientVersion,
142                                     serverVersion);
143     }
144 
145     return proxy;
146   }
147 
148 
149 
150   /** Expert: Make multiple, parallel calls to a set of servers. */
151   @Override
152   public Object[] call(Method method, Object[][] params,
153                        InetSocketAddress[] addrs,
154                        Class<? extends VersionedProtocol> protocol,
155                        User ticket, Configuration conf)
156     throws IOException, InterruptedException {
157     if (this.client == null) {
158       throw new IOException("Client must be initialized by calling setConf(Configuration)");
159     }
160 
161     Invocation[] invocations = new Invocation[params.length];
162     for (int i = 0; i < params.length; i++) {
163       invocations[i] = new Invocation(method, protocol, params[i]);
164     }
165 
166     Writable[] wrappedValues =
167         client.call(invocations, addrs, protocol, ticket);
168 
169     if (method.getReturnType() == Void.TYPE) {
170       return null;
171     }
172 
173     Object[] values =
174         (Object[])Array.newInstance(method.getReturnType(), wrappedValues.length);
175     for (int i = 0; i < values.length; i++) {
176       if (wrappedValues[i] != null) {
177         values[i] = ((HbaseObjectWritable)wrappedValues[i]).get();
178       }
179     }
180 
181     return values;
182   }
183 
184   @Override
185   public void close() {
186     if (this.client != null) {
187       this.client.stop();
188     }
189   }
190 
191   /** Construct a server for a protocol implementation instance listening on a
192    * port and address. */
193   public Server getServer(Class<? extends VersionedProtocol> protocol,
194                           Object instance,
195                           Class<?>[] ifaces,
196                           String bindAddress, int port,
197                           int numHandlers,
198                           int metaHandlerCount, boolean verbose,
199                           Configuration conf, int highPriorityLevel)
200     throws IOException {
201     return new Server(instance, ifaces, conf, bindAddress, port, numHandlers,
202         metaHandlerCount, verbose, highPriorityLevel);
203   }
204 
205   /** An RPC Server. */
206   public static class Server extends HBaseServer {
207     private Object instance;
208     private Class<?> implementation;
209     private Class<?>[] ifaces;
210     private boolean verbose;
211     private boolean authorize = false;
212 
213     // for JSON encoding
214     private static ObjectMapper mapper = new ObjectMapper();
215 
216     private static final String WARN_RESPONSE_TIME =
217       "hbase.ipc.warn.response.time";
218     private static final String WARN_RESPONSE_SIZE =
219       "hbase.ipc.warn.response.size";
220 
221     /** Default value for above params */
222     private static final int DEFAULT_WARN_RESPONSE_TIME = 10000; // milliseconds
223     private static final int DEFAULT_WARN_RESPONSE_SIZE = 100 * 1024 * 1024;
224 
225     /** Names for suffixed metrics */
226     private static final String ABOVE_ONE_SEC_METRIC = ".aboveOneSec.";
227 
228     private final int warnResponseTime;
229     private final int warnResponseSize;
230 
231     private static String classNameBase(String className) {
232       String[] names = className.split("\\.", -1);
233       if (names == null || names.length == 0) {
234         return className;
235       }
236       return names[names.length-1];
237     }
238 
239     /** Construct an RPC server.
240      * @param instance the instance whose methods will be called
241      * @param conf the configuration to use
242      * @param bindAddress the address to bind on to listen for connection
243      * @param port the port to listen for connections on
244      * @param numHandlers the number of method handler threads to run
245      * @param verbose whether each call should be logged
246      * @throws IOException e
247      */
248     public Server(Object instance, final Class<?>[] ifaces,
249                   Configuration conf, String bindAddress,  int port,
250                   int numHandlers, int metaHandlerCount, boolean verbose,
251                   int highPriorityLevel) throws IOException {
252       super(bindAddress, port, Invocation.class, numHandlers, metaHandlerCount,
253           conf, classNameBase(instance.getClass().getName()),
254           highPriorityLevel);
255       this.instance = instance;
256       this.implementation = instance.getClass();
257       this.verbose = verbose;
258 
259       this.ifaces = ifaces;
260 
261       // create metrics for the advertised interfaces this server implements.
262       String [] metricSuffixes = new String [] {ABOVE_ONE_SEC_METRIC};
263       this.rpcMetrics.createMetrics(this.ifaces, false, metricSuffixes);
264 
265       this.authorize =
266         conf.getBoolean(
267             ServiceAuthorizationManager.SERVICE_AUTHORIZATION_CONFIG, false);
268 
269       this.warnResponseTime = conf.getInt(WARN_RESPONSE_TIME,
270           DEFAULT_WARN_RESPONSE_TIME);
271       this.warnResponseSize = conf.getInt(WARN_RESPONSE_SIZE,
272           DEFAULT_WARN_RESPONSE_SIZE);
273     }
274 
275     @Override
276     public Writable call(Class<? extends VersionedProtocol> protocol,
277         Writable param, long receivedTime, MonitoredRPCHandler status)
278     throws IOException {
279       try {
280         Invocation call = (Invocation)param;
281         if(call.getMethodName() == null) {
282           throw new IOException("Could not find requested method, the usual " +
283               "cause is a version mismatch between client and server.");
284         }
285         if (verbose) log("Call: " + call);
286         status.setRPC(call.getMethodName(), call.getParameters(), receivedTime);
287         status.setRPCPacket(param);
288         status.resume("Servicing call");
289 
290         Method method =
291           protocol.getMethod(call.getMethodName(),
292                                    call.getParameterClasses());
293         method.setAccessible(true);
294 
295         //Verify protocol version.
296         //Bypass the version check for VersionedProtocol
297         if (!method.getDeclaringClass().equals(VersionedProtocol.class)) {
298           long clientVersion = call.getProtocolVersion();
299           ProtocolSignature serverInfo = ((VersionedProtocol) instance)
300               .getProtocolSignature(protocol.getCanonicalName(), call
301                   .getProtocolVersion(), call.getClientMethodsHash());
302           long serverVersion = serverInfo.getVersion();
303           if (serverVersion != clientVersion) {
304             LOG.warn("Version mismatch: client version=" + clientVersion
305                 + ", server version=" + serverVersion);
306             throw new RPC.VersionMismatch(protocol.getName(), clientVersion,
307                 serverVersion);
308           }
309         }
310         Object impl = null;
311         if (protocol.isAssignableFrom(this.implementation)) {
312           impl = this.instance;
313         }
314         else {
315           throw new HBaseRPC.UnknownProtocolException(protocol);
316         }
317 
318         long startTime = System.currentTimeMillis();
319         Object[] params = call.getParameters();
320         Object value = method.invoke(impl, params);
321         int processingTime = (int) (System.currentTimeMillis() - startTime);
322         int qTime = (int) (startTime-receivedTime);
323         if (TRACELOG.isDebugEnabled()) {
324           TRACELOG.debug("Call #" + CurCall.get().id +
325               "; Served: " + protocol.getSimpleName()+"#"+call.getMethodName() +
326               " queueTime=" + qTime +
327               " processingTime=" + processingTime +
328               " contents=" + Objects.describeQuantity(params));
329         }
330         rpcMetrics.rpcQueueTime.inc(qTime);
331         rpcMetrics.rpcProcessingTime.inc(processingTime);
332         rpcMetrics.inc(call.getMethodName(), processingTime);
333         if (verbose) log("Return: "+value);
334 
335         HbaseObjectWritable retVal =
336           new HbaseObjectWritable(method.getReturnType(), value);
337         long responseSize = retVal.getWritableSize();
338         // log any RPC responses that are slower than the configured warn
339         // response time or larger than configured warning size
340         boolean tooSlow = (processingTime > warnResponseTime
341             && warnResponseTime > -1);
342         boolean tooLarge = (responseSize > warnResponseSize
343             && warnResponseSize > -1);
344         if (tooSlow || tooLarge) {
345           // when tagging, we let TooLarge trump TooSmall to keep output simple
346           // note that large responses will often also be slow.
347           logResponse(call, (tooLarge ? "TooLarge" : "TooSlow"),
348               status.getClient(), startTime, processingTime, qTime,
349               responseSize);
350           // provides a count of log-reported slow responses
351           if (tooSlow) {
352             rpcMetrics.rpcSlowResponseTime.inc(processingTime);
353           }
354         }
355         if (processingTime > 1000) {
356           // we use a hard-coded one second period so that we can clearly
357           // indicate the time period we're warning about in the name of the 
358           // metric itself
359           rpcMetrics.inc(call.getMethodName() + ABOVE_ONE_SEC_METRIC,
360               processingTime);
361         }
362 
363         return retVal;
364       } catch (InvocationTargetException e) {
365         Throwable target = e.getTargetException();
366         if (target instanceof IOException) {
367           throw (IOException)target;
368         }
369         IOException ioe = new IOException(target.toString());
370         ioe.setStackTrace(target.getStackTrace());
371         throw ioe;
372       } catch (Throwable e) {
373         if (!(e instanceof IOException)) {
374           LOG.error("Unexpected throwable object ", e);
375         }
376         IOException ioe = new IOException(e.toString());
377         ioe.setStackTrace(e.getStackTrace());
378         throw ioe;
379       }
380     }
381 
382     /**
383      * Logs an RPC response to the LOG file, producing valid JSON objects for
384      * client Operations.
385      * @param call The call to log.
386      * @param tag  The tag that will be used to indicate this event in the log.
387      * @param clientAddress   The address of the client who made this call.
388      * @param startTime       The time that the call was initiated, in ms.
389      * @param processingTime  The duration that the call took to run, in ms.
390      * @param qTime           The duration that the call spent on the queue 
391      *                        prior to being initiated, in ms.
392      * @param responseSize    The size in bytes of the response buffer.
393      */
394     private void logResponse(Invocation call, String tag, String clientAddress,
395         long startTime, int processingTime, int qTime, long responseSize)
396       throws IOException {
397       Object params[] = call.getParameters();
398       // for JSON encoding
399       ObjectMapper mapper = new ObjectMapper();
400       // base information that is reported regardless of type of call
401       Map<String, Object> responseInfo = new HashMap<String, Object>();
402       responseInfo.put("starttimems", startTime);
403       responseInfo.put("processingtimems", processingTime);
404       responseInfo.put("queuetimems", qTime);
405       responseInfo.put("responsesize", responseSize);
406       responseInfo.put("client", clientAddress);
407       responseInfo.put("class", instance.getClass().getSimpleName());
408       responseInfo.put("method", call.getMethodName());
409       if (params.length == 2 && instance instanceof HRegionServer &&
410           params[0] instanceof byte[] &&
411           params[1] instanceof Operation) {
412         // if the slow process is a query, we want to log its table as well 
413         // as its own fingerprint
414         byte [] tableName =
415           HRegionInfo.parseRegionName((byte[]) params[0])[0];
416         responseInfo.put("table", Bytes.toStringBinary(tableName));
417         // annotate the response map with operation details
418         responseInfo.putAll(((Operation) params[1]).toMap());
419         // report to the log file
420         LOG.warn("(operation" + tag + "): " +
421             mapper.writeValueAsString(responseInfo));
422       } else if (params.length == 1 && instance instanceof HRegionServer &&
423           params[0] instanceof Operation) {
424         // annotate the response map with operation details
425         responseInfo.putAll(((Operation) params[0]).toMap());
426         // report to the log file
427         LOG.warn("(operation" + tag + "): " +
428             mapper.writeValueAsString(responseInfo));
429       } else {
430         // can't get JSON details, so just report call.toString() along with 
431         // a more generic tag.
432         responseInfo.put("call", call.toString());
433         LOG.warn("(response" + tag + "): " +
434             mapper.writeValueAsString(responseInfo));
435       }
436     }
437   }
438 
439   protected static void log(String value) {
440     String v = value;
441     if (v != null && v.length() > 55)
442       v = v.substring(0, 55)+"...";
443     LOG.info(v);
444   }
445 }