本地文件隊(duì)列-異步隔離設(shè)計(jì)

常見的異步方式:

創(chuàng)建異步線程

每個(gè)新創(chuàng)建一個(gè)線程來執(zhí)行異步任務(wù),任務(wù)結(jié)束線程也終止。
線程的創(chuàng)建成本比較大,不建議使用。

使用Queue,producer/consumer方式

在內(nèi)部創(chuàng)建一個(gè)Queue,worker線程直接將異步處理的任務(wù)放入queue,一個(gè)或多個(gè)異步線程從queue中消費(fèi)并執(zhí)行任務(wù)。

線程池

用線程池來替換每次創(chuàng)建線程,減少線程創(chuàng)建的成本,線程被復(fù)用,一次創(chuàng)建多處使用。

和使用Queue類似,也是通過BlockingQueue實(shí)現(xiàn),但策略上更復(fù)雜,向線程池提交Callable&Runnable任務(wù),由線程池調(diào)度執(zhí)行。

參考:java.util.concurrent.ThreadPoolExecutor#execute

spring @Async注解

通過注解來來簡(jiǎn)化了異步編程,只需要在需要異步的方法上使用@Async注解即可。
其本質(zhì)也是在線程池功能上擴(kuò)展的,將異步執(zhí)行方法封裝為一個(gè)Callable,然后提交給線程池。

org.springframework.aop.interceptor.AsyncExecutionInterceptor:

public Object invoke(final MethodInvocation invocation) throws Throwable {
        Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
        Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
        final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);

        AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
        if (executor == null) {
            throw new IllegalStateException(
                    "No executor specified and no default executor set on AsyncExecutionInterceptor either");
        }

        Callable<Object> task = new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                try {
                    Object result = invocation.proceed();
                    if (result instanceof Future) {
                        return ((Future<?>) result).get();
                    }
                }
                catch (ExecutionException ex) {
                    handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments());
                }
                catch (Throwable ex) {
                    handleError(ex, userDeclaredMethod, invocation.getArguments());
                }
                return null;
            }
        };

        return doSubmit(task, executor, invocation.getMethod().getReturnType());
    }

詳細(xì)參考:http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#scheduling-annotation-support-async

背景和場(chǎng)景

產(chǎn)生的背景

在項(xiàng)目中使用了@Async來執(zhí)行異步任務(wù),但在線上運(yùn)行時(shí)出現(xiàn)了一次OOM的故障。通過分析發(fā)現(xiàn)是,線程池隊(duì)列設(shè)置的比較大,當(dāng)時(shí)的JVM內(nèi)存給的也比較少(2048M),異步任務(wù)方法參數(shù)中傳了大量的數(shù)據(jù),任務(wù)執(zhí)行被后端數(shù)據(jù)庫(kù)阻塞(后端數(shù)據(jù)庫(kù)變慢),最后導(dǎo)致緩存了大量的數(shù)據(jù)被放到線程池隊(duì)列。其實(shí)JVM內(nèi)存配置合適,線程池隊(duì)列數(shù)合適,并配置合適的RejectedExecutionHandler策略。

產(chǎn)生這個(gè)組件,1) 旨在替換內(nèi)存隊(duì)列的異步方式 2) 用來方便擴(kuò)展集成分布式MQ

異步隔離

除了上面背景和場(chǎng)景,開發(fā)這個(gè)組件的另一個(gè)初衷就是有效異步隔離和作為一個(gè)降級(jí)備份方案。
也是主要實(shí)現(xiàn)了文件隊(duì)列方式的一個(gè)原因。

當(dāng)我們使用分布式MQ時(shí),難免分布式MQ宕機(jī)或者其他網(wǎng)絡(luò)等原因?qū)е虏荒苌a(chǎn)消息,或者阻塞影響到本身的業(yè)務(wù),出現(xiàn)這種情況時(shí)可以降級(jí)到本地文件隊(duì)列。

本地文件隊(duì)列的優(yōu)點(diǎn)是速度快,只要文件系統(tǒng)不出問題可以認(rèn)為不會(huì)被阻塞。缺點(diǎn)是本地文件隊(duì)列生產(chǎn)的消息必須自己來消費(fèi),出現(xiàn)故障時(shí)消息消費(fèi)會(huì)延遲,文件系統(tǒng)的損壞也會(huì)導(dǎo)致消息丟失。主要看使用的姿勢(shì),更看重哪一方面了。

基本架構(gòu)設(shè)計(jì)思路

采用producer/consumer生產(chǎn)消費(fèi)設(shè)計(jì)模式。

參考了@Async思路,定義一個(gè)注解@AsyncExecutable, 使用Spring攔截器攔截注解了@AsyncExecutable的方法,可以使用AOP或者BeanPostProcessor來應(yīng)用攔截器。

producer

攔截器攔截到@AsyncExecutable方法后,將該方法所有的參數(shù)和方法信息作為Message,并序列化Message,序列化采用Kryo或者Json,將序列化后的信息放入隊(duì)列。

class Message {

     String beanName;
     String klassName;
     String methodName;
     Class<?>[] argTypes;
     Object[] args;
     boolean hasTransactional = true;
     
}

consumer

有1個(gè)調(diào)度主線程和worker線程組成,主線程負(fù)責(zé)從隊(duì)列中拉取消息,并分發(fā)到worker線程,worker線程采用線程池,使用了spring提供的TaskExecutor。

worker線程反序列化消息為Message對(duì)象,并根據(jù)Message中的方法信息在spring ApplicationContext中查找到spring 管理的bean,并通過反射來調(diào)用。

隊(duì)列

隊(duì)列抽象了一個(gè)BlockableQueue, 通過BlockableQueue具體實(shí)現(xiàn)來擴(kuò)展,可以是內(nèi)存,文件,或分布式MQ。

 public interface BlockableQueue<T> {

    String DefaultQueueName = "fileQueue";

    /**
     * push一個(gè)消息到隊(duì)列
     *
     * @param t
     * @return
     */
    boolean offer(T t);

    /**
     * 從隊(duì)列pop一個(gè)消息,如果隊(duì)列中無可用消息,則阻塞
     *
     * @return
     * @throws InterruptedException
     */
    T take() throws InterruptedException;

    /**
     * 從隊(duì)列pop一個(gè)消息,如果隊(duì)列中無可用消息,則返回null
     *
     * @return
     */
    T poll();

    /**
     * 隊(duì)列中消息數(shù)量
     *
     * @return
     */
    int size();
}

通用的默認(rèn)實(shí)現(xiàn):

 
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class DefaultBlockableQueue implements BlockableQueue<byte[]> {
    final Lock lock = new ReentrantLock();
    final Condition notEmpty = lock.newCondition();
    private Queue<byte[]> queue = null;


    public FileBlockableQueue(Queue<byte[]> queue) {
        this.queue = queue;
    }

    @Override
    public boolean offer(byte[] bytes) {
        lock.lock();
        try {
            boolean v = queue.offer(bytes);
            notEmpty.signal();
            return v;
        } finally {
            lock.unlock();
        }
    }


    @Override
    public byte[] take() throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() == 0) {
                notEmpty.await();
            }
            byte[] bytes = queue.poll();
            return bytes;
        } finally {
            lock.unlock();
        }

    }

    @Override
    public byte[] poll() {
        return queue.poll();
    }

    @Override
    public int size() {
        return queue.size();
    }
}

本文實(shí)現(xiàn)了一個(gè)文件隊(duì)列,采用去哪兒文件隊(duì)列實(shí)現(xiàn),這是一個(gè)fork:https://github.com/tietang/fqueue

對(duì)編程模型來說不用關(guān)心異步細(xì)節(jié),只需要在需要異步的方法上注解@AsyncExecutable即可。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容