public void processUnacks(String queueName) { ((RedisDynoQueue) queues.get(queueName)).processUnacks(); }
rdq.clear(); rdq.push(messages); messages = rdq.peek(count); long size = rdq.size(); assertEquals(count, size); List<Message> messages2 = rdq.peek(count); assertNotNull(messages2); assertEquals(messages, messages2); List<Message> poped = rdq.pop(count, 1, TimeUnit.SECONDS); assertNotNull(poped); assertEquals(count, poped.size()); ((RedisDynoQueue)rdq).processUnacks(); Message found = rdq.get(msg.getId()); assertNotNull(found); assertEquals(msg.getId(), found.getId()); assertEquals(msg.getTimeout(), found.getTimeout()); assertNull(rdq.get("some fake id")); List<Message> messages3 = rdq.pop(count, 1, TimeUnit.SECONDS); if(messages3.size() < count){ List<Message> messages4 = rdq.pop(count, 1, TimeUnit.SECONDS);
public RedisDynoQueue(Clock clock, String redisKeyPrefix, String queueName, Set<String> allShards, String shardName, int unackScheduleInMS, ShardingStrategy shardingStrategy) { this.clock = clock; this.redisKeyPrefix = redisKeyPrefix; this.queueName = queueName; this.allShards = ImmutableList.copyOf(allShards.stream().collect(Collectors.toList())); this.shardName = shardName; this.messageStoreKey = redisKeyPrefix + ".MESSAGE." + queueName; this.myQueueShard = getQueueShardKey(queueName, shardName); this.shardingStrategy = shardingStrategy; ObjectMapper om = new ObjectMapper(); om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); om.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false); om.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false); om.setSerializationInclusion(Include.NON_NULL); om.setSerializationInclusion(Include.NON_EMPTY); om.disable(SerializationFeature.INDENT_OUTPUT); this.om = om; this.monitor = new QueueMonitor(queueName, shardName); this.prefetchedIds = new ConcurrentLinkedQueue<>(); schedulerForUnacksProcessing = Executors.newScheduledThreadPool(1); schedulerForUnacksProcessing.scheduleAtFixedRate(() -> processUnacks(), unackScheduleInMS, unackScheduleInMS, TimeUnit.MILLISECONDS); logger.info(RedisDynoQueue.class.getName() + " is ready to serve " + queueName); }
/** * * @param queueName Name of the queue * @return Returns the DynoQueue hosting the given queue by name * @see DynoQueue * @see RedisDynoQueue */ public DynoQueue get(String queueName) { String key = queueName.intern(); return queues.computeIfAbsent(key, (keyToCompute) -> new RedisDynoQueue(clock, redisKeyPrefix, queueName, allShards, shardName, unackHandlerIntervalInMS, shardingStrategy) .withUnackTime(unackTime) .withNonQuorumConn(nonQuorumConn) .withQuorumConn(quorumConn)); }
@Override public void clear() { execute("clear", "(a shard in) " + queueName, () -> { for (String shard : allShards) { String queueShard = getQueueShardKey(queueName, shard); String unackShard = getUnackKey(queueName, shard); quorumConn.del(queueShard); quorumConn.del(unackShard); } quorumConn.del(messageStoreKey); return null; }); }
@Override public long size() { Stopwatch sw = monitor.size.start(); try { return execute("size", "(a shard in) " + queueName, () -> { long size = 0; for (String shard : allShards) { size += nonQuorumConn.zcard(getQueueShardKey(queueName, shard)); } return size; }); } finally { sw.stop(); } }
@Test public void testClearQueues() { rdq.clear(); int count = 10; List<Message> messages = new LinkedList<>(); for (int i = 0; i < count; i++) { Message msg = new Message("x" + i, "Hello World-" + i); msg.setPriority(count - i); messages.add(msg); } rdq.push(messages); assertEquals(count, rdq.size()); rdq.clear(); assertEquals(0, rdq.size()); }
@Override public boolean setUnackTimeout(String messageId, long timeout) { Stopwatch sw = monitor.ack.start(); try { return execute("setUnackTimeout", "(a shard in) " + queueName, () -> { double unackScore = Long.valueOf(clock.millis() + timeout).doubleValue(); for (String shard : allShards) { String unackShardKey = getUnackKey(queueName, shard); Double score = quorumConn.zscore(unackShardKey, messageId); if(score != null) { quorumConn.zadd(unackShardKey, unackScore, messageId); return true; } } return false; }); } finally { sw.stop(); } }
@Override public void ack(List<Message> messages) { for(Message message : messages) { ack(message.getId()); } }
@Override public List<Message> pop(int messageCount, int wait, TimeUnit unit) { if (messageCount < 1) { return Collections.emptyList(); } Stopwatch sw = monitor.start(monitor.pop, messageCount); try { long start = clock.millis(); long waitFor = unit.toMillis(wait); prefetch.addAndGet(messageCount); prefetchIds(); while(prefetchedIds.size() < messageCount && ((clock.millis() - start) < waitFor)) { Uninterruptibles.sleepUninterruptibly(200, TimeUnit.MILLISECONDS); prefetchIds(); } return _pop(messageCount); } catch(Exception e) { throw new RuntimeException(e); } finally { sw.stop(); } }
@Override public List<Message> peek(final int messageCount) { Stopwatch sw = monitor.peek.start(); try { Set<String> ids = peekIds(0, messageCount); if (ids == null) { return Collections.emptyList(); } List<Message> msgs = execute("peek", messageStoreKey, () -> { List<Message> messages = new LinkedList<Message>(); for (String id : ids) { String json = nonQuorumConn.hget(messageStoreKey, id); Message message = om.readValue(json, Message.class); messages.add(message); } return messages; }); return msgs; } finally { sw.stop(); } }
private Set<String> peekIds(int offset, int count) { return execute("peekIds", myQueueShard, () -> { double now = Long.valueOf(clock.millis() + 1).doubleValue(); Set<String> scanned = quorumConn.zrangeByScore(myQueueShard, 0, now, offset, count); return scanned; }); }
private <R> R execute(String opName, String keyName, Callable<R> r) { return executeWithRetry(opName, keyName, r, 0); }
@Override public Map<String, Map<String, Long>> shardSizes() { Stopwatch sw = monitor.size.start(); Map<String, Map<String, Long>> shardSizes = new HashMap<>(); try { return execute("shardSizes", "(a shard in) " + queueName, () -> { for (String shard : allShards) { long size = nonQuorumConn.zcard(getQueueShardKey(queueName, shard)); long uacked = nonQuorumConn.zcard(getUnackKey(queueName, shard)); Map<String, Long> shardDetails = new HashMap<>(); shardDetails.put("size", size); shardDetails.put("uacked", uacked); shardSizes.put(shard, shardDetails); } return shardSizes; }); } finally { sw.stop(); } }
@Override public List<String> push(final List<Message> messages) { Stopwatch sw = monitor.start(monitor.push, messages.size()); try { execute("push", "(a shard in) " + queueName, () -> { for (Message message : messages) { String json = om.writeValueAsString(message); quorumConn.hset(messageStoreKey, message.getId(), json); double priority = message.getPriority() / 100.0; double score = Long.valueOf(clock.millis() + message.getTimeout()).doubleValue() + priority; String shard = shardingStrategy.getNextShard(allShards, message); String queueShard = getQueueShardKey(queueName, shard); quorumConn.zadd(queueShard, score, message.getId()); } return messages; }); return messages.stream().map(msg -> msg.getId()).collect(Collectors.toList()); } finally { sw.stop(); } }
@Override public boolean ack(String messageId) { Stopwatch sw = monitor.ack.start(); try { return execute("ack", "(a shard in) " + queueName, () -> { for (String shard : allShards) { String unackShardKey = getUnackKey(queueName, shard); Long removed = quorumConn.zrem(unackShardKey, messageId); if (removed > 0) { quorumConn.hdel(messageStoreKey, messageId); return true; } } return false; }); } finally { sw.stop(); } }
@Override public Message get(String messageId) { Stopwatch sw = monitor.get.start(); try { return execute("get", messageStoreKey, () -> { String json = quorumConn.hget(messageStoreKey, messageId); if(json == null){ if (logger.isDebugEnabled()) { logger.debug("Cannot get the message payload " + messageId); } return null; } Message msg = om.readValue(json, Message.class); return msg; }); } finally { sw.stop(); } }
private <R> R executeWithRetry(String opName, String keyName, Callable<R> r, int retryCount) { try { return r.call(); } catch (ExecutionException e) { if (e.getCause() instanceof DynoException) { if (retryCount < this.retryCount) { return executeWithRetry(opName, keyName, r, ++retryCount); } } throw new RuntimeException(e.getCause()); } catch (Exception e) { throw new RuntimeException( "Operation: ( " + opName + " ) failed on key: [" + keyName + " ].", e); } }