在之前的文章中我们详细介绍过Netty中的NioEventLoop,NioEventLoop从本质上讲是一个事件循环执行器,每个NioEventLoop都会绑定一个对应的线程通过一个for(;;)循环来处理事件消息。今天我们就借鉴NioEventLoop,并加入消息分发策略构建一个基础的Eventloop线程模型。

整个线程模型可以划分为三部分

事件监听及分发(Event):  监听消息事件并通过分发器分发;

分发器(Dispatch):将消息按照分发策略分发到对应的事件循环器中;

事件循环器 (EventLoop) :绑定一个对应的线程与消息队列,循环处理消息;

模型图如下所示

事件监听器把监听到的事件交给分发器进行分发,分发器根据一定的分发策略把消息事件分发至对应的Eventloop的消息事件队列中,每个EventLoop内部会启动一个线程轮询队列中的消息事件并进行处理;

下面结合代码看下具体实现

一、事件监听及分发

首先是事件监听机制,定义了事件、事件源、事件监听器三部分

/**
* 事件类,用于封装事件源及一些与事件相关的参数.
*/
public class LoopEvent extends EventObject { private static final long serialVersionUID = 1L; public LoopEvent(Object source) {
super(source);
// TODO Auto-generated constructor stub
} } /**
* 事件源
*
*/
public class LoopEventSource {
// 监听器容器
private static Set<AbstractEventListener> listeners; public LoopEventSource() {
listeners = new HashSet<AbstractEventListener>();
} // 给事件源注册监听器
public void addEventListener(AbstractEventListener listener) {
listeners.add(listener);
} // 当事件发生时,通知注册在该事件源上的所有监听器做出相应的反应(调用回调方法)
public void notifies(LoopEvent event) {
AbstractEventListener listener = null;
try {
Iterator<AbstractEventListener> iterator = listeners.iterator();
while (iterator.hasNext()) {
listener = iterator.next();
listener.fireEvent(event); // 如果事件消息类型不同,可以进行封装
} } catch (Exception e) {
e.printStackTrace();
} } } /**
* 事件监听器
*
*/
public class LoopEventListener extends AbstractEventListener { EventLoopDispatch<String,String> dispatcher = new EventLoopDispatch<String,String>(4);//定义及初始化一个消息分发器 @Override
public void fireEvent(LoopEvent e) {
EventRecord<String,String> eventRecord = new EventRecord<String, String>(null, e.getSource().toString());//转为内部统一的消息类型
dispatcher.dispatch(eventRecord);//分发消息事件
} }

以上代码就是实现一个简单的事件监听机制,需要注意两点:

1、事件监听器中对消息分发器的定义及初始化,因为在分发器中会同时构造及初始化EventLoopGroup与DefaultPartitioner,前者顾名思义是指一组事件循环器,后者则是分发策略的具体实现;

2、为了配置分发策略需要把事件数据转为线程模型内部统一的消息类型;

二、分发器(Dispatch)

接收到事件触发后,我们会把事件转为统一的内部消息类型,然后交给dispatch进行分发,来看下分发的具体代码。

    //消息分发与执行
public void dispatch(EventRecord<K, V> record) {
int partation = partitioner.partition(core,record.partition(), record.key());//根据传入的消息数据,确定partation
group.next(partation, record);//根据partation,确定执行的EventLoop
}

分发方法实现的功能也很简单,第一步根据传入的消息体数据拿到具体的partation,第二步把拿到的partation与消息数据传入group的next方法,分配指定的EventLoop;

在AbstractDispatch构造函数中对EventLoopGroup与DefaultPartitioner进行初始化操作;

    protected AbstractDispatch(int core) {
this.core = core;
this.partitioner = new DefaultPartitioner();
this.group = new ThreadEventLoopGroup(core);
}

DefaultPartitioner类借鉴了Kafka Consumer的实现,主要实现partitioner的分配策略,可以通过指定消息实体EventRecord的Key或Partition,保证消息数据按照一定的规则落入对应的EventLoop中。

根据EventRecord消息构造的不同,三种策略如下:

1、构造EventRecord时,确定了partition,判断后直接返回;

2、构造EventRecord时,Key与partition均为空,自增取余;

3、构造EventRecord时,partition为空,Key不为空,hash取余;

public class DefaultPartitioner implements Partitioner {

    private final ConcurrentMap<Integer, AtomicInteger> topicCounterMap = new ConcurrentHashMap<>();

    public int partition(int core, Integer partition, Object key) {
if (partition != null ) {
if(partition>core) {//可以自己指定partition,但不能大于core,也就是group个数
throw new IllegalArgumentException(String
.format("Invalid partition: %d. partition should always be greater than core.", partition));
}
return partition;
} else if (partition == null && key == null) {//如果没有指定key或partition,则采用自增取余模式
int nextValue = nextValue(core);
return ConstantUtil.toPositive(nextValue) % core; } else {//如果partition为空,key不为空,则根据hash取余
return Math.abs(key.hashCode() % core);
}
} private int nextValue(int core) {
AtomicInteger counter = topicCounterMap.get(core);
if (null == counter) {
counter = new AtomicInteger(ThreadLocalRandom.current().nextInt());
AtomicInteger currentCounter = topicCounterMap.putIfAbsent(core, counter);
if (currentCounter != null) {
counter = currentCounter;
}
}
return counter.getAndIncrement();
}
}

而ThreadEventLoopGroup 顾名思义就是维护一组EventLoop,会根据你传入的参数大小创建并初始化一个EventLoop数组;next方法会根据传入的partition,把消息数据交给EventLoop数组中的一个来执行

public class ThreadEventLoopGroup {

    private ThreadEventLoop[] children;

    public ThreadEventLoopGroup(int threads) {
this(threads, null);
} public ThreadEventLoopGroup(int threads, Executor executor) {
if (executor == null) {//自定义线程工厂
executor = new ThreadPerTaskExecutor(EventThreadFactory());
}
//根据传入的线程数创建一个EventLoop数组
children = new ThreadEventLoop[threads]; //初始化EventLoop
for (int i = 0; i < threads; i++) {
children[i] = new ThreadEventLoop(executor);
}
} protected ThreadFactory EventThreadFactory() {
return new EventThreadFactory();
} //分配EventLoop
public void next(int partaion, EventRecord<?, ?> context) {
children[partaion].execute(context);
} }

以上部分主要实现了消息数据的接收与分发,具体分发策略由DefaultPartitioner与 EventRecord相互配合实现  ,使用者既可以通过构造EventRecord自定义分发策略,也可以使用默认分发策略;

三、事件循环器 (EventLoop)

消息经过分发器分发后,会进入对应EventLoop,我们首先看下EventLoop及其抽象类的实现

ThreadEventLoop类

public class ThreadEventLoop extends AbstractEventLoop {

    public ThreadEventLoop(Executor executor) {
super(executor); } public void run() {
//通过for(;;)轮询的方式从阻塞队列中获取数据进行处理
for (;;) {
try {
EventRecord<?, ?> record = arrayBlockingQueue.take();//从阻塞队列中获取数据
iHandler.execute(record);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
System.err.println(e.getMessage());
}
} } public void execute(EventRecord<?, ?> record) {
try {
if (record == null) {//判断非空
throw new NullPointerException("EventRecord");
} boolean inEventLoop = inEventLoop(Thread.currentThread());
arrayBlockingQueue.put(record);//把消息放入阻塞队列中
if (!inEventLoop) {
startThread();//启动EventLoop中改的线程
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
System.err.println(e.getMessage());
}
} }

execute作为执行方法入口主要起到两点作用:一是要把收到的消息数据放入阻塞队列中,二是启动EventLoop中的线程,从而执行run方法轮询队列进行处理;

在抽象类AbstractEventLoop 中,startThread方法通过CAS的方式,保证了每个EventLoop 中自定义的线程工厂只创建和启动一次线程;

public abstract class AbstractEventLoop {

    //阻塞队列,消息会先进入阻塞队列中,再由线程循环处理
protected final ArrayBlockingQueue<EventRecord<?,?>> arrayBlockingQueue = new ArrayBlockingQueue<EventRecord<?,?>>(1024); //绑定线程
protected volatile Thread thread; protected final Executor executor; protected IHandler iHandler = new EventHandler(); //声明AtomicIntegerFieldUpdater类,用于控制线程启动状态
private static final AtomicIntegerFieldUpdater<AbstractEventLoop> STATE_UPDATER = AtomicIntegerFieldUpdater
.newUpdater(AbstractEventLoop.class, "state"); private volatile int state = ST_NOT_STARTED;
private static final int ST_NOT_STARTED = 1;
private static final int ST_STARTED = 2; protected AbstractEventLoop(Executor executor) {
this.executor = executor;
} public boolean inEventLoop(Thread thread) {
return thread == this.thread;
} //启动线程
protected void startThread() {
if (state == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {//通过CAS的方式保证线程不会重复启动
try {
doStartThread();//
} catch (Throwable cause) {
STATE_UPDATER.set(this, ST_NOT_STARTED);
}
}
}
} protected void doStartThread() {
assert thread == null;
executor.execute(new Runnable() {//通过自定义线程工厂开始执行线程
@Override
public void run() {
thread = Thread.currentThread();
AbstractEventLoop.this.run();//执行实现类的run方法 }
});
} protected abstract void run();
}

可以看到EventLoop要实现的目标很明确,每个EventLoop都会绑定与维护一个对应的线程,该线程轮询队列中的消息进行处理;

四、总结

以上就是一个由事件消息驱动的EventLoop线程模型的构建,其中涉及到了事件监听、消息分发、线程轮询等内容,相比与一般的生产者消费者模型,具备以下特点:

1、结合事件监听机制,统一内部消息模型,为分发策略的制定与无锁化提供基础;
2、线程内部结合阻塞队列循环执行,保证同一EventLoo中数据处理的有序性;
3、针对需要线程同步数据,可根据一定的分发策略保证由同一线程执行,实现无锁化;

当然相比Netty等框架中的EventLoop线程模型,本文只是基于自己理解实现的简单实例,其中有很多复杂的细节都未考虑,但仍然希望对大家能有所帮助,其中如有不足与不正确的地方还望指出与海涵。

github地址:https://github.com/dafanjoy/fan-eventLoop

关注微信公众号,查看更多技术文章。

转载说明:未经授权不得转载,授权后务必注明来源(注明:来源于公众号:架构空间, 作者:大凡)

构建一个基于事件分发驱动的EventLoop线程模型的更多相关文章

  1. 使用 XMPP 构建一个基于 web 的通知工具——转

    Inserting of file(使用 XMPP 构建一个基于 web 的通知工具.docx) failed. Please try again. http://www.ibm.com/develo ...

  2. 利用Dockerfile构建一个基于CentOS 7镜像

    利用Dockerfile构建一个基于CentOS 7,包括java 8, tomcat 7,php ,mysql+mycat的镜像. Dockerfile内容如下: FROM centosMAINTA ...

  3. 构建一个基于 Spring 的 RESTful Web Service

    本文详细介绍了基于Spring创建一个“hello world” RESTful web service工程的步骤. 目标 构建一个service,接收如下HTTP GET请求: http://loc ...

  4. 开源低代码平台开发实践二:从 0 构建一个基于 ER 图的低代码后端

    前后端分离了! 第一次知道这个事情的时候,内心是困惑的. 前端都出去搞 SPA,SEO 们同意吗? 后来,SSR 来了. 他说:"SEO 们同意了!" 任何人的反对,都没用了,时代 ...

  5. Spring MVC第一课:用IDEA构建一个基于Spring MVC, Hibernate, My SQL的Maven项目

    作为一个Spring MVC新手最基本的功夫就是学会如何使用开发工具创建一个完整的Spring MVC项目,本文站在一个新手的角度讲述如何一步一步创建一个基于Spring MVC, Hibernate ...

  6. 利用Dockerfile构建一个基于centos 7,包括java 8, tomcat 7,php ,mysql+mycat的镜像

    Dockerfile内容如下: FROM centos MAINTAINER Victor ivictor@foxmail.com WORKDIR /root RUN rm -f /etc/yum.r ...

  7. 构建一个基于UIView的类别

    很多时候,如果我们想给我们的控件赋值,例如给控件的长度.宽度等赋值,很麻烦 需要先获取到当前frame,再整个临时frame来保存,修改赋值后再还给当前的frame,这都是重复性高的苦力活,解决方法就 ...

  8. 游戏AI(三)—行为树优化之基于事件的行为树

    上一篇我们讲到了关于行为树的内存优化,这一篇我们将讲述行为树的另一种优化方法--基于事件的行为树. 问题 在之前的行为树中,我们每帧都要从根节点开始遍历行为树,而目的仅仅是为了得到最近激活的节点,既然 ...

  9. Android事件分发机制二:viewGroup与view对事件的处理

    前言 很高兴遇见你~ 在上一篇文章 Android事件分发机制一:事件是如何到达activity的? 中,我们讨论了触摸信息从屏幕产生到发送给具体 的view处理的整体流程,这里先来简单回顾一下: 触 ...

随机推荐

  1. P1640 [SCOI2010]连续攻击游戏【并查集】

    题目描述 lxhgww最近迷上了一款游戏,在游戏里,他拥有很多的装备,每种装备都有2个属性,这些属性的值用[1,10000]之间的数表示.当他使用某种装备时,他只能使用该装备的某一个属性.并且每种装备 ...

  2. Js中各种类型的变量在if条件中是true还是false

    如果操作数是一个对象,返回true如果操作数是一个空字符串,返回false如果操作数是一个非空字符串,返回true如果操作数是数值0,返回false如果操作数是任意非0数值(包括Infinity),返 ...

  3. Python之浅谈生成器

    目录 三元表达式 列表推导式 字典生成式 生成器 生成器表达式 匿名函数 三元表达式 a=0 b=6 print (a)if a>b else print(b) 三元表达式只能写if的双分支结构 ...

  4. 看完这篇Redis缓存三大问题,保你面试能造火箭,工作能拧螺丝。

    前言 日常的开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题. 一旦涉及大数据量的需求,如一些商品抢购的情景,或者主页访问量瞬间较 ...

  5. Centos 6.4 安装/卸载 Adobe Reader 9(.bin .tar.bz2 rpm 包)

    一.To install Adobe Reader 9.1 using a tarball installer 1. Open a terminal window. 2. Change directo ...

  6. python编程从入门到实践笔记

    我的第一个hello world 程序 print("hello python world") print("hello python world"*3) 打印 ...

  7. npm和webpack

    npm是前端开发中常用的一种工具,对于普通开发者来说,便于管理依赖. 往大了说,便于共享代码.写完代码,使用npm发布以后,然后别人用npm可以方便地共享到你的代码. npm的使用: mac环境下的安 ...

  8. windows快速安装redis

    下载地址:https://github.com/microsoftarchive/redis/releases 下载解压,命令行:redis-server.exe redis.windows.conf

  9. HashMap等集合初始化时应制定初始化大小

    阿里巴巴开发规范中,推荐用户在初始化HashMap时,应指定集合初始值大小. 一.原因 这个不用多想,肯定是效率问题,那为什么会造成效率问题呢? 当我们new一个HashMap没有对其容量进行初始化的 ...

  10. 数据分析07 /matplotlib绘图

    数据分析07 /matplotlib绘图 目录 数据分析07 /matplotlib绘图 1. 绘制线性图:plt.plot() 2. 绘制柱状图:plt.bar() 3. 绘制直方图:plt.his ...