任务调度新境界:探秘ScheduledExecutorService的异步魔力

news2024/10/25 8:24:27

欢迎来到我的博客,代码的世界里,每一行都是一个故事


在这里插入图片描述

任务调度新境界:探秘ScheduledExecutorService的异步魔力

    • 前言
    • ScheduledExecutorService的基本概念
      • 基本概念:
      • 为何它是 Java 中任务调度的首选工具:
      • 基本用法:
    • ScheduledExecutorService的创建与配置
      • 配置项:
    • 任务的添加与取消
      • 添加定时任务:
      • 取消定时任务:
    • 不同类型的定时任务
      • 定时执行任务:
      • 固定频率执行任务:
      • 固定延迟执行任务:
    • 异常处理与容错机制
      • 1. 异常处理:
      • 2. 使用 UncaughtExceptionHandler:
      • 3. 封装任务逻辑:
      • 4. 返回 Future 对象:
      • 5. 合理的重试机制:
      • 注意事项:
    • ScheduledExecutorService的优势与劣势
      • 优势:
      • 局限性与风险:

前言

在编程的世界里,我们经常需要让某些任务在未来的特定时间点执行。这就是ScheduledExecutorService登场的时刻,它是一个任务调度的专业管家,能够精确地掌握时间的舞步。让我们一同踏入这个时间的王国,探索其中的奇妙之处。

ScheduledExecutorService的基本概念

ScheduledExecutorService 是 Java 并发包提供的接口,用于支持任务的调度和执行。它是一个更强大、更灵活的定时任务调度工具,相较于传统的 Timer 类,ScheduledExecutorService 具有更多的功能和更好的性能。

基本概念:

  1. 定义: ScheduledExecutorService 接口是 ExecutorService 的子接口,用于在给定的时间延迟之后,或者周期性地执行任务。

  2. 基本原理: ScheduledExecutorService 使用线程池来管理和执行任务,可以异步地执行任务,支持延迟执行和周期性执行。

为何它是 Java 中任务调度的首选工具:

  1. 灵活性: ScheduledExecutorService 提供了更灵活的任务调度机制,可以支持延迟执行、周期性执行等多种调度方式。这使得它适用于各种不同的定时任务场景。

  2. 可控性: 通过使用线程池,ScheduledExecutorService 提供了对任务执行线程的管理和控制,能够更好地适应不同的并发需求。

  3. 异常处理:Timer 不同,ScheduledExecutorService 对于任务执行中的异常有更好的处理机制,不会因为一个任务的异常导致整个调度器终止。

  4. 相对线程安全: ScheduledExecutorService 在设计上相对于 Timer 更加线程安全,更适合在多线程环境中使用。

  5. 替代 Timer: 由于 ScheduledExecutorService 具有更多功能且更健壮,它通常被认为是 Timer 的替代品,特别是在需要更复杂调度需求和更好性能的情况下。

  6. ExecutorService 的扩展: 作为 ExecutorService 的子接口,ScheduledExecutorService 不仅可以执行定时任务,还能执行普通的异步任务,使得任务的管理更加一致和统一。

基本用法:

使用 ScheduledExecutorService 的基本流程如下:

  1. 创建 ScheduledExecutorService 实例:

    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
    
  2. 创建任务(实现 RunnableCallable 接口):

    Runnable task = () -> {
        // 任务逻辑
        System.out.println("Task executed at: " + System.currentTimeMillis());
    };
    
  3. 安排任务的执行:

    • 在延迟一定时间后执行任务:

      scheduledExecutorService.schedule(task, 1, TimeUnit.SECONDS); // 1秒后执行
      
    • 周期性执行任务:

      scheduledExecutorService.scheduleAtFixedRate(task, 0, 2, TimeUnit.SECONDS); // 每2秒执行一次
      
  4. 关闭 ScheduledExecutorService

    scheduledExecutorService.shutdown();
    

总体而言,ScheduledExecutorService 提供了更灵活和强大的任务调度功能,是 Java 中任务调度的首选工具之一。

ScheduledExecutorService的创建与配置

ScheduledExecutorService 的创建和配置通常通过 Executors 工厂类完成。下面是一个基本的实例化和配置 ScheduledExecutorService 的例子:

import java.util.concurrent.*;

public class ScheduledExecutorServiceExample {
    public static void main(String[] args) {
        // 创建一个具有固定线程数的 ScheduledExecutorService
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);

        // 创建任务
        Runnable task = () -> {
            // 任务逻辑
            System.out.println("Task executed at: " + System.currentTimeMillis());
        };

        // 配置任务的执行方式
        ScheduledFuture<?> scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(
                task,    // 任务
                0,       // 初始延迟
                2,       // 间隔时间
                TimeUnit.SECONDS  // 时间单位
        );

        // 关闭 ScheduledExecutorService
        scheduledExecutorService.shutdown();
    }
}

在上述例子中,我们通过 Executors.newScheduledThreadPool(3) 创建了一个固定线程数为 3 的 ScheduledExecutorService。接着,我们定义了一个简单的任务 task,并使用 scheduleAtFixedRate 方法配置了任务的执行方式。最后,我们通过 shutdown 方法关闭了 ScheduledExecutorService

配置项:

newScheduledThreadPool 方法允许你传递一个整数参数,用于指定线程池的大小。这个参数表示同时执行的线程数,也即池中的最大线程数。除了这个参数外,newScheduledThreadPool 方法还允许你传递一个 ThreadFactory 对象,用于创建线程。

对于更高级的配置,可以使用 ScheduledThreadPoolExecutor 的构造函数,允许你手动配置线程池的各种参数,如核心线程数、最大线程数、线程空闲时间等。

ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(
        corePoolSize,      // 核心线程数
        threadFactory,      // 线程工厂
        handler             // 拒绝策略
);

其中,corePoolSize 是核心线程数,threadFactory 是线程工厂,handler 是拒绝策略。这样的创建方式更为灵活,可以根据实际需求进行配置。

任务的添加与取消

ScheduledExecutorService 中,可以使用不同的方法来添加和取消定时任务。以下是添加和取消定时任务的基本方法:

添加定时任务:

  1. 使用 schedule 方法:

    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
    
    Runnable task = () -> {
        // 任务逻辑
        System.out.println("Task executed at: " + System.currentTimeMillis());
    };
    
    // 在延迟一定时间后执行任务
    ScheduledFuture<?> scheduledFuture = scheduledExecutorService.schedule(task, 1, TimeUnit.SECONDS);
    
    // 关闭 ScheduledExecutorService
    scheduledExecutorService.shutdown();
    
  2. 使用 scheduleAtFixedRatescheduleWithFixedDelay 方法:

    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
    
    Runnable task = () -> {
        // 任务逻辑
        System.out.println("Task executed at: " + System.currentTimeMillis());
    };
    
    // 周期性执行任务,scheduleAtFixedRate 方法
    // 或者使用 scheduleWithFixedDelay 方法
    
    // 关闭 ScheduledExecutorService
    scheduledExecutorService.shutdown();
    

取消定时任务:

  1. 使用 ScheduledFuture 对象取消任务:

    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
    
    Runnable task = () -> {
        // 任务逻辑
        System.out.println("Task executed at: " + System.currentTimeMillis());
    };
    
    // 在延迟一定时间后执行任务
    ScheduledFuture<?> scheduledFuture = scheduledExecutorService.schedule(task, 1, TimeUnit.SECONDS);
    
    // 取消任务
    boolean cancelled = scheduledFuture.cancel();
    
    // 关闭 ScheduledExecutorService
    scheduledExecutorService.shutdown();
    

    cancel 方法返回一个布尔值,表示任务是否被取消成功。如果任务已经开始执行或已经完成,取消操作将失败。

  2. 使用 shutdownNow 方法取消所有任务:

    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
    
    Runnable task = () -> {
        // 任务逻辑
        System.out.println("Task executed at: " + System.currentTimeMillis());
    };
    
    // 在延迟一定时间后执行任务
    ScheduledFuture<?> scheduledFuture = scheduledExecutorService.schedule(task, 1, TimeUnit.SECONDS);
    
    // 取消所有任务
    List<Runnable> cancelledTasks = scheduledExecutorService.shutdownNow();
    
    // 关闭 ScheduledExecutorService
    scheduledExecutorService.shutdown();
    

    shutdownNow 方法返回一个 List<Runnable>,包含所有被取消的任务。

注意事项:

  • 使用 cancel 方法时,需要注意任务是否已经开始执行或已经完成。取消操作只在任务尚未开始执行时才能成功。
  • 在使用 shutdownNow 方法取消所有任务时,可能会中断正在执行的任务。因此,需要确保任务的设计和实现能够处理中断。

不同类型的定时任务

ScheduledExecutorService 中,有多种方法可以配置不同类型的定时任务,包括定时执行任务、固定频率执行任务等。以下是不同类型的定时任务以及使用不同的方法配置的示例:

定时执行任务:

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

Runnable task = () -> {
    // 任务逻辑
    System.out.println("Task executed at: " + System.currentTimeMillis());
};

// 在延迟一定时间后执行任务
ScheduledFuture<?> scheduledFuture = scheduledExecutorService.schedule(task, 1, TimeUnit.SECONDS);

// 关闭 ScheduledExecutorService
scheduledExecutorService.shutdown();

上述代码中,schedule 方法用于在延迟一定时间后执行任务,即定时执行任务。

固定频率执行任务:

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

Runnable task = () -> {
    // 任务逻辑
    System.out.println("Task executed at: " + System.currentTimeMillis());
};

// 周期性执行任务,scheduleAtFixedRate 方法
ScheduledFuture<?> scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(
        task,    // 任务
        0,       // 初始延迟
        2,       // 间隔时间
        TimeUnit.SECONDS  // 时间单位
);

// 关闭 ScheduledExecutorService
scheduledExecutorService.shutdown();

上述代码中,scheduleAtFixedRate 方法用于周期性地执行任务,可以指定初始延迟和执行间隔。

固定延迟执行任务:

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

Runnable task = () -> {
    // 任务逻辑
    System.out.println("Task executed at: " + System.currentTimeMillis());
};

// 固定延迟执行任务,scheduleWithFixedDelay 方法
ScheduledFuture<?> scheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(
        task,    // 任务
        0,       // 初始延迟
        2,       // 间隔时间
        TimeUnit.SECONDS  // 时间单位
);

// 关闭 ScheduledExecutorService
scheduledExecutorService.shutdown();

上述代码中,scheduleWithFixedDelay 方法用于固定延迟地执行任务,即任务执行完毕后等待指定的时间再执行下一次。

注意事项:

  • 定时任务的配置方法根据具体需求选择。schedule 适用于延迟一定时间后执行一次的任务,scheduleAtFixedRate 适用于周期性执行任务,而 scheduleWithFixedDelay 适用于固定延迟执行任务。
  • 在使用这些方法时,需要考虑任务的执行时间和任务之间的依赖关系,以确保任务能够按照预期执行。

异常处理与容错机制

ScheduledExecutorService 中,处理任务执行中的异常是关键的一部分,以确保定时任务的稳定性。以下是一些处理异常和容错机制的方法:

1. 异常处理:

在任务的 run 方法中进行异常处理是一种常见的做法,可以使用 try-catch 块捕获异常,并在异常发生时执行适当的处理逻辑。例如,记录日志、发送警报或执行备用逻辑。

Runnable task = () -> {
    try {
        // 任务逻辑
        // ...
    } catch (Exception e) {
        // 异常处理逻辑
        // 记录日志、发送警报等
        e.printStackTrace();
    }
};

2. 使用 UncaughtExceptionHandler:

ScheduledThreadPoolExecutor 类提供了 setUncaughtExceptionHandler 方法,可以设置一个全局的未捕获异常处理器。这个处理器将在任务抛出未捕获的异常时被调用。

Thread.UncaughtExceptionHandler exceptionHandler = (thread, throwable) -> {
    // 全局未捕获异常处理逻辑
    // 记录日志、发送警报等
    throwable.printStackTrace();
};

ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1);
((ScheduledThreadPoolExecutor) scheduledExecutorService).setUncaughtExceptionHandler(exceptionHandler);

Runnable task = () -> {
    // 任务逻辑
    // ...
};

3. 封装任务逻辑:

将任务逻辑封装在一个方法中,并在方法内进行异常处理。这样可以使任务逻辑更加清晰,异常处理也更为集中。

Runnable task = () -> {
    try {
        // 封装的任务逻辑
        executeTask();
    } catch (Exception e) {
        // 异常处理逻辑
        // 记录日志、发送警报等
        e.printStackTrace();
    }
};

private void executeTask() {
    // 具体的任务逻辑
    // ...
}

4. 返回 Future 对象:

ScheduledExecutorServiceschedule 方法返回一个 ScheduledFuture 对象,可以使用这个对象检查任务的执行状态和获取任务的执行结果。通过检查 ScheduledFuture 对象,可以在任务执行失败时获取异常信息。

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

Runnable task = () -> {
    // 任务逻辑
    // ...
};

ScheduledFuture<?> scheduledFuture = scheduledExecutorService.schedule(task, 1, TimeUnit.SECONDS);

try {
    // 获取任务执行结果,这里会抛出异常,可以在这里处理异常
    scheduledFuture.get();
} catch (Exception e) {
    // 异常处理逻辑
    // 记录日志、发送警报等
    e.printStackTrace();
}

// 关闭 ScheduledExecutorService
scheduledExecutorService.shutdown();

5. 合理的重试机制:

在异常发生时,可以考虑使用重试机制,即在一定次数内尝试重新执行任务。这可以通过在任务逻辑中使用循环来实现。

Runnable task = () -> {
    int maxAttempts = 3;
    for (int attempt = 1; attempt <= maxAttempts; attempt++) {
        try {
            // 任务逻辑
            // ...
            break; // 任务成功执行,跳出循环
        } catch (Exception e) {
            // 异常处理逻辑
            // 记录日志、发送警报等
            e.printStackTrace();
            if (attempt < maxAttempts) {
                // 等待一段时间后重试
                Thread.sleep(1000);
            } else {
                // 达到最大重试次数,放弃任务执行
                break;
            }
        }
    }
};

注意事项:

  • 在异常处理中,需要根据具体业务需求选择合适的处理方式,例如记录日志、发送警报、重试等。
  • 在定时任务中,为了确保任务执行的稳定性,合理的异常处理和容错机制是至关重要的。

ScheduledExecutorService的优势与劣势

优势:

  1. 灵活性: ScheduledExecutorService 提供了更灵活的任务调度机制,支持延迟执行、周期性执行等多种调度方式。这使得它适用于各种不同的定时任务场景。

  2. 可控性: 通过使用线程池,ScheduledExecutorService 提供了对任务执行线程的管理和控制,能够更好地适应不同的并发需求。

  3. 异常处理: 相对于 TimerScheduledExecutorService 对于任务执行中的异常有更好的处理机制,不会因为一个任务的异常导致整个调度器终止。

  4. 相对线程安全: ScheduledExecutorService 在设计上相对于 Timer 更加线程安全,更适合在多线程环境中使用。

  5. ExecutorService 的扩展: 作为 ExecutorService 的子接口,ScheduledExecutorService 不仅可以执行定时任务,还能执行普通的异步任务,使得任务的管理更加一致和统一。

  6. 更好的性能: 相较于 TimerScheduledExecutorService 的性能通常更好。它能够更好地处理任务的并发执行,提高系统的吞吐量。

局限性与风险:

  1. 不适用于复杂场景: 对于一些复杂的任务调度场景,例如需要更高级的调度策略、任务间的依赖关系等,ScheduledExecutorService 可能显得力不从心,因为其功能相对有限。

  2. 定时器线程生命周期管理: ScheduledExecutorService 的定时器线程在 shutdown 方法被调用后不会被及时终止,可能导致应用程序无法正常退出。需要谨慎管理定时器线程的生命周期。

  3. 不支持任务的取消和修改: 一旦定时任务被安排,就不能取消或修改其执行时间,只能取消整个定时器并重新创建。

  4. 任务执行时间长: 如果某个任务的执行时间过长,可能会影响后续任务的调度,因为任务是按照顺序执行的。

总体而言,ScheduledExecutorService 是一个更灵活、更可控且相对线程安全的定时任务调度工具,适用于大多数场景。然而,在一些复杂的调度需求下,可能需要考虑使用其他更为高级的调度工具或框架。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1497634.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Milvus 向量数据库实践 - 1

假定你已经安装了docker、docker-compose 环境 参考的文档如下&#xff1a; Milvus技术探究 - 知乎 MilvusClient() - Pymilvus v2.3.x for Milvus 一文带你入门向量数据库milvus 一、在docker上安装单机模式milvus数据库 1、 进入milvus官网&#xff1a; Install Milvus Stand…

【C++】string类的基础操作

&#x1f497;个人主页&#x1f497; ⭐个人专栏——C学习⭐ &#x1f4ab;点击关注&#x1f929;一起学习C语言&#x1f4af;&#x1f4ab; 目录 导读 1. 基本概述 2. string类对象的常见构造 3. string类对象的容量操作 4. string类对象的访问及遍历操作 5. 迭代器 6.…

noetic ros配置因时机械夹爪的驱动

noetic ros配置因时机械夹爪的驱动文件 配置编译教程解决方案 配置编译教程 1.inspire_robot 包支持因时机器人公司的机械夹爪在ROS平台上的使用&#xff0c;我们在ros noetic环境下进行了测试。 2.为了使程序能够正常运行&#xff0c;需要执行以下环境配置操作&#xff1a;&a…

从一个问题开始聊聊clickhouse的物化视图

【问题】 今天有A问我一个问题&#xff0c;我明明创建了一个物化视图&#xff0c;源表是有数据的&#xff0c;为什么查询物化视图就没有数据&#xff1f; 创建物化视图的SQL示意如下&#xff1a; CREATE MATERIALIZED VIEW schema1.test_mvon cluster clusterNameTO schema1…

玩转安卓之配置gradle-8.2.1

概述&#xff1a;看了一下&#xff0c;由于gradle是国外的&#xff0c;所以下载速度很慢&#xff0c;这个老师又是很菜的类型&#xff0c;同学又不会&#xff0c;于是曹某就写这一篇文章&#xff0c;教大家学会简单的为安卓配置gradle-8.2.1。 第一步&#xff1a;下载gradle-8…

【问题解决】| 关于vscode调试python文件 报错 且直接运行正常的诡异情况记录

关于python的debug报错&#xff0c;其实很奇怪 首先&#xff0c;对于工作区代码&#xff0c;我们可以通过CtrlShiftP 来切换Python解释器 这样的话&#xff0c;工作区的代码就不会报import error 而且这样的话是可以运行跑通的&#xff0c;但最抽象的一集来了&#xff0c;这…

JavaScript 作用域详解:如何影响变量生命周期

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

总结Redis的原理

一、为什么要使用Redis 缓解数据库访问压力mysql读请求进行磁盘I/O速度慢&#xff0c;给数据库加Redis缓存&#xff08;参考CPU缓存&#xff09;&#xff0c;将数据缓存在内存中&#xff0c;省略了I/O操作 二、Redis数据管理 2.1 redis数据的删除 定时删除惰性删除内存淘汰…

多层菜单的实现方案(含HierarchicalDataTemplate使用)

1、递归 下面是Winform的递归添加菜单栏数据&#xff0c;数据设置好父子id方便递归使用 在TreeView的控件窗口加载时&#xff0c;调用递归加载菜单 private void LoadTvMenu(){this.nodeList objService.GetAllMenu(); // 通过Service得到全部数据// 创建一个根节点this.t…

NCDA设计大赛中设定画命题解读

一年一度的未来设计师全国高校数字艺术设计大赛&#xff08;NCDA&#xff09;正在如火如荼的进行中&#xff0c;各高校的大学生和指导老师们也都在着手准备中。今天我们就特地来说说它的数字绘画命题之一的设定画选项&#xff0c;为了使大家更好地参加本次比赛&#xff0c;本文…

博客系统测试

文章目录 1.项目背景介绍2.功能介绍3.手动测试3.1编写测试用例3.2项目测试3.2.1登录测试3.2.2查看详情页面3.2.3编辑页面3.2.4删除博客3.2.5注销用户 大家好&#xff0c;我是晓星航。今天为大家带来的是 博客系统测试 相关的讲解&#xff01;&#x1f600; 1.项目背景介绍 项…

Vue.js+SpringBoot开发农村物流配送系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统登录、注册界面2.2 系统功能2.2.1 快递信息管理&#xff1a;2.2.2 位置信息管理&#xff1a;2.2.3 配送人员分配&#xff1a;2.2.4 路线规划&#xff1a;2.2.5 个人中心&#xff1a;2.2.6 退换快递处理&#xff1a;…

DxO ViewPoint:摄影师的最 佳拍档,记录世界的每一刻精彩 mac/win版

DxO ViewPoint是一款革命性的摄影软件&#xff0c;它以其独特的功能和卓越的性能&#xff0c;重新定义了摄影体验。这款软件不仅提供了丰富的摄影工具&#xff0c;还通过先进的算法和技术&#xff0c;让摄影师能够轻松捕捉、管理和展示他们的作品。 DxO ViewPoint 软件获取 Dx…

《幸运的基督徒》Python

题目描述 有15个基督徒和15个非基督徒在海上遇险&#xff0c; 为了能让一部分人活下来不得不将其中15个人扔到海里面去&#xff0c; 有个人想了个办法就是大家围成一个圈&#xff0c;由某个人开始从1报数&#xff0c; 报到9的人就扔到海里面&#xff0c;他后面的人接着从1开始报…

unity学习(49)——服务器三次注册限制以及数据库化角色信息4--角色信息数据库化

1.此处下断开始调试,list函数内就有问题&#xff1a; 2. 现在的问题是只读不写&#xff01;32行就是写入部分的代码&#xff1a; 3. 很奇怪&#xff0c;调试的时候确实是写进来了 程序正常执行后&#xff0c;文件中数据也没有消失 关闭服务器文件内容依旧正常。 players包含所…

px2rem实现vue项目响应式布局

第一步 首先需要在项目中安装px2rem插件 npm install postcss-px2rem px2rem-loader --save 第二步 在项目src目录下新建util文件夹&#xff0c;在util文件夹下新建rem.js文件&#xff0c;内容如下&#xff1a; // rem等比适配配置文件 // 基准大小 const baseSize 14 //…

day14_异常

今日内容 零、 复习昨日 一、日期类 二、异常 零、 复习昨日 1为什么要重写toString Object类toString返回的是对象名字地址,无意义子类重写toString() 返回的对象属性内容 2为什么要重写equals Object类equals判断是对象的地址值是否相等,无意义子类重写equals,为了判断对象的…

电商分享沙龙干货:做印尼电商如何提高顾客购买意愿?

“得印尼者得东南亚” 这是诸多在印尼掘金的电商人的共识。2.7亿人口、GDP年增速稳定在5%、平均年龄在30岁上下、较强的消费能力……这些都使得印尼成为电商人掘金东南亚的首选之地。 图源&#xff1a;freepik 但近几年来&#xff0c;印尼政府不断调整关税&#xff0c;限制电商…

SAP MM学习笔记 - 错误 BMG140 - The material number is longer than the length set

错误 BMG140 - The material number is longer than the length set 品目编号大于长度设置 1&#xff0c;在新规品目的时候&#xff0c;出的错 2&#xff0c;OMSL 品目Code书式变更 IMG path>Logistic general>Material Master>Basic settings>Define output for…

滴滴基于 Clickhouse 构建新一代日志存储系统

ClickHouse 是2016年开源的用于实时数据分析的一款高性能列式分布式数据库&#xff0c;支持向量化计算引擎、多核并行计算、高压缩比等功能&#xff0c;在分析型数据库中单表查询速度是最快的。2020年开始在滴滴内部大规模地推广和应用&#xff0c;服务网约车和日志检索等核心平…