View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  
21  package org.apache.hadoop.hbase.master;
22  
23  
24  import org.apache.hadoop.classification.InterfaceAudience;
25  import org.apache.hadoop.conf.Configuration;
26  import org.apache.hadoop.hbase.Chore;
27  import org.apache.hadoop.hbase.ClusterStatus;
28  import org.apache.hadoop.hbase.HConstants;
29  import org.apache.hadoop.hbase.ServerName;
30  import org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos;
31  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
32  import org.apache.hadoop.hbase.util.Pair;
33  import org.apache.hadoop.hbase.util.Threads;
34  import org.apache.hadoop.hbase.util.VersionInfo;
35  import org.jboss.netty.bootstrap.ConnectionlessBootstrap;
36  import org.jboss.netty.channel.Channels;
37  import org.jboss.netty.channel.socket.DatagramChannel;
38  import org.jboss.netty.channel.socket.DatagramChannelFactory;
39  import org.jboss.netty.channel.socket.oio.OioDatagramChannelFactory;
40  import org.jboss.netty.handler.codec.protobuf.ProtobufEncoder;
41  
42  import java.io.Closeable;
43  import java.io.IOException;
44  import java.net.InetAddress;
45  import java.net.InetSocketAddress;
46  import java.net.UnknownHostException;
47  import java.util.ArrayList;
48  import java.util.Collections;
49  import java.util.Comparator;
50  import java.util.List;
51  import java.util.Map;
52  import java.util.concurrent.ConcurrentHashMap;
53  import java.util.concurrent.ConcurrentMap;
54  import java.util.concurrent.ExecutorService;
55  import java.util.concurrent.Executors;
56  
57  /**
58   * Class to publish the cluster status to the client. This allows them to know immediately
59   *  the dead region servers, hence to cut the connection they have with them, eventually stop
60   *  waiting on the socket. This improves the mean time to recover, and as well allows to increase
61   *  on the client the different timeouts, as the dead servers will be detected separately.
62   */
63  @InterfaceAudience.Private
64  public class ClusterStatusPublisher extends Chore {
65    /**
66     * The implementation class used to publish the status. Default is null (no publish).
67     * Use org.apache.hadoop.hbase.master.ClusterStatusPublisher.MulticastPublisher to multicast the
68     * status.
69     */
70    public static final String STATUS_PUBLISHER_CLASS = "hbase.status.publisher.class";
71    public static final Class<? extends ClusterStatusPublisher.Publisher>
72        DEFAULT_STATUS_PUBLISHER_CLASS = null;
73  
74    /**
75     * The minimum time between two status messages, in milliseconds.
76     */
77    public static final String STATUS_PUBLISH_PERIOD = "hbase.status.publish.period";
78    public static final int DEFAULT_STATUS_PUBLISH_PERIOD = 10000;
79  
80    private long lastMessageTime = 0;
81    private final HMaster master;
82    private final int messagePeriod; // time between two message
83    private final ConcurrentMap<ServerName, Integer> lastSent =
84        new ConcurrentHashMap<ServerName, Integer>();
85    private Publisher publisher;
86    private boolean connected = false;
87  
88    /**
89     * We want to limit the size of the protobuf message sent, do fit into a single packet.
90     * a reasonable size for ip / ethernet is less than 1Kb.
91     */
92    public static int MAX_SERVER_PER_MESSAGE = 10;
93  
94    /**
95     * If a server dies, we're sending the information multiple times in case a receiver misses the
96     * message.
97     */
98    public static int NB_SEND = 5;
99  
100   public ClusterStatusPublisher(HMaster master, Configuration conf,
101                                 Class<? extends Publisher> publisherClass)
102       throws IOException {
103     super("HBase clusterStatusPublisher for " + master.getName(),
104         conf.getInt(STATUS_PUBLISH_PERIOD, DEFAULT_STATUS_PUBLISH_PERIOD), master);
105     this.master = master;
106     this.messagePeriod = conf.getInt(STATUS_PUBLISH_PERIOD, DEFAULT_STATUS_PUBLISH_PERIOD);
107     try {
108       this.publisher = publisherClass.newInstance();
109     } catch (InstantiationException e) {
110       throw new IOException("Can't create publisher " + publisherClass.getName(), e);
111     } catch (IllegalAccessException e) {
112       throw new IOException("Can't create publisher " + publisherClass.getName(), e);
113     }
114     this.publisher.connect(conf);
115     connected = true;
116   }
117 
118   // For tests only
119   protected ClusterStatusPublisher() {
120     master = null;
121     messagePeriod = 0;
122   }
123 
124   @Override
125   protected void chore() {
126     if (!connected) {
127       return;
128     }
129 
130     List<ServerName> sns = generateDeadServersListToSend();
131     if (sns.isEmpty()) {
132       // Nothing to send. Done.
133       return;
134     }
135 
136     final long curTime = EnvironmentEdgeManager.currentTimeMillis();
137     if (lastMessageTime > curTime - messagePeriod) {
138       // We already sent something less than 10 second ago. Done.
139       return;
140     }
141 
142     // Ok, we're going to send something then.
143     lastMessageTime = curTime;
144 
145     // We're reusing an existing protobuf message, but we don't send everything.
146     // This could be extended in the future, for example if we want to send stuff like the
147     //  META server name.
148     ClusterStatus cs = new ClusterStatus(VersionInfo.getVersion(),
149         master.getMasterFileSystem().getClusterId().toString(),
150         null,
151         sns,
152         master.getServerName(),
153         null,
154         null,
155         null,
156         null);
157 
158 
159     publisher.publish(cs);
160   }
161 
162   protected void cleanup() {
163     connected = false;
164     publisher.close();
165   }
166 
167   /**
168    * Create the dead server to send. A dead server is sent NB_SEND times. We send at max
169    * MAX_SERVER_PER_MESSAGE at a time. if there are too many dead servers, we send the newly
170    * dead first.
171    */
172   protected List<ServerName> generateDeadServersListToSend() {
173     // We're getting the message sent since last time, and add them to the list
174     long since = EnvironmentEdgeManager.currentTimeMillis() - messagePeriod * 2;
175     for (Pair<ServerName, Long> dead : getDeadServers(since)) {
176       lastSent.putIfAbsent(dead.getFirst(), 0);
177     }
178 
179     // We're sending the new deads first.
180     List<Map.Entry<ServerName, Integer>> entries = new ArrayList<Map.Entry<ServerName, Integer>>();
181     entries.addAll(lastSent.entrySet());
182     Collections.sort(entries, new Comparator<Map.Entry<ServerName, Integer>>() {
183       @Override
184       public int compare(Map.Entry<ServerName, Integer> o1, Map.Entry<ServerName, Integer> o2) {
185         return o1.getValue().compareTo(o2.getValue());
186       }
187     });
188 
189     // With a limit of MAX_SERVER_PER_MESSAGE
190     int max = entries.size() > MAX_SERVER_PER_MESSAGE ? MAX_SERVER_PER_MESSAGE : entries.size();
191     List<ServerName> res = new ArrayList<ServerName>(max);
192 
193     for (int i = 0; i < max; i++) {
194       Map.Entry<ServerName, Integer> toSend = entries.get(i);
195       if (toSend.getValue() >= (NB_SEND - 1)) {
196         lastSent.remove(toSend.getKey());
197       } else {
198         lastSent.replace(toSend.getKey(), toSend.getValue(), toSend.getValue() + 1);
199       }
200 
201       res.add(toSend.getKey());
202     }
203 
204     return res;
205   }
206 
207   /**
208    * Get the servers which died since a given timestamp.
209    * protected because it can be subclassed by the tests.
210    */
211   protected List<Pair<ServerName, Long>> getDeadServers(long since) {
212     if (master.getServerManager() == null) {
213       return Collections.emptyList();
214     }
215 
216     return master.getServerManager().getDeadServers().copyDeadServersSince(since);
217   }
218 
219 
220   public static interface Publisher extends Closeable {
221 
222     public void connect(Configuration conf) throws IOException;
223 
224     public void publish(ClusterStatus cs);
225 
226     @Override
227     public void close();
228   }
229 
230   public static class MulticastPublisher implements Publisher {
231     private DatagramChannel channel;
232     private final ExecutorService service = Executors.newSingleThreadExecutor(
233         Threads.newDaemonThreadFactory("hbase-master-clusterStatus-worker"));
234 
235     public MulticastPublisher() {
236     }
237 
238     @Override
239     public void connect(Configuration conf) throws IOException {
240       String mcAddress = conf.get(HConstants.STATUS_MULTICAST_ADDRESS,
241           HConstants.DEFAULT_STATUS_MULTICAST_ADDRESS);
242       int port = conf.getInt(HConstants.STATUS_MULTICAST_PORT,
243           HConstants.DEFAULT_STATUS_MULTICAST_PORT);
244 
245       // Can't be NiO with Netty today => not implemented in Netty.
246       DatagramChannelFactory f = new OioDatagramChannelFactory(service);
247 
248       ConnectionlessBootstrap b = new ConnectionlessBootstrap(f);
249       b.setPipeline(Channels.pipeline(new ProtobufEncoder()));
250 
251 
252       channel = (DatagramChannel) b.bind(new InetSocketAddress(0));
253       channel.getConfig().setReuseAddress(true);
254 
255       InetAddress ina;
256       try {
257         ina = InetAddress.getByName(mcAddress);
258       } catch (UnknownHostException e) {
259         throw new IOException("Can't connect to " + mcAddress, e);
260       }
261       channel.joinGroup(ina);
262       channel.connect(new InetSocketAddress(mcAddress, port));
263     }
264 
265     @Override
266     public void publish(ClusterStatus cs) {
267       ClusterStatusProtos.ClusterStatus csp = cs.convert();
268       channel.write(csp);
269     }
270 
271     @Override
272     public void close() {
273       if (channel != null) {
274         channel.close();
275       }
276       service.shutdown();
277     }
278   }
279 }