Примите Kubernetes, до свидания, SpringBoot @Scheduled

Java

При разработке проекта всегда необходимо выполнять некоторые запланированные задачи, такие как отправка электронных писем после регулярной обработки данных, регулярное обновление кеша и так далее.

Запланированная задача Java

  1. На основе таймера java.util.Timer реализуйте задачу синхронизации, аналогичную будильнику.
  2. Используйте Quartz, elastic-job, xxl-job и другие сторонние платформы запланированных задач с открытым исходным кодом, подходящие для распределенных проектных приложений.
  3. Используйте аннотацию, предоставленную 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.

  1. concurrencyPolicy=Allow, что также является значением по умолчанию, что означает, что эти задания могут существовать одновременно;
  2. concurrencyPolicy=Forbid, что означает, что новый под не будет создан, цикл создания пропущен;
  3. concurrencyPolicy=Replace, что означает, что вновь сгенерированное задание заменит старое незавершенное задание.

Несколько ключевых параметров объясняются ниже:

  1. schedule : выражение в формате Unix Cron.Пять частей в выражении cron представляют минуты, часы, дни, месяцы и недели.
  2. startDeadlineSeconds: указывает, сколько секунд в прошлом (здесь установлено 180), если данные о сбое создания задания достигают 100 раз, задание не будет создано и выполнено.
  3. 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, чтобы дополнять друг друга.