文章連結: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或者有更好的建議,歡迎批評指正,不吝感激
- 微博地址: 小灰灰Blog
- QQ: 一灰灰/3302797840