Java tutorial
/******************************************************************************* * * Copyright 2015 Walmart, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *******************************************************************************/ package com.oneops.daq.jms; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.oneops.sensor.events.BasicEvent; import com.oneops.sensor.events.PerfEvent; import com.oneops.sensor.thresholds.Threshold; import com.oneops.sensor.thresholds.ThresholdsDao; import com.oneops.util.AMQConnectorURI; import org.apache.activemq.ActiveMQConnection; import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.pool.PooledConnectionFactory; import org.apache.log4j.Logger; import org.springframework.jms.JmsException; import org.springframework.jms.core.JmsTemplate; import javax.jms.DeliveryMode; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.ObjectMessage; import java.io.IOException; import java.util.HashMap; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; //import org.springframework.jms.connection.CachingConnectionFactory; /** * The Class SensorPublisher. */ public class SensorPublisher { private static final Logger logger = Logger.getLogger(SensorPublisher.class); private static final int MINUTE = 60 * 1000; private String user = ActiveMQConnection.DEFAULT_USER; private String password = ActiveMQConnection.DEFAULT_PASSWORD; private String url = ActiveMQConnection.DEFAULT_BROKER_URL + "?connectionTimeout=1000"; private String queueBase = "perf-in-q"; private ConcurrentHashMap<Long, Long> manifestCache = new ConcurrentHashMap<>(); private final AtomicLong eventCounter = new AtomicLong(); private final AtomicLong missingManifestCounter = new AtomicLong(); private final AtomicLong failedThresholdLoadCounter = new AtomicLong(); private static final Threshold NO_OP_THRESHOLD = new Threshold(); private class ThresholdHolderWithExpiration { private long expiration; private Threshold threshold; ThresholdHolderWithExpiration(Threshold threshold) { this(threshold, -1); } ThresholdHolderWithExpiration(Threshold threshold, long expiration) { this.threshold = threshold; this.expiration = (expiration == -1) ? -1 : (System.currentTimeMillis() + expiration); } Threshold getThreshold() { return threshold; } boolean isExpired() { return expiration > 0 && System.currentTimeMillis() > expiration; } } private static int ttlForNoThresholdCache = Integer.parseInt(System.getProperty("no_threshold_cache_ttl", "2")); private CacheLoader<String, ThresholdHolderWithExpiration> loader = new CacheLoader<String, ThresholdHolderWithExpiration>() { @Override public ThresholdHolderWithExpiration load(String key) throws Exception { String[] keyParts = key.split(":"); Long manifestId = Long.parseLong(keyParts[0]); Threshold threshold = thresholdsDao.getThreshold(manifestId, keyParts[1]); logger.debug("loading: " + manifestId.toString() + " " + keyParts[1]); if (threshold == null || (threshold.getThresholdJson().equals("n") && !threshold.isHeartbeat())) { return new ThresholdHolderWithExpiration(NO_OP_THRESHOLD, ttlForNoThresholdCache * MINUTE); } return new ThresholdHolderWithExpiration(threshold); } }; private static int thresholdTTL = Integer.parseInt(System.getProperty("threshold_cache_ttl", "15")); private static String mqConnectionTimeout = System.getProperty("mqTimeout", "1000"); // timeout message send after 1 second private static String mqConnectionStartupRetries = System.getProperty("mqStartupRetries", "5"); // only reconnect 5 times on startup (to avoid publisher being stuck if MQ is down on startup private static int mqConnectionThreshold = Integer.parseInt(System.getProperty("mqRetryTimeout", "10000")); // discard all the published messages for mqRetryTimeout milliseconds before attempting to send message again private long lastFailureTimestamp = -1; private LoadingCache<String, ThresholdHolderWithExpiration> thresholdCache = CacheBuilder.newBuilder() .refreshAfterWrite(thresholdTTL, TimeUnit.MINUTES).build(loader); // -Dpoolsize=n private static int poolsize = Integer.parseInt(System.getProperty("poolsize", "1")); private JmsTemplate[] producers = new JmsTemplate[poolsize]; private ThresholdsDao thresholdsDao = null; /** * Sets the threshold dao. * * @param thresholdDao the new threshold dao */ public void setThresholdDao(ThresholdsDao thresholdDao) { this.thresholdsDao = thresholdDao; } private void showParameters() { logger.info("Connecting to URL: " + url); logger.info("Base queue name : " + queueBase); logger.info("poolsize : " + poolsize); } /** * Inits the. * * @throws JMSException the jMS exception */ public void init() throws JMSException { Properties properties = new Properties(); try { properties.load(this.getClass().getResourceAsStream("/sink.properties")); } catch (IOException e) { logger.error("got: " + e.getMessage()); } user = properties.getProperty("amq.user"); password = System.getenv("KLOOPZ_AMQ_PASS"); if (password == null) { throw new JMSException("missing KLOOPZ_AMQ_PASS env var"); } AMQConnectorURI connectStringGenerator = new AMQConnectorURI(); connectStringGenerator.setHost("opsmq"); connectStringGenerator.setProtocol("tcp"); connectStringGenerator.setPort(61616); connectStringGenerator.setTransport("failover"); connectStringGenerator.setDnsResolve(true); connectStringGenerator.setKeepAlive(true); HashMap<String, String> transportOptions = new HashMap<>(); transportOptions.put("initialReconnectDelay", "1000"); transportOptions.put("startupMaxReconnectAttempts", mqConnectionStartupRetries); transportOptions.put("timeout", mqConnectionTimeout); transportOptions.put("useExponentialBackOff", "false"); connectStringGenerator.setTransportOptions(transportOptions); url = connectStringGenerator.build(); showParameters(); // Create the connection. ActiveMQConnectionFactory amqConnectionFactory = new ActiveMQConnectionFactory(user, password, url); amqConnectionFactory.setUseAsyncSend(true); PooledConnectionFactory pooledConnectionFactory = new PooledConnectionFactory(amqConnectionFactory); pooledConnectionFactory.setMaxConnections(4); pooledConnectionFactory.setIdleTimeout(10000); for (int i = 0; i < poolsize; i++) { JmsTemplate producerTemplate = new JmsTemplate(pooledConnectionFactory); producerTemplate.setSessionTransacted(false); int shard = i + 1; Destination perfin = new org.apache.activemq.command.ActiveMQQueue(queueBase + "-" + shard); producerTemplate.setDefaultDestination(perfin); producerTemplate.setDeliveryPersistent(false); producers[i] = producerTemplate; } } /** * Enrich and publish. * * @param event the event * @throws JMSException the jMS exception * @throws ExecutionException */ @SuppressWarnings("unused") public void enrichAndPublish(PerfEvent event) throws JMSException { if (eventCounter.incrementAndGet() % 1000 == 0) logger.info( "Publish event count: " + eventCounter.get() + " manifest miss: " + missingManifestCounter.get() + " failed threshold load count: " + failedThresholdLoadCounter.get()); Long manifestId; if (manifestCache.containsKey(event.getCiId())) manifestId = manifestCache.get(event.getCiId()); else { manifestId = thresholdsDao.getManifestId(event.getCiId()); if (manifestId != null) manifestCache.put(event.getCiId(), manifestId); } if (manifestId == null) { long missCount = missingManifestCounter.incrementAndGet(); logger.warn("Failed to map ciId: " + event.getCiId() + " to manifestId. Please fix"); return; } String key = manifestId.toString() + ":" + event.getSource(); try { ThresholdHolderWithExpiration holder = thresholdCache.get(key); if (holder.isExpired()) { thresholdCache.refresh(key); holder = thresholdCache.get(key); // get it again after cash refresh because it is expired } Threshold tr = holder.getThreshold(); if (tr == NO_OP_THRESHOLD) { return; } logger.debug("Threshold: " + tr.getSource() + " " + tr.getThresholdJson()); event.setManifestId(manifestId); event.setChecksum(tr.getCrc()); } catch (Exception e) { logger.warn("Failed threshold load:" + manifestId + "::" + event.getSource(), e); long missCount = failedThresholdLoadCounter.incrementAndGet(); return; } publishMessage(event); } /** * Publish message. * * @param event the event * @throws JMSException the jMS exception */ public void publishMessage(final BasicEvent event) throws JMSException { if (System.currentTimeMillis() > lastFailureTimestamp) { int shard = (int) (event.getManifestId() % poolsize); try { producers[shard].send(session -> { ObjectMessage message = session.createObjectMessage(event); message.setJMSDeliveryMode(DeliveryMode.NON_PERSISTENT); message.setLongProperty("ciId", event.getCiId()); message.setLongProperty("manifestId", event.getManifestId()); message.setStringProperty("source", event.getSource()); if (logger.isDebugEnabled()) { logger.debug("Published: ciId:" + event.getCiId() + "; source:" + event.getSource()); } return message; }); lastFailureTimestamp = -1; } catch (JmsException exception) { logger.warn("There was an error sending a message. Discarding messages for " + mqConnectionThreshold + " ms"); lastFailureTimestamp = System.currentTimeMillis() + mqConnectionThreshold; } } } /** * Cleanup. */ public void cleanup() { logger.info("Closing AMQ connection"); closeConnection(); } /** * Close connection. */ public void closeConnection() { for (JmsTemplate jt : producers) { ((PooledConnectionFactory) jt.getConnectionFactory()).stop(); } producers = null; } }