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.activemq.broker.region.policy; 018 019import java.util.ArrayList; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.Map.Entry; 024import java.util.concurrent.ConcurrentHashMap; 025 026import org.apache.activemq.broker.Broker; 027import org.apache.activemq.broker.ConnectionContext; 028import org.apache.activemq.broker.region.Destination; 029import org.apache.activemq.broker.region.Subscription; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033/** 034 * Abort slow consumers when they reach the configured threshold of slowness, 035 * 036 * default is that a consumer that has not Ack'd a message for 30 seconds is slow. 037 * 038 * @org.apache.xbean.XBean 039 */ 040public class AbortSlowAckConsumerStrategy extends AbortSlowConsumerStrategy { 041 042 private static final Logger LOG = LoggerFactory.getLogger(AbortSlowAckConsumerStrategy.class); 043 044 private final Map<String, Destination> destinations = new ConcurrentHashMap<String, Destination>(); 045 private long maxTimeSinceLastAck = 30*1000; 046 private boolean ignoreIdleConsumers = true; 047 private boolean ignoreNetworkConsumers = true; 048 049 public AbortSlowAckConsumerStrategy() { 050 this.name = "AbortSlowAckConsumerStrategy@" + hashCode(); 051 } 052 053 @Override 054 public void setBrokerService(Broker broker) { 055 super.setBrokerService(broker); 056 057 // Task starts right away since we may not receive any slow consumer events. 058 if (taskStarted.compareAndSet(false, true)) { 059 scheduler.executePeriodically(this, getCheckPeriod()); 060 } 061 } 062 063 @Override 064 public void slowConsumer(ConnectionContext context, Subscription subs) { 065 // Ignore these events, we just look at time since last Ack. 066 } 067 068 @Override 069 public void run() { 070 071 if (maxTimeSinceLastAck < 0) { 072 // nothing to do 073 LOG.info("no limit set, slowConsumer strategy has nothing to do"); 074 return; 075 } 076 077 if (getMaxSlowDuration() > 0) { 078 // For subscriptions that are already slow we mark them again and check below if 079 // they've exceeded their configured lifetime. 080 for (SlowConsumerEntry entry : slowConsumers.values()) { 081 entry.mark(); 082 } 083 } 084 085 List<Destination> disposed = new ArrayList<Destination>(); 086 087 for (Destination destination : destinations.values()) { 088 if (destination.isDisposed()) { 089 disposed.add(destination); 090 continue; 091 } 092 093 // Not explicitly documented but this returns a stable copy. 094 List<Subscription> subscribers = destination.getConsumers(); 095 096 updateSlowConsumersList(subscribers); 097 } 098 099 // Clean up an disposed destinations to save space. 100 for (Destination destination : disposed) { 101 destinations.remove(destination.getName()); 102 } 103 104 abortAllQualifiedSlowConsumers(); 105 } 106 107 private void updateSlowConsumersList(List<Subscription> subscribers) { 108 for (Subscription subscriber : subscribers) { 109 if (isIgnoreNetworkSubscriptions() && subscriber.getConsumerInfo().isNetworkSubscription()) { 110 if (slowConsumers.remove(subscriber) != null) { 111 LOG.info("network sub: {} is no longer slow", subscriber.getConsumerInfo().getConsumerId()); 112 } 113 continue; 114 } 115 116 if (isIgnoreIdleConsumers() && subscriber.getDispatchedQueueSize() == 0) { 117 // Not considered Idle so ensure its cleared from the list 118 if (slowConsumers.remove(subscriber) != null) { 119 LOG.info("idle sub: {} is no longer slow", subscriber.getConsumerInfo().getConsumerId()); 120 } 121 continue; 122 } 123 124 long lastAckTime = subscriber.getTimeOfLastMessageAck(); 125 long timeDelta = System.currentTimeMillis() - lastAckTime; 126 127 if (timeDelta > maxTimeSinceLastAck) { 128 if (!slowConsumers.containsKey(subscriber)) { 129 LOG.debug("sub: {} is now slow", subscriber.getConsumerInfo().getConsumerId()); 130 SlowConsumerEntry entry = new SlowConsumerEntry(subscriber.getContext()); 131 entry.mark(); // mark consumer on first run 132 slowConsumers.put(subscriber, entry); 133 } else if (getMaxSlowCount() > 0) { 134 slowConsumers.get(subscriber).slow(); 135 } 136 } else { 137 if (slowConsumers.remove(subscriber) != null) { 138 LOG.info("sub: {} is no longer slow", subscriber.getConsumerInfo().getConsumerId()); 139 } 140 } 141 } 142 } 143 144 private void abortAllQualifiedSlowConsumers() { 145 HashMap<Subscription, SlowConsumerEntry> toAbort = new HashMap<Subscription, SlowConsumerEntry>(); 146 for (Entry<Subscription, SlowConsumerEntry> entry : slowConsumers.entrySet()) { 147 if (getMaxSlowDuration() > 0 && (entry.getValue().markCount * getCheckPeriod() >= getMaxSlowDuration()) || 148 getMaxSlowCount() > 0 && entry.getValue().slowCount >= getMaxSlowCount()) { 149 150 LOG.trace("Transferring consumer{} to the abort list: {} slow duration = {}, slow count = {}", 151 new Object[]{ entry.getKey().getConsumerInfo().getConsumerId(), 152 entry.getValue().markCount * getCheckPeriod(), 153 entry.getValue().getSlowCount() }); 154 155 toAbort.put(entry.getKey(), entry.getValue()); 156 slowConsumers.remove(entry.getKey()); 157 } else { 158 159 LOG.trace("Not yet time to abort consumer {}: slow duration = {}, slow count = {}", new Object[]{ entry.getKey().getConsumerInfo().getConsumerId(), entry.getValue().markCount * getCheckPeriod(), entry.getValue().slowCount }); 160 161 } 162 } 163 164 // Now if any subscriptions made it into the aborts list we can kick them. 165 abortSubscription(toAbort, isAbortConnection()); 166 } 167 168 @Override 169 public void addDestination(Destination destination) { 170 this.destinations.put(destination.getName(), destination); 171 } 172 173 /** 174 * Gets the maximum time since last Ack before a subscription is considered to be slow. 175 * 176 * @return the maximum time since last Ack before the consumer is considered to be slow. 177 */ 178 public long getMaxTimeSinceLastAck() { 179 return maxTimeSinceLastAck; 180 } 181 182 /** 183 * Sets the maximum time since last Ack before a subscription is considered to be slow. 184 * 185 * @param maxTimeSinceLastAck 186 * the maximum time since last Ack (mills) before the consumer is considered to be slow. 187 */ 188 public void setMaxTimeSinceLastAck(long maxTimeSinceLastAck) { 189 this.maxTimeSinceLastAck = maxTimeSinceLastAck; 190 } 191 192 /** 193 * Returns whether the strategy is configured to ignore consumers that are simply idle, i.e 194 * consumers that have no pending acks (dispatch queue is empty). 195 * 196 * @return true if the strategy will ignore idle consumer when looking for slow consumers. 197 */ 198 public boolean isIgnoreIdleConsumers() { 199 return ignoreIdleConsumers; 200 } 201 202 /** 203 * Sets whether the strategy is configured to ignore consumers that are simply idle, i.e 204 * consumers that have no pending acks (dispatch queue is empty). 205 * 206 * When configured to not ignore idle consumers this strategy acks not only on consumers 207 * that are actually slow but also on any consumer that has not received any messages for 208 * the maxTimeSinceLastAck. This allows for a way to evict idle consumers while also 209 * aborting slow consumers. 210 * 211 * @param ignoreIdleConsumers 212 * Should this strategy ignore idle consumers or consider all consumers when checking 213 * the last ack time verses the maxTimeSinceLastAck value. 214 */ 215 public void setIgnoreIdleConsumers(boolean ignoreIdleConsumers) { 216 this.ignoreIdleConsumers = ignoreIdleConsumers; 217 } 218 219 /** 220 * Returns whether the strategy is configured to ignore subscriptions that are from a network 221 * connection. 222 * 223 * @return true if the strategy will ignore network connection subscriptions when looking 224 * for slow consumers. 225 */ 226 public boolean isIgnoreNetworkSubscriptions() { 227 return ignoreNetworkConsumers; 228 } 229 230 /** 231 * Sets whether the strategy is configured to ignore consumers that are part of a network 232 * connection to another broker. 233 * 234 * When configured to not ignore idle consumers this strategy acts not only on consumers 235 * that are actually slow but also on any consumer that has not received any messages for 236 * the maxTimeSinceLastAck. This allows for a way to evict idle consumers while also 237 * aborting slow consumers however for a network subscription this can create a lot of 238 * unnecessary churn and if the abort connection option is also enabled this can result 239 * in the entire network connection being torn down and rebuilt for no reason. 240 * 241 * @param ignoreNetworkConsumers 242 * Should this strategy ignore subscriptions made by a network connector. 243 */ 244 public void setIgnoreNetworkConsumers(boolean ignoreNetworkConsumers) { 245 this.ignoreNetworkConsumers = ignoreNetworkConsumers; 246 } 247 248}