001: /**
002: * Sequoia: Database clustering technology.
003: * Copyright (C) 2002-2004 French National Institute For Research In Computer
004: * Science And Control (INRIA).
005: * Contact: sequoia@continuent.org
006: *
007: * Licensed under the Apache License, Version 2.0 (the "License");
008: * you may not use this file except in compliance with the License.
009: * You may obtain a copy of the License at
010: *
011: * http://www.apache.org/licenses/LICENSE-2.0
012: *
013: * Unless required by applicable law or agreed to in writing, software
014: * distributed under the License is distributed on an "AS IS" BASIS,
015: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016: * See the License for the specific language governing permissions and
017: * limitations under the License.
018: *
019: * Initial developer(s): Emmanuel Cecchet.
020: * Contributor(s): Julie Marguerite.
021: */package org.continuent.sequoia.controller.loadbalancer.raidb2;
022:
023: import java.sql.SQLException;
024: import java.util.ArrayList;
025: import java.util.Collection;
026:
027: import org.continuent.sequoia.common.exceptions.NoMoreBackendException;
028: import org.continuent.sequoia.common.exceptions.UnreachableBackendException;
029: import org.continuent.sequoia.common.i18n.Translate;
030: import org.continuent.sequoia.common.xml.DatabasesXmlTags;
031: import org.continuent.sequoia.controller.backend.DatabaseBackend;
032: import org.continuent.sequoia.controller.backend.result.ControllerResultSet;
033: import org.continuent.sequoia.controller.backend.result.ExecuteResult;
034: import org.continuent.sequoia.controller.cache.metadata.MetadataCache;
035: import org.continuent.sequoia.controller.loadbalancer.policies.WaitForCompletionPolicy;
036: import org.continuent.sequoia.controller.loadbalancer.policies.createtable.CreateTablePolicy;
037: import org.continuent.sequoia.controller.requests.SelectRequest;
038: import org.continuent.sequoia.controller.requests.StoredProcedure;
039: import org.continuent.sequoia.controller.virtualdatabase.VirtualDatabase;
040:
041: /**
042: * RAIDb-2 Round Robin load balancer.
043: * <p>
044: * The read requests coming from the request manager are sent in a round robin
045: * to the backend nodes. Write requests are broadcasted to all backends.
046: *
047: * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
048: * @author <a href="mailto:Julie.Marguerite@inria.fr">Julie Marguerite </a>
049: * @version 1.0
050: */
051: public class RAIDb2_RR extends RAIDb2 {
052: /*
053: * How the code is organized ? 1. Member variables 2. Constructor(s) 3.
054: * Request handling 4. Debug/Monitoring
055: */
056:
057: private int index; // index in the backend vector the Round-Robin
058:
059: /*
060: * Constructors
061: */
062:
063: /**
064: * Creates a new RAIDb-2 Round Robin request load balancer.
065: *
066: * @param vdb the virtual database this load balancer belongs to.
067: * @param waitForCompletionPolicy How many backends must complete before
068: * returning the result?
069: * @param createTablePolicy The policy defining how 'create table' statements
070: * should be handled
071: * @exception Exception if an error occurs
072: */
073: public RAIDb2_RR(VirtualDatabase vdb,
074: WaitForCompletionPolicy waitForCompletionPolicy,
075: CreateTablePolicy createTablePolicy) throws Exception {
076: super (vdb, waitForCompletionPolicy, createTablePolicy);
077: index = -1;
078: }
079:
080: /*
081: * Request Handling
082: */
083:
084: /**
085: * Chooses the node to execute the request using a round-robin algorithm. If
086: * the next node has not the tables needed to execute the requests, we try the
087: * next one and so on until a suitable backend is found.
088: *
089: * @param request an <code>SelectRequest</code>
090: * @param metadataCache cached metadata to use to construct the result set
091: * @return the corresponding <code>java.sql.ResultSet</code>
092: * @exception SQLException if an error occurs
093: * @see org.continuent.sequoia.controller.loadbalancer.raidb2.RAIDb2#statementExecuteQuery(SelectRequest,
094: * MetadataCache)
095: */
096: public ControllerResultSet statementExecuteQuery(
097: SelectRequest request, MetadataCache metadataCache)
098: throws SQLException {
099: // Choose a backend
100: try {
101: vdb.acquireReadLockBackendLists();
102: } catch (InterruptedException e) {
103: String msg = Translate.get(
104: "loadbalancer.backendlist.acquire.readlock.failed",
105: e);
106: logger.error(msg);
107: throw new SQLException(msg);
108: }
109:
110: DatabaseBackend backend = null; // The backend that will execute the query
111:
112: // Note that vdb lock is released in the finally clause of this try/catch
113: // block
114: try {
115: ArrayList backends = vdb.getBackends();
116: int size = backends.size();
117:
118: if (size == 0)
119: throw new NoMoreBackendException(Translate.get(
120: "loadbalancer.execute.no.backend.available",
121: request.getId()));
122:
123: // Take the next backend that has the needed tables
124: int maxTries = size;
125: int enabledBackends = 0;
126: Collection tables = request.getFrom();
127: if (tables == null)
128: throw new SQLException(Translate.get(
129: "loadbalancer.from.not.found", request
130: .getSqlShortForm(vdb
131: .getSqlShortFormLength())));
132:
133: synchronized (this ) {
134: do {
135: index = (index + 1) % size;
136: backend = (DatabaseBackend) backends.get(index);
137: if (backend.isReadEnabled()) {
138: enabledBackends++;
139: if (backend.hasTables(tables))
140: break;
141: }
142: maxTries--;
143: } while (maxTries >= 0);
144: }
145:
146: if (maxTries < 0) { // No suitable backend found
147: if (enabledBackends == 0)
148: throw new NoMoreBackendException(Translate.get(
149: "loadbalancer.execute.no.backend.enabled",
150: request.getId()));
151: else
152: throw new SQLException(Translate.get(
153: "loadbalancer.backend.no.required.tables",
154: tables.toString()));
155: }
156: } catch (RuntimeException e) {
157: String msg = Translate.get(
158: "loadbalancer.request.failed.on.backend",
159: new String[] {
160: request.getSqlShortForm(vdb
161: .getSqlShortFormLength()),
162: backend.getName(), e.getMessage() });
163: logger.error(msg, e);
164: throw new SQLException(msg);
165: } finally {
166: vdb.releaseReadLockBackendLists();
167: }
168:
169: // Execute the request on the chosen backend
170: ControllerResultSet rs = null;
171: try {
172: rs = executeReadRequestOnBackend(request, backend,
173: metadataCache);
174: } catch (UnreachableBackendException se) {
175: // Try on another backend
176: return statementExecuteQuery(request, metadataCache);
177: } catch (SQLException se) {
178: String msg = Translate.get("loadbalancer.request.failed",
179: new String[] { String.valueOf(request.getId()),
180: se.getMessage() });
181: if (logger.isInfoEnabled())
182: logger.info(msg);
183: throw new SQLException(msg);
184: } catch (RuntimeException e) {
185: String msg = Translate.get(
186: "loadbalancer.request.failed.on.backend",
187: new String[] {
188: request.getSqlShortForm(vdb
189: .getSqlShortFormLength()),
190: backend.getName(), e.getMessage() });
191: logger.error(msg, e);
192: throw new SQLException(msg);
193: }
194:
195: return rs;
196: }
197:
198: /**
199: * Chooses the node to execute the stored procedure using a round-robin
200: * algorithm. If the next node has not the needed stored procedure, we try the
201: * next one and so on until a suitable backend is found.
202: *
203: * @see org.continuent.sequoia.controller.loadbalancer.AbstractLoadBalancer#readOnlyCallableStatementExecuteQuery(StoredProcedure,
204: * MetadataCache)
205: */
206: public ControllerResultSet readOnlyCallableStatementExecuteQuery(
207: StoredProcedure proc, MetadataCache metadataCache)
208: throws SQLException {
209: return (ControllerResultSet) callStoredProcedure(proc,
210: CALLABLE_STATEMENT_EXECUTE_QUERY, metadataCache);
211: }
212:
213: /**
214: * Chooses the node to execute the stored procedure using a round-robin
215: * algorithm. If the next node has not the needed stored procedure, we try the
216: * next one and so on until a suitable backend is found.
217: *
218: * @see org.continuent.sequoia.controller.loadbalancer.AbstractLoadBalancer#readOnlyCallableStatementExecute(org.continuent.sequoia.controller.requests.StoredProcedure,
219: * org.continuent.sequoia.controller.cache.metadata.MetadataCache)
220: */
221: public ExecuteResult readOnlyCallableStatementExecute(
222: StoredProcedure proc, MetadataCache metadataCache)
223: throws SQLException {
224: return (ExecuteResult) callStoredProcedure(proc,
225: CALLABLE_STATEMENT_EXECUTE, metadataCache);
226: }
227:
228: /**
229: * Common code to execute a StoredProcedure using executeQuery or
230: * executeUpdate on a backend chosen using a LPRF algorithm.
231: *
232: * @param proc a <code>StoredProcedure</code>
233: * @param callType one of CALLABLE_STATEMENT_EXECUTE_QUERY or
234: * CALLABLE_STATEMENT_EXECUTE
235: * @param metadataCache the metadataCache if any or null
236: * @return a <code>ControllerResultSet</code> or an
237: * <code>ExecuteResult</code> object
238: * @throws SQLException if an error occurs
239: */
240: private Object callStoredProcedure(StoredProcedure proc,
241: int callType, MetadataCache metadataCache)
242: throws SQLException {
243: // Choose a backend
244: try {
245: vdb.acquireReadLockBackendLists();
246: } catch (InterruptedException e) {
247: String msg = Translate.get(
248: "loadbalancer.backendlist.acquire.readlock.failed",
249: e);
250: logger.error(msg);
251: throw new SQLException(msg);
252: }
253:
254: DatabaseBackend backend = null; // The backend that will execute the query
255:
256: // Note that vdb lock is released in the finally clause of this try/catch
257: // block
258: try {
259: DatabaseBackend failedBackend = null;
260: SQLException failedException = null;
261: Object result = null;
262: do {
263: ArrayList backends = vdb.getBackends();
264: int size = backends.size();
265:
266: if (size == 0)
267: throw new NoMoreBackendException(
268: Translate
269: .get(
270: "loadbalancer.execute.no.backend.available",
271: proc.getId()));
272:
273: // Take the next backend that has the needed tables
274: int maxTries = size;
275: int enabledBackends = 0;
276:
277: synchronized (this ) {
278: do {
279: index = (index + 1) % size;
280: backend = (DatabaseBackend) backends.get(index);
281: if (backend.isReadEnabled()) {
282: enabledBackends++;
283: if ((backend != failedBackend)
284: && backend.hasStoredProcedure(proc
285: .getProcedureKey(), proc
286: .getNbOfParameters()))
287: break;
288: }
289: maxTries--;
290: } while (maxTries >= 0);
291: }
292:
293: if (maxTries < 0) { // No suitable backend found
294: if (enabledBackends == 0)
295: throw new SQLException(
296: Translate
297: .get(
298: "loadbalancer.execute.no.backend.enabled",
299: proc.getId()));
300: else if (failedBackend == null)
301: throw new SQLException(
302: Translate
303: .get(
304: "loadbalancer.backend.no.required.storedprocedure",
305: proc.getProcedureKey()));
306: else
307: // Bad query, the only backend that could execute it has failed
308: throw failedException;
309: }
310:
311: // Execute the request on the chosen backend
312: boolean toDisable = false;
313: try {
314: switch (callType) {
315: case CALLABLE_STATEMENT_EXECUTE_QUERY:
316: result = executeStoredProcedureOnBackend(proc,
317: true, backend, metadataCache);
318: break;
319: case CALLABLE_STATEMENT_EXECUTE:
320: result = executeStoredProcedureOnBackend(proc,
321: false, backend, metadataCache);
322: break;
323: default:
324: throw new RuntimeException(
325: "Unhandled call type " + callType
326: + " in executeLPRF");
327: }
328: if (failedBackend != null) { // Previous backend failed
329: if (logger.isWarnEnabled())
330: logger
331: .warn(Translate
332: .get(
333: "loadbalancer.storedprocedure.status",
334: new String[] {
335: String
336: .valueOf(proc
337: .getId()),
338: backend
339: .getName(),
340: failedBackend
341: .getName() }));
342: toDisable = true;
343: }
344: } catch (UnreachableBackendException se) {
345: // Retry on an other backend.
346: continue;
347: } catch (SQLException se) {
348: if (failedBackend != null) { // Bad query, no backend can execute it
349: String msg = Translate
350: .get(
351: "loadbalancer.storedprocedure.failed.twice",
352: new String[] {
353: String.valueOf(proc
354: .getId()),
355: se.getMessage() });
356: if (logger.isInfoEnabled())
357: logger.info(msg);
358: throw new SQLException(msg);
359: } else { // We are the first to fail on this query
360: failedBackend = backend;
361: failedException = se;
362: if (logger.isInfoEnabled())
363: logger
364: .info(Translate
365: .get(
366: "loadbalancer.storedprocedure.failed.on.backend",
367: new String[] {
368: proc
369: .getSqlShortForm(vdb
370: .getSqlShortFormLength()),
371: backend
372: .getName(),
373: se
374: .getMessage() }));
375: continue;
376: }
377: }
378:
379: if (toDisable) { // retry has succeeded and we need to disable the first node that
380: // failed
381: try {
382: if (logger.isWarnEnabled())
383: logger.warn(Translate.get(
384: "loadbalancer.backend.disabling",
385: failedBackend.getName()));
386: disableBackend(failedBackend, true);
387: } catch (SQLException ignore) {
388: } finally {
389: failedBackend = null; // to exit the do{}while
390: }
391: }
392: } while (failedBackend != null);
393: return result;
394: } catch (Throwable e) {
395: String msg = Translate.get(
396: "loadbalancer.storedprocedure.failed.on.backend",
397: new String[] {
398: proc.getSqlShortForm(vdb
399: .getSqlShortFormLength()),
400: backend.getName(), e.getMessage() });
401: logger.fatal(msg, e);
402: throw new SQLException(msg);
403: } finally {
404: vdb.releaseReadLockBackendLists();
405: }
406: }
407:
408: /*
409: * Debug/Monitoring
410: */
411:
412: /**
413: * Gets information about the request load balancer.
414: *
415: * @return <code>String</code> containing information
416: */
417: public String getInformation() {
418: // We don't lock since we don't need a completely accurate value
419: int size = vdb.getBackends().size();
420:
421: if (size == 0)
422: return "RAIDb-2 Round-Robin Request load balancer: !!!Warning!!! No backend nodes found\n";
423: else
424: return "RAIDb-2 Round-Robin Request load balancer (" + size
425: + " backends)\n";
426: }
427:
428: /**
429: * @see org.continuent.sequoia.controller.loadbalancer.raidb2.RAIDb2#getRaidb2Xml
430: */
431: public String getRaidb2Xml() {
432: return "<" + DatabasesXmlTags.ELT_RAIDb_2_RoundRobin + "/>";
433: }
434: }
|