定時任務管理中心(dubbo+spring)-我們到底能走多遠系列47

来源:http://www.cnblogs.com/killbug/archive/2017/01/22/6339178.html
-Advertisement-
Play Games

我們到底能走多遠系列47 扯淡: 又是一年新年時,不知道上一年你付出了多少,收穫了多少呢?也許你正想著老闆會發多少獎金,也許你正想著明年去哪家公司投靠。 這個時間點好好整理一下,思考總結一下,的確是個非常好的機會。 年終的時候各個公司總會評一下績效,拉出各位的成績單,你是不是想說:去你媽的成績單,我 ...


我們到底能走多遠系列47

扯淡:

  又是一年新年時,不知道上一年你付出了多少,收穫了多少呢?也許你正想著老闆會發多少獎金,也許你正想著明年去哪家公司投靠。

  這個時間點好好整理一下,思考總結一下,的確是個非常好的機會。

  年終的時候各個公司總會評一下績效,拉出各位的成績單,你是不是想說:去你媽的成績單,我不是你的學生,老子努力工作不是為了看你臉色!當然啦,你想說這話的前提是:你很牛b,如果不是也可以想想,然後默默去變牛b。

  我大多數的朋友同事都是漂在城市裡的人,我們努力的活得更好,想過自己想過的生活,打心裡佩服我們自己,選擇這個行業,正嘗試改變著世界。

  所以,加油,各位!

  另外,程式員過什麼新年?寫bug的時間都不夠呢!

  最後還是祝看到這個文字的朋友:身體健康,闔家歡樂,雞年大吉公司上市

主題:

  一般,開一個定時任務很簡單,spring寫個註解就能跑了,或者單應用的定時任務還有很多其他豐富jar支持。

常規的一個場景:   一個系統一般都會有很多業務模塊組成,這些業務模塊被封裝成一個個獨立部署的應用拆分出去,獨立維護。各個業務模塊都會有自己定製的定時任務要跑,一般都會依賴自己業務數據和邏輯,很自然的寫在鴿子應用中。 那麼定時任務管理中心要做的是統一管理這些散落在各個業務模塊中的定時任務。    統一管理的好處是: 1,全系統定時任務一目瞭然,便於排查 2,任務執行相關信息統一到一起,比如日誌,而任務業務代碼開發和任務配置解耦  3,針對任務功能的開發升級集中到一個應用中了   這裡粗略設計一個定時任務管理系統拋磚引玉。 大致劃分以下三個部分: 1,任務管理系統 2,任務調度系統 3,業務實現的任務邏輯
任務管理系統用於配置任務的一些信息,任務調度使用這些信息實現對業務系統進行調度,實現定時任務。 拆解後各個組件的關係如下:   當然,為了跟好的描述這個系統,以上圖是一個簡化的設計圖。 如何實現呢? 這裡提供一個代碼的方案,實際開發中結合實際場景和當時技術遺產還有很多的技術方案可以設計,還可以深度挖掘。 首先我們給每個應用提供任務管理中心的jar包,在業務應用啟動的時候我們要把任務service收集起來,放入一個map,然後統一提供出一個dubbo介面,用dubbo的group區分各個應用。當任務調度需要調用到這個應用的某個任務service時,再從map中拿出spring bean執行任務方法。   以上功能的jar的核心代碼如下:
public class TaskSupport implements BeanPostProcessor, ApplicationListener<ApplicationContextEvent>,
        ApplicationContextAware {

    private static final Logger logger = LoggerFactory.getLogger(TaskSupport.class);
    private ApplicationContext applicationContext;
    private RegistryConfig registryConfig;
    private ApplicationConfig applicationConfig;
    private ProtocolConfig protocolConfig;
  // 存儲任務bean
    private Map<String, Object> taskBeanMap = new HashMap<String, Object>();
  // dubbo config
    private ServiceConfig<Object> serviceConfig;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        collectTaskBean(bean, beanName);
        return bean;
    }

    private Object getTarget(Object bean) {
        Object target = bean;
        while (target instanceof Advised) {
            try {
                target = ((Advised) bean).getTargetSource().getTarget();
            } catch (Exception e) {
                target = null;
                break;
            }
        }
        return target;
    }

    private void collectTaskBean(Object bean, String beanName) {
        Object target = getTarget(bean);
        if (target != null) {
            Class<?> clazz = target.getClass();
            if (!clazz.isAnnotationPresent(Service.class) || !clazz.isAnnotationPresent(Task.class)) {
                return;
            }
            if (!taskBeanMap.containsKey(beanName)) {
                logger.info("add task bean {}", beanName);
                taskBeanMap.put(beanName, bean);
            }
        }
    }
    @Override
    public void onApplicationEvent(ApplicationContextEvent event) {
        if (isCurrentApplicationContextRefresh(event)) {
            exportTaskDispatcher();
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 向Task暴露任務分發器服務
     */
    protected void exportTaskDispatcher() {
        if (serviceConfig != null && serviceConfig.isExported()) {
            return;
        }
        applicationConfig = applicationContext.getBean(ApplicationConfig.class);
        registryConfig = applicationContext.getBean("soaRegistryConfig", RegistryConfig.class);
        protocolConfig = applicationContext.getBean(ProtocolConfig.class);
        TaskDispatcherImpl taskServiceProxyImpl = wireTaskServiceProxy();
        exportServiceConfig(taskServiceProxyImpl);
    }

    protected void unexportTaskDispatcher() {
        if (serviceConfig != null && serviceConfig.isExported()) {
            serviceConfig.unexport();
        }
    }

    private TaskDispatcherImpl wireTaskServiceProxy() {
        AutowireCapableBeanFactory beanFactory = applicationContext.getAutowireCapableBeanFactory();
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;
        TaskDispatcherImpl taskServiceProxy = new TaskDispatcherImpl();
        // ITaskDispatcher的實現bean載入到spring bean容器中,等待dubbo介面暴露
        taskServiceProxy = (TaskDispatcherImpl) beanFactory.initializeBean(taskServiceProxy, "taskServiceProxy");
        // 這裡把篩選出的taskBeanMap註入到TaskDispatcherImpl,直接可以使用
        taskServiceProxy.setTaskBeanMap(taskBeanMap);
        taskServiceProxy.setApplicationConfig(applicationConfig);
        taskServiceProxy.setRegistryConfig(registryConfig);
        defaultListableBeanFactory.registerSingleton("taskServiceProxy", taskServiceProxy);
        return taskServiceProxy;
    }
 /**
     * dubbo介面暴露給任務調度系統
     * 
     */
    private void exportServiceConfig(Object proxy) {
        serviceConfig = new ServiceConfig<Object>();
        serviceConfig.setApplication(applicationConfig);
        serviceConfig.setRegistry(registryConfig);
        serviceConfig.setProtocol(protocolConfig);
        // 把這個介面暴露出去
        serviceConfig.setInterface(ITaskDispatcher.class);
        serviceConfig.setRef(proxy);
        serviceConfig.setRetries(0);
        // 各個業務系統的group不同,這裡充分利用了dubbo的屬性
        serviceConfig.setGroup(applicationConfig.getName());
        serviceConfig.export();
    }

    /**
     * 是否是當前上下文,防止重覆載入和過早載入
     * 
     * @param event
     * @return
     */
    private boolean isCurrentApplicationContextRefresh(ApplicationEvent event) {
        return event instanceof ContextRefreshedEvent
                && ((ContextRefreshedEvent) event).getApplicationContext() == applicationContext;
    }
}

這樣一來,所有應用都會暴露一個ITaskDispatcher 類的方法出去,但是各個group不一樣。ITaskDispatcher定義的方法:

public interface ITaskDispatcher {
   public void dispatch(TaskInvokeInfoDto taskInvokeInfoDto);
}

dispatch方法是調度中心調度觸發啟動任務的方法,根據TaskInvokeInfoDto這個參數里的定義,需要定位到哪一個應用的哪一個類的那一個方法,這個方法的參數是什麼,定位到後執行它,這就是dispatch要實現的功能。

先看一下TaskInvokeInfoDto的定義:

private String appName;//定位到哪個應用,dubbo的group區分
private String beanName;//定位到哪個類
private String methodName;// 定位到哪個方法
private String[] parameterTypes;//方法的參數類型,有重載的情況
private String[] args;//參數值

那麼dispatch的核心代碼:

public void dispatch(TaskInvokeInfoDto taskInvokeInfoDto) {
   try {
      Method method = findMethod(taskInvokeInfoDto);
      Class<?>[] parameterClazzs = method.getParameterTypes();
      if (parameterClazzs.length == 0) {
         ReflectionUtils.invokeMethod(method, taskBeanMap.get(taskInvokeInfoDto.getBeanName()));
      } else {
         Object[] parameterObjs = new Object[parameterClazzs.length];
         for (int i = 0; i < parameterClazzs.length; i++) {
            parameterObjs[i] = Jackson.base().readValue(taskInvokeInfoDto.getArgs()[i], parameterClazzs[i]);
         }
         ReflectionUtils.invokeMethod(method, taskBeanMap.get(taskInvokeInfoDto.getBeanName()), parameterObjs);
      }
    } catch (Exception e) {
      logger.error("execute error...", e);
   }
}
// 上面將的定位邏輯
private Method findMethod(TaskInvokeInfoDto taskInvokeInfoDto) {
        Object bean = taskBeanMap.get(taskInvokeInfoDto.getBeanName());
        Method method = null;
        if (ArrayUtils.isEmpty(taskInvokeInfoDto.getParameterTypes())) {
            method = ReflectionUtils.findMethod(bean.getClass(), taskInvokeInfoDto.getMethodName());
        } else {
            final int paramCount = taskInvokeInfoDto.getParameterTypes().length;
            Class<?>[] clazzArray = new Class<?>[paramCount];
            for (int i = 0; i < paramCount; i++) {
                try {
                    clazzArray[i] = ClassUtils.getClass(taskInvokeInfoDto.getParameterTypes()[i]);
                } catch (ClassNotFoundException e) {
                    logger.info("根據參數類型的字元串創建class對象時失敗", e);
                    return null;
                }
            }
            method = ReflectionUtils.findMethod(bean.getClass(), taskInvokeInfoDto.getMethodName(), clazzArray);
        }
        return method;
    }

以上只要在調度中心處調用dubbo來控制任務執行就可以實現整個任務中心的核心功能。

當然,這裡只是簡單的嘗試性的實現,還有很多優化和擴展可以做,比如任務日誌列印收集,任務應用存活狀態心跳監控,等等。

以前看到過一篇去哪網的吹b文章,吹了半天,仔細看了他提到的功能和沒實現的功能,搞過的人都會覺得做一個其實不難,只是人家分享的時候感覺很厲害,其實他自己心裡清楚自己這個系統也是處處是坑。雖然吹b,不過也會給我們各種啟發。

 

總結:

1,代碼中利用spring的BeanPostProcessor,篩選出自己需要的bean的方式又是一種新的技巧,我在《請求路由到業務方法設計(2)》中需要篩選bean map用了另一種方式。不知道網友還有其他的想法嗎?

2,反射相關的api還可以繼續深入學習。

 

讓我們繼續前行

----------------------------------------------------------------------

努力不一定成功,但不努力肯定不會成功。

 
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 關於C#調用廣州醫保HG_Interface.dll調用的一些總結(外部組件異常) ...
  • 在SuperSocket入門(二)中我們已經簡單瞭解了通過配置App.config文件使用BootStrap啟動SuperSocket服務。我們先來看一下上個案例中的基本配置文件示例: <?xml version="1.0" encoding="utf-8"?> <configuration> <c ...
  • 這個問題說起來,我有點慚愧 想當初在大學里學的就是ASP.NET WebForms 在實習期間也是用的WebForms來開髮網站,然後就覺得.NET開髮網站就是用這個開發模式 現在想想都想笑。。。實在忍不住了,我要笑了。哈哈哈!!! 好,回到正題 ASP.NET 是一個使用 HTML、CSS、Jav ...
  • 一、構造方法 類的構造方法是類的成員方法的一種,它的作用是對類中的成員進行初始化操作。類的構造方法分為: 1.靜態構造方法 2.實例構造方法 1.靜態構造方法 類的靜態構造方法是類的成員方法的一種,它的作用是對類中的靜態成員進行初始化操作。下麵請看代碼實例: 1 using System; 2 na ...
  • 1.直接在Global文件中配置: 1 var formatters = GlobalConfiguration.Configuration.Formatters; 2 var jsonFormatter = formatters.JsonFormatter; 3 var settings = js... ...
  • Mac系統上雖然自帶PHP和Apache,但是有時不是我們想要的版本呢。今天我們就在macOS Sierra(10.12.1)上安裝比較新的版本的PHP版本,也就是PHP7.0+了。本篇博客我們安裝的Apache是2.4的版本, MySQL5.7.16。稍後會詳細介紹這一過程。 一、安裝前的準備 1 ...
  • 題目描述 一條消息被編碼為一個文本流,被逐字元地讀取。這個流包含了一系列由逗號分隔的整數,每個整數都可以用C的int類型表示。但是,一個特定整數所表示的字元取決於當前的解碼模式。共有3種這樣的模式:大寫字母、小寫字母和標點符號。 在大寫字母模式下,每個整數表示一個大寫字母:這個整數除以27的餘數表示 ...
  • Q:Access denied for user 'root'@'localhost' 錯誤 A:第一種:配置文件中把資料庫的用戶名密碼再改一遍,把runtime里的文件刪除 第二種:修改system的host文件,關聯 127.0.0.1 localhost ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...