Quick-Task 動態指令碼支援框架之使用介紹篇

一灰灰發表於2018-07-19

logo

文章連結:liuyueyi.github.io/hexblog/201…

Quick-Task 動態指令碼支援框架之使用介紹篇

相關博文:

QuickTask這個專案主要就是為了解決資料訂正和介面驗證不方便的場景,設計的一個及其簡單的動態指令碼排程框架,前面一篇整體介紹篇博文,主要介紹了這是個什麼東西,整體的執行原理,以及一些簡單的使用demo

本篇博文將主要放在應用場景的探討上,在實際的專案環境中,可以怎麼用

I. 框架使用姿勢

支目前來說,有兩種簡單的使用方式,一是以獨立的jar包來執行,二是整合在已有的專案中執行;下面分別給出介紹

1. 獨立jar包執行

獨立jar包下載,首先下載原始工程,然後打出一個可執行的jar包即可

git clone https://github.com/liuyueyi/quick-task
cd quick-task/task-core
mvn clean package -Dmaven.test.skip
cd target
java -jar task-core-0.0.1.jar --task /tmp/script
複製程式碼

注意上面的jar包執行中,傳入的--task引數,這個就是制定監聽動態指令碼的目錄,如上面的指令碼,表示框架會自動載入 /tmp/script 目錄下的Groovy指令碼,並執行

當指令碼發生變動時,同樣會重新載入更新後的groovy並執行,且會停掉原來的指令碼

2. 專案依賴使用

作為一個依賴來使用也是可以的,首先是新增pom依賴

<repositories>
    <repository>
        <id>yihui-maven-repo</id>
        <url>https://raw.githubusercontent.com/liuyueyi/maven-repository/master/repository</url>
    </repository>
</repositories>

<dependency>
    <groupId>com.git.hui</groupId>
    <artifactId>task-core</artifactId>
    <version>0.0.1</version>
</dependency>
複製程式碼

然後在自己的程式碼中,顯示的呼叫下面一行程式碼即可,其中run方法的引數為動態指令碼的目錄

new ScriptExecuteEngine().run("/tmp/script");
複製程式碼

對於SpringBoot專案而言,可以在入口Application類的run方法中呼叫,一個demo如下

@SpringBootApplication
public class Application implements CommandLineRunner {
    public static void main(String[] args) throws Exception {
        SpringApplication app = new SpringApplication(Application.class);
        app.run(args);
    }

    @Override
    public void run(String... strings) throws Exception {
        new ScriptExecuteEngine().run("/tmp/script");
    }
}
複製程式碼

對於傳統的Spring專案而言,可以新建一個Listener, 監聽所有的bean初始化完成之後,開始註冊任務引擎,一個可參考的使用case如下

import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class RegisterTaskEngineListener implements SmartApplicationListener {
    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
        return aClass == ContextRefreshedEvent.class;
    }

    @Override
    public boolean supportsSourceType(Class<?> aClass) {
        return true;
    }

    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        new ScriptExecuteEngine().run("/tmp/script");
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
複製程式碼

3. 對比小結

兩種使用方式,從個人角度出發,並沒有什麼優劣之別,主要還是看具體的業務場景,當希望部署一個獨立的任務指令碼支援時,可能獨立的部署更加的方便,可以在內部進行資源隔離,減少對線上生產環境的影響;

若是單純的把這個作為一個檢測專案執行的輔助工具時,如回撥線上的服務介面,判斷輸出,獲取執行專案中的內部引數等,整合在已有的專案中也是比較簡單的

II. 實際場景演示

使用了這個框架,到底有什麼用處呢?或者說是否有一些適用的經典case呢?

1. 資料檢視

這種場景比較常見,但一般配套設施齊全的公司,也不會出現這個問題,我們最常見的檢視資料有以下幾類

  • DB資料檢視
  • 快取資料檢視
  • 記憶體資料檢視

對於DB檢視,一般沒啥問題,要麼可以直連查詢要麼就是有查詢工具;而快取資料的查詢,主要是我們通過序列化後存入的資料,直接從快取中獲取可能並不太友好;對於執行時記憶體中的資料,就不太好獲取了,特別是我們使用Guava快取的資料,如何在專案執行中判斷快取中的資料是否有問題呢?

一個檢視記憶體的虛擬碼

class DemoScript implements ITask {
    @Override
    void run() {
        // 獲取目標物件
        xxxBean = ApplicationContextHolder.getBean(xxx.class);
        xxxBean.getXXX();
    }
}
複製程式碼

上面的指令碼中,關鍵就是在於獲取目標物件,拿到目標物件之後,再獲取內部的區域性變數或者記憶體資料就比較簡單了(不能直接訪問的區域性變數可以通過反射獲取)

所以關鍵就是獲取目標物件,有下面幾種思路可供參考:

  • 目標物件時單例或者靜態類,則可以直接訪問
  • 如果專案執行在Spring容器中,且目標物件為Bean,則可以通過 ApplicationContext#getBean 方式獲取

2. 介面呼叫

在問題復現的場景下,比較常用了,傳入相同的引數,判斷介面的返回結果是否ok,用於定位資料異常

class DemoScript implements ITask {
    @Override
    void run() {
        // 獲取目標物件
        xxxService = ApplicationContextHolder.getBean(xxx.class);
        
        req = buildRequest();
        result = xxxService.execute(req);
        log.info("result: {}", result);
    }
}
複製程式碼

其實實際使用起來和前面沒什麼區別,無非是線獲取到對應的Service,然後執行介面,當然在Spring的生態體系中,一個可展望的點就是支援自動注入依賴的bean

3. 定時任務

首先明確一點,在我們的框架中,所有的任務都是隔離的,獨立的執行緒中排程的,當我們希望一個新的任務每隔多久執行一次,可以怎麼做?

一個簡單的虛擬碼如下

class DemoScript implements ITask {
    private volatile boolean run = false;
    @Override
    void run() {
        run = true;
        while(true) {
          doXXX();
          
          try {
            Thread.sleep(1000);
          } catch(Exception e) {
          }
          
          if(!run) break;
        }
    }
    
    @Override
    void interrupt() {
        run = false;
    }
}
複製程式碼

注意下上面的實現,在run方法中,有一個死迴圈,一直在重複的呼叫 doxxx() 方法,在內部通過 Thread.sleep() 來控制頻率

在指令碼改變或刪除之後,框架會回撥 interrupt 方法,因此會將上面的run變數設定為false,從而結束死迴圈

注意:

  • 對於定時任務而言,後續會擴充套件一個對應ScheduleTask抽象類出來,將迴圈和中斷的邏輯封裝一下,對於使用方而言,只需要寫業務邏輯即可,不需要關心這些重複的邏輯

4. mq訊息消費

這種更多的是把這個框架作為一個排程來用,我們接收mq的訊息,然後在動態指令碼中進行處理,再傳給第三方(如果整合在自己的專案中時,一個demo就是可以直接呼叫專案中的Dao儲存資料)

一個RabbitMq的消費任務,對應的虛擬碼如下

class DemoScript implements ITask {
    @Override
    void run() {
      ConnectionFactory fac = new CachingConnectionFactory();
      fac.setHost("127.0.0.1");
      fac.setPort(5672);
      fac.setUsername("admin")
      fac.setPassword("admin")
      fac.setVirtualHost("/")
      
      //建立連線
      Connection connection = factory.newConnection();

      //建立訊息通道
      final Channel channel = connection.createChannel();

      //訊息佇列
      channel.queueDeclare(queue, true, false, false, null);
      //繫結佇列到交換機
      channel.queueBind(queue, exchange, routingKey);
     
      Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                    byte[] body) throws IOException {
                String message = new String(body, "UTF-8");

                try {
                    System.out.println(" [" + queue + "] Received '" + message);
                } finally {
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        // 取消自動ack
        channel.basicConsume(queue, false, consumer);   
    }
}
複製程式碼

注意:

  • 對於RabbitMQ的任務,後續計劃封裝一個抽象的任務指令碼,使業務方只需要關注自己的訊息處理即可,上面只是一個業務場景的使用演示

III. 其他

0. 相關

博文:

專案:

1. 一灰灰Blog: https://liuyueyi.github.io/hexblog

一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛

2. 宣告

盡信書則不如,已上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激

3. 掃描關注

blogInfoV2.png

相關文章