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 org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  import org.apache.hadoop.conf.Configuration;
26  import org.apache.hadoop.hbase.DoNotRetryIOException;
27  import org.apache.hadoop.hbase.HConstants;
28  import org.apache.hadoop.hbase.client.RetriesExhaustedException;
29  import org.apache.hadoop.hbase.security.User;
30  import org.apache.hadoop.io.Writable;
31  import org.apache.hadoop.net.NetUtils;
32  import org.apache.hadoop.util.ReflectionUtils;
33  import javax.net.SocketFactory;
34  import java.io.IOException;
35  import java.lang.reflect.Method;
36  import java.lang.reflect.Proxy;
37  import java.net.ConnectException;
38  import java.net.InetSocketAddress;
39  import java.net.SocketTimeoutException;
40  import java.util.HashMap;
41  import java.util.Map;
42  
43  /** A simple RPC mechanism.
44   *
45   * This is a local hbase copy of the hadoop RPC so we can do things like
46   * address HADOOP-414 for hbase-only and try other hbase-specific
47   * optimizations like using our own version of ObjectWritable.  Class has been
48   * renamed to avoid confusing it w/ hadoop versions.
49   * <p>
50   *
51   *
52   * A <i>protocol</i> is a Java interface.  All parameters and return types must
53   * be one of:
54   *
55   * <ul> <li>a primitive type, <code>boolean</code>, <code>byte</code>,
56   * <code>char</code>, <code>short</code>, <code>int</code>, <code>long</code>,
57   * <code>float</code>, <code>double</code>, or <code>void</code>; or</li>
58   *
59   * <li>a {@link String}; or</li>
60   *
61   * <li>a {@link Writable}; or</li>
62   *
63   * <li>an array of the above types</li> </ul>
64   *
65   * All methods in the protocol should throw only IOException.  No field data of
66   * the protocol instance is transmitted.
67   */
68  public class HBaseRPC {
69    // Leave this out in the hadoop ipc package but keep class name.  Do this
70    // so that we dont' get the logging of this class's invocations by doing our
71    // blanket enabling DEBUG on the o.a.h.h. package.
72    protected static final Log LOG =
73      LogFactory.getLog("org.apache.hadoop.ipc.HBaseRPC");
74  
75    private HBaseRPC() {
76      super();
77    }                                  // no public ctor
78  
79    /**
80     * Configuration key for the {@link RpcEngine} implementation to load to
81     * handle connection protocols.  Handlers for individual protocols can be
82     * configured using {@code "hbase.rpc.engine." + protocol.class.name}.
83     */
84    public static final String RPC_ENGINE_PROP = "hbase.rpc.engine";
85  
86    // cache of RpcEngines by protocol
87    private static final Map<Class,RpcEngine> PROTOCOL_ENGINES
88      = new HashMap<Class,RpcEngine>();
89  
90    // track what RpcEngine is used by a proxy class, for stopProxy()
91    private static final Map<Class,RpcEngine> PROXY_ENGINES
92      = new HashMap<Class,RpcEngine>();
93  
94    // thread-specific RPC timeout, which may override that of RpcEngine
95    private static ThreadLocal<Integer> rpcTimeout = new ThreadLocal<Integer>() {
96      @Override
97        protected Integer initialValue() {
98          return HConstants.DEFAULT_HBASE_CLIENT_OPERATION_TIMEOUT;
99        }
100     };
101 
102   // set a protocol to use a non-default RpcEngine
103   static void setProtocolEngine(Configuration conf,
104                                 Class protocol, Class engine) {
105     conf.setClass(RPC_ENGINE_PROP+"."+protocol.getName(), engine, RpcEngine.class);
106   }
107 
108   // return the RpcEngine configured to handle a protocol
109   private static synchronized RpcEngine getProtocolEngine(Class protocol,
110                                                           Configuration conf) {
111     RpcEngine engine = PROTOCOL_ENGINES.get(protocol);
112     if (engine == null) {
113       // check for a configured default engine
114       Class<?> defaultEngine =
115           conf.getClass(RPC_ENGINE_PROP, WritableRpcEngine.class);
116 
117       // check for a per interface override
118       Class<?> impl = conf.getClass(RPC_ENGINE_PROP+"."+protocol.getName(),
119                                     defaultEngine);
120       LOG.debug("Using "+impl.getName()+" for "+protocol.getName());
121       engine = (RpcEngine) ReflectionUtils.newInstance(impl, conf);
122       if (protocol.isInterface())
123         PROXY_ENGINES.put(Proxy.getProxyClass(protocol.getClassLoader(),
124                                               protocol),
125                           engine);
126       PROTOCOL_ENGINES.put(protocol, engine);
127     }
128     return engine;
129   }
130 
131   // return the RpcEngine that handles a proxy object
132   private static synchronized RpcEngine getProxyEngine(Object proxy) {
133     return PROXY_ENGINES.get(proxy.getClass());
134   }
135 
136   /**
137    * A version mismatch for the RPC protocol.
138    */
139   @SuppressWarnings("serial")
140   public static class VersionMismatch extends IOException {
141     private static final long serialVersionUID = 0;
142     private String interfaceName;
143     private long clientVersion;
144     private long serverVersion;
145 
146     /**
147      * Create a version mismatch exception
148      * @param interfaceName the name of the protocol mismatch
149      * @param clientVersion the client's version of the protocol
150      * @param serverVersion the server's version of the protocol
151      */
152     public VersionMismatch(String interfaceName, long clientVersion,
153                            long serverVersion) {
154       super("Protocol " + interfaceName + " version mismatch. (client = " +
155             clientVersion + ", server = " + serverVersion + ")");
156       this.interfaceName = interfaceName;
157       this.clientVersion = clientVersion;
158       this.serverVersion = serverVersion;
159     }
160 
161     /**
162      * Get the interface name
163      * @return the java class name
164      *          (eg. org.apache.hadoop.mapred.InterTrackerProtocol)
165      */
166     public String getInterfaceName() {
167       return interfaceName;
168     }
169 
170     /**
171      * @return the client's preferred version
172      */
173     public long getClientVersion() {
174       return clientVersion;
175     }
176 
177     /**
178      * @return the server's agreed to version.
179      */
180     public long getServerVersion() {
181       return serverVersion;
182     }
183   }
184 
185   /**
186    * An error requesting an RPC protocol that the server is not serving.
187    */
188   public static class UnknownProtocolException extends DoNotRetryIOException {
189     private Class<?> protocol;
190 
191     public UnknownProtocolException(String mesg) {
192       // required for unwrapping from a RemoteException
193       super(mesg);
194     }
195 
196     public UnknownProtocolException(Class<?> protocol) {
197       this(protocol, "Server is not handling protocol "+protocol.getName());
198     }
199 
200     public UnknownProtocolException(Class<?> protocol, String mesg) {
201       super(mesg);
202       this.protocol = protocol;
203     }
204 
205     public Class getProtocol() {
206       return protocol;
207     }
208   }
209 
210   /**
211    * @param protocol protocol interface
212    * @param clientVersion which client version we expect
213    * @param addr address of remote service
214    * @param conf configuration
215    * @param maxAttempts max attempts
216    * @param rpcTimeout timeout for each RPC
217    * @param timeout timeout in milliseconds
218    * @return proxy
219    * @throws IOException e
220    */
221   @SuppressWarnings("unchecked")
222   public static VersionedProtocol waitForProxy(Class protocol,
223                                                long clientVersion,
224                                                InetSocketAddress addr,
225                                                Configuration conf,
226                                                int maxAttempts,
227                                                int rpcTimeout,
228                                                long timeout
229                                                ) throws IOException {
230     // HBase does limited number of reconnects which is different from hadoop.
231     long startTime = System.currentTimeMillis();
232     IOException ioe;
233     int reconnectAttempts = 0;
234     while (true) {
235       try {
236         return getProxy(protocol, clientVersion, addr, conf, rpcTimeout);
237       } catch(ConnectException se) {  // namenode has not been started
238         ioe = se;
239         if (maxAttempts >= 0 && ++reconnectAttempts >= maxAttempts) {
240           LOG.info("Server at " + addr + " could not be reached after " +
241             reconnectAttempts + " tries, giving up.");
242           throw new RetriesExhaustedException("Failed setting up proxy " +
243             protocol + " to " + addr.toString() + " after attempts=" +
244             reconnectAttempts, se);
245       }
246       } catch(SocketTimeoutException te) {  // namenode is busy
247         LOG.info("Problem connecting to server: " + addr);
248         ioe = te;
249       }
250       // check if timed out
251       if (System.currentTimeMillis()-timeout >= startTime) {
252         throw ioe;
253       }
254 
255       // wait for retry
256       try {
257         Thread.sleep(1000);
258       } catch (InterruptedException ie) {
259         // IGNORE
260       }
261     }
262   }
263 
264   /**
265    * Construct a client-side proxy object that implements the named protocol,
266    * talking to a server at the named address.
267    *
268    * @param protocol interface
269    * @param clientVersion version we are expecting
270    * @param addr remote address
271    * @param conf configuration
272    * @param factory socket factory
273    * @param rpcTimeout timeout for each RPC
274    * @return proxy
275    * @throws IOException e
276    */
277   public static VersionedProtocol getProxy(Class<? extends VersionedProtocol> protocol,
278       long clientVersion, InetSocketAddress addr, Configuration conf,
279       SocketFactory factory, int rpcTimeout) throws IOException {
280     return getProxy(protocol, clientVersion, addr,
281         User.getCurrent(), conf, factory, rpcTimeout);
282   }
283 
284   /**
285    * Construct a client-side proxy object that implements the named protocol,
286    * talking to a server at the named address.
287    *
288    * @param protocol interface
289    * @param clientVersion version we are expecting
290    * @param addr remote address
291    * @param ticket ticket
292    * @param conf configuration
293    * @param factory socket factory
294    * @param rpcTimeout timeout for each RPC
295    * @return proxy
296    * @throws IOException e
297    */
298   public static VersionedProtocol getProxy(
299       Class<? extends VersionedProtocol> protocol,
300       long clientVersion, InetSocketAddress addr, User ticket,
301       Configuration conf, SocketFactory factory, int rpcTimeout)
302   throws IOException {
303     VersionedProtocol proxy =
304         getProtocolEngine(protocol,conf)
305             .getProxy(protocol, clientVersion, addr, ticket, conf, factory, Math.min(rpcTimeout, HBaseRPC.getRpcTimeout()));
306     long serverVersion = proxy.getProtocolVersion(protocol.getName(),
307                                                   clientVersion);
308     if (serverVersion == clientVersion) {
309       return proxy;
310     }
311     throw new VersionMismatch(protocol.getName(), clientVersion,
312                               serverVersion);
313   }
314 
315   /**
316    * Construct a client-side proxy object with the default SocketFactory
317    *
318    * @param protocol interface
319    * @param clientVersion version we are expecting
320    * @param addr remote address
321    * @param conf configuration
322    * @param rpcTimeout timeout for each RPC
323    * @return a proxy instance
324    * @throws IOException e
325    */
326   public static VersionedProtocol getProxy(
327       Class<? extends VersionedProtocol> protocol,
328       long clientVersion, InetSocketAddress addr, Configuration conf,
329       int rpcTimeout)
330       throws IOException {
331 
332     return getProxy(protocol, clientVersion, addr, conf, NetUtils
333         .getDefaultSocketFactory(conf), rpcTimeout);
334   }
335 
336   /**
337    * Stop this proxy and release its invoker's resource
338    * @param proxy the proxy to be stopped
339    */
340   public static void stopProxy(VersionedProtocol proxy) {
341     if (proxy!=null) {
342       getProxyEngine(proxy).stopProxy(proxy);
343     }
344   }
345 
346   /**
347    * Expert: Make multiple, parallel calls to a set of servers.
348    *
349    * @param method method to invoke
350    * @param params array of parameters
351    * @param addrs array of addresses
352    * @param conf configuration
353    * @return values
354    * @throws IOException e
355    * @deprecated Instead of calling statically, use
356    *     {@link HBaseRPC#getProtocolEngine(Class, org.apache.hadoop.conf.Configuration)}
357    *     to obtain an {@link RpcEngine} instance and then use
358    *     {@link RpcEngine#call(java.lang.reflect.Method, Object[][], java.net.InetSocketAddress[], Class, org.apache.hadoop.hbase.security.User, org.apache.hadoop.conf.Configuration)}
359    */
360   @Deprecated
361   public static Object[] call(Method method, Object[][] params,
362       InetSocketAddress[] addrs,
363       Class<? extends VersionedProtocol> protocol,
364       User ticket,
365       Configuration conf)
366     throws IOException, InterruptedException {
367     return getProtocolEngine(protocol, conf)
368       .call(method, params, addrs, protocol, ticket, conf);
369   }
370 
371   /**
372    * Construct a server for a protocol implementation instance listening on a
373    * port and address.
374    *
375    * @param instance instance
376    * @param bindAddress bind address
377    * @param port port to bind to
378    * @param numHandlers number of handlers to start
379    * @param verbose verbose flag
380    * @param conf configuration
381    * @return Server
382    * @throws IOException e
383    */
384   public static RpcServer getServer(final Object instance,
385                                  final Class<?>[] ifaces,
386                                  final String bindAddress, final int port,
387                                  final int numHandlers,
388                                  int metaHandlerCount, final boolean verbose, Configuration conf, int highPriorityLevel)
389     throws IOException {
390     return getServer(instance.getClass(), instance, ifaces, bindAddress, port, numHandlers, metaHandlerCount, verbose, conf, highPriorityLevel);
391   }
392 
393   /** Construct a server for a protocol implementation instance. */
394   public static RpcServer getServer(Class protocol,
395                                  final Object instance,
396                                  final Class<?>[] ifaces, String bindAddress,
397                                  int port,
398                                  final int numHandlers,
399                                  int metaHandlerCount, final boolean verbose, Configuration conf, int highPriorityLevel)
400     throws IOException {
401     return getProtocolEngine(protocol, conf)
402         .getServer(protocol, instance, ifaces, bindAddress, port, numHandlers, metaHandlerCount, verbose, conf, highPriorityLevel);
403   }
404 
405   public static void setRpcTimeout(int rpcTimeout) {
406     HBaseRPC.rpcTimeout.set(rpcTimeout);
407   }
408 
409   public static int getRpcTimeout() {
410     return HBaseRPC.rpcTimeout.get();
411   }
412 
413   public static void resetRpcTimeout() {
414     HBaseRPC.rpcTimeout.remove();
415   }
416 }