001: /**
002: * Sequoia: Database clustering technology.
003: * Copyright (C) 2002-2004 French National Institute For Research In Computer
004: * Science And Control (INRIA).
005: * Copyright (C) 2005 AmicoSoft, Inc. dba Emic Networks
006: * Contact: sequoia@continuent.org
007: *
008: * Licensed under the Apache License, Version 2.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: * Initial developer(s): Emmanuel Cecchet.
021: * Contributor(s): _______________________
022: */package org.continuent.sequoia.controller.loadbalancer.raidb1;
023:
024: import java.sql.SQLException;
025: import java.util.ArrayList;
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.requests.AbstractRequest;
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-1 Round Robin load balancer featuring (Least Pending Requests First
043: * load balancing algorithm).
044: * <p>
045: * The read requests coming from the Request Manager are sent to the node that
046: * has the least pending read requests among the nodes that can execute the
047: * request. Write requests are broadcasted to all backends.
048: *
049: * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
050: * @version 1.0
051: */
052: public class RAIDb1_LPRF extends RAIDb1 {
053: // How the code is organized ?
054: //
055: // 1. Member variables
056: // 2. Constructor(s)
057: // 3. Request handling
058: // 4. Debug/Monitoring
059:
060: /*
061: * Constructors
062: */
063:
064: /**
065: * Creates a new RAIDb-1 Round Robin request load balancer.
066: *
067: * @param vdb the virtual database this load balancer belongs to.
068: * @param waitForCompletionPolicy how many backends must complete before
069: * returning the result?
070: * @throws Exception if an error occurs
071: */
072: public RAIDb1_LPRF(VirtualDatabase vdb,
073: WaitForCompletionPolicy waitForCompletionPolicy)
074: throws Exception {
075: super (vdb, waitForCompletionPolicy);
076: }
077:
078: /*
079: * Request Handling
080: */
081:
082: /**
083: * Selects the backend using a least pending request first policy. The backend
084: * that has the shortest queue of currently executing queries is chosen to
085: * execute this query.
086: *
087: * @see org.continuent.sequoia.controller.loadbalancer.raidb1.RAIDb1#statementExecuteQuery(SelectRequest,
088: * MetadataCache)
089: */
090: public ControllerResultSet execSingleBackendReadRequest(
091: SelectRequest request, MetadataCache metadataCache)
092: throws SQLException {
093: return (ControllerResultSet) executeLPRF(request,
094: STATEMENT_EXECUTE_QUERY, "Request ", metadataCache);
095: }
096:
097: /**
098: * Selects the backend using a least pending request first policy. The backend
099: * that has the shortest queue of currently executing queries is chosen to
100: * execute this stored procedure.
101: *
102: * @see org.continuent.sequoia.controller.loadbalancer.AbstractLoadBalancer#readOnlyCallableStatementExecuteQuery(StoredProcedure,
103: * MetadataCache)
104: */
105: public ControllerResultSet readOnlyCallableStatementExecuteQuery(
106: StoredProcedure proc, MetadataCache metadataCache)
107: throws SQLException {
108: return (ControllerResultSet) executeLPRF(proc,
109: CALLABLE_STATEMENT_EXECUTE_QUERY, "Stored procedure ",
110: metadataCache);
111: }
112:
113: /**
114: * Selects the backend using a least pending request first policy. The backend
115: * that has the shortest queue of currently executing queries is chosen to
116: * execute this stored procedure.
117: *
118: * @see org.continuent.sequoia.controller.loadbalancer.AbstractLoadBalancer#readOnlyCallableStatementExecute(StoredProcedure,
119: * MetadataCache)
120: */
121: public ExecuteResult readOnlyCallableStatementExecute(
122: StoredProcedure proc, MetadataCache metadataCache)
123: throws SQLException {
124: return (ExecuteResult) executeLPRF(proc,
125: CALLABLE_STATEMENT_EXECUTE, "Stored procedure ",
126: metadataCache);
127: }
128:
129: /**
130: * Common code to execute a SelectRequest or a StoredProcedure on a backend
131: * chosen using a LPRF algorithm.
132: *
133: * @param request a <code>SelectRequest</code> or
134: * <code>StoredProcedure</code>
135: * @param callType one of STATEMENT_EXECUTE_QUERY,
136: * CALLABLE_STATEMENT_EXECUTE_QUERY or CALLABLE_STATEMENT_EXECUTE
137: * @param errorMsgPrefix the error message prefix, usually "Request " or
138: * "Stored procedure " ... failed because ...
139: * @param metadataCache the metadataCache if any or null
140: * @return a <code>ControllerResultSet</code> or an
141: * <code>ExecuteResult</code> object
142: * @throws SQLException if an error occurs
143: */
144: private Object executeLPRF(AbstractRequest request, int callType,
145: String errorMsgPrefix, MetadataCache metadataCache)
146: throws SQLException {
147: // Choose a backend
148: try {
149: vdb.acquireReadLockBackendLists();
150: } catch (InterruptedException e) {
151: String msg = Translate.get(
152: "loadbalancer.backendlist.acquire.readlock.failed",
153: e);
154: logger.error(msg);
155: throw new SQLException(msg);
156: }
157:
158: /*
159: * The backend that will execute the query
160: */
161: DatabaseBackend backend = null;
162:
163: // Note that vdb lock is released in the finally clause of this try/catch
164: // block
165: try {
166: ArrayList backends = vdb.getBackends();
167: int size = backends.size();
168:
169: // Choose the backend that has the least pending requests
170: int leastRequests = 0;
171: for (int i = 0; i < size; i++) {
172: DatabaseBackend b = (DatabaseBackend) backends.get(i);
173: if (b.isReadEnabled()) {
174: int pending = b.getPendingRequests().size();
175: if ((backend == null) || (pending < leastRequests)) {
176: backend = b;
177: if (pending == 0)
178: break; // Stop here we will never find a less loaded node
179: else
180: leastRequests = pending;
181: }
182: }
183: }
184:
185: } catch (Throwable e) {
186: String msg = Translate.get(
187: "loadbalancer.execute.find.backend.failed",
188: new String[] {
189: request.getSqlShortForm(vdb
190: .getSqlShortFormLength()),
191: e.getMessage() });
192: logger.error(msg, e);
193: throw new SQLException(msg);
194: } finally {
195: vdb.releaseReadLockBackendLists();
196: }
197:
198: if (backend == null)
199: throw new NoMoreBackendException(Translate.get(
200: "loadbalancer.execute.no.backend.enabled", request
201: .getId()));
202:
203: // Execute the request on the chosen backend
204: try {
205: switch (callType) {
206: case STATEMENT_EXECUTE_QUERY:
207: return executeRequestOnBackend((SelectRequest) request,
208: backend, metadataCache);
209: case CALLABLE_STATEMENT_EXECUTE_QUERY:
210: return executeStoredProcedureOnBackend(
211: (StoredProcedure) request, true, backend,
212: metadataCache);
213: case CALLABLE_STATEMENT_EXECUTE:
214: return executeStoredProcedureOnBackend(
215: (StoredProcedure) request, false, backend,
216: metadataCache);
217: default:
218: throw new RuntimeException("Unhandled call type "
219: + callType + " in executeLPRF");
220: }
221: } catch (UnreachableBackendException urbe) {
222: // Try to execute query on different backend
223: return executeLPRF(request, callType, errorMsgPrefix,
224: metadataCache);
225: } catch (SQLException se) {
226: String msg = Translate.get("loadbalancer.something.failed",
227: new String[] { errorMsgPrefix,
228: String.valueOf(request.getId()),
229: se.getMessage() });
230: if (logger.isInfoEnabled())
231: logger.info(msg);
232: throw se;
233: } catch (RuntimeException e) {
234: String msg = Translate.get(
235: "loadbalancer.something.failed.on", new String[] {
236: errorMsgPrefix,
237: request.getSqlShortForm(vdb
238: .getSqlShortFormLength()),
239: backend.getName(), e.getMessage() });
240: logger.error(msg, e);
241: throw new SQLException(msg);
242: }
243: }
244:
245: /*
246: * Debug/Monitoring
247: */
248:
249: /**
250: * Gets information about the request load balancer.
251: *
252: * @return <code>String</code> containing information
253: */
254: public String getInformation() {
255: // We don't lock since we don't need a top accurate value
256: int size = vdb.getBackends().size();
257:
258: if (size == 0)
259: return "RAIDb-1 Least Pending Request First load balancer: !!!Warning!!! No backend nodes found\n";
260: else
261: return "RAIDb-1 Least Pending Request First load balancer ("
262: + size + " backends)\n";
263: }
264:
265: /**
266: * @see org.continuent.sequoia.controller.loadbalancer.raidb1.RAIDb1#getRaidb1Xml
267: */
268: public String getRaidb1Xml() {
269: return "<"
270: + DatabasesXmlTags.ELT_RAIDb_1_LeastPendingRequestsFirst
271: + "/>";
272: }
273:
274: }
|