disruptor有两种事件处理器,一个是EventHandler
,另一个是WorkHandler
.
EventHandler
可以彼此独立消费同一个队列中的事件,WorkHandler
可以共同竞争消费同一个队列中的事件。
举例说明:
前提:环形事件队列中有a、b、c、d三个事件。
如果disruptor中有两个EventHandler
类型的处理器eventHandler1
、eventHandler2
,在这种情况下这两个处理器会分别独立去处理这4个事件,也就是说每个事件会被处理两次,一共处理8次事件。
而如果disruptor中两个WorkHandler
类型的workHandler1
、workHandler2
,在这种情况下那么每个任务只会被其中一个处理器处理,这两个处理器的处理事件数加起来是4次。workHandler1
处理了a、c事件,那么workHandler2
就只会处理b、d事件。
了解了这些,就可以能理解使用EventHandler
为啥能实现多线程顺序处理了。
可以给事件定义一个hash函数,根据哈希取余的槽位下标和当前处理器的下标比较,判断出此事件是否应被当前的处理器处理,不该其处理的事件直接忽略掉,避免重复消费
定义的哈希函数接口和统一的基础父类
public interface Hashed<K> {
/**
* 哈希码计算所需key
*
* @return key值
*/
K getKey();
/**
* 计算哈希码
*
* <p>
* 默认实现是使用{@link java.util.HashMap HashMap}的哈希计算公式</br>
* 建议对此计算做个缓存,进一步提高性能
* </p>
*
* @param key 哈希码生成的输入key,此入参是{@link #getKey()}方法的返回值
* @return 哈希码
* @see #getKey()
*/
default int hash(K key) {
if (key == null) {
throw new NullPointerException("key must not be null");
}
int h;
return (h = key.hashCode()) ^ (h >>> 16);
}
}
public abstract class PartitionEventHandler<E extends Hashed<K>, K> implements EventHandler<E>, Cloneable {
protected final Logger log = LoggerFactory.getLogger(getClass());
private final static int NO_INIT_VALUE = -1;
private int index;
private int indexMask;
public final int getIndex() {
return index;
}
public final int getIndexMask() {
return indexMask;
}
@Override
public final void onEvent(E event, long sequence, boolean endOfBatch) throws Exception {
if (index == NO_INIT_VALUE || indexMask == NO_INIT_VALUE) {
throw new IllegalStateException("this should be inited");
}
int position = event.hash(event.getKey()) & indexMask;
if (index != position) {
if (log.isTraceEnabled()) {
log.trace("The event with key [{}] should be handled on slot [{}],but current slot is [{}], it will be ignored.",
event.getKey(), position, index);
}
return;
}
if (log.isDebugEnabled()) {
log.debug("The event with key [{}] is being handled on slot [{}].", event.getKey(), position);
}
doOnEvent(event, sequence, endOfBatch);
}
protected abstract void doOnEvent(E event, long sequence, boolean endOfBatch) throws Exception;
@SafeVarargs
public static <E extends Hashed<K>, K> PartitionEventHandler<E, K>[] initHandlers(final PartitionEventHandler<E, K>... handlers) {
if (handlers == null) {
throw new NullPointerException("handlers must not be null");
}
if (handlers.length == 0) {
throw new IllegalArgumentException("handlers length must be more than zero");
}
if (Integer.bitCount(handlers.length) != 1) {
throw new IllegalArgumentException("handlers count must be a power of 2");
}
int indexMask = handlers.length - 1;
for (int i = 0; i < handlers.length; i++) {
PartitionEventHandler<E, K> handler = handlers[i];
handler.indexMask = indexMask;
handler.index = i;
}
return handlers;
}
@Override
public PartitionEventHandler<E, K> clone() {
try {
@SuppressWarnings("unchecked")
PartitionEventHandler<E, K> handler = (PartitionEventHandler<E, K>) super.clone();
handler.index = NO_INIT_VALUE;
handler.indexMask = NO_INIT_VALUE;
return handler;
} catch (CloneNotSupportedException e) {
//never occur
throw new RuntimeException("can not clone " + getClass(), e);
}
}
public PartitionEventHandler<E, K>[] clones(int count) {
if (count < 0) {
throw new IllegalArgumentException("count must be more then zero");
}
@SuppressWarnings("unchecked")
PartitionEventHandler<E, K>[] result = new PartitionEventHandler[count];
for (int i = 0; i < count; i++) {
result[i] = clone();
}
return result;
}
}
测试代码
public class DisruptorTest05 {
@Test
public void test1() {
CustomizableThreadFactory threadFactory = new CustomizableThreadFactory("event-handler-");
threadFactory.setDaemon(false);
Disruptor<ChatGroupEvent> disruptor = new Disruptor<>(new ChatGroupEventFactory(), 1024, threadFactory);
ChatGroupEventHandler handler = new ChatGroupEventHandler();
PartitionEventHandler<ChatGroupEvent, String>[] handlers = handler.clones(8);
disruptor.handleEventsWith(PartitionEventHandler.initHandlers(handlers));
disruptor.start();
RingBuffer<ChatGroupEvent> ringBuffer = disruptor.getRingBuffer();
Random rdm = new Random();
long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
long seq = ringBuffer.next();
ChatGroupEvent chatGroupEvent = ringBuffer.get(seq);
int rdmInt = rdm.nextInt(12);
chatGroupEvent.setGroupId(String.format("groupId-%02d", rdmInt));
chatGroupEvent.setGroupOwner(String.format("owner-%03d", i));
String type = i % 2 == 0 ? "add" : "exit";
chatGroupEvent.setChangeType(type);
ringBuffer.publish(seq);
}
disruptor.shutdown();
long spent = System.currentTimeMillis() - start;
System.out.println("total time " + spent);
System.out.println();
}
static class ChatGroupEventFactory implements EventFactory<ChatGroupEvent> {
@Override
public ChatGroupEvent newInstance() {
return new ChatGroupEvent();
}
}
static class ChatGroupEventHandler extends PartitionEventHandler<ChatGroupEvent, String> {
@Override
protected void doOnEvent(ChatGroupEvent event, long sequence, boolean endOfBatch) throws Exception {
Thread.sleep(50);
log.info("event key={},owner={},changeType={}, index={}", event.getKey(), event.getGroupOwner(), event.getChangeType(), getIndex());
}
}
static class ChatGroupEvent implements Hashed<String> {
private volatile int hashcode=-1;
private String groupId;
private String changeType;
private String groupOwner;
@Override
public String getKey() {
return groupId;
}
@Override
public int hash(String key) {
if (hashcode == -1) {
synchronized (this){
if (hashcode == -1) {
int h;
hashcode = (h = key.hashCode()) ^ (h >>> 16);
return hashcode;
}
}
}
return hashcode;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getChangeType() {
return changeType;
}
public void setChangeType(String changeType) {
this.changeType = changeType;
}
public String getGroupOwner() {
return groupOwner;
}
public void setGroupOwner(String groupOwner) {
this.groupOwner = groupOwner;
}
}
}