项目实战--JUC之CompletableFuture温故

news2024/11/16 2:16:20

CompletableFuture温故

  • 一、前言
  • 二、Future
  • 三、CompletableFuture
    • 3.1 CompletableFuture定义
    • 3.2 CompletableFuture使用场景
    • 3.3 CompletableFuture 常见操作
      • 3.3.1 创建CompletableFuture
      • 3.3.2 使用CompletableFuture
      • 3.3.3 异常处理
      • 3.3.4 注意事项
  • 四、CompletableFuture处理工具类
  • 五、异步化收益

一、前言

最近项目上线完成,但随着用户增加和金融产品买卖量持续上升,算法取数接口的特征工程计算作为推荐系统的核心环节,对系统吞吐量的要求越来越高,对内调度各个数据源取数服务获取数据进行聚合,具有鲜明的I/O密集型(I/O Bound)特点。从而将同步加载改为并行加载的可行性方案。
在这里插入图片描述
下面就从一个简单案例讲起。
项目开发中有很常见的逻辑:一个接口可能需要调用多个其他服务的接口。例如:用户请求获取订单信息,可能需要调取用户信息、商品详情、物流信息、商品推荐等接口,最后汇总处理数据统一返回:

public class OrderServiceImpl im OrderService{
	 @Autowired
    private UserService userService; // 用于获取用户信息
 
    @Autowired
    private ProductService productService; // 用于获取商品详情
 
    @Autowired
    private LogisticsService logisticsService; // 用于获取物流信息
    
	@Autowired
    private relService relService; // 商品推荐信息
	
	 public OrderDTO getOrderInfo(Long orderId) {
	    // 假设这里从数据库获取订单信息
        Order order = getOrderFromDatabase(orderId);
        OrderDTO orderDTO = new OrderDTO();
        BeanUtils.copyProperties(order, orderDTO);
		 // 获取并设置用户信息
        orderDTO.setUser(userService.getUserById(order.getUserId()));
        // 耗时任务:获取并设置商品详情
        orderDTO.setProducts(productService.getProductsByIds(order.getProductIds()));
        // 获取并设置物流信息
        orderDTO.setLogistics(logisticsService.getLogisticsInfo(order.getLogisticsNumber()));
        // 耗时任务:推荐商品,召回客户
        orderDTO.setRel(relService.recall(orderId));
 		return orderDTO;
	 }
}

栗子中是串行(按顺序依次执行)执行任务,接口的响应速度非常慢。
这些接口之间的数据请求大多是无前后顺序关联 的,可并行执行 ,例如调用获取商品详情的时候,可同时调用获取物流信息。并行执行多个任务,接口的响应速度就可提升。
在这里插入图片描述
对于有顺序执行的任务,可进行任务编排,例如:

  • 获取用户信息之后,才能调用商品详情和物流信息接口。
  • 成功获取商品详情和物流信息之后,才能调用商品推荐接口。
    在这里插入图片描述
    故需要使用异步编程工具帮助实现多个任务的编排。

二、Future

在Java8之前一般通过Future实现异步,主要用于需要执行耗时任务的场景,避免程序一直等待耗时任务执行完成,导致执行效率太低问题。
但只能通过阻塞或者轮询的方式获取结果,获取计算结果的 get() 方法为阻塞调用,而且不支持设置回调方法,不支持异步任务的编排组合,Java8之前若要设置回调,通常使用guava的ListenableFuture,又会导致回调地狱,加深阅读和实现的难度。
例如使用ListenableFuture实现(回调地狱):三个操作step1、step2、step3存在依赖关系,其中step3的执行依赖step1和step2的结果
在这里插入图片描述

ExecutorService executor = Executors.newFixedThreadPool(5);

ListeningExecutorService guavaExecutor = MoreExecutors.listeningDecorator(executor);

ListenableFuture<String> future1 = guavaExecutor.submit(() -> {
    //step 1
    System.out.println("执行step 1");
    return "step1 result";
});

ListenableFuture<String> future2 = guavaExecutor.submit(() -> {
    //step 2
    System.out.println("执行step 2");
    return "step2 result";
});

ListenableFuture<List<String>> future1And2 = Futures.allAsList(future1, future2);

Futures.addCallback(future1And2, new FutureCallback<List<String>>() {
    @Override
    public void onSuccess(List<String> result) {
        System.out.println(result);
        
        ListenableFuture<String> future3 = guavaExecutor.submit(() -> {
            System.out.println("执行step 3");
            return "step3 result";
        });
        
        Futures.addCallback(future3, new FutureCallback<String>() {
            @Override
            public void onSuccess(String result) {
                System.out.println(result);
            }        
            @Override
            public void onFailure(Throwable t) {
            }
        }, guavaExecutor);
    }

    @Override
    public void onFailure(Throwable t) {
    }}, guavaExecutor);

而使用CompletableFuture实现:

ExecutorService executor = Executors.newFixedThreadPool(5);

CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
    System.out.println("执行step 1");
    return "step1 result";
}, executor);

CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
    System.out.println("执行step 2");
    return "step2 result";
});

cf1.thenCombine(cf2, (result1, result2) -> {
    System.out.println(result1 + " , " + result2);
    System.out.println("执行step 3");
    return "step3 result";
}).thenAccept(System.out::println);

显然,CompletableFuture的实现更为简洁,可读性更好,无需引入外部依赖。

三、CompletableFuture

ompletableFuture对Future进行扩展,可通过设置回调的方式处理计算结果,同时也支持组合操作,支持进一步的编排,一定程度解决回调地狱问题。

3.1 CompletableFuture定义

CompletableFuture 类的定义:

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
}

CompletableFuture实现两个接口(如图):Future、CompletionStage。Future表示异步计算的结果,CompletionStage用于表示异步执行过程中的一个步骤(Stage),这个步骤可能是由另一个CompletionStage触发的,若当前步骤的完成,也可能会触发其他一系列CompletionStage的执行。从而可以根据实际业务对这些步骤进行多样化的编排组合。
在这里插入图片描述
Future 接口有 5 个方法:
在这里插入图片描述

boolean cancel(boolean mayInterruptIfRunning):尝试取消执行任务。
boolean isCancelled():判断任务是否被取消。
boolean isDone():判断任务是否已经被执行完成。
get():等待任务执行完成并获取运算结果。
get(long timeout, TimeUnit unit):多一个超时时间。

CompletionStage 接口中的方法比较多,CompletableFuture 的函数式能力就是这个接口赋予的。

3.2 CompletableFuture使用场景

  • IO密集型操作:如数据库操作、文件读写、网络请求等,可使用 CompletableFuture 异步执行,避免阻塞主线程。
  • 计算密集型操作:如果计算可以在不同的线程中并行执行,使用 CompletableFuture 可以提高计算效率。
  • 异步编程模式:在需要处理多个异步操作的结果,并且这些操作之间存在依赖关系时,CompletableFuture 可提供解决方案。

3.3 CompletableFuture 常见操作

3.3.1 创建CompletableFuture

创建 CompletableFuture 对象的方法:

  • 通过 new 关键字。
CompletableFuture<Result<T>> resultFuture = new CompletableFuture<>();
// 在某些条件下手动完成Future
if (checkCondition()) {
    // complete() 方法只能调用一次,后续调用将被忽略。
	resultFuture.complete(rpcResponse);
	rpcResponse = completableFuture.get();
}
  • 基于 CompletableFuture 自带的静态工厂方法:runAsync()、supplyAsync() 。
// supplyAsync()方法需要一个Supplier函数接口,通常用于执行异步计算。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 模拟耗时的计算
    simulateTask("数据加载中");
    return "结果";
});


//runAsync()方法接受一个Runnable函数接口,不返回任何结果。不关心异步任务的结果,只想执行一个异步操作
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    simulateTask("正在执行一些处理");
});


// 也可用自定义线程池执行任务
ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 4, 3,
                TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),
                new ThreadPoolExecutor.DiscardOldestPolicy());
CompletableFuture.runAsync(() -> System.out.println("Hello World!"), pool);

3.3.2 使用CompletableFuture

1、组合调用:CompletableFuture的组合能力。假设有两个独立的异步任务,组合使用:

ompletableFuture<User> future1 = CompletableFuture.supplyAsync(() -> {
	// 加载用户数据
    return  userService.getUserInfo(userId);
});

CompletableFuture<Product> future2 = CompletableFuture.supplyAsync(() -> {
    // 获取用户购买的产品
    return productService.getUserBuyProd(userId);
});

// 组合两个future,等待它们都完成
CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (user, product) -> {
    return "组合处理结果: " + user::getId + "购买产品:" + product::getProdName;
});

combinedFuture.thenAccept(System.out::println);

// 若一个异步操作依赖于另一个异步操作的结果
CompletableFuture<String> masterFuture = CompletableFuture.supplyAsync(() -> {
    simulateTask("获取主数据");
    return "主数据结果";
});

CompletableFuture<String> dependentFuture = masterFuture.thenCompose(result -> {
    return CompletableFuture.supplyAsync(() -> {
        simulateTask("处理依赖于" + result + "的数据");
        return "处理后的数据";
    });
});



// 需要等所有操作都完成后再进行下一步
CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2);

allFutures.thenRun(() -> {
    System.out.println("所有任务完成");
});

2、链式调用:把系列操作依次执行,前一个操作的结果作为下一个操作的输入。
CompletableFuture支持多种链式调用方法,比如thenApply, thenAccept和thenRun。

thenApply()用于处理和转换CompletableFuture的结果。
thenAccept()用于消费CompletableFuture的结果,不返回新的CompletableFuturethenRun()则不关心前一个任务的结果,只是在前一个任务执行完后,执行一些后续操作。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    simulateTask("查询数据库");
    return "查询结果";
});

future.thenApply(result -> {
    // 对结果进行处理
    return "处理后的结果:" + result;
}).thenAccept(processedResult -> {
    // 消费处理后的结果
    System.out.println("最终结果:" + processedResult);
}).thenRun(() -> {
    // 执行一些不需要前一个结果的操作
    System.out.println("所有操作完成");
});

用supplyAsync启动一个异步任务来查询数据库。然后用thenApply处理查询结果,用thenAccept消费处理后的结果,最后用thenRun标记所有操作完成。

3.3.3 异常处理

1、基本异常处理:抛出异常,exceptionally方法就会被调用,返回一个包含错误信息的回退结果。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    if (new Random().nextBoolean()) {
        throw new RuntimeException("出错啦!");
    }
    return "正常结果";
}).exceptionally(ex -> {
    return "错误的回退结果:" + ex.getMessage();
});

future.thenAccept(System.out::println);

2、细粒度异常处理:无论异步操作是成功还是失败,handle方法都会被调用。如果有异常,它会处理异常;如果没有,就处理正常结果。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    if (new Random().nextBoolean()) {
        throw new RuntimeException("出错啦!");
    }
    return "正常结果";
}).handle((result, ex) -> {
    if (ex != null) {
        return "处理异常:" + ex.getMessage();
    }
    return "处理结果:" + result;
});

future.thenAccept(System.out::println);

3、管道式异常处理:把多个异步操作链接起来,并在链的任意位置处理异常。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 第一个异步操作
    return "第一步结果";
}).thenApply(result -> {
    // 第二个异步操作,可能会出错
    throw new RuntimeException("第二步出错啦!");
}).exceptionally(ex -> {
    // 处理异常
    return "在第二步捕获异常:" + ex.getMessage();
}).thenApply(result -> {
    // 第三个异步操作
    return "第三步使用结果:" + result;
});

future.thenAccept(System.out::println);

4、组合异步操作时的错误处理
当组合多个CompletableFuture时,需对每一个Future都进行错误处理。避免一个未捕获的异常破坏整个操作链。

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "任务1").exceptionally(ex -> "默认值1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "任务2").exceptionally(ex -> "默认值2");

CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (result1, result2) -> result1 + " 和 " + result2);

3.3.4 注意事项

1、避免过多的链式调用
过度使用可能会导致代码难以理解和维护。建议把复杂的逻辑分解成多个方法或类:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "原始数据")
    .thenApply(this::step1)
    .thenApply(this::step2)
    .thenApply(this::step3);

// 将每个步骤的逻辑封装在不同的方法中
private String step1(String data) {
    return "处理1:" + data;
}

private String step2(String data) {
    return "处理2:" + data;
}

private String step3(String data) {
    return "处理3:" + data;
}

2、谨慎处理阻塞操作
若CompletableFuture链中包含阻塞调用,如数据库操作或文件I/O,最好将这些操作放在独立的线程池中,避免阻塞ForkJoinPool中的线程。

ExecutorService dbExecutor = Executors.newCachedThreadPool();

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    // 这里是阻塞的数据库操作
    simulateTask("数据库操作");
}, dbExecutor);

// 或者
private ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>());

CompletableFuture.runAsync(() -> {
   //...
}, executor);

3、尽量避免使用 get()
CompletableFuture的get()方法是阻塞的,尽量避免使用。如果必须要使用的话,需要添加超时时间,否则可能会导致主线程一直等待,无法执行其他任务。

 CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(10_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello, world!";
    });

    // 获取异步任务的返回值,设置超时时间为 5 秒
    try {
        String result = future.get(5, TimeUnit.SECONDS);
        System.out.println(result);
    } catch (InterruptedException | ExecutionException | TimeoutException e) {
        // 处理异常
        e.printStackTrace();
    }
}

四、CompletableFuture处理工具类

/**
 * CompletableFuture封装工具类
 */
@Slf4j
public class FutureUtils {
/**
 * 该方法为美团内部rpc注册监听的封装,可以作为其他实现的参照
 * OctoThriftCallback 为thrift回调方法
 * ThriftAsyncCall 为自定义函数,用来表示一次thrift调用(定义如上)
 */
public static <T> CompletableFuture<T> toCompletableFuture(final OctoThriftCallback<?,T> callback , ThriftAsyncCall thriftCall) {
    CompletableFuture<T> thriftResultFuture = new CompletableFuture<>();
    callback.addObserver(new OctoObserver<T>() {
        @Override
        public void onSuccess(T t) {
            thriftResultFuture.complete(t);
        }
        @Override
        public void onFailure(Throwable throwable) {
            thriftResultFuture.completeExceptionally(throwable);
        }
    });
    if (thriftCall != null) {
        try {
            thriftCall.invoke();
        } catch (TException e) {
            thriftResultFuture.completeExceptionally(e);
        }
    }
    return thriftResultFuture;
}
  /**
   * 设置CF状态为失败
   */
  public static <T> CompletableFuture<T> failed(Throwable ex) {
   CompletableFuture<T> completableFuture = new CompletableFuture<>();
   completableFuture.completeExceptionally(ex);
   return completableFuture;
  }
  /**
   * 设置CF状态为成功
   */
  public static <T> CompletableFuture<T> success(T result) {
   CompletableFuture<T> completableFuture = new CompletableFuture<>();
   completableFuture.complete(result);
   return completableFuture;
  }
  /**
   * 将List<CompletableFuture<T>> 转为 CompletableFuture<List<T>>
   */
  public static <T> CompletableFuture<List<T>> sequence(Collection<CompletableFuture<T>> completableFutures) {
   return CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture<?>[0]))
           .thenApply(v -> completableFutures.stream()
                   .map(CompletableFuture::join)
                   .collect(Collectors.toList())
           );
  }
  /**
   * 将List<CompletableFuture<List<T>>> 转为 CompletableFuture<List<T>>
   * 多用于分页查询的场景
   */
  public static <T> CompletableFuture<List<T>> sequenceList(Collection<CompletableFuture<List<T>>> completableFutures) {
   return CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture<?>[0]))
           .thenApply(v -> completableFutures.stream()
                   .flatMap( listFuture -> listFuture.join().stream())
                   .collect(Collectors.toList())
           );
  }
  /*
   * 将List<CompletableFuture<Map<K, V>>> 转为 CompletableFuture<Map<K, V>>
   * @Param mergeFunction 自定义key冲突时的merge策略
   */
  public static <K, V> CompletableFuture<Map<K, V>> sequenceMap(
       Collection<CompletableFuture<Map<K, V>>> completableFutures, BinaryOperator<V> mergeFunction) {
   return CompletableFuture
           .allOf(completableFutures.toArray(new CompletableFuture<?>[0]))
           .thenApply(v -> completableFutures.stream().map(CompletableFuture::join)
                   .flatMap(map -> map.entrySet().stream())
                   .collect(Collectors.toMap(Entry::getKey, Entry::getValue, mergeFunction)));
  }
  /**
   * 将List<CompletableFuture<T>> 转为 CompletableFuture<List<T>>,并过滤调null值
   */
  public static <T> CompletableFuture<List<T>> sequenceNonNull(Collection<CompletableFuture<T>> completableFutures) {
   return CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture<?>[0]))
           .thenApply(v -> completableFutures.stream()
                   .map(CompletableFuture::join)
                   .filter(e -> e != null)
                   .collect(Collectors.toList())
           );
  }
  /**
   * 将List<CompletableFuture<List<T>>> 转为 CompletableFuture<List<T>>,并过滤调null值
   * 多用于分页查询的场景
   */
  public static <T> CompletableFuture<List<T>> sequenceListNonNull(Collection<CompletableFuture<List<T>>> completableFutures) {
   return CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture<?>[0]))
           .thenApply(v -> completableFutures.stream()
                   .flatMap( listFuture -> listFuture.join().stream().filter(e -> e != null))
                   .collect(Collectors.toList())
           );
  }
  /**
   * 将List<CompletableFuture<Map<K, V>>> 转为 CompletableFuture<Map<K, V>>
   * @Param filterFunction 自定义过滤策略
   */
  public static <T> CompletableFuture<List<T>> sequence(Collection<CompletableFuture<T>> completableFutures,
                                                     Predicate<? super T> filterFunction) {
   return CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture<?>[0]))
           .thenApply(v -> completableFutures.stream()
                   .map(CompletableFuture::join)
                   .filter(filterFunction)
                   .collect(Collectors.toList())
           );
  }
  /**
   * 将List<CompletableFuture<List<T>>> 转为 CompletableFuture<List<T>>
   * @Param filterFunction 自定义过滤策略
   */
  public static <T> CompletableFuture<List<T>> sequenceList(Collection<CompletableFuture<List<T>>> completableFutures,
                                                         Predicate<? super T> filterFunction) {
   return CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture<?>[0]))
           .thenApply(v -> completableFutures.stream()
                   .flatMap( listFuture -> listFuture.join().stream().filter(filterFunction))
                   .collect(Collectors.toList())
           );
  }
/**
 * 将CompletableFuture<Map<K,V>>的list转为 CompletableFuture<Map<K,V>>。 多个map合并为一个map。 如果key冲突,采用新的value覆盖。
 */
  public static <K, V> CompletableFuture<Map<K, V>> sequenceMap(
       Collection<CompletableFuture<Map<K, V>>> completableFutures) {
   return CompletableFuture
           .allOf(completableFutures.toArray(new CompletableFuture<?>[0]))
           .thenApply(v -> completableFutures.stream().map(CompletableFuture::join)
                   .flatMap(map -> map.entrySet().stream())
                   .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> b)));
  }}

五、异步化收益

通过异步化改造,推荐系统的性能得到明显提升,与改造前对比的收益如下:

  • 核心接口吞吐量大幅提升,其中产品买卖轮询接口的TP99从865ms降为408ms。
  • 取数服务器数量减少1/3。

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

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

相关文章

51单片机嵌入式开发:21、STC89C52R控制抢答器+数码管+后台显示+LCD1602x显示

配套程序 STC89C52R控制抢答器数码管后台显示LCD1602x显示 STC89C52R控制抢答器数码管后台显示LCD1602x显示1 概述1.1 项目概述1.2 项目组成部分1.3 功能描述 2 开发环境2.1 支持设备2.2 硬件电路 3 软件代码工程4 演示Proteus仿真5 总结 配套程序 STC89C52R控制抢答器数码管后…

oracle登录报“ORA-27101: shared memory realm does not exist”

oracle登录报“ORA-27101: shared memory realm does not exist” 问题&#xff1a; 1、使用ip:1521/服务名方式连库报错" ORA-27101: shared memory realm does not exist Linux-x86_64 Error: 2: No such file or directory" 2、sqlplus XX/密码 可以登录数据库 …

linux下使用yum安装mysql

本文使用常规方式手动安装mysql 第一步 下载mysql的repo源 wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm第二步 安装mysql-community-release-el7-5.noarch.rpm包 rpm -ivh mysql-community-release-el7-5.noarch.rpm第三步 安装mysql-server yum -y…

一文步让成功搭建AI数字人直播系统!

随着AI数字人直播技术的不断成熟&#xff0c;越来越多的企业都开始使用或计划引入AI数字人直播&#xff0c;来实现直播板块的降本增效。这也让不少创业者看到了AI数字人直播所蕴含着的巨大市场需求和广阔收益空间&#xff0c;从而有了搭建AI数字人直播系统源码的想法。 ​不过&…

flask学习小结

背景 通过官方文档学习 quickstart 第一个app 代码 文件命名为hello.py windows执行set FLASK_APPhello&#xff0c;linux执行export FLASK_APPhello&#xff0c;然后执行flask run即可运行app 默认运行的app是127.0.0.1:5000&#xff0c;可通过-h -p参数改ip地址和端口。…

windows下定时执行bat脚本删除无用文件和文件夹

文章目录 1. 删除脚本编写2. 配置任务计划程序1.搜索任务计划程序2. 创建任务3. 触发器配置4. 创建任务–>操作5. 创建任务–>条件6. 创建任务–>设置7. 确定保存设置 1. 删除脚本编写 echo off REM 设置源目录和要删除的文件的天数阈值 SrcDirE:\xxx DaysAgo30REM 从…

二阶段测试

二阶段测试 1、部署框架前准备工作 服务器类型部署组件ip地址DR1调度服务器 主&#xff08;ha01&#xff09;KeepalivedLVS-DR192.168.168.21DR2调度服务器 备 (ha02)KeepalivedLVS-DR192.168.168.22web1节点服务器 (slave01)NginxTomcatMySQL 备MHA managerMHA node192.168.1…

【OpenCV C++20 学习笔记】图片融合

图片融合 原理实现结果展示完整代码 原理 关于OpenCV的配置和基础用法&#xff0c;请参阅本专栏的其他文章&#xff1a;垚武田的OpenCV合集 这里采用的图片熔合的算法来自Richard Szeliski的书《Computer Vision: Algorithms and Applications》&#xff08;《计算机视觉&#…

【C++】循环结构-for循环

for 循环语法格式&#xff1a; for(起始表达式;条件表达式;末尾循环体) {循环语句} 注意&#xff1a;括号内的三者也可以写出来写在循环语句里&#xff0c;但使用for循环就是为了使循环更加简洁明了&#xff0c;因此不仅以这么做 下面是一个实例 #include<iostream> …

ROS安装key NO_PUBKEY F42ED6FBAB17C654

解决方案 可以手工添加。 ROS1云课→18一键配置_linux ros1配置-CSDN博客 下载ros.key&#xff0c;然后手工添加到对应位置。 ros2407.key master zhangrelay / ros_book GitCode 问题描述 ros2ros2-20l1a001cd:~$ sudo apt update Hit:1 http://ftp.sjtu.edu.cn/ubunt…

010 仿muduo实现高性能服务器组件_Http协议模块

​&#x1f308;个人主页&#xff1a;Fan_558 &#x1f525; 系列专栏&#xff1a;仿muduo &#x1f4d2;代码仓库&#xff1a; 项目代码 &#x1f339;关注我&#x1f4aa;&#x1f3fb;带你学更多知识 文章目录 前言Util模块设计意义整体设计代码如下 HttpRequest模块代码如下…

初始K8s

K8S 基本概念: K8S 的全称为 Kubernetes (K12345678S)&#xff0c;PS&#xff1a;“嘛&#xff0c;写全称也太累了吧&#xff0c;不如整个缩写”。 作用&#xff1a; 用于自动部署、扩展和管理“容器化&#xff08;containerized&#xff09;应用程序”的开源系统。 可以理解成…

web自动化6-pytest③实践测试用例-回归用例web自动化

# -*- coding: utf-8 -*- """ lemut_select - 业务受理 Author: duxiaowei Date: 2024/7/17 """ import timeimport allure import pytest from selenium.webdriver.common.by import By# 业务受理 allure.feature("业务受理") class …

SQL Date Functions | SQL Time Functions 概述

SQL Date Functions | SQL Time Functions 在SQL中&#xff0c;日期和时间函数是用于处理日期和时间数据的重要工具。 SQL Date Functions | SQL Time Functions函数可以从日期时间数据中提取特定的部分、执行日期计算、格式化日期时间等操作。 No.FunctionDescription描述1…

sql注入详解【从数据库架构分析】

文章目录 简介数据库的架构sql注入概念正常语句正常回显页面在页面中使用sql语句 跨库查询sql文件读写影响条件复现读写的路径的问题 sql注入请求分类sql注入请求类型sql注入请求方式&#xff1a;sql注入数据请求格式 数据库的增删改查数据库查询数据库添加数据库删除数据库修改…

【TS】TypeScript中的接口(Interface):对象类型的强大工具

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 TypeScript中的接口(Interface):对象类型的强大工具引言1. 接口的基本概念1.1 什…

MybatisPlus核心用法

MybatisPlus核心用法 目录 MybatisPlus核心用法1.使用MybatisPlus的基本步骤&#xff1a;2.MybatisPlus是如何获取实现CRUD的数据库表信息的&#xff1f;3.MybatisPlus的常用注解有哪些&#xff1f;如果表名称和实体类名称不符合约定该怎么做&#xff1f;IdType的常见类型有哪些…

IOS微软语音转文本,lame压缩音频

在IOS开发中&#xff0c;用微软进行语音转文本操作&#xff0c;并将录音文件压缩后返回 项目中遇到了利用微软SDK进行实时录音转文本操作&#xff0c;如果操作失败&#xff0c;那么就利用原始音频文件通过网络请求操作&#xff0c;最终这份文件上传到阿里云保存&#xff0c;考…

监控服务器状态,夜莺( nightingale)方案介绍

前提 选择一台服务器&#xff0c;部署一个监控服务端&#xff0c;然后在各个windows或者linux中部署数据采集节点。 类似如下结构 目前我这边采用的技术是 https://flashcat.cloud/product/nightingale/ Nightingale 的解决方案 1 部署内容 服务端&#xff08;服务端为 c…