001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.camel.impl; 018 019import java.util.EventObject; 020import java.util.LinkedHashSet; 021import java.util.Set; 022import java.util.concurrent.locks.Lock; 023import java.util.concurrent.locks.ReentrantLock; 024 025import org.apache.camel.CamelContext; 026import org.apache.camel.CamelContextAware; 027import org.apache.camel.Consumer; 028import org.apache.camel.Exchange; 029import org.apache.camel.LoggingLevel; 030import org.apache.camel.Route; 031import org.apache.camel.management.event.ExchangeCompletedEvent; 032import org.apache.camel.support.EventNotifierSupport; 033import org.apache.camel.util.CamelLogger; 034import org.apache.camel.util.ObjectHelper; 035import org.apache.camel.util.ServiceHelper; 036import org.slf4j.LoggerFactory; 037 038/** 039 * A throttle based {@link org.apache.camel.spi.RoutePolicy} which is capable of dynamic 040 * throttling a route based on number of current inflight exchanges. 041 * <p/> 042 * This implementation supports two scopes {@link ThrottlingScope#Context} and {@link ThrottlingScope#Route} (is default). 043 * If context scope is selected then this implementation will use a {@link org.apache.camel.spi.EventNotifier} to listen 044 * for events when {@link Exchange}s is done, and trigger the {@link #throttle(org.apache.camel.Route, org.apache.camel.Exchange)} 045 * method. If the route scope is selected then <b>no</b> {@link org.apache.camel.spi.EventNotifier} is in use, as there is already 046 * a {@link org.apache.camel.spi.Synchronization} callback on the current {@link Exchange} which triggers the 047 * {@link #throttle(org.apache.camel.Route, org.apache.camel.Exchange)} when the current {@link Exchange} is done. 048 * 049 * @version 050 */ 051public class ThrottlingInflightRoutePolicy extends RoutePolicySupport implements CamelContextAware { 052 053 public enum ThrottlingScope { 054 Context, Route 055 } 056 057 private final Set<Route> routes = new LinkedHashSet<Route>(); 058 private ContextScopedEventNotifier eventNotifier; 059 private CamelContext camelContext; 060 private final Lock lock = new ReentrantLock(); 061 private ThrottlingScope scope = ThrottlingScope.Route; 062 private int maxInflightExchanges = 1000; 063 private int resumePercentOfMax = 70; 064 private int resumeInflightExchanges = 700; 065 private LoggingLevel loggingLevel = LoggingLevel.INFO; 066 private CamelLogger logger; 067 068 public ThrottlingInflightRoutePolicy() { 069 } 070 071 @Override 072 public String toString() { 073 return "ThrottlingInflightRoutePolicy[" + maxInflightExchanges + " / " + resumePercentOfMax + "% using scope " + scope + "]"; 074 } 075 076 public CamelContext getCamelContext() { 077 return camelContext; 078 } 079 080 public void setCamelContext(CamelContext camelContext) { 081 this.camelContext = camelContext; 082 } 083 084 @Override 085 public void onInit(Route route) { 086 // we need to remember the routes we apply for 087 routes.add(route); 088 } 089 090 @Override 091 public void onExchangeDone(Route route, Exchange exchange) { 092 // if route scoped then throttle directly 093 // as context scoped is handled using an EventNotifier instead 094 if (scope == ThrottlingScope.Route) { 095 throttle(route, exchange); 096 } 097 } 098 099 /** 100 * Throttles the route when {@link Exchange}s is done. 101 * 102 * @param route the route 103 * @param exchange the exchange 104 */ 105 protected void throttle(Route route, Exchange exchange) { 106 // this works the best when this logic is executed when the exchange is done 107 Consumer consumer = route.getConsumer(); 108 109 int size = getSize(route, exchange); 110 boolean stop = maxInflightExchanges > 0 && size > maxInflightExchanges; 111 if (log.isTraceEnabled()) { 112 log.trace("{} > 0 && {} > {} evaluated as {}", new Object[]{maxInflightExchanges, size, maxInflightExchanges, stop}); 113 } 114 if (stop) { 115 try { 116 lock.lock(); 117 stopConsumer(size, consumer); 118 } catch (Exception e) { 119 handleException(e); 120 } finally { 121 lock.unlock(); 122 } 123 } 124 125 // reload size in case a race condition with too many at once being invoked 126 // so we need to ensure that we read the most current size and start the consumer if we are already to low 127 size = getSize(route, exchange); 128 boolean start = size <= resumeInflightExchanges; 129 if (log.isTraceEnabled()) { 130 log.trace("{} <= {} evaluated as {}", new Object[]{size, resumeInflightExchanges, start}); 131 } 132 if (start) { 133 try { 134 lock.lock(); 135 startConsumer(size, consumer); 136 } catch (Exception e) { 137 handleException(e); 138 } finally { 139 lock.unlock(); 140 } 141 } 142 } 143 144 public int getMaxInflightExchanges() { 145 return maxInflightExchanges; 146 } 147 148 /** 149 * Sets the upper limit of number of concurrent inflight exchanges at which point reached 150 * the throttler should suspend the route. 151 * <p/> 152 * Is default 1000. 153 * 154 * @param maxInflightExchanges the upper limit of concurrent inflight exchanges 155 */ 156 public void setMaxInflightExchanges(int maxInflightExchanges) { 157 this.maxInflightExchanges = maxInflightExchanges; 158 // recalculate, must be at least at 1 159 this.resumeInflightExchanges = Math.max(resumePercentOfMax * maxInflightExchanges / 100, 1); 160 } 161 162 public int getResumePercentOfMax() { 163 return resumePercentOfMax; 164 } 165 166 /** 167 * Sets at which percentage of the max the throttler should start resuming the route. 168 * <p/> 169 * Will by default use 70%. 170 * 171 * @param resumePercentOfMax the percentage must be between 0 and 100 172 */ 173 public void setResumePercentOfMax(int resumePercentOfMax) { 174 if (resumePercentOfMax < 0 || resumePercentOfMax > 100) { 175 throw new IllegalArgumentException("Must be a percentage between 0 and 100, was: " + resumePercentOfMax); 176 } 177 178 this.resumePercentOfMax = resumePercentOfMax; 179 // recalculate, must be at least at 1 180 this.resumeInflightExchanges = Math.max(resumePercentOfMax * maxInflightExchanges / 100, 1); 181 } 182 183 public ThrottlingScope getScope() { 184 return scope; 185 } 186 187 /** 188 * Sets which scope the throttling should be based upon, either route or total scoped. 189 * 190 * @param scope the scope 191 */ 192 public void setScope(ThrottlingScope scope) { 193 this.scope = scope; 194 } 195 196 public LoggingLevel getLoggingLevel() { 197 return loggingLevel; 198 } 199 200 public CamelLogger getLogger() { 201 if (logger == null) { 202 logger = createLogger(); 203 } 204 return logger; 205 } 206 207 /** 208 * Sets the logger to use for logging throttling activity. 209 * 210 * @param logger the logger 211 */ 212 public void setLogger(CamelLogger logger) { 213 this.logger = logger; 214 } 215 216 /** 217 * Sets the logging level to report the throttling activity. 218 * <p/> 219 * Is default <tt>INFO</tt> level. 220 * 221 * @param loggingLevel the logging level 222 */ 223 public void setLoggingLevel(LoggingLevel loggingLevel) { 224 this.loggingLevel = loggingLevel; 225 } 226 227 protected CamelLogger createLogger() { 228 return new CamelLogger(LoggerFactory.getLogger(ThrottlingInflightRoutePolicy.class), getLoggingLevel()); 229 } 230 231 private int getSize(Route route, Exchange exchange) { 232 if (scope == ThrottlingScope.Context) { 233 return exchange.getContext().getInflightRepository().size(); 234 } else { 235 return exchange.getContext().getInflightRepository().size(route.getId()); 236 } 237 } 238 239 private void startConsumer(int size, Consumer consumer) throws Exception { 240 boolean started = super.startConsumer(consumer); 241 if (started) { 242 getLogger().log("Throttling consumer: " + size + " <= " + resumeInflightExchanges + " inflight exchange by resuming consumer: " + consumer); 243 } 244 } 245 246 private void stopConsumer(int size, Consumer consumer) throws Exception { 247 boolean stopped = super.stopConsumer(consumer); 248 if (stopped) { 249 getLogger().log("Throttling consumer: " + size + " > " + maxInflightExchanges + " inflight exchange by suspending consumer: " + consumer); 250 } 251 } 252 253 @Override 254 protected void doStart() throws Exception { 255 ObjectHelper.notNull(camelContext, "CamelContext", this); 256 if (scope == ThrottlingScope.Context) { 257 eventNotifier = new ContextScopedEventNotifier(); 258 // must start the notifier before it can be used 259 ServiceHelper.startService(eventNotifier); 260 // we are in context scope, so we need to use an event notifier to keep track 261 // when any exchanges is done on the camel context. 262 // This ensures we can trigger accordingly to context scope 263 camelContext.getManagementStrategy().addEventNotifier(eventNotifier); 264 } 265 } 266 267 @Override 268 protected void doStop() throws Exception { 269 ObjectHelper.notNull(camelContext, "CamelContext", this); 270 if (scope == ThrottlingScope.Context) { 271 camelContext.getManagementStrategy().removeEventNotifier(eventNotifier); 272 } 273 } 274 275 /** 276 * {@link org.apache.camel.spi.EventNotifier} to keep track on when {@link Exchange} 277 * is done, so we can throttle accordingly. 278 */ 279 private class ContextScopedEventNotifier extends EventNotifierSupport { 280 281 @Override 282 public void notify(EventObject event) throws Exception { 283 ExchangeCompletedEvent completedEvent = (ExchangeCompletedEvent) event; 284 for (Route route : routes) { 285 throttle(route, completedEvent.getExchange()); 286 } 287 } 288 289 @Override 290 public boolean isEnabled(EventObject event) { 291 return event instanceof ExchangeCompletedEvent; 292 } 293 294 @Override 295 protected void doStart() throws Exception { 296 // noop 297 } 298 299 @Override 300 protected void doStop() throws Exception { 301 // noop 302 } 303 304 @Override 305 public String toString() { 306 return "ContextScopedEventNotifier"; 307 } 308 } 309 310}