多執行緒應用--Http請求阻塞回撥處理

雲逸_發表於2018-09-05

1.需求描述

1.1場景說明:

由於,微信端的業務需求量越來越大.將業務與微信第三方事件處理耦合在一起的單一專案的結構已經逐漸暴露出,承載能力不足的缺點.所以,需要將與微信的互動從業務邏輯中分離出,單獨進行管理和處理.
這樣做有以下幾點好處:

  1. 可以達到業務解耦分離.
  2. 可以為業務系統微服務化做準備.
  3. 可以在解耦後針對性的對不同業務系統進行優化.
  4. 減少業務系統錯誤的影響面.

1.2技術難點說明:

微信中的通過http呼叫客戶配置的回撥地址的方式來進行事件通知. 事件通知分為兩種型別:

  1. 傳送http請求和資料以後,客戶伺服器預設回覆success字串.然後,業務系統業務處理完成後通過指定的http地址通知微信方
  2. 傳送http請求和資料的同一個請求中需要客戶伺服器在response中返回業務處理的結果.

由於,我們已經將事件通知從主業務系統中抽離出.所以,微信事件管理系統會在收到微信事件通知以後,通過mq的方式進行釋出事件.但是,事件通知的第二種型別需要在一個http請求中將業務處理資料帶回微信方.此時,就需要微信事件管理系統阻塞微信方傳送來的http請求,直到業務系統處理完業務資料並返回給系統.
這樣,我們就需要有一個靈活,可靠的容器對被阻塞的微信方請求進行管理.

2.理論基礎

2.1Future多執行緒模型:

2.1.1模型介紹:

future多執行緒模型,是一種比較常用的多執行緒阻塞回撥的模式.具體邏輯結構如下圖所示:

future時序圖.png

具體處理邏輯是這樣的.當一個請求傳送到一個future模式的入口以後,此時這個執行緒是阻塞的.這時future的執行緒會進行後面的回撥業務,或者是直接開始等待.直到,其他執行緒喚醒future執行緒或者future等待超時.此時,future執行緒喚醒,然後將具體的結果返回給呼叫執行緒.

2.2執行緒間通訊:

2.2.1wait,notify,notifyAll

執行緒間通訊有很多種方式,這次用到的是比較簡單的wait notify notifyall組合.這3個方法是object類中的3個方法.他們控制的目標是對於這個例項的控制.所以,需要執行緒在獲取到這個物件操作的monitor以後才能控制.一般使用的方法是通過synchronized關鍵字獲取物件鎖再呼叫這3個方法.如果,沒有在同步程式碼塊中執行,這時候java會報IllegalMonitorStateException異常.這樣主要是為了控制當同一個物件例項被多個執行緒佔用以後的操作問題.可以避免不同步的情況產生.

2.2.1.1wait

wait方法主要是用來將這個物件例項上的當前執行緒進行掛起.可以輸入timeout時間,超過timeout時間以後執行緒會自動喚醒

2.2.1.2notify

notify方法用來喚醒在對應的物件例項上休眠的執行緒,但是需要注意的是,這個是非公平的.具體喚醒哪一個執行緒由jvm自行決定

2.2.1.3notifyall

notifyall方法顧名思義,是將在這個例項物件上所有掛起的執行緒喚醒.

3.實現思路:

  1. 容錯能力:由於需要給多個業務服務提供訊息分發,訊息回覆.需要有業務系統超時的處理能力.所以,提供的阻塞服務都會有timeout設定.
  2. 持續服務能力:我們需要提供持續穩定的服務.在專案中.對阻塞的請求會有一個溢位的管理.如果超出某個最大值,先入的請求就會被直接返回預設值.所以,在業務服務中需要自行處理冪等的問題,避免業務處理完成後,但是由於溢位導致業務處理失敗.這樣就會導致業務服務資料或者業務出現問題

4.具體實現:

ThreadHolder(訊息載體):

import lombok.Data;

import java.util.concurrent.Callable;

/**
 * <p>
 * Description: com.javanewb.service
 * </p>
 * date:2017/10/31
 *
 * @author Dean.Hwang
 */
@Data
public abstract class ThreadHolder<T> implements Callable<T> {
    protected abstract T proData();//TODO 正常邏輯處理,以及預設資料返回

    private T defaultData;//返回的預設資料
    private Object needProData;//接受到需要處理的資料
    private Long createTime = System.currentTimeMillis();
    private Long maxWaitTime;
    private String mdc;
    private RequestHolder<T> holder;

    @Override
    public T call() throws Exception {
        waitThread();
        System.out.println("Thread mdc:" + mdc + "  notify");
        if (needProData == null) {
            holder.removeThread(mdc, false);
            return defaultData;
        }
        return proData();
    }

    public synchronized void waitThread() throws InterruptedException {
        this.wait(maxWaitTime);
    }

    public synchronized void notifyThread(Object needProData) {
        this.needProData = needProData;
        this.notify();
    }

    public synchronized void notifyDefault() {
        this.notify();
    }
}
複製程式碼

RequestHolder(請求管理容器):


import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * <p>
 * Description: com.javanewb.entity
 * </p>
 * date:2017/10/26
 *
 * @author Dean.Hwang
 */
public class RequestHolder<T> {
    private Integer maxSize;
    private Long waitTime;

    public RequestHolder(Integer maxSize, Long maxWait, ExecutorService executorService) {
        if (maxSize > 1000) {
            throw new BusinessException(1022, "Bigger than max size num");
        }
        this.maxSize = maxSize;
        this.waitTime = maxWait;
        if (executorService != null) {
            this.executorService = executorService;
        } else {
            this.executorService = new ThreadPoolExecutor(Math.max(1, maxSize / 5), maxSize, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(maxSize));
        }
    }

    public RequestHolder(Integer maxSize, Long maxWait) {
        if (maxSize > 1000) {
            throw new BusinessException(1022, "Bigger than  max size num");
        }
        this.waitTime = maxWait;
        this.maxSize = maxSize;
        this.executorService = new ThreadPoolExecutor(Math.max(1, maxSize / 5), maxSize, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(maxSize));
    }

    private ExecutorService executorService;
    private final Map<String, ThreadHolder<T>> holderMap = new ConcurrentHashMap<>();
    private List<String> mdcOrderList = new CopyOnWriteArrayList<>();
    private AtomicBoolean isCleaning = new AtomicBoolean(false);

    public ThreadHolder<T> removeThread(String mdc, boolean needNotifyDefault) {
        mdcOrderList.remove(mdc);
        ThreadHolder<T> holder;
        synchronized (holderMap) {
            holder = holderMap.get(mdc);
            holderMap.remove(mdc);
        }
        if (holder != null && needNotifyDefault) {
            holder.notifyDefault();
        }
        return holder;
    }

    public void notifyThread(String mdc, Object data) {
        ThreadHolder<T> holder = removeThread(mdc, false);
        if (holder != null) {
            holder.notifyThread(data);
        }
    }


    public Future<T> getFuture(String mdcStr, Class<? extends ThreadHolder<T>> holder) {
        if (StringUtil.isEmpty(mdcStr) || holder == null) {
            throw new BusinessException(1020, "Mdc target missing!!!");
        }
        Future<T> future;
        try {
            ThreadHolder<T> thread = holder.newInstance();
            holderMap.put(mdcStr, thread);
            mdcOrderList.add(mdcStr);
            thread.setMaxWaitTime(waitTime);
            thread.setMdc(mdcStr);
            thread.setHolder(this);
            future = executorService.submit(thread);
            cleanThreadPool();
        } catch (InstantiationException | IllegalAccessException e) {
            holderMap.remove(mdcStr);
            mdcOrderList.remove(mdcStr);
            throw new BusinessException(1021, "Thread Holder initialized failed");
        }
        return future;
    }

    private void cleanThreadPool() {
        if (mdcOrderList.size() >= maxSize && isCleaning.compareAndSet(false, true)) {

            try {
                mdcOrderList.subList(0, mdcOrderList.size() - maxSize).forEach(//看測試效率,看是否用並行stream處理
                        mdc -> removeThread(mdc, true)
                );
            } finally {
                isCleaning.set(false);
            }
        }
    }
}
複製程式碼

TestController(測試入口):

import com.javanewb.entity.TestThreadHolder;
import com.javanewb.thread.tools.RequestHolder;
import com.keruyun.portal.common.filter.LoggerMDCFilter;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * <p>
 * Description: com.javanewb.controller
 * </p>
 * <p>
 * Copyright: Copyright (c) 2015
 * </p>
 * <p>
 
 * </p>
 * date:2017/10/25
 *
 * @author Dean.Hwang
 */
@Api
@RestController
@Slf4j
public class TestController {
    private RequestHolder<String> holder = new RequestHolder<>(100, 500000L);
    private List<String> mdcList = new ArrayList<>();

    @ApiOperation(value = "請求同步測試", notes = "請求同步測試")
    @RequestMapping(value = "/async", method = RequestMethod.GET)
    public void async(HttpServletRequest request, HttpServletResponse response, String id) {
        Long startTime = System.currentTimeMillis();
        String mdc = MDC.get(LoggerMDCFilter.IDENTIFIER);
        mdcList.add(mdc);
        Future<String> future = holder.getFuture(id, TestThreadHolder.class);
        log.info(Thread.currentThread().getName());
        try {
            System.out.println(mdc + " Thread Wait");
            String result = future.get();
            response.getOutputStream().print(result);
            System.out.println(" time: " + (System.currentTimeMillis() - startTime));
        } catch (IOException | ExecutionException | InterruptedException e) {
            e.printStackTrace();
        }
    }

    @ApiOperation(value = "釋放list第一個", notes = "請求同步測試")
    @RequestMapping(value = "/notify", method = RequestMethod.GET)
    public String notifyFirst() {
        String mdc = mdcList.get(0);
        mdcList.remove(0);
        holder.notifyThread(mdc, "");
        return mdc;
    }

    @ApiOperation(value = "釋放list第一個", notes = "請求同步測試")
    @RequestMapping(value = "/notifyThis", method = RequestMethod.GET)
    public String notifyThis(String mdc) {
        int idx = 0;
        for (int i = 0; i < mdcList.size(); i++) {
            if (mdcList.get(i).equals(mdc)) {
                idx = i;
                break;
            }
        }
        mdcList.remove(idx);
        holder.notifyThread(mdc, "");
        return mdc;
    }
}
複製程式碼

5.後話:

本專案會放在github上,如果有興趣,或者發現有bug需要處理的可以直接從部落格聯絡我,也可以直接去github 地址:github.com/crowhyc/Thr… 郵箱:crowhyc@163.com

相關文章