1.概述
XXL-JOB 是一個輕量級 的 分布式任務調度平臺,其核心設計目標是開發迅速、學習簡單、輕量級、易擴展,
開源社區:
https://www.xuxueli.com/xxl-job/#%E3%80%8A%E5%88%86%E5%B8%83%E5%BC%8F%E4%BB%BB%E5%8A%A1%E8%B0%83%E5%BA%A6%E5%B9%B3%E5%8F%B0XXL-JOB%E3%80%8B
參考資料:
《分布式任務調度平臺 XXL-JOB 官方檔案》一文讀懂分布式任務調度平臺XXL-JOB
2.特性
XXL-JOB 提供了 35 點特性串列,我們將其重新整理如下:
① 功能強大
- 1、簡單:支持通過 Web 頁面對任務進行 CRUD 操作,操作簡單,一分鐘上手;
- 2、動態:支持動態修改任務狀態、啟動/停止任務,以及終止運行中任務,即時生效;
- 3、事件觸發:除了”Cron 方式”和”任務依賴方式”觸發任務執行之外,支持基于事件的觸發任務方式,調度中心提供觸發任務單次執行的 API 服務,可根據業務事件靈活觸發,
- 4、GLUE:提供 Web IDE,支持在線開發任務邏輯代碼,動態發布,實時編譯生效,省略部署上線的程序,支持 30 個版本的歷史版本回溯,
- 5、腳本任務:支持以 GLUE 模式開發和運行腳本任務,包括 Shell、Python、NodeJS、PHP、PowerShell 等型別腳本;
- 6、支持通過命令列任務:原生提供通用命令列任務 Handler(Bean任務,”CommandJobHandler”);業務方只需要提供命令列即可;
- 7、支持任務間的依賴觸發:支持配置子任務依賴,當父任務執行結束且執行成功后將會主動觸發一次子任務的執行, 多個子任務用逗號分隔;
- 8、自定義任務引數:支持在線配置調度任務入參,即時生效;
- 9、跨平臺:原生提供通用 HTTP 任務Handler(Bean 任務,”HttpJobHandler”);業務方只需要提供 HTTP 鏈接,調度方直接呼叫即可,不限制語言、平臺;
② 高性能
- 1、分片廣播任務:執行器集群部署時,任務路由策略選擇”分片廣播”情況下,一次任務調度將會廣播觸發集群中所有執行器執行一次任務,可根據分片引數開發分片任務;
- 2、動態分片:分片廣播任務以執行器為維度進行分片,支持動態擴容執行器集群從而動態增加分片數量,協同進行業務處理;在進行大資料量業務操作時可顯著提升任務處理能力和速度,
- 3、調度執行緒池:調度系統多執行緒觸發調度運行,確保調度精確執行,不被堵塞;
- 4、全異步:任務調度流程全異步化設計實作,如異步調度、異步運行、異步回呼等,有效對密集調度進行流量削峰,理論上支持任意時長任務的運行;
- 5、執行緒池隔離:調度執行緒池進行隔離拆分,慢任務自動降級進入 ”Slow” 執行緒池,避免耗盡調度執行緒,提高系統穩定性;
③ 高可用
- 1、調度中心 HA(中心式):調度采用中心式設計,“調度中心”自研調度組件并支持集群部署,可保證調度中心 HA;
- 2、執行器 HA(分布式):任務分布式執行,任務”執行器”支持集群部署,可保證任務執行 HA;
- 3、路由策略:執行器集群部署時提供豐富的路由策略,包括:第一個、最后一個、輪詢、隨機、一致性 HASH、最不經常使用、最近最久未使用、故障轉移、忙碌轉移等;
- 4、故障轉移:任務路由策略選擇”故障轉移”情況下,如果執行器集群中某一臺機器故障,將會自動 Failover 切換到一臺正常的執行器發送調度請求,
- 5、阻塞處理策略:調度過于密集執行器來不及處理時的處理策略,策略包括:單機串行(默認)、丟棄后續調度、覆寫之前調度;
- 6、任務超時控制:支持自定義任務超時時間,任務運行超時將會主動中斷任務;
- 7、任務失敗重試:支持自定義任務失敗重試次數,當任務失敗時將會按照預設的失敗重試次數主動進行重試;其中分片任務支持分片粒度的失敗重試;
- 8、一致性:“調度中心”通過DB鎖保證集群分布式調度的一致性, 一次任務調度只會觸發一次執行;
④ 監控治理
- 1、注冊中心: 執行器會周期性自動注冊任務到調度中心, 調度中心將會自動發現注冊的任務并讓調度器去觸發執行,同時,也支持手動錄入執行器地址;
- 2、彈性擴容縮容:一旦有新執行器機器上線或者下線,下次調度時將會重新分配任務;
- 3、任務失敗告警;默認提供郵件方式失敗告警,同時預留擴展介面,可方便的擴展短信、釘釘等告警方式;
- 4、任務進度監控:支持實時監控任務進度;
- 5、Rolling 實時日志:支持在線查看調度結果,并且支持以 Rolling 方式實時查看執行器輸出的完整的執行日志;
- 6、郵件報警:任務失敗時支持郵件報警,支持配置多郵件地址群發報警郵件;
- 7、推送 Maven 中央倉庫: 將會把最新穩定版推送到 Maven 中央倉庫, 方便用戶接入和使用;
- 8、運行報表:支持實時查看運行資料,如任務數量、調度次數、執行器數量等;以及調度報表,如調度日期分布圖,調度成功分布圖等;
- 9、容器化:提供官方 Docker 鏡像,并實時更新推送 Docker Hub,進一步實作產品開箱即用;
- 10、用戶管理:支持在線管理系統用戶,存在管理員、普通用戶兩種角色;
- 11、權限控制:執行器維度進行權限控制,管理員擁有全量權限,普通用戶需要分配執行器權限后才允許相關操作;
3. 架構設計
3.1 設計思想
- 將調度行為抽象形成“調度中心”公共平臺,而平臺自身并不承擔業務邏輯,“調度中心”負責發起調度請求,
- 將任務抽象成分散的 JobHandler ,交由“執行器”統一管理,“執行器”負責接收調度請求并執行對應的 JobHandler中 業務邏輯,
因此,“調度”和“任務”兩部分可以相互解耦,提高系統整體穩定性和擴展性,
如果胖友對分布式任務調度平臺有一定了解的話,如果從調度系統的角度來看,可以分成兩類:
- 中心化: 調度中心和執行器分離,調度中心統一調度,通知某個執行器處理任務,
- 去中心化:調度中心和執行器一體化,自己調度自己執行處理任務,
如此可知 XXL-Job 屬于中心化的任務調度平臺,目前采用這種方案的還有:
- 鏈家的 kob
- 美團的 Crane(暫未開源)
去中心化的任務調度平臺,目前有:
- Elastic Job
- 唯品會的 Saturn
- Quartz 基于資料庫的集群方案
- 淘寶的 TBSchedule(暫停更新,只能使用阿里云 SchedulerX 服務)
3.2 系統組成
整個 XXL-JOB 系統,由調度中心和執行器兩個角色組成,分別處于不同的行程中,
調度中心
- 負責管理調度資訊,按照調度配置發出調度請求,自身不承擔業務代碼,
- 調度系統與任務解耦,提高了系統可用性和穩定性,同時調度系統性能不再受限于任務模塊,
- 支持可視化、簡單且動態的管理調度資訊,包括任務新建,更新,洗掉, GLUE 開發和任務報警等,所有上述操作都會實時生效,
- 支持監控調度結果以及執行日志,支持執行器 Failover ,
執行器
- 負責接收調度請求并執行任務邏輯,任務模塊專注于任務的執行等操作,開發和維護更加簡單和高效,
- 接收“調度中心”的執行請求、終止請求和日志請求等,
一般來說,XXL-JOB 執行器可以內嵌到應用服務里,例如說,一個提供 Restful API 的 Spring Boot 專案中,引入
xxl-job-core依賴,同時也作為一個 XXL-JOB 執行器,😈 本質上,每次 Restful API 是請求任務,而每次任務調度是定時任務,3.3 架構圖
整體架構如下圖所示:
注意,
【】中填寫調度中心和執行器;[]中填寫組件名,
- 注意,左邊是調度中心,右邊是執行器,
- [執行器]:[注冊執行緒] 根據配置的【調度中心】的地址,自動注冊到【調度中心】,
- [調度中心]:達到任務觸發條件,【調度中心】通過調度器下發任務給【執行器】,
- [執行器]:基于 [任務執行緒池] 執行任務,并把執行結果放入 [記憶體佇列] 中、把 [執行日志] 寫入 Log 日志檔案中,
- [執行器]:[回呼執行緒] 消費 [記憶體佇列] 中的調度結果,主動上報給[調度中心],
- 當用戶在[度中心]查看 [Rolling 任務日志],[調度中心] 請求 [執行器],[執行器]讀取 Log 日志檔案并回傳日志詳情,
3.4 高可用
XXL-JOB 的高可用,需要考慮調度中心的高可用、以及執行器的高可用,
注意,雖然說 XXL-JOB 執行的是后臺任務,即使掛掉,用戶的感知度也比較低,但是考慮高可用是一種良好的習慣,在高性能之前請做好高可用,
3.4.1 調度中心的高可用
調度中心支持多節點部署,基于資料庫行鎖,保證觸發器的名稱和執行時間相同,則只且僅有一個調度中心節點去下發任務給執行器,
核心代碼可見 XXL-JOB 的
JobScheduleHelper#start()方法:
// JobScheduleHelper.java // 獲得行鎖 conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection(); connAutoCommit = conn.getAutoCommit(); conn.setAutoCommit(false); preparedStatement = conn.prepareStatement( "select * from xxl_job_lock where lock_name = 'schedule_lock' for update" ); preparedStatement.execute(); // ...觸發任務調度 // 事務提交 conn.commit();
- 😈 XXL-JOB 的這塊方法,小 200 行,有點頭鐵,
3.4.2 執行器的高可用
執行器支持多節點部署,通過調度中心選擇其中的執行器,下發任務來執行,
當任務”路由策略”選擇”故障轉移(FAILOVER)”時,當調度中心每次發起調度請求時,會按照順序對執行器發出心跳檢測請求,第一個檢測為存活狀態的執行器將會被選定并發送調度請求,
① 路由策略
調度中心基于路由策略,選擇一個執行器節點下發任務,從而讓執行器執行任務,XXL-JOB 提供了如下路由策略保證任務調度高可用:
- 忙碌轉移(BUSYOVER)策略:當調度中心每次發起調度請求時,會按照順序對執行器發出空閑檢測請求,第一個檢測為空閑狀態的執行器將會被選定并發送調度請求,具體代碼,見 ExecutorRouteFailover 類,
- 故障轉移(FAILOVER)策略:當調度中心每次發起調度請求時,會按照順序對執行器發出心跳檢測請求,第一個檢測為存活狀態的執行器將會被選定并發送調度請求,具體代碼,見 ExecutorRouteFailover 類,
還有第一個(FIRST)、LAST(最后一個)、輪詢(ROUND)、隨機(RANDOM)、CONSISTENT_HASH(一致性 HASH)、最不經常使用(LEAST_FREQUENTLY_USED)、最近最久未使用(LEAST_RECENTLY_USED)、分片廣播(SHARDING_BROADCAST)路由策略,胖友可以看看
com.xxl.job.admin.core.route.strategy/包下的代碼,嚴格來說,②、③、④ 不算實作執行器的高可用的點,列出來只是不知道放在哪里,又覺得用戶需要了解,
② 阻塞處理策略
當調度過于密集時,執行器來不及處理時,則當執行器節點存在多個相同任務編號的任務未執行完成,則需要基于阻塞處理策略對任務進行取舍:
- 單機串行(默認):調度請求進入單機執行器后,調度請求進入 FIFO 佇列并以串行方式運行,
- 丟棄后續調度:調度請求進入單機執行器后,發現執行器存在運行的調度任務,本次請求將會被丟棄并標記為失敗,
- 覆寫之前調度:調度請求進入單機執行器后,發現執行器存在運行的調度任務,將會終止運行中的調度任務并清空佇列,然后運行本地調度任務,
具體可見
ExecutorBizImpl#run()方法的代碼,③ 故障轉移 & 失敗重試
一次完整任務流程包括”調度(調度中心) + 執行(執行器)”兩個階段,
- “故障轉移”發生在調度階段,在執行器集群部署時,如果某一臺執行器發生故障,該策略支持自動進行 Failover 切換到一臺正常的執行器機器并且完成調度請求流程,
- “失敗重試”發生在”調度 + 執行”兩個階段,支持通過自定義任務失敗重試次數,當任務失敗時,將會按照預設的失敗重試次數主動進行重試,
④ 任務超時時間
支持設定任務超時時間,任務運行超時的情況下,將會主動中斷任務,
需要注意的是,任務超時中斷時與任務終止機制類似,也是通過 “interrupt” 中斷任務,因此業務代碼需要將 InterruptedException 外拋,否則功能不可用,
3.5 同類框架比較
特性 quartz elastic-job-lite xxl-job LTS 依賴 MySQL、jdk jdk、zookeeper mysql、jdk jdk、zookeeper、maven 高可用 多節點部署,通過競爭資料庫鎖來保證只有一個節點執行任務 通過zookeeper的注冊與發現,可以動態的添加服務器 基于競爭資料庫鎖保證只有一個節點執行任務,支持水平擴容,可以手動增加定時任務,啟動和暫停任務,有監控 集群部署,可以動態的添加服務器,可以手動增加定時任務,啟動和暫停任務,有監控 任務分片 × √ √ √ 管理界面 × √ √ √ 難易程度 簡單 簡單 簡單 略復雜 高級功能 - 彈性擴容,多種作業模式,失效轉移,運行狀態收集,多執行緒處理資料,冪等性,容錯處理,spring命名空間支持 彈性擴容,分片廣播,故障轉移,Rolling實時日志,GLUE(支持在線編輯代碼,免發布),任務進度監控,任務依賴,資料加密,郵件報警,運行報表,國際化 支持spring,spring boot,業務日志記錄器,SPI擴展支持,故障轉移,節點監控,多樣化任務執行結果支持,FailStore容錯,動態擴容, 版本更新 半年沒更新 2年沒更新 最近有更新
1年沒更新 也推薦看看如下文章:
- 《分布式定時任務調度系統技術選型》
- 《Azkaban、Xxl-Job 與 Airflow 對比分析》
4. 搭建調度中心
本小節,我們來搭建一個調度中心,XXL-JOB 暫未提供直接直接啟動的
jar包,所以需要自己編譯原始碼,考慮到降低大家的學習成本,我們使用 IDEA 進行操作,
4.1 克隆原始碼
使用 IDEA ,從碼云 https://gitee.com/xuxueli0323/xxl-job 克隆原始碼,從碼云克隆的原因是,速度比較快,
如果胖友想直接下載原始碼,可以到 XXL-JOB Releases 下載需要的版本,
克隆完成后,耐心等待下載完依賴,完成后,整體專案結構如下圖:專案結構
xxl-job-core模塊:XXL-JOB 核心,后續我們在撰寫執行器時,會引入該模塊,xxl-job-admin模塊:調度中心,xxl-job-executor-samples模塊:提供了在 Spring、Spring Boot、JFinal、Nutz 等框架下的使用示例,這里,我們需要編譯的主要是
xxl-job-admin模塊,即調度中心,4.2 初始化 XXL-JOB 表結構
在
doc/db/tables_xxl_job.sql地址,是 XXL-JOB 表結構的初始化腳本,我們需要在資料庫中執行該腳本,完成初始化 XXL-JOB 表結構,如下圖所示:XXL-JOB 表結構
- xxl_job_lock:任務調度鎖表;
- xxl_job_group:執行器資訊表,維護任務執行器資訊;
- xxl_job_info:調度擴展資訊表: 用于保存 XXL-JOB 調度任務的擴展資訊,如任務分組、任務名、機器地址、執行器、執行入參和報警郵件等等;
- xxl_job_log:調度日志表: 用于保存 XXL-JOB 任務調度的歷史資訊,如調度結果、執行結果、調度入參、調度機器和執行器等等;
- xxl_job_log_report:調度日志報表:用戶存盤 XXL-JOB 任務調度日志的報表,調度中心報表功能頁面會用到;
- xxl_job_logglue:任務GLUE日志:用于保存 GLUE 更新歷史,用于支持 GLUE 的版本回溯功能;
- xxl_job_registry:執行器注冊表,維護在線的執行器和調度中心機器地址資訊;
- xxl_job_user:系統用戶表;
自 XXL-JOB 2.1.0 Release 版本,去除對 Quartz 的依賴,所以我們就看不到 Quartz 相關的表哈,
4.3 修改組態檔
打開
xxl-job-admin模塊,修改src/main/resources/application.properties組態檔,如下:
### web # Web 服務器 server.port=8080 server.context-path=/xxl-job-admin ### actuator management.context-path=/actuator management.health.mail.enabled=false ### resources spring.mvc.static-path-pattern=/static/** spring.resources.static-locations=classpath:/static/ ### freemarker spring.freemarker.templateLoaderPath=classpath:/templates/ spring.freemarker.suffix=.ftl spring.freemarker.charset=UTF-8 spring.freemarker.request-context-attribute=request spring.freemarker.settings.number_format=0.########## ### mybatis mybatis.mapper-locations=classpath:/mybatis-mapper/*Mapper.xml ### xxl-job, datasource 調度中心 JDBC 鏈接 spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?Unicode=true&characterEncoding=UTF-8 spring.datasource.username=root spring.datasource.password=root_pwd spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource spring.datasource.tomcat.max-wait=10000 spring.datasource.tomcat.max-active=30 spring.datasource.tomcat.test-on-borrow=true spring.datasource.tomcat.validation-query=SELECT 1 spring.datasource.tomcat.validation-interval=30000 ### xxl-job email 報警郵箱 spring.mail.host=smtp.qq.com spring.mail.port=25 spring.mail.username=xxx@qq.com spring.mail.password=xxx spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true spring.mail.properties.mail.smtp.starttls.required=true spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory ### xxl-job, access token 調度中心通訊TOKEN [選填]:非空時啟用;調度中心國際化配置 [選填]: 默認為空,表示中文; "en" 表示英文; xxl.job.accessToken= ### xxl-job, i18n (default empty as chinese, "en" as english) xxl.job.i18n= ## xxl-job, triggerpool max size 調度執行緒池最大執行緒配置 xxl.job.triggerpool.fast.max=200 xxl.job.triggerpool.slow.max=100 ### xxl-job, log retention days 調度中心日志表資料保存天數 [必填]:過期日志自動清理;限制大于等于7時生效,否則, 如-1,關閉自動清理功能; xxl.job.logretentiondays=30
可以看到 XXL-JOB 使用了 Spring Boot ,嘿嘿,配置項比較多,說下必須要改的項:
server.port:XXL-JOB 調度中心的服務器地址,可以根據自己的需要,修改該埠,spring.datasource:XXL-JOB 調度中心的資料源地址,必須修改成自己準備提供給 XXL-JOB 的資料庫地址,spring.mail:報警郵箱,生產環境下必須配置,不然定時任務執行報錯都不知道,簡直要命,😈 一般來說下,建議有時間的胖友,修改下 XXL-JOB 的原始碼,把釘釘告警接入,xxl.job.accessToken:調度中心通訊令牌,建議填寫,雖然說,內網一般很安全,但是以防萬一,并且又沒啥成本,直接給整上,4.4 修改日志組態檔
打開
xxl-job-admin模塊,修改src/main/resources/logback.xml組態檔,如下:
<?xml version="1.0" encoding="UTF-8"?> <configuration debug="false" scan="true" scanPeriod="1 seconds"> <contextName>logback</contextName> <property name="log.path" value="/data/applogs/xxl-job/xxl-job-admin.log"/> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${log.path}</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${log.path}.%d{yyyy-MM-dd}.zip</fileNamePattern> </rollingPolicy> <encoder> <pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n </pattern> </encoder> </appender> <root level="info"> <appender-ref ref="console"/> <appender-ref ref="file"/> </root> </configuration>
- 默認情況下,日志輸出的地址是
/data/applogs/xxl-job/xxl-job-admin.log,可以根據自己的需要,進行調整,4.5 IDEA 啟動調度中心
在開始編譯原始碼之前,我們先直接使用 XxlJobAdminApplication 類,運行啟動調度中心,這樣,避免我們后面編譯原始碼,進行打包查出來的
jar包,結果組態檔不對的尷尬,當看到如下日志,代表啟動成功:
23:19:11.950 logback [main] INFO o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8080"] 23:19:12.026 logback [main] INFO o.a.tomcat.util.net.NioSelectorPool - Using a shared selector for servlet write/read 23:19:12.181 logback [main] INFO o.s.b.c.e.t.TomcatEmbeddedServletContainer - Tomcat started on port(s): 8080 (http)啟動成功后,瀏覽器 http://127.0.0.1:8080/xxl-job-admin 地址,并使用默認 "admin/123456" 進行登錄,如果登錄成功,說明我們已經配置正確啦,
4.6 編譯原始碼
# 進入 xxl-job-admin 模塊根目錄 $ cd /Users/yunai/Java/xxl-job2/ # 使用 Maven 打包指定 xxl-job-admin 模塊,并忽略測驗 $ mvn clean package -pl xxl-job-admin -am -DskipTests打包完成后,在
xxl-job-admin/target/xxl-job-admin-2.1.2-SNAPSHOT.jar地址下,就是我們要啟動的 XXL-JOB 調度中心的jar包,4.7 命令列啟動調度中心
# 進入 xxl-job-admin jar 包所在目錄 $ cd xxl-job-admin/target/ # 啟動調度中心 $ jar -jar xxl-job-admin-2.1.2-SNAPSHOT.jar當看到如下日志,代表啟動成功:
23:19:11.950 logback [main] INFO o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8080"] 23:19:12.026 logback [main] INFO o.a.tomcat.util.net.NioSelectorPool - Using a shared selector for servlet write/read 23:19:12.181 logback [main] INFO o.s.b.c.e.t.TomcatEmbeddedServletContainer - Tomcat started on port(s): 8080 (http)啟動成功后,瀏覽器 http://127.0.0.1:8080/xxl-job-admin 地址,并使用默認 "admin/123456" 進行登錄,如果登錄成功,說明我們已經配置正確啦,
另外,啟動完成之后,記得去「用戶管理」選單,修改下管理員的密碼喲,不然嘿嘿嘿,
4.8 搭建集群
艿艿:如果胖友僅僅是簡單入門,這步為可選項,
在生產環境下,一定要部署 XXL-JOB 調度中心的集群,提升調度系統容災和可用性,
調度中心集群部署時,幾點要求和建議:
- DB 配置保持一致;
- 集群機器時鐘保持一致(單機集群忽視);
- 推薦通過 Nginx 為調度中心集群做負載均衡,分配域名,調度中心訪問、執行器回呼配置、呼叫 API 服務等操作均通過該域名進行,
另外,如果胖友想要使用 Docker 鏡像方式搭建調度中心,可以自行參看 XXL-JOB 的官方檔案,
5. 搭建執行器
在 《芋道 Spring Boot 定時任務入門》 的 「5. 快速入門 XXL-JOB」 小節,我們提供了如何在 Spring Boot 中,使用 XXL-JOB 執行器,嘻嘻,
666. 彩蛋
暫時木有彩蛋,后續艿艿在補充下我們對 XXL-JOB 的使用的最佳實踐和改造,例如說:
- 接入釘釘告警
- 修改成 Spring Boot 2.X 版本
- 監控 XXL-JOB 調度中心
- 等等
建議的話,有時間在細細看看 《分布式任務調度平臺 XXL-JOB 官方檔案》 吧,查漏補缺,嘿嘿,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/252114.html
標籤:其他
上一篇:apache 測驗 5





