При разработке проекта всегда необходимо выполнять некоторые запланированные задачи, такие как отправка электронных писем после регулярной обработки данных, регулярное обновление кеша и так далее.
Запланированная задача Java
- На основе таймера java.util.Timer реализуйте задачу синхронизации, аналогичную будильнику.
- Используйте Quartz, elastic-job, xxl-job и другие сторонние платформы запланированных задач с открытым исходным кодом, подходящие для распределенных проектных приложений.
- Используйте аннотацию, предоставленную Spring: @Scheduled
Платформа проекта использует SpringBoot, поэтому предыдущие запланированные задачи использовали @Scheduled в SpringBoot. Однако этот метод не подходит для нашей текущей облачной среды.Чтобы быть более облачным, я удалил 37 запланированных задач, написанных с помощью SpringBoot, и вместо этого использовал метод cronjob Kubernetes.
Написание кода задачи на время
public interface Command {
/**
* 遵循Unix约定,如果命令执行正常,则返回0;否则为非0。
*/
int execute(String... args);
}
Сначала определите интерфейс, все конкретные задачи синхронизации должны реализовывать этот интерфейс. Далее идет конкретное задание на время
@Component
@Slf4j
public class ProjectCommandLineRunner implements CommandLineRunner {
Map<String, Command> commandMap = new HashMap<>();
@Autowired
private SendEmailCommand sendEmailCommand;
@PostConstruct
private void init() {
commandMap.put("sendEmail", sendEmailCommand);
}
@Override
public void run(String... args) throws Exception {
if (args.length == 0) {
return;
}
if (!commandMap.containsKey(args[0])) {
log.error("'{}' command not found", args[0]);
System.exit(-1);
}
Command command = commandMap.get(args[0]);
String[] arguments = Arrays.copyOfRange(args, 1, args.length);
System.exit(command.execute(arguments));
}
}
@Component
@Slf4j
public class SendEmailCommand implements Command {
@Override
public int execute(String... args) {
try {
// 省略业务逻辑代码
log.info("send email success");
return 0;
} catch (Exception e) {
log.error("send email error", e);
return -1;
}
}
}
В приведенном выше коде мы используем режим стратегии, и даже если другие временные задачи будут добавлены позже, будет изменен лишь небольшой объем кода.
локальная отладка
cronjob не нужно упаковывать в отдельный образ, он напрямую разделяет тот же образ с нашим веб-приложением, и его чрезвычайно удобно отлаживать локально, если мы указываем параметры при запуске приложения SpringBoot.
После выполнения соответствующей запланированной задачи приложение завершит работу.
cronjob yaml
Базовый yaml cronjob выглядит так
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: send-email-job
spec:
failedJobsHistoryLimit: 3
successfulJobsHistoryLimit: 1
startingDeadlineSeconds: 180
concurrencyPolicy: Forbid
schedule: "0 4 * * 1-5"
jobTemplate:
spec:
template:
spec:
containers:
- name: send-email-job
image: harbor.xxx.com/think123/project
imagePullPolicy: Always
command: ["java"]
args: ["-jar","/app/target/think123-task.jar","sendEmail"]
envFrom:
- configMapRef:
name: smcp-config
- secretRef:
name: smcp-service-secret
resources:
requests:
cpu: "250m"
memory: 1024Mi
limits:
cpu: "500m"
memory: 1024Mi
restartPolicy: Never
В запланированной задаче другое задание может быть сгенерировано до того, как будет выполнено определенное задание. В настоящее время мы можем определить конкретную политику обработки через поле spec.concurrencyPolicy.
- concurrencyPolicy=Allow, что также является значением по умолчанию, что означает, что эти задания могут существовать одновременно;
- concurrencyPolicy=Forbid, что означает, что новый под не будет создан, цикл создания пропущен;
- concurrencyPolicy=Replace, что означает, что вновь сгенерированное задание заменит старое незавершенное задание.
Несколько ключевых параметров объясняются ниже:
- schedule : выражение в формате Unix Cron.Пять частей в выражении cron представляют минуты, часы, дни, месяцы и недели.
- startDeadlineSeconds: указывает, сколько секунд в прошлом (здесь установлено 180), если данные о сбое создания задания достигают 100 раз, задание не будет создано и выполнено.
- restartPolicy: политика перезапуска (есть два варианта: Never и OnFailure). Нужно ли его перезапускать после нормального завершения работы?
В объекте «Задание» для параметра «restartPolicy» может быть задано значение «Никогда» и «При сбое», а в объекте «Развертывание» параметру «restartPolicy» может быть присвоено значение «Всегда».
на самом деле вjobTemplate.spec.template
В поде можно указать том и указать nodeSelector как в поде. Этот шаблон на самом деле относится к шаблону модуля. Например, в приведенном выше примере мы указали переменные среды, и некоторые из наших параметров могут быть введены через переменные среды, такие как адрес Redis, имя пользователя и пароль mongodb и т. д.
фактическое использование
Хотя приведенный выше yaml можно использовать напрямую, нам не нужно писать один и тот же шаблон для каждого задания, фактически мы будем использовать шаблон управления Kustomize для создания задания. Например, у нас появилась новая задача, которая заключается в подсчете горячих статей и обновлении redis.
Для этой задачи есть два основных изменения: во-первых, отличается время выполнения запланированной задачи, а во-вторых, отличаются заданные параметры. Таким образом, каждая из наших задач должна обновлять только эти два параметра.
Для использования kustomize, пожалуйста, обратитесь к моему предыдущемуВведение в настройку, вы можете посмотреть на упаковкеСтатьи по сборке springboot
Для настройки шаблона мы сформировали следующую структуру каталогов
$ tree
.
|-- base
| |-- cronjob.yaml
| `-- kustomization.yaml
`-- overlay
`-- beta
|-- kustomization.yaml
`-- send-email-patch-args.yaml
cronjob.yaml используется в качестве шаблона для всех заданий, а send-emial-patch-args.yaml — замена для конкретного задания. Содержимое задействованных файлов yaml выглядит следующим образом:
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- cronjob.yaml
# base/cronjob.yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: think123-
spec:
failedJobsHistoryLimit: 3
successfulJobsHistoryLimit: 1
startingDeadlineSeconds: 180
concurrencyPolicy: Forbid
schedule: "0 0 1 * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: cron-job
image: harbor.xxx.com/think123/my-task
imagePullPolicy: Always
args:
- "help"
envFrom:
- configMapRef:
name: smcp-config
- secretRef:
name: smcp-service-secret
resources:
requests:
cpu: "250m"
memory: 1024Mi
limits:
cpu: "500m"
memory: 1024Mi
restartPolicy: Never
# overlay/beta/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
nameSuffix: send-email-job
resources:
- ../../base/
patchesStrategicMerge:
- send-email-patch-args.yaml
# overlay/beta/send-email-patch-args.yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: think123-
spec:
schedule: "0 4 * * 1-5"
jobTemplate:
spec:
template:
spec:
containers:
- name: send-email-job
args: ["sendEmail"]
ты можешь использоватьkustomize build beta > send-email-cron-job.yaml
команду, а затем просмотрите файл send-email-cron-job.yaml, вы можете увидеть детали конкретного сгенерированного задания cron.
Документацию по kustomize можно найти по адресу: https://kubernetes-sigs.github.io/kustomize/api-reference/.
Зачем использовать Kubernetes Cron Job
Разве не хорошо использовать временные задачи SpringBoot? Зачем привносить что-то новое. Когда снова думаете об этой проблеме, подумайте, почему вы не пишете сервлет в SpringBoot, разве это не то же самое?
На самом деле, есть повод подумать об этом.Во-первых, наш сервис распределенный.Наша задача по времени должна запускаться только один раз, а не каждый экземпляр.Если мы используем задачу SpringBoot, нам нужно использовать код для обеспечения такого поведения . . .
Если вводится структура распределенных задач, вводится куча других новых вещей, таких как реестр и т. д., и приходится изучать новую технологию.
А поскольку наши сервисы развертываются через Kubernetes, наши рабочие места используют Kubernetes, чтобы дополнять друг друга.