Disruptor的应用-->发送消息


写的比较粗糙。

需求背景

在某些场景下(物料入库成功或者物料装箱成功)需要给特定的人发送消息(可以是短信、邮件或者系统内的消息)。

需求分析

场景多个、消息类型多个
如在入库中可能存在【一段时间】内存在很多人操作,这里假设操作库房的人数有100人,他们可能1分钟之内都会操作入库操作。这意味着1分钟会有100次请求访问,100次消息发送请求。
如果场景增多(目前15个),这里认为最高峰,假设 1分钟内执行15个场景,每个场景 50人操作:这就有 大概 15*50=750条消息请求需要发送。

思考

每个场景设计的业务逻辑很复杂,IO操作频繁。所以我们需要思考如何能最小程度的进行发送消息,而不去大量占用 业务逻辑时间呢。
这里有人就会问,那发消息直接使用异步不就行了吗?但是需求还要求能够同步获取到 发送消息的结果。如果使用异步,我们选择那种异步框架?对异步线程如何处理?这都是需要思考的地方。

技术选型

我们最终也还是选择了异步执行,同步阻塞获取结果的方式来实现的发送消息。对主业务逻辑影响较小,而且也提供了完全异步的方式。异步我们一开始想过使用 AQS来进行实现,多个生产消息的线程进行对AQS填充,可以设置多个线程进行消费消息。而我在了解到Disruptor以及读了log4j异步写消息的部分源码之后最终决定使用 Disruptor 来实现对消息的分发。

消息服务实现

在说具体的实现之前,我们必须简单了解一下Disruptor主要的组件是什么。

Disruptor 组件

这里有很多知识点,比如 CAS、缓存行(伪共享)、内存屏障、队列、锁等。有兴趣可以自行搜索下,这里不过多介绍。
用户指南

  • Ring Buffer:Ring Buffer 通常被认为是 Disruptor 的主要方面。但是,从 3.0 开始,RingBuffer 只负责存储和更新Event通过 Disruptor 的数据。对于一些高级用例,它甚至可以完全由用户替换。

  • Sequence:Disruptor 使用Sequences 作为识别特定组件在哪里的一种手段。每个消费者(事件处理器)Sequence都像Disruptor本身一样维护一个。大多数并发代码依赖于这些序列值的移动,因此Sequence支持许多当前的特性AtomicLong。事实上,两者之间唯一真正的区别是Sequence包含额外的功能来防止Sequences和其他值之间的错误共享。

  • Sequencer:Sequencer 是 Disruptor 的真正核心。该接口的两种实现(单生产者、多生产者)实现了所有并发算法,以在生产者和消费者之间快速、正确地传递数据。

  • Sequence Barrier:Sequencer 生成一个 Sequence Barrier,其中包含对Sequence从Sequencer 发布的 main 和Sequence任何依赖消费者的 s 的引用。它包含确定是否有任何事件可供消费者处理的逻辑。

  • Wait Strategy:等待策略决定了消费者将如何等待生产者将事件放入 Disruptor。有关可选无锁的部分提供了更多详细信息。

  • Event:从生产者传递给消费者的数据单位。事件没有特定的代码表示,因为它完全由用户定义。

  • Event Processor:用于处理来自 Disruptor 的事件的主事件循环,并拥有消费者序列的所有权。有一个称为BatchEventProcessor 的表示,它包含事件循环的有效实现,并将回调到 EventHandler 接口的使用提供的实现。

  • Event Handler:由用户实现的接口,代表 Disruptor 的消费者。

  • Producer:这是调用 Disruptor 入队Event的用户代码。

log4j2 的使用

Log4j2基于Disruptor异步日志优化(部分源码学习)
这里查看的源码版本是:log4j-core-2.13.3;下面重点关注异步方式的两个类

  1. 日志接收发送
    AsyncLogger 发送日志
public class AsyncLogger extends Logger implements EventTranslatorVararg<RingBufferLogEvent> {
	
	//发送日志
	private final TranslatorType varargTranslatorType = new TranslatorType() {
        @Override
        void log(String fqcn, StackTraceElement location, Level level, Marker marker, Message message,
            Throwable thrown) {
            logWithVarargTranslator(fqcn, location, level, marker, message, thrown);
        }

        @Override
        void log(String fqcn, Level level, Marker marker, Message message, Throwable thrown) {
           
            //发送日志
            logWithVarargTranslator(fqcn, level, marker, message, thrown);
        }
    };

	private void logWithVarargTranslator(final String fqcn, final Level level, final Marker marker,
            final Message message, final Throwable thrown) {
         //获取  disruptor
        final Disruptor<RingBufferLogEvent> disruptor = loggerDisruptor.getDisruptor();
        if (disruptor == null) {
            LOGGER.error("Ignoring log event after Log4j has been shut down.");
            return;
        }
        // if the Message instance is reused, there is no point in freezing its message here
        if (!isReused(message)) {
            InternalAsyncUtil.makeMessageImmutable(message);
        }
        StackTraceElement location = null;
        //不阻塞,尝试往  ringbuffer中 添加任务
        if (!disruptor.getRingBuffer().tryPublishEvent(this,
                this, // asyncLogger: 0
                (location = calcLocationIfRequested(fqcn)), // location: 1
                fqcn, // 2
                level, // 3
                marker, // 4
                message, // 5
                thrown)) { // 6
                // 满了之后,执行这个方法
            handleRingBufferFull(location, fqcn, level, marker, message, thrown);
        }
    }
    
	private void handleRingBufferFull(final StackTraceElement location,
                                      final String fqcn,
                                      final Level level,
                                      final Marker marker,
                                      final Message msg,
                                      final Throwable thrown) {
        if (AbstractLogger.getRecursionDepth() > 1) { // LOG4J2-1518, LOG4J2-2031
            //如果递归 则用当前线程执行
            AsyncQueueFullMessageUtil.logWarningToStatusLogger();
            logMessageInCurrentThread(fqcn, level, marker, msg, thrown);
            return;
        }
        final EventRoute eventRoute = loggerDisruptor.getEventRoute(level);
        switch (eventRoute) {
            case ENQUEUE:
                // 队列 查看 【AsyncLoggerDisruptor】 
                loggerDisruptor.enqueueLogMessageWhenQueueFull(this,
                        this, // asyncLogger: 0
                        location, // location: 1
                        fqcn, // 2
                        level, // 3
                        marker, // 4
                        msg, // 5
                        thrown); // 6
                break;
            case SYNCHRONOUS:
                //  同步:用当前线程执行
                logMessageInCurrentThread(fqcn, level, marker, msg, thrown);
                break;
            case DISCARD:
                break;
            default:
                throw new IllegalStateException("Unknown EventRoute " + eventRoute);
        }
    }
	
	
}

AsyncLoggerDisruptor 日志事件容器

	public synchronized void start() {
	        if (disruptor != null) {
	            return;
	        }
	        //获取 长度,4KB  或者 256KB
	        ringBufferSize = DisruptorUtil.calculateRingBufferSize("AsyncLogger.RingBufferSize");
	        //等待策略
	        final WaitStrategy waitStrategy = DisruptorUtil.createWaitStrategy("AsyncLogger.WaitStrategy");
	       
	        final ThreadFactory threadFactory = new Log4jThreadFactory("AsyncLogger[" + contextName + "]", true, Thread.NORM_PRIORITY) {
	            @Override
	            public Thread newThread(final Runnable r) {
	                final Thread result = super.newThread(r);
	                backgroundThreadId = result.getId();
	                return result;
	            }
	        };
	        //队列满了之后的 策略 : 同步 队列  丢弃
	        asyncQueueFullPolicy = AsyncQueueFullPolicyFactory.create();
	
	        disruptor = new Disruptor<>(RingBufferLogEvent.FACTORY, ringBufferSize, threadFactory, ProducerType.MULTI,
	                waitStrategy);
	
	        final ExceptionHandler<RingBufferLogEvent> errorHandler = DisruptorUtil.getAsyncLoggerExceptionHandler();
	        disruptor.setDefaultExceptionHandler(errorHandler);
			//*** 处理任务线程,只用了一个线程 来进行消费日志事件。
	        final RingBufferLogEventHandler[] handlers = {new RingBufferLogEventHandler()};
	        disruptor.handleEventsWith(handlers);
	
	        disruptor.start();
	
	        LOGGER.trace("[{}] AsyncLoggers use a {} translator", contextName, useThreadLocalTranslator ? "threadlocal"
	                : "vararg");
	        super.start();
	 }
	 
	
	void enqueueLogMessageWhenQueueFull(
            final EventTranslatorVararg<RingBufferLogEvent> translator,
            final AsyncLogger asyncLogger,
            final StackTraceElement location,
            final String fqcn,
            final Level level,
            final Marker marker,
            final Message msg,
            final Throwable thrown) {
        try {
             
            if (synchronizeEnqueueWhenQueueFull()) {
                //默认策略: 同步发送任务
                synchronized (queueFullEnqueueLock) {
                    disruptor.getRingBuffer().publishEvent(translator,
                            asyncLogger, // asyncLogger: 0
                            location, // location: 1
                            fqcn, // 2
                            level, // 3
                            marker, // 4
                            msg, // 5
                            thrown); // 6
                }
            } else {
                disruptor.getRingBuffer().publishEvent(translator,
                        asyncLogger, // asyncLogger: 0
                        location, // location: 1
                        fqcn, // 2
                        level, // 3
                        marker, // 4
                        msg, // 5
                        thrown); // 6
            }
        } catch (final NullPointerException npe) {
            // LOG4J2-639: catch NPE if disruptor field was set to null in stop()
            logWarningOnNpeFromDisruptorPublish(level, fqcn, msg, thrown);
        }
    }
		
   // 是否需要 获取锁 同步发送
    private boolean synchronizeEnqueueWhenQueueFull() {
        return DisruptorUtil.ASYNC_LOGGER_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL
                // 是否是 将消息消费的后台线程
                && backgroundThreadId != Thread.currentThread().getId();
    }

RingBufferLogEventHandler 日志处理器

public class RingBufferLogEventHandler implements
        SequenceReportingEventHandler<RingBufferLogEvent>, LifecycleAware {

    private static final int NOTIFY_PROGRESS_THRESHOLD = 50;
    private Sequence sequenceCallback;
    private int counter;
    private long threadId = -1;

    @Override
    public void setSequenceCallback(final Sequence sequenceCallback) {
        this.sequenceCallback = sequenceCallback;
    }

    @Override
    public void onEvent(final RingBufferLogEvent event, final long sequence,
            final boolean endOfBatch) throws Exception {
        try {
           // 这个才是 正在执行 日志的方法,会交给 apperder 来执行。
           //请查看 【AsyncAppender】
            event.execute(endOfBatch);
        }
        finally {
           //帮助 GC,有可能 ringbuffer 来不及清空。
            event.clear();
            notifyCallback(sequence);
        }
    }

    private void notifyCallback(long sequence) {
        if (++counter > NOTIFY_PROGRESS_THRESHOLD) {
            sequenceCallback.set(sequence);
            counter = 0;
        }
    }

    /**
     * Returns the thread ID of the background consumer thread, or {@code -1} if the background thread has not started
     * yet.
     * @return the thread ID of the background consumer thread, or {@code -1}
     */
    public long getThreadId() {
        return threadId;
    }

    @Override
    public void onStart() {
        threadId = Thread.currentThread().getId();
    }

    @Override
    public void onShutdown() {
    }
}
  1. 日志消费
    AsyncAppender 异步消费,使用了 DisruptorBlockingQueue

	private AsyncAppender(final String name, final Filter filter, final AppenderRef[] appenderRefs,
            final String errorRef, final int queueSize, final boolean blocking, final boolean ignoreExceptions,
            final long shutdownTimeout, final Configuration config, final boolean includeLocation,
            final BlockingQueueFactory<LogEvent> blockingQueueFactory, final Property[] properties) {
        super(name, filter, null, ignoreExceptions, properties);
        //事件队列
        this.queue = blockingQueueFactory.create(queueSize);
        this.queueSize = queueSize;
        this.blocking = blocking;
        this.shutdownTimeout = shutdownTimeout;
        this.config = config;
        this.appenderRefs = appenderRefs;
        this.errorRef = errorRef;
        this.includeLocation = includeLocation;
    }


	@Override
    public void start() {
        final Map<String, Appender> map = config.getAppenders();
        final List<AppenderControl> appenders = new ArrayList<>();
        for (final AppenderRef appenderRef : appenderRefs) {
            final Appender appender = map.get(appenderRef.getRef());
            if (appender != null) {
                appenders.add(new AppenderControl(appender, appenderRef.getLevel(), appenderRef.getFilter()));
            } else {}
        }
        
        if (appenders.size() > 0) {
            //消费  队列中的  任务。
            thread = new AsyncThread(appenders, queue);
            thread.setName("AsyncAppender-" + getName());
        } 
        asyncQueueFullPolicy = AsyncQueueFullPolicyFactory.create();
        thread.start();
        super.start();
    }

   //发送消息 会将消息 保存到 队列中
	@Override
    public void append(final LogEvent logEvent) {
        if (!isStarted()) {
            throw new IllegalStateException("AsyncAppender " + getName() + " is not active");
        }
        final Log4jLogEvent memento = Log4jLogEvent.createMemento(logEvent, includeLocation);
        InternalAsyncUtil.makeMessageImmutable(logEvent.getMessage());
        //尝试添加到队列中
        if (!transfer(memento)) {
            //队列满了 并且又 阻塞策略
            if (blocking) {
                if (AbstractLogger.getRecursionDepth() > 1) { // LOG4J2-1518, LOG4J2-2031
                 
                    AsyncQueueFullMessageUtil.logWarningToStatusLogger();
                    logMessageInCurrentThread(logEvent);
                } else {
                   
                    final EventRoute route = asyncQueueFullPolicy.getRoute(thread.getId(), memento.getLevel());
                    //执行具体策略 : 队列  同步  舍弃
                    route.logMessage(this, memento);
                }
            } else {
                logToErrorAppenderIfNecessary(false, memento);
            }
        }
    }

    private boolean transfer(final LogEvent memento) {
        return queue instanceof TransferQueue
            ? ((TransferQueue<LogEvent>) queue).tryTransfer(memento)
            : queue.offer(memento);
    }
	
   // 异步线程,消费消息
	private class AsyncThread extends Log4jThread {

        private volatile boolean shutdown = false;
        private final List<AppenderControl> appenders;
        private final BlockingQueue<LogEvent> queue;

        public AsyncThread(final List<AppenderControl> appenders, final BlockingQueue<LogEvent> queue) {
            super("AsyncAppender-" + THREAD_SEQUENCE.getAndIncrement());
            this.appenders = appenders;
            this.queue = queue;
            setDaemon(true);
        }

        @Override
        public void run() {
            while (!shutdown) {
                LogEvent event;
                try {
                   //获取消息
                    event = queue.take();
                    if (event == SHUTDOWN_LOG_EVENT) {
                        shutdown = true;
                        continue;
                    }
                } catch (final InterruptedException ex) {
                    break; // LOG4J2-830
                }
                event.setEndOfBatch(queue.isEmpty());
                //具体的输出方法,输出到到 文件 还是 控制台 看xml 配置
                final boolean success = callAppenders(event);
                if (!success && errorAppender != null) {
                    try {
                        errorAppender.callAppender(event);
                    } catch (final Exception ex) {
                        // Silently accept the error.
                    }
                }
            }
           
        }
	

	}

设计图

消息服务设计图

接口定义

我们接口也仿造 log4j2 来创建消息分发消费的 框架。
这里我们直接看代码。还有demo演示。


实现

这里我们直接看代码。还有demo演示。


客户端发送消息示例

//客户端如何发送消息
//接收人
MessageReceiver messageReceiver = new MessageReceiver.Builder()
    .commonIds(new HashSet<>(Arrays.asList("user1","user2")))
    .userIds(new HashSet<>(Arrays.asList("user3","user4")))
	.emails(new HashSet<>(ImmutableList.of("XXX@ZZ.com")))
	.phones(new HashSet<>(ImmutableList.of("15802222222269"))))
	.build();

//不使用模板,自定义内容
SendContent sendContent = new SendContent.Builder().sms("这是一条自定义短信内容").build();

//变量替换
//您的备件归还单@&b variable1@(SR单号@&b variable2@)审核驳回,含@&b variable3@,驳回原因:@&b variable4@。
List<String> variables = Arrays.asList("归还单号", "申请单号","物料编码", "驳回原因");
				ContentVariable contentVariable = new ContentVariable.Builder()
						.sendVarSMS(variables)
						.sendVarEmail(variables)
						.build();
MessageContext messageContext = new 
MessageContext("BJ.BH","发送人",messageReceiver,sendContent ,contentVariable);
//发送---> 不关注结果
service.sendMessageAsync(messageContext);
//关注结果,提供获取结果时才阻塞,这一步不会阻塞
SendMessageResponse.AsyncResult asyncResult = service.sendMessage(messageContext);
//这里才阻塞
SendMessageResponse result = asyncResult.getResult();


总结

本文主要介绍 Disruptor 在该需求下的使用,以及简单介绍了Log4j2对Disruptor的使用。


springcloud 脚手架(wx-cloud)

  • spring boot 2.3.3.RELEASE
  • nacos
  • geteway
  • oauth2
  • cache(spring+caffeine)
  • mybaits

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值