Gobrs-Async — это мощный, гибко настраиваемый фреймворк многопоточной асинхронной параллельной обработки и динамической компоновки с полным цепочным обратным вызовом ошибок, оптимизацией памяти и управлением состояниями. Он предоставляет предприятиям возможность динамического управления задачами в сложных сценариях.
Для решения проблем, связанных с асинхронностью, зависимостью задач и трудностями контроля состояний при работе со сложными сценариями, был создан Gobrs-Async.
При разработке сложных корпоративных систем часто возникают ситуации, когда требуется обращение к различным данным корпоративных сервисов, что приводит к сложным зависимостям данных. В таких случаях сложность кода увеличивается. Пример такой ситуации представлен ниже:
<dependency>
<groupId>io.github.memorydoc</groupId>
<artifactId>gobrs-async-starter</artifactId>
<version>1.2.9-RELEASE</version>
</dependency>
Клонировать исходный код Gobrs-Async здесь.Запустить приложение GobrsAsyncExampleApplication
модуля Gobrs-Async-Example.
Изменять конфигурацию пула потоков через HTTP GET запрос по адресу: http://localhost:8888/gobrs/testGobrs
. Вывод в консоли IntelliJ IDEA:
EService Begin
AService Begin
AService Finish
BService Begin
BService Finish
FService Begin
EService Finish
CService Begin
CService Finish
FService Finish
GService Begin
GService Finish
HService Begin
HService Finish
2022-11-27 19:08:51.080 INFO 61949 --- [nio-8888-exec-1] com.gobrs.async.core.TaskLoader : 【ProcessTrace】Общее время выполнения: 2536 мс | traceId = 11702850586978176 | 【задача】AService затратил времени: 302 мс【состояние】: успех; ->【задача】BService затратил времени: 0 мс【состояние】: успех; ->【задача】EService затратил времени: 602 мс【состояние】: успех; ->【задача】CService затратил времени: 305 мс【состояние】: успех; ->【задача】FService затратил времени: 2006 мс【состояние】: успех; ->【задача】GService затратил времени: 105 мс【состояние】: успех; ->【задача】HService затратил времени: 102 мс【состояние】: успех;
2551
Из логов можно видеть процесс выполнения всего цикла```sh [ProcessTrace] Общее время выполнения: 2536 мс | traceId = 11702850586978176 | [Задача] Время выполнения AService: 302 мс [Состояние]: Успех; -> [Задача] Время выполнения BService: 0 мс [Состояние]: Успех; -> [Задача] Время выполнения EService: 602 мс [Состояние]: Успех; -> [Задача] Время выполнения CService: 305 мс [Состояние]: Успех; -> [Задача] Время выполнения FService: 2006 мс [Состояние]: Успех; -> [Задача] Время выполнения GService: 105 мс [Состояние]: Успех; -> [Задача] Время выполнения HService: 102 мс [Состояние]: Успех;
* Общее время выполнения: общее время выполнения задач
* traceId: уникальный идентификатор трассировки задач
* время выполнения: время выполнения отдельной задачи### Конфигурационный файл
```yaml
server:
port: 8888 # порт: 8888
gobrs:
async:
config:
правила:
# правила являются массивами с множеством правил
- имя: "общий"
содержание: "AService->BService->FService->GService->HService;EService->CService;AService"
прерывание_задачи: false # прерывать основной поток при локальном сбое по умолчанию false
транзакция: false
server:
port: 8888 # порт: 8080
gobrs:
async:
включено: false # отключение движка компоновки
правила:
# поддерживает несколько пространств имён
- имя: "ruleName" # название правила
содержание: "AService->BService,CService,FService; BService->FService,GService;"
- имя: "azh"
содержание: "AService->BService,CService,FService; BService->FService,GService;"
прерывание_задачи: false # прерывать основной поток при локальном сбое
gobrs: async: config: правила: # правила являются массивами с множеством правил - имя: "общий" содержание: "AService->BService->FService->GService->HService;EService->CService;AService" прерывание_задачи: true # прерывать основной поток при локальном сбое по умолчанию false транзакция: true - имя: "любоеУсловиеОбщий" содержание: "AServiceCondition,BServiceCondition,CServiceCondition->DServiceCondition" конфигЛога: логируемоВремяВыполнения: false # включить вывод времени выполнения задач в логах уровня ошибки по умолчанию true логируемоСбои: true # включить вывод сообщений об ошибках задач по умолчанию true # AServiceCondition, BServiceCondition, CServiceCondition — задачи, одна из которых должна вернуть true для продолжения выполнения - имя: "любоеУсловиеПравило" содержание: "AServiceCondition,BServiceCondition,CServiceCondition->DServiceCondition:любоеУсловие"
``````markdown
- name: "anyConditionRuleAppend"
content: "AServiceCondition,BServiceCondition,CServiceCondition->DServiceCondition:anyCondition->EServiceCondition"
``````markdown
# Официальный сценарий 1 https://async.sizegang.cn/pages/2f844b/#%E5%9C%BA%E6%99%AF%E4%B8%80
- name: "caseOne"
content: "caseOneTaskA->caseOneTaskB,caseOneTaskC,caseOneTaskD"
# Официальный сценарий 2 https://async.sizegang.cn/pages/2f844b/#%E5%9C%BA%E6%99%AF%E4%B8%8D
- name: "caseTwo"
content: "caseTwoTaskA->caseTwoTaskB->caseTwoTaskC,caseTwoTaskD"
# Официальный сценарий 3 https://async.sizegang.cn/pages/2f844b/#%E5%9C%BA%E6%99%AF%E4%B8%89
- name: "caseThree"
content: "caseThreeTaskA->caseThreeTaskB,caseThreeTaskC,caseFourTaskD->caseThreeTaskG;
caseThreeTaskA->caseThreeTaskE,caseThreeTaskF->caseThreeTaskG;"
# Официальный сценарий 4 https://async.sizegang.cn/pages/2f844b/#%E5%9C%BA%E6%99%AF%E5%9B%9B
- name: "caseFour"
content: "caseFourTaskA->caseFourTaskB->caseFourTaskC,caseFourTaskD,caseFourTaskE;
caseFourTaskA->caseFourTaskH->caseFourTaskI,caseFourTaskJ,caseFourTaskK;"
logConfig:
costLoggable: false # Отключение вывода информации о времени выполнения задач
# Официальный сценарий 5 https://async.sizegang.cn/pages/2f844b/#%E5%9C%BA%E6%99%AF%E4%BA%94
- name: "caseFive"
content: "caseFourTaskA,caseFourTaskB,caseFourTaskC->caseFourTaskD:any:exclusive" # any — выполнение задачи D после завершения любого из A, B или C
# exclusive — предотвращает выполнение незавершенных задач после завершения задачи D
server:
port: 9999 # Порт: 9999
## Конфигурационный файл расположения
Если разработчики используют большое количество конфигураций, то совместное использование с конфигурационными файлами Spring Boot может создать избыточность. В этом случае можно создать конфигурационные файлы в директории `resources/config`, поддерживаются следующие варианты:
```* `gobrs.yaml`
* `gobrs.yml`
* `gobrs.properties`
## Структура правил
### Название правила name
Объект правила состоит из двух частей:
* Название правила
* Содержание правила
**Название правила** (name) представляет собой уникальный идентификатор правила: используется при запуске задач через триггер.
### Содержание правила content
**Содержание правила** (content) является основной частью для парсинга правил управления. В зависимости от различных сценариев выполнения задач, конфигурация правил также будет отличаться, но она не будет слишком сложной. Подробные сценарии конфигурации представлены ниже.
## Конфигурация логов logConfig
* costLogabled — вывод журнала процесса включает время выполнения, цепочку вызовов и т.д.
* errLogabled — вывод журнала ошибок выполнения задачи
:::tip Подсказка
Рассмотрим несколько сценариев, после чего вы сможете самостоятельно понять правила конфигурации.
**Примечание:** В нижеследующих конфигурациях A, B, C представляют собой имена бинов Spring для динамических задач, которые можно определить с помощью `<code>@Task("xxx")</code>`. ruleName1 представляет имя правила,
:::## Обработка исключений
При настройке конфигурации задачи `task-interrupt`, некоторые разработчики не хотят, чтобы исключения при выполнении задач были упакованы **gobrs-async**. Они предпочитают самостоятельно обрабатывать исключения, выполняя различные действия в зависимости от типа исключения. Для этого можно использовать конфигурацию
вывода исключений: `catchable`
```yaml
- name: "stopAsyncRule"
content: "stopAsyncTaskA,stopAsyncTaskB,stopAsyncTaskC;stopAsyncTaskD->stopAsyncTaskE->stopAsyncTaskF"
catchable: true
```## Примеры правил
## Сценарий 1
Как показано на рисунке 1-1

**Описание**
После завершения выполнения задачи А продолжаются выполнение Б, В и Д
**Конфигурация**
```yaml
gobrs:
async:
rules:
- name: "ruleName1"
content: "A->B,C,D"
Как показано на рисунке 1-2
Описание После завершения выполнения задачи А выполняется задача Б, а затем В и Д
Конфигурация
gobrs:
async:
rules:
- name: "ruleName1"
content: "A->B->C,D"
Как показано на рисунке 1-3
Описание После завершения выполнения задачи А выполняются задачи Б и Е, затем последовательно В, Д и Г для Б, Ф и Г для Е
Конфигурация
gobrs:
async:
rules:
- name: "ruleName1"
content: "A->B->C->D->G;A->E->F->G"
Как показано на рисунке 1-4
Описание Этот сценарий задач также легко поддерживаются gobrs-async
Конфигурация
gobrs:
async:
rules:
- name: "ruleName1"
content: "A->B->C,D,E;A->H->I,J,K"
Как показано на рисунке 1-5
Описание После выполнения задач А, Б и В выполняется задача ДКонфигурация
gobrs:
async:
rules:
- name: "ruleName1"
content: "A,B,C->D"
Описание
Если любой из задач A, B или C завершается выполнением, то сразу выполняется задача D (выполняет тот, кто быстрее завершил свою задачу, аналогично конкурирующим процессам). Для этого можно использовать ключевые слова конфигурации <code>:any</code>
.Конфигурация
gobrs:
async:
## :any — это ключевое слово, которое указывает на произвольное выполнение зависимости задачи при её завершении
правила:
- имя: "ruleName1"
содержание: "A,B,C->D:any"
Описание
Если любой из задач A, B или C завершается выполнением, то сразу выполняется задача D (выполняет тот, кто быстрее завершил свою задачу, аналогично конкурирующим процессам). Разница с примером 2 заключается в том, что если задача D получает право на выполнение, она прерывает выполнение своих зависимых незавершенных задач (чтобы избежать потери ресурсов и обеспечить нормальное выполнение бизнес-процессов). Для этого можно использовать ключевые слова конфигурации :exclusive
.
Конфигурация
gobrs:
async:
## :exclusive — это ключевое слово
правила:
- имя: "ruleName1"
содержание: "A,B,C->D:any:exclusive"
```### Пример 4
Похож на пример 2, но имеет некоторые различия. В контексте примера 2 невозможно было бы контролировать последующие задачи на основе успешного или неудачного выполнения конкретной задачи. В примере 2 выполнение происходит строго в соответствии со случайным порядком выполнения потока, то есть тот, кто первым завершает свою задачу, продолжает выполнение. Поэтому, чтобы контролировать выполнение задач на основе условий, таких как метод <code>task</code>, который возвращает `true`, чтобы немедленно выполнить следующую задачу, или вернуть `false`, чтобы прекратить выполнение, используется следующий подход.### Шаг 1: Разработка задачи
(Если вы не знаете, как создать динамическую задачу, прочитайте следующий раздел: **динамическая задача**)
```java
@Override
public Boolean task(Object o, TaskSupport support) {
try {
System.out.println("AServiceCondition Begin");
Thread.sleep(300);
for (int i1 = 0; i1 < i; i1++) {
i1 += i1;
}
System.out.println("AServiceCondition Finish");
} catch (InterruptedException e) {
// логирование
//e.printStackTrace();
// возвращаем false, если нет права на выполнение подзадач
return false;
}
// возвращаем true, чтобы продолжить выполнение подзадач
return true;
}
gobrs:
async:
## :any — это ключевое слово, которое указывает на произвольное выполнение зависимости задачи при её завершении
правила:
- имя: "ruleName1"
содержание: "A,B,C->D:anyCondition"
Пример 3 следует за примером 2, так как эти два примера представляют собой объединённую сценарий.
Конфигурация правил очень похожа на схемы процессов.
,
для разделения различных задач.->
для разделения потока задач.;
.:::tip
Если вам неудобно использовать указанные выше конфигурационные символы, вы можете создать свои собственные. В Gobrs-Async
это также поддерживается. Конфигурацию можно настроить в соответствии с вашими предпочтениями. Для этого достаточно настроить её в файле application.yml
.
:::## Как разработать асинхронную задачу:::warning Подсказка
Дойдя до этой точки, вероятно, вы уже нетерпеливо ждете возможности попробовать, как разрабатывается асинхронная задача в Gobrs-Async
. Давайте вместе исследуем, как должна быть реализована полная AsyncTask
.
:::
AsyncTask
@Task
Чтобы глобально перехватывать задачу, каждую задачу следует пометить специальным идентификатором. Это позволяет различать различные задачи при глобальном перехвате, выполнять различные логики или выводить различные журналы. Например, MQ, мониторинг трассировки и т.д. В Gobrs-Async
есть поддержка для этого. Вам просто нужно добавить аннотацию в бин задачи, чтобы сообщить фреймворку имя задачи. То есть глобальный перехватчик задач.
@Task(name = "itemTask")
Здесь
AsyncTask<T, V>
является параметризованным интерфейсом.
T
представляет тип параметров задачи.V
представляет тип возвращаемого значения задачи.Например:
/**
* @program: gobrs-async
* @ClassName TaskBean
* @description: Асинхронная задача с параметрами типа Object; возвращаемое значение типа Object
*/
@Task
public class BService extends AsyncTask<Object, Object> {
``````java
@Override
public void prepare(Object o) {
// operations before executing the task
}
@Override
public Object task(Object o, TaskSupport support) {
String result = getResult(support, AService.class);
System.out.println("Received result: " + result);
System.out.println("Executing BService");
return null;
}
}
## Conditions for task execution
Conditions for task execution are defined by the method `nessary`. This method checks whether tasks should be executed.
```java
@Override
public boolean nessary(Object o, TaskSupport support) {
return true;
}
Upon successful completion of a task, the method onSuccess
is called.
@Override
public void onSuccess(TaskSupport support) {
}
Upon unsuccessful completion of a task, the method onFail
is called.
@Override
public void onFail(TaskSupport support) {
}
The main method for task execution is the method task
, which implements business logic such as RPC, HTTP, IO and so on, consuming system resources.
@Override
public Object task(Object o, TaskSupport support) {
// todo business logic
return null;
}
The class TaskSupport
provides the method getResult
, which allows obtaining dependencies without user intervention.
@Override
public Object task(Object o, TaskSupport support) {
// Obtain result from AService (task bean)
String result = getResult(support, AService.class, String.class);
``` return null;
}
Метод getResult
принимает три параметра:
TaskSupport
: основной параметр, необходимый для работы с задачами.Class
: тип Java-класса, на котором основан бин зависимости.Class
: тип результата.Метод nessary
используется для определения того, должна ли задача выполняться. Возвращаемое значение true
указывает на то, что задача должна быть выполнена; в противном случае — нет.
Пример использования:
@Override
public boolean necessary(String params, TaskSupport support) {
// Если параметр равен "cancel", задача не будет выполнена
if ("cancel".equals(params)) {
return false;
}
return true;
}
Для выполнения дополнительных действий после успешного выполнения задачи можно использовать метод onSuccess
.
@Override
public void onSuccess(TaskSupport support) {
// Логика успешного выполнения задачи
}
Для отправки уведомлений при возникновении ошибок можно использовать метод onFail
.
@Override
public void onFail(TaskSupport support, Throwable e) {
// Логика обработки ошибок
}
``````java
@Override
public void onFail(TaskSupport support) {
// TODO Логика обратного вызова при неудачном выполнении задачи
}
При выполнении задач могут возникнуть ошибки. Если пользователю требуется возможность повторной попытки при сбое задачи, **Gobrs-Async** предоставляет такую возможность. Для этого достаточно использовать аннотацию <code>@Task(retryCount = 10)</code> в бине задачи, которую нужно запустить с возможностью повторной попытки. Число, указанное после параметра <code>retryCount</code>, указывает количество попыток повторного выполнения.
``````java
@Component
@Task(retryCount = 10)
public class BService extends AsyncTask<Object, Object> {
// ...
}
2022-12-09 19:36:35.444 INFO 99720 --- [pool-2-thread-1] c.g.a.test.task.retry.CaseRetryTaskA : caseRetryTaskA использует поток pool-2-thread-1
CaseRetryTaskA Begin
CaseRetryTaskA End
2022-12-09 19:36:35.458 INFO 99720 --- [pool-2-thread-1] c.g.a.test.task.retry.CaseRetryTaskC : caseRetryTaskC использует поток pool-2-thread-1
2022-12-09 19:36:35.458 INFO 99720 --- [pool-2-thread-2] c.g.a.test.task.retry.CaseRetryTaskB : caseRetryTaskB использует поток pool-2-thread-2
CaseRetryTaskC Begin
CaseRetryTaskB Begin
CaseRetryTaskC Finish
CaseRetryTaskB Begin
CaseRetryTaskB Begin
CaseRetryTaskB Begin
CaseRetryTaskB Begin
2022-12-09 19:36:35.475 ERROR 99720 --- [pool-2-thread-2] com.gobrs.async.core.task.AsyncTask : [traceId:11770907551682560] caseRetryTaskB задача выполнена с ошибкой
```java.lang.ArithmeticException: / by zero
at com.gobrs.async.test.task.retry.CaseRetryTaskB.task(CaseRetryTaskB.java:27) ~[classes/:na]
at com.gobrs.async.core.task.AsyncTask.taskAdapter(AsyncTask.java:124) ~[classes/:na]
at com.gobrs.async.core.TaskActuator.call(TaskActuator.java:145) ~[classes/:na]
at com.alibaba.ttl.TtlCallable.call(TtlCallable.java:58) [transmittable-thread-local-2.11.2.jar:na]
at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266) [na:1.8.0_251]
at java.util.concurrent.FutureTask.run(FutureTask.java) [na:1.8.0_251]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_251]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_251]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_251]
## Транзакционные задачи
Пользователи могут иметь бизнес-требования, связанные с транзакциями, такие как сценарий A -> B -> C. В случае, если выполнение C завершается ошибкой, то это должно уведомлять A и B о необходимости отката назад в бизнес-процессах. Такой подход также поддерживается **Gobrs-Async**.Для транзакционных задач требуется наследование от `<code>AsyncTask</code>`. Единственным отличием являются следующие три шага:
Необходимо настроить в файле <code>application.yml</code>
### Конфигурация application.yml
```yaml
gobrs:
async:
config:
rules:
# правило — массив типов, несколько правил
- имя: "общее"
контент: "AService->BService->FService->GService->HService;EService->CService;AService"
транзакция: true
Добавьте аннотацию отката на те задачи, где требуется транзакция. Gobrs-Async найдет все задачи до AService
в цепочке задач и вызовет метод rollback
.
@Task(возврат = true)
public class AService extends AsyncTask<Object, Object> {
// ...
}
// Откат транзакции, конкретная логика отката должна быть реализована самостоятельно. Этот метод является методом по умолчанию и требует переопределения.
@Override
public void rollback(DataContext dataContext) {
//super.rollback(dataContext);
//todo откатывать бизнес-логику
}
Обязательно используйте аннотацию @Task(возврат = true)
над теми задачами, которые могут вызвать исключение Пример из официальной документации
Что такое задачи со временем ожидания? Как следует из названия, это одиночная задача, которая может продолжать своё выполнение или прекращаться в зависимости от установленного времени ожидания. Настройка проста.### Аннотация конфигурации
Используйте атрибут timeoutInMilliseconds
аннотации @Task
для настройки.
package com.gobrs.async.test.task.timeout;
import com.gobrs.async.core.TaskSupport;
import com.gobrs.async.core.anno.Task;
import com.gobrs.async.core.task.AsyncTask;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
/**
* Тип A сервис.
* @program: gobrs-async-starter
* @description:
* @author: sizegang
*/
@Slf4j
@Task(timeoutInMilliseconds = 300)
public class CaseTimeoutTaskA extends AsyncTask {```markdown
### Настройка пула прослушивания задач
**Настройка пула прослушивания задач включает количество основных потоков, отслеживающих превышение времени выполнения задач. Подробнее о необходимости этой настройки см. раздел [Причины](#особенности).**
```yaml
gobrs:
async:
config:
правила:
- имя: "цепочка1"
содержание: "задачаA->задачаB->задачаC"
время_ожидания_количество_основных_потоков: 200 # Количество основных потоков
Если настройка не указана, используется значение по умолчанию, рассчитываемое следующим образом:
public static Integer calculateCoreNum() {
int cpuCoreNum = Runtime.getRuntime().availableProcessors();
return new BigDecimal(cpuCoreNum).divide(new BigDecimal("0.2")).intValue();
}
Запустить код### Результат выполнения
2022-12-09 16:51:41.031 INFO 37083 --- [pool-2-thread-1] c.g.a.t.task.timeout.CaseTimeoutTaskA : CaseTimeoutTaskA использует поток --- pool-2-thread-1
CaseTimeoutTaskA Begin
2022-12-09 16:51:41.355 ERROR 37083 --- [ GobrsTimer-1] com.gobrs.async.core.timer.GobrsTimer :
```com.gobrs.async.core.common.exception.TimeoutException: задача caseTimeoutTaskA истекло время ожидания```
```ru
2022-12-09 16:51:41.355 ОШБИКА 37083 --- [ GobrsTimer-1] com.gobrs.async.core.timer.GobrsTimer :
```com.gobrs.async.core.common.exception.TimeoutException: задача caseTimeoutTaskA истекло время ожидания``` в com.gobrs.async.core.TaskLoader$1.tick(TaskLoader.java:394) ~[classes/:na]
в com.gobrs.async.core.timer.GobrsTimer.lambda$addTimerListener$0(GobrsTimer.java:80) ~[classes/:na]
в java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) ~[na:1.8.0_251]
в java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308) ~[na:1.8.0_251]
в java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180) ~[na:1.8.0_251]
в java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294) ~[na:1.8.0_251]
в java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_251]
в java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_251]
в java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_251]
2022-12-09 16:51:41.375 INFO 37083 --- [pool-2-thread-2] c.g.a.t.task.timeout.CaseTimeoutTaskB : CaseTimeoutTaskB начал работу с использованием потока ---pool-2-thread-2
CaseTimeoutTaskB Начало
CaseTimeoutTaskB Конец
2022-12-09 16:51:41.377 INFO 37083 --- [pool-2-thread-2] c.g.a.t.task.timeout.CaseTimeoutTaskC : CaseTimeoutTaskC начал работу с использованием потока ---pool-2-thread-2
CaseTimeoutTaskC Начало
CaseTimeoutTaskC Конец
2022-12-09 16:51:43.426 INFO 37083 --- [ main] com.gobrs.async.core.TaskLoader : 【ProcessTrace】Общее время выполнения: 2413 мс【задача】caseTimeoutTaskA затратила: 312 мс【состояние】:неудача【сообщение об ошибке】: sleep interrupted; ->【задача】caseTimeoutTaskB затратила: 0 мс【состояние】:успех; ->【задача】caseTimeoutTaskC затратила: 2011 мс【состояние】:успех;
```### Особое примечание
* Задачи со временем ожидания не поддерживают переиспользование потоков, так как требуется контроль за превышением времени ожидания для логических проверок. Поддержка переиспользования потоков может привести к прерыванию выполнения задач в переиспользуемых потоках.
* Принцип работы "отключения" и "понижения уровня" основан на методе `Hystrix`. Если вы незнакомы с `Hystrix`, можно обратиться к [анализу принципа работы Hystrix](https://my.oschina.net/13426421702/blog/3071368).
* Из-за планирования потоков, время ожидания может иметь погрешность до 10 мс, которую можно игнорировать!## Переиспользование потоков
Предположим, что в следующей последовательности задач `taskA->taskB,TaskC->taskD->taskE,taskF` минимальное количество потоков, необходимых для завершения всей последовательности, составляет 6 потоков или меньше? Ответ очевиден — всего два потока (кроме основного потока) достаточно для выполнения этой сложной многопоточной последовательности. Почему? В условиях параллельного выполнения, задачи TaskB, TaskC или TaskE, TaskF имеют максимум две задачи, выполняющиеся одновременно. Таким образом, ключевым условием количества используемых потоков является количество **параллельно выполняющихся задач**. Давайте посмотрим, сколько потоков используется в данный момент в gobrs-async.
### Тестовый пример
[Адрес](https://gitee.com/dromara/gobrs-async/blob/master/gobrs-async-test/src/test/java/com/gobrs/async/test/performance/CasePerformance.java)### Результат выполнения
```sh
основной поток использует main
использует main
TaskA
2022-12-11 13:58:22.581 INFO 8039 --- [ main] com.gobrs.async.core.task.AsyncTask : <11780902254572224> [taskA] выполнение
использует main
TaskC
использует pool-2-thread-1
TaskB
2022-12-11 13:58:22.694 INFO 8039 --- [ main] com.gobrs.async.core.task.AsyncTask : <11780902254572224> [TaskC] выполнение
2022-12-11 13:58:22.694 INFO 8039 --- [pool-2-thread-1] com.gobrs.async.core.task.AsyncTask : <11780902254572224> [taskB] выполнение
использует pool-2-thread-1
TaskD
2022-12-11 13:58:22.804 INFO 8039 --- [pool-2-thread-1] com.gobrs.async.core.task.AsyncTask : <11780902254572224> [taskD] выполнение
использует pool-2-thread-1
использует pool-2-thread-2
TaskE
2022-12-11 13:58:22.909 INFO 8039 --- [pool-2-thread-2] com.gobrs.async.core.task.AsyncTask : <11780902254572224> [taskE] выполнение
TaskF
2022-12-11 13:58:22.910 INFO 8039 --- [pool-2-thread-1] com.gobrs.async.core.task.AsyncTask : <11780902254572224> [taskF] выполнение
2022-12-11 13:58:22.913 INFO 8039 --- [ main] com.gobrs.async.core.TaskLoader : 【ProcessTrace】Общее время затраченное: 440мс | traceId = 11780902254572224 | 【задача】taskA затратил: 103мс【состояние】: успех; ->【задача】taskB затратил: 103мс【состояние】: успех; ->【задача】TaskC затратил: 103мс【состояние】: успех; ->【задача】taskD затратил: 106мс【состояние】: успех; ->【задача】taskE затратил: 101мс【состояние】: успех; ->【задача】taskF затратил: 101мс【состояние】: успех;
затрачено времени 462
По логам видно, что задача TaskC использовала поток задачи TaskA для выполнения задачи.Поскольку задачи TaskB и TaskC выполняются параллельно, в данный момент требуется создание нового потока для выполнения задачи TaskB. После завершения выполнения задачи TaskB, задача TaskD продолжает использовать поток пула потоков pool-2-thread-1 для выполнения задачи. В этот момент после завершения выполнения задачи TaskC выясняется, что её подзадачи были взяты на выполнение другими потоками, освобожденными после завершения задачи TaskB, поэтому нет необходимости использовать свои собственные потоки для выполнения задач. Аналогичным образом процесс задач продолжается дальше. Весь процесс использует всего три потока (включая основной поток).Редактор сравнил множество многопоточных конкурирующих фреймворков в отрасли, и в настоящее время только gobrs-Async обладает возможностью повторного использования потоков. Конкретные тестовые примеры представлены ниже в разделе о производительности.
Логи, печатающиеся с помощью log.error(getFormattedTraceId(), exception);
, автоматически содержат traceId. Метод getFormattedTraceId
предоставлен методом AsyncTask
.
@Slf4j
@Task(failSubExec = true)
public class BService extends AsyncTask {
int i = 10000;
@Override
public void prepare(Object o) {
log.info(this.getName() + " использует поток ---" + Thread.currentThread().getName());
}
@Override
public Object task(Object o, TaskSupport support) {
System.out.println("BService Begin");
for (int i1 = 0; i1 < i; i1++) {
i1 += i1;
}
try {
System.out.println(1 / 0);
} catch (Exception exception) {
log.error(getFormattedTraceId(), exception);
}
System.out.println("BService Finish");
return null;
}
}
costLogabled
: вывод полного пути выполнения задачиerrLogabled
: включение вывода ошибок задачи (по умолчанию true)gobrs:
async:
config:
rules:
- name: "optionalRule"
content: "caseOptionalTaskA->caseOptionalTaskB->caseOptionalTaskC,caseOptionalTaskD->caseOptionalTaskE->caseOptionalTaskF"
task-interrupt: false # прерывание основного потока при локальной ошибке (по умолчанию false)
transaction: false
logConfig:
costLogabled: true # включение вывода времени выполнения задачи (по умолчанию true)
errLogabled: true # включение вывода ошибок задачи (по умолчанию true)
CaseOptionalTaskA Задача выполнена CaseOptionalTaskA Задача выполнена успешно 2022-12-11 15:47:32.511 INFO 13458 --- [o-8888-exec-152] com.gobrs.async.core.task.AsyncTask : <11781331511388032> [caseOptionalTaskA] выполнение CaseOptionalTaskB Задача выполнена CaseOptionalTaskB Задача выполнена успешно 2022-12-11 15:47:32.613 INFO 13458 --- [o-8888-exec-152] com.gobrs.async.core.task.AsyncTask : <11781331511388032> [caseOptionalTaskB] выполнение CaseOptionalTaskD Задача выполнена CaseOptionalTaskD Задача выполнена успешно 2022-12-11 15:47:32.718 INFO 13458 --- [o-8888-exec-152] com.gobrs.async.core.task.AsyncTask : <11781331511388032> [caseOptionalTaskD] выполнение 2022-12-11 15:47:32.718 INFO 13458 --- [o-8888-exec-152] com.gobrs.async.core.TaskLoader : 【ProcessTrace】Общее время выполнения: 311мс | traceId = 11781331511388032 | 【задача】caseOptionalTaskA затраты времени: 102мс【состояние】: успех; ->【задача】caseOptionalTaskB затраты времени: 102мс【состояние】: успех; ->【задача】caseOptionalTaskD затраты времени: 105мс【состояние】: успех; затраты времени: 311мс
```## Интеграция с внешними системами логирования
Здесь выбрана система логирования [Tlog](https://tlog.yomahub.com/). Подробное описание использования здесь приведено не будет.
### Интеграция и использование
```java
/**
* Запуск приложения
*/
@SpringBootApplication
/**
* Используется модуль gobrs-async-test для создания задач. Для удобства повторное создание задач не требуется.
*/
@ComponentScan(value = {"com.gobrs.async"})
public class GobrsAsyncExampleApplication {
/**
* Tlog - система логирования. Официальный сайт: https://tlog.yomahub.com/
*/
static {
AspectLogEnhance.enhance();
}
``` /**
* Точка входа в приложение.
*
* @param args аргументы командной строки
*/
public static void main(String[] args) {
SpringApplication.run(GobrsAsyncExampleApplication.class, args);
}
}
Для получения более подробной информации о способах интеграции обратитесь к примерам проекта gobrs-async-example или скачайте исходный код. Адрес источника кода
По умолчанию в Gobrs-Async правила загружаются всего один раз. Однако могут возникнуть ситуации, когда вам потребуется возможность изменения правил в реальном времени без необходимости перезапуска программы. В таком случае Gobrs-Async также предоставляет такую возможность.
По умолчанию Gobrs-Async использует механизм
CopyOnWrite
для горячей загрузки правил, что обеспечивает высокую производительность и безопасность многопоточной работы.
// Объект для горячей загрузки правил
@Resource
private RuleThermalLoad ruleThermalLoad;
```// Задача для горячей загрузки правил. Необходимость перезапуска программы отсутствует, достаточно передать правило объекту горячей загрузки.
public void updateRule(Rule rule) {
// Изменение одного правила
Rule r = new Rule();
r.setName("правило_название");
r.setContent("AService->CService->EService->GService; BService->DService->FService->HService;");
ruleThermalLoad.load(rule);
// Более масштабируемое изменение нескольких правил одновременно
List<Rule> updateRules = new ArrayList<>();
updateRules.add(r);
// updateRules.add(...);
ruleThermalLoad.load(updateRules);
}
```### Проверка
Если в логах появится следующий вывод, значит горячая загрузка правил выполнена успешно.
```sh
com.gobrs.async.engine.RuleThermalLoad : правило тестовое обновление выполнено успешно
При выполнении процесса вы можете вручную остановить выполнение рабочего процесса в определённом бизнес-сценарии. После завершения выполнения триггера задачи, можно использовать результат AsyncResult
, чтобы определить различные бизнес-логики.
Gobrs-Async также предоставляет возможность сделать это. Пользователи могут вручную вызвать метод stopAsync
внутри метода task
асинхронной задачи, передав TaskSupport
. Вам не нужно беспокоиться о внутренней реализации, чтобы легко завершить основной процесс. Метод <code>stopAsync</code>
имеет два параметра, которые описаны ниже:
<code>TaskSupport</code>
: параметр, используемый в Gobrs-Async, который можно просто передать дальше.<code>expCode</code>
: код состояния прерывания, который может быть пользовательским перечнем значений. @Override
public Map task(DataContext dataContext, TaskSupport support) {
try {
// todo выполнение задачи
} catch (Exception e) {
// todo обработка различных исключений с возвратом разных кодов прерывания
if (e instanceof RedirectSimpleException) {
// прерывание процесса выполнения задачи
stopAsync(support, PRODUCT_NOT_FOUND);
return null;
}
stopAsync(support, SYSTEM_DEMOTION);
}
return null;
}
После завершения выполнения триггера задачи будет получен объект <code>AsyncResult</code>
— результат выполнения процесса задачи.
В зависимости от различных кодов прерывания выполняются различные бизнес-логики.
Map<String, Object> params = new HashMap<>();
// имя процесса задачи, параметры передачи процесса задачи, время ожидания завершения процесса задачи
AsyncResult asyncResult = gobrsAsync.go("ruleName", () -> params, timeOut);
if(asyncResult.getExpCode().equals(100)) {
// бизнес-логика Yöntem 1
} else if(asyncResult.getExpCode().equals(200)) {
// бизнес-логика 2
}
При выполнении последовательности A->B->C, если A выдает ошибку, то Gobrs-Async по умолчанию прекращает выполнение B и C задач. Однако, если пользователь хочет продолжить выполнение B и C задач,
Gobrs-Async предоставляет такую возможность. Для этого достаточно указать параметр failSubExec
в аннотации <code>Task</code>
.
По умолчанию failSubExec=false
.
@Service
@Task(failSubExec = true)
public class BService extends AsyncTask<Object, Object> {
// ...
}
Исправленный текст:
После завершения выполнения [триггера задачи](/pages/2f674a/#начало_процесса_выполнения_задачи) будет получен объект `<code>AsyncResult</code>` — результат выполнения процесса задачи.
В зависимости от различных кодов прерывания выполняются различные бизнес-логики.
```java
Map<String, Object> params = new HashMap<>();
// имя процесса задачи, параметры передачи процесса задачи, время ожидания завершения процесса задачи
AsyncResult asyncResult = gobrsAsync.go("ruleName", () -> params, timeOut);
if(asyncResult.getExpCode().equals(100)) {
// бизнес-логика 1
} else if(asyncResult.getExpCode().equals(200)) {
// бизнес-логика 2
}
При выполнении последовательности A->B->C, если A выдает ошибку, то Gobrs-Async по умолчанию прекращает выполнение B и C задач. Однако, если пользователь хочет продолжить выполнение B и C задач,
Gobrs-Async предоставляет такую возможность. Для этого достаточно указать параметр failSubExec
в аннотации <code>Task</code>
.
По умолчанию failSubExec=false
.
@Service
@Task(failSubExec = true)
public class BService extends AsyncTask<Object, Object> {
// ...
}
```## Процесс выполнения задачи со статусами
### Что такое процесс выполнения задачи со статусами?
Это довольно простое понятие. Например, есть четыре задачи: A, B, C и D. Выполнение задачи D зависит от выполнения задач A, B и C. Однако состояние выполнения этих задач (A, B, C) может быть неопределенным во время выполнения.
Как решить проблему, когда требуется принять решение о дальнейших действиях в зависимости от состояния выполнения задач? Например, задачи A, B и C могут вернуть различные состояния выполнения в зависимости от бизнес-логики.
Для решения такой проблемы можно использовать возможности динамического управления состоянием, предоставляемые **Gobrs-Async**.Например: если задача A вернет значение `true`, тогда выполнится задача D, без необходимости следить за выполнением задач B и C. Разница с выполнением обычной задачи заключается в том, что результат должен быть типа `AnyConditionResult`.
```java
package com.gobrs.async.test.task.condition;
import com.gobrs.async.core.TaskSupport;
import com.gobrs.async.core.anno.Task;
import com.gobrs.async.core.common.domain.AnyConditionResult;
import com.gobrs.async.core.task.AsyncTask;
import org.springframework.stereotype.Component;
/**
* Тип AService.
*
* @program: gobrs-async-starter
* @ClassName AService
* @description: тип зависимости задачи
* AServiceCondition,BServiceCondition,CServiceCondition->DServiceCondition:anyCondition
* <p>
* Упрощенная конфигурация
* <p>
* A,B,C->D:anyCondition
* <p>
* D использует состояние AnyCondition в результате задач A,B,C для принятия решения о продолжении выполнения подзадач.
* @author: sizegang
* @create: 2022-03-20
*/
@Task(failSubExec = true)
public class AServiceCondition extends AsyncTask {
/**
* .
*/
int sums = 10000;
@Override
public AnyConditionResult<String> task(Object o, TaskSupport support) {
AnyConditionResult.Builder<String> builder = AnyConditionResult.builder();
try {
System.out.println("AServiceCondition Begin");
Thread.sleep(300);
for (int i1 = 0; i1 < sums; i1++) {
i1 += i1;
}
System.out.println("AServiceCondition Finish");
} catch (InterruptedException e) {
e.printStackTrace();
// возврат false при возникновении ошибки
return builder.setState(false).build();
}
return builder.setState(true).build();
}
}
````AnyConditionResult.Builder<String> builder = AnyConditionResult.builder();` создает по умолчанию объект со значением состояния `true`.
## Пример теста
[Адрес теста](https://gitee.com/dromara/gobrs-async/blob/master/gobrs-async-test/src/test/java/com/gobrs/async/test/CaseAnyCondition.java)
## Результат выполнения
```sh
2022-12-09 17:48:44.676 INFO 58639 --- [ main] com.gobrs.async.core.GobrsPrint : Успешная загрузка Gobrs-Async
Начало CServiceCondition
Начало BServiceCondition
Начало AServiceCondition
Завершение BServiceCondition
Завершение AServiceCondition
Начало DServiceCondition
Завершение DServiceCondition
2022-12-09 17:48:45.435 INFO 58639 --- [pool-1-thread-1] com.gobrs.async.core.TaskLoader : 【ProcessTrace】Общее время затраченное: 334мс | traceId = 11770483512420224 | 【задача】BServiceCondition затратил времени: 3мс【состояние】: успех; ->【задача】AServiceCondition затратил времени: 305мс【состояние】: успех; ->【задача】DServiceCondition:anyCondition затратил времени: 0мс【состояние】: успех;
377
【gobrs-async】 выполнение testCondition завершено
Если вы хотите, чтобы при выполнении задач в процессе выполнения одной из задач возникло исключение, весь процесс выполнения останавливался. Также можно настроить перехват этого исключения, выполняя оповещение или вывод ошибки при возникновении исключения.
application.yml
gobrs:
async:
config:
rules:
# Rules are arrays with multiple rules
- name: "general"
content: "AService->BService->FService->GService->HService;EService->CService;AService"
task-interrupt: true # Local exception interrupts the main process by default false
```### Обработчик исключений
Реализуйте интерфейс `<code>AsyncExceptionInterceptor</code>` для создания пользовательского обработчика исключений
```java
/**
* @program: gobrs-async
* @ClassName GobrsExceptionInter
* @description: Обработка прерывания основного процесса
* @author: sizegang
* @create: OnClickListener 2022-02-19 22:55
* @Version OnClickListener 1.0
**/
@Component
public class GobrsExceptionInter implements AsyncTaskExceptionInterceptor {
@Override
public CompletionException exception(Throwable throwable, boolean state) {
System.out.println("Глобальный обработчик исключений был активирован");
return new CompletionException(throwable);
}
}
По умолчанию Gobrs-Async имеет отключенное состояние глобального обработчика исключений. Если в процессе выполнения одна из задач завершается с ошибкой, то только эта задача будет прекращена, а остальные задачи продолжат выполняться.
Некоторым пользователям может потребоваться более сложный подход, чем обработка каждой задачи отдельно. Поэтому Gobrs-Async предоставляет возможность для конфигурирования глобального обработчика задач.### Предварительный глобальный обработчик задач
Реализуйте интерфейс <code>AsyncTaskPreInterceptor</code>
для создания пользовательского предварительного глобального обработчика задач.
/**
* @program: m-detail
* @ClassName AsyncTaskPreInterceptor
* @description:
* @author: sizegang
* @create: 2022-03-24
**/
@Component
public class TaskPreInterceptor implements AsyncTaskPreInterceptor<DataContext> {
/**
*
* @param params Параметры
* @param taskName Название задачи
*/
@Override
public void preProcess(DataContext params, String taskName) {
Реализуйте интерфейс TaskPostInterceptor
, чтобы создать собственный глобальный пост-обработчик задач.```java
/**
@program: m-detail
@ClassName AsyncTaskPostInterceptor
@description:
@author: sizegang
@create: 2022-03-24 / @Component public class TaskPostInterceptor implements AsyncTaskPostInterceptor { / *
} }
## Настройка фиксированного пула потоков
По умолчанию **Gobrs-Async** использует пул потоков <code>Executors.newCachedThreadPool()</code>. Если вы хотите настроить свой пул потоков, вам потребуется объект <code>GobrsAsyncThreadPoolFactory</code>.
```java
@Configuration
public class ThreadPoolConfig {
@Autowired
private GobrsAsyncThreadPoolFactory factory;
@PostConstruct
public void gobrsThreadPoolExecutor() {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(300, 500, 30, TimeUnit.SECONDS,
new LinkedBlockingQueue<>());
factory.setThreadPoolExecutor(threadPoolExecutor);
}
}
Иногда разработчики сталкиваются с проблемой изменения пула потоков после запуска приложения. Если ваша компания имеет распределённую систему конфигураций, которая позволяет в реальном времени обновлять конфигурацию пула потоков, то gobrs предоставляет возможность для этого.
Пример использования:
{
"corePoolSize": 210,
"maxPoolSize": 600,
"keepAliveTime": 30,
"capacity": 10000,
"threadNamePrefix": "m-detail",
"rejectedExecutionHandler": "CallerRunsPolicy"
}
@Slf4j
@Configuration
public class ThreadPoolConfig {
@Autowired
private GobrsAsyncThreadPoolFactory factory;
@Resource
private DUCCConfigService duccConfigService;
}
@PostConstruct
public void gobrsThreadPoolExecutor() {
// Получаем конфигурацию пула потоков DuccConstant.GOBRS_ASYNC_THREAD_POOL — ключ конфигурации пула потоков в центре конфигураций
String config = duccConfigService.getString(DuccConstant.GOBRS_ASYNC_THREAD_POOL);
ThreadPool threadPool = JSONObject.parseObject(config, ThreadPool.class);
}
``` // Создаем пул потоков с помощью конструктора gobrs-async
ThreadPoolExecutor executor = ThreadPoolBuilder.buildByThreadPool(threadPool);
factory.setThreadPoolExecutor(executor);
listenerDucc();
}
// Отслеживаем изменения конфигурации пула потоков в центре конфигураций
private void listenerDucc() {
duccConfigService.addListener(new DuccListener(DuccConstant.GOBRS_ASYNC_THREAD_POOL, property -> {
log.warn("Обнаружено изменение конфигурации пула потоков GobrsAsync в DUCC, свойство: {}", JSON.toJSONString(property.getValue()));
ThreadPool threadPool = JSONObject.parseObject(property.getValue().toString(), ThreadPool.class);
ThreadPoolExecutor executor = ThreadPoolBuilder.buildByThreadPool(threadPool);
factory.setThreadPoolExecutor(executor);
// Успешное обновление пула потоков
log.warn("Успешное обновление пула потоков GobrsAsync");
}));
}---
## Опциональный процесс выполнения
### Настройка задач
```yaml
- name: "optionalRule"
content: "caseOptionalTaskA->caseOptionalTaskB->caseOptionalTaskC,caseOptionalTaskD->caseOptionalTaskE->caseOptionalTaskF"
Если разработчики хотят выполнять только caseOptionalTaskD
, то в цепочке задач нужно выполнить только три задачи: caseOptionalTaskA
, caseOptionalTaskB
, caseOptionalTaskD
. Остальные задачи не требуются.
Третьим параметром метода gobrsAsync.go()
требуется передать множество (Set
) имен бинов задач, которые следует выполнить.
@Test
public void testOptional() {
Map<Class, Object> params = new HashMap<>();
Set<String> options = new HashSet<>();
options.add("caseOptionalTaskD"); // В множестве options указывается имя бина задачи, которое должно быть выполнено
AsyncResult asyncResult = gobrsAsync.go("optionalRule", () -> params, options, 300000);
}
```### Пример тестирования
[Исходный код](https://gitee.com/dromara/gobrs-async/blob/master/gobrs-async-test/src/test/java/com/gobrs/async/test/optional/CaseOptional.java)
### Результат выполнения
```sh
Выполнение задачи CaseOptionalTaskA
Задача CaseOptionalTaskA выполнена
2022-12-11 15:47:32.511 INFO 13458 --- [o-8888-exec-152] com.gobrs.async.core.task.AsyncTask : <0><11781331511388032> <11781331511388032> [caseOptionalTaskA] выполнение
Выполнение задачи CaseOptionalTaskB
Задача CaseOptionalTaskB выполнена
2022-12-11 15:47:32.613 INFO 13458 --- [o-8888-exec-152] com.gobrs.async.core.task.AsyncTask : <0><11781331511388032> <11781331511388032> [caseOptionalTaskB] выполнение
Выполнение задачи CaseOptionalTaskD
Задача CaseOptionalTaskD выполнена
2022-12-11 15:47:32.718 INFO 13458 --- [o-8888-exec-152] com.gobrs.async.core.task.AsyncTask : <0><11781331511388032> <11781331511388032> [caseOptionalTaskD] выполнение
2022-12-11 15:47:32.718 INFO 13458 --- [o-8888-exec-152] com.gobrs.async.core.TaskLoader : <0><11781331511388032> 【ProcessTrace】Общее время выполнения: 311 мс | traceId = 11781331511388032 | 【задача】caseOptionalTaskA затраты времени: 102 мс【состояние】: успех; ->【задача】caseOptionalTaskB затраты времени: OnClickListener мс【состояние】: успех; ->【задача】caseOptionalTaskD затраты времени: 105 мс【состояние】: успех;
время выполнения: 311
При выполнении ISV (компонента ISV JD Cloud) строительства в одном уровне могут существовать несколько компонентов, имеющих различные задачи потока, требующие различное количество входных данных. В этом случае требуется выбор и выполнение задач в процессе организации.Далее приведены примеры организации данных для каждого компонента.
SkyWalking — это платформа полносистемного мониторинга. Поскольку SkyWalking несовместим с многопоточными traceId, gobrs-async
предоставляет плагин SkyWalking.
<dependency>
<groupId>io.github.memorydoc</groupId>
<artifactId>gobrs-async-skywalking-plugin</artifactId>
<version>1.2.9-RELEASE</version>
</dependency>
Необходимо просто добавить зависимость для идеального соответствия SkyWalking. Не кажется ли вам это волшебством?
Как известно всем разработчикам, полносистемный traceId — это уникальный номер, отображаемый в логах для удобной трассировки цепочки. Благодаря этому можно легко отслеживать проблемы в продакшене, что очень удобно.
<dependency>
<groupId>io.github.memorydoc</groupId>
<artifactId>gobrs-async-trace-plugin</artifactId>
<version>1.2.9-RELEASE</version>
</dependency>
Необходимо просто добавить зависимость для идеального соответствия SkyWalking. Не кажется ли вам это волшебством?
Необходимо добавить следующий код в запускающий класс SpringBoot
static {
GobrsLogger.logger();
}
Плагин логов Gobrs-Async использует Tlog. При возникновении проблем при использовании обратитесь на официальный сайт Tlog.## Вступите в группу для обсуждения Если у вас есть какие-либо замечания относительно этого проекта, добро пожаловать в раздел Issues для обсуждения; QR-код группы действителен семь дней, можно также добавить автора WeChat для вступления в группу.
![]() |
![]() |
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )