HOOOS

CompletableFuture实战:电商商品详情页与微服务性能优化秘籍

0 55 代码小旋风 JavaCompletableFuture并发编程
Apple

CompletableFuture 实战:电商商品详情页与微服务性能优化秘籍

你好呀!我是你们的编程小助手“代码小旋风”!今天咱们来聊聊 Java 并发编程中的一个神器——CompletableFuture。相信不少小伙伴在实际开发中都遇到过这样的场景:电商网站的商品详情页加载慢、微服务架构中服务调用耗时长,导致系统吞吐量上不去,用户体验也大打折扣。别担心,CompletableFuture 就是解决这些问题的利器!

1. 为什么需要 CompletableFuture?

在传统的 Java 并发编程中,我们通常使用 Future 来获取异步任务的执行结果。但是,Future 有一些明显的局限性:

  • 获取结果不方便: Future.get() 方法会阻塞当前线程,直到任务完成并返回结果。如果任务执行时间较长,会导致线程长时间阻塞,降低系统吞吐量。
  • 不支持回调: Future 无法设置回调函数,在任务完成后自动执行某些操作。我们只能通过轮询或者阻塞的方式来获取结果,非常不灵活。
  • 难以组合多个异步任务: 如果我们需要将多个异步任务的结果组合起来,使用 Future 会非常麻烦,代码会变得冗长且难以维护。

CompletableFuture 的出现,正是为了解决这些问题。它提供了更强大的功能和更灵活的 API,让我们可以更轻松地编写高性能的并发程序。

2. CompletableFuture 核心概念与 API

CompletableFuture 实现了 FutureCompletionStage 接口,它既可以表示一个异步任务的结果,也可以表示一个异步任务的执行阶段。

2.1 创建 CompletableFuture 对象

我们可以通过以下几种方式创建 CompletableFuture 对象:

  • CompletableFuture.completedFuture(value) 创建一个已完成的 CompletableFuture 对象,其值为给定的 value

    CompletableFuture<String> future = CompletableFuture.completedFuture("Hello, CompletableFuture!");
    
  • CompletableFuture.runAsync(runnable) 创建一个异步执行的 CompletableFuture 对象,执行给定的 Runnable 任务(无返回值)。

    CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
        // 模拟耗时操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Task executed asynchronously.");
    });
    
  • CompletableFuture.supplyAsync(supplier) 创建一个异步执行的 CompletableFuture 对象,执行给定的 Supplier 任务(有返回值)。

    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        // 模拟耗时操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Task executed asynchronously and returned a result.";
    });
    

2.2 获取异步任务的结果

CompletableFuture 提供了多种获取异步任务结果的方法:

  • get() 阻塞当前线程,直到任务完成并返回结果。与 Future.get() 类似,但 CompletableFuture.get() 在任务执行过程中抛出异常时,会抛出 CompletionException,而不是 ExecutionException
  • join()get() 类似,但 join() 不会抛出受检异常(Checked Exception),而是抛出非受检异常(Unchecked Exception)。
  • getNow(valueIfAbsent) 立即返回结果,如果任务已完成,则返回结果;否则,返回给定的 valueIfAbsent 值。
  • complete(value) 如果任务尚未完成,则将任务的结果设置为给定的 value,并触发依赖该任务的其他任务执行。如果任务已完成,则该方法不执行任何操作。
  • completeExceptionally(ex) 如果任务尚未完成,则将任务的结果设置为给定的异常 ex,并触发依赖该任务的其他任务执行。如果任务已完成,则该方法不执行任何操作。

2.3 异步任务的回调

CompletableFuture 最大的优势之一就是支持回调。我们可以在任务完成后自动执行某些操作,而无需手动阻塞或轮询。

  • thenApply(fn) / thenApplyAsync(fn) 在任务完成后,将任务的结果作为参数传递给给定的函数 fn,并返回一个新的 CompletableFuture 对象,其值为函数 fn 的返回值。

    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
            .thenApply(s -> s + ", World!")
            .thenApply(String::toUpperCase);
    
    System.out.println(future.join()); // 输出 HELLO, WORLD!
    
  • thenAccept(consumer) / thenAcceptAsync(consumer) 在任务完成后,将任务的结果作为参数传递给给定的消费者 consumer,不返回任何结果。

    CompletableFuture.supplyAsync(() -> "Hello")
            .thenAccept(s -> System.out.println("Computation returned: " + s));
    
  • thenRun(action) / thenRunAsync(action) 在任务完成后,执行给定的 Runnable 任务 action,不关心任务的结果。

    CompletableFuture.supplyAsync(() -> "Hello")
            .thenRun(() -> System.out.println("Computation finished."));
    

thenApplyAsyncthenAcceptAsyncthenRunAsync 方法会使用默认的线程池(ForkJoinPool.commonPool())来执行回调函数。我们也可以指定自定义的线程池。

2.4 组合多个异步任务

CompletableFuture 提供了强大的 API 来组合多个异步任务,实现复杂的并发逻辑。

  • thenCompose(fn) / thenComposeAsync(fn) 在一个 CompletableFuture 对象完成后,将其结果作为参数传递给一个函数,该函数返回另一个CompletableFuture 对象。

        //获取店铺基本信息
    public CompletableFuture<Shop> getShopInfo(long shopId) {
      return CompletableFuture.supplyAsync(()->{
          Shop shop = new Shop();
          shop.setShopId(shopId);
          shop.setName("店铺" + shopId);
          //模拟耗时
          try {
              Thread.sleep(100);
          } catch (InterruptedException e) {
              throw new RuntimeException(e);
          }
          return  shop;
      });
    }
    //获取店铺商品列表
    public CompletableFuture<List<Product>> getProductList(long shopId){
        return CompletableFuture.supplyAsync(()->{
            List<Product> productList = new ArrayList<>();
            productList.add(new Product(1,"商品1"));
            productList.add(new Product(2,"商品2"));
            //模拟耗时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return productList;
        });
    }
    

public void testThenCompose(){
long shopId = 123L;
CompletableFuture<List<Product>> completableFuture = getShopInfo(shopId).thenCompose(this::getProductList);
List<Product> products = completableFuture.join();
System.out.println(products);

}
```
  • thenCombine(other, fn) / thenCombineAsync(other, fn) 将两个 CompletableFuture 对象的结果组合起来,并将组合后的结果作为参数传递给给定的函数 fn

      //获取店铺基本信息
    public CompletableFuture<Shop> getShopInfo(long shopId) {
      return CompletableFuture.supplyAsync(()->{
          Shop shop = new Shop();
          shop.setShopId(shopId);
          shop.setName("店铺" + shopId);
          //模拟耗时
          try {
              Thread.sleep(100);
          } catch (InterruptedException e) {
              throw new RuntimeException(e);
          }
          return  shop;
      });
    }
    
        //获取店铺评分
    public CompletableFuture<Double> getShopScore(long shopId){
        return CompletableFuture.supplyAsync(()->{
            //模拟耗时
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return 4.5d;
        });
    }
      public void thenCombineTest(){
        long shopId = 123L;
        CompletableFuture<String> completableFuture = getShopInfo(shopId).thenCombine(getShopScore(shopId), (shop, score) -> {
            return shop.getName() + "的评分是:" + score;
        });
        System.out.println(completableFuture.join());
    
    }
    
  • allOf(cfs) 等待所有给定的 CompletableFuture 对象完成。

        //获取店铺基本信息
    public CompletableFuture<Shop> getShopInfo(long shopId) {
      return CompletableFuture.supplyAsync(()->{
          Shop shop = new Shop();
          shop.setShopId(shopId);
          shop.setName("店铺" + shopId);
          //模拟耗时
          try {
              Thread.sleep(100);
          } catch (InterruptedException e) {
              throw new RuntimeException(e);
          }
          return  shop;
      });
    }
    //获取店铺商品列表
    public CompletableFuture<List<Product>> getProductList(long shopId){
        return CompletableFuture.supplyAsync(()->{
            List<Product> productList = new ArrayList<>();
            productList.add(new Product(1,"商品1"));
            productList.add(new Product(2,"商品2"));
            //模拟耗时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return productList;
        });
    }
        //获取店铺评分
    public CompletableFuture<Double> getShopScore(long shopId){
        return CompletableFuture.supplyAsync(()->{
            //模拟耗时
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return 4.5d;
        });
    }
    public void allOfTest(){
        long shopId = 123L;
        CompletableFuture<Shop> shopInfoFuture = getShopInfo(shopId);
        CompletableFuture<List<Product>> productListFuture = getProductList(shopId);
        CompletableFuture<Double> shopScoreFuture = getShopScore(shopId);
    
        CompletableFuture<Void> allFuture = CompletableFuture.allOf(shopInfoFuture, productListFuture, shopScoreFuture);
    
        // 等待所有任务完成
        allFuture.join();
    
        // 获取结果
        Shop shopInfo = shopInfoFuture.join();
        List<Product> productList = productListFuture.join();
        Double shopScore = shopScoreFuture.join();
    
        System.out.println("店铺信息:" + shopInfo);
        System.out.println("商品列表:" + productList);
        System.out.println("店铺评分:" + shopScore);
    
    }
    
  • anyOf(cfs) 只要有一个给定的 CompletableFuture 对象完成,就返回该对象的结果。

3. 电商商品详情页实战

现在,我们来看一个具体的例子:如何使用 CompletableFuture 优化电商网站的商品详情页加载。

假设一个商品详情页需要加载以下信息:

  • 商品基本信息(名称、价格、描述等)
  • 商品图片列表
  • 商品库存信息
  • 商品评价列表
  • 商家信息

这些信息可能来自不同的服务,如果串行加载,会导致页面加载时间过长。我们可以使用 CompletableFuture 并发加载这些信息,大大缩短页面加载时间。

// 模拟商品基本信息服务
CompletableFuture<ProductInfo> getProductInfo(long productId) {
    return CompletableFuture.supplyAsync(() -> {
        // 模拟数据库查询
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new ProductInfo(productId, "商品名称", 99.9, "商品描述");
    });
}

// 模拟商品图片列表服务
CompletableFuture<List<String>> getProductImages(long productId) {
    return CompletableFuture.supplyAsync(() -> {
        // 模拟网络请求
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return Arrays.asList("图片1.jpg", "图片2.jpg", "图片3.jpg");
    });
}

// 模拟商品库存信息服务
CompletableFuture<Integer> getProductStock(long productId) {
    return CompletableFuture.supplyAsync(() -> {
        // 模拟数据库查询
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 100;
    });
}

// 模拟商品评价列表服务
CompletableFuture<List<Comment>> getProductComments(long productId) {
    return CompletableFuture.supplyAsync(() -> {
        // 模拟网络请求
        try {
            Thread.sleep(150);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return Arrays.asList(new Comment("用户1", "好评!"), new Comment("用户2", "不错!"));
    });
}

// 模拟商家信息服务
CompletableFuture<ShopInfo> getShopInfo(long shopId) {
    return CompletableFuture.supplyAsync(() -> {
        // 模拟数据库查询
        try {
            Thread.sleep(80);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new ShopInfo(shopId, "商家名称");
    });
}

// 加载商品详情页
ProductDetailPage loadProductDetailPage(long productId, long shopId) {
    CompletableFuture<ProductInfo> productInfoFuture = getProductInfo(productId);
    CompletableFuture<List<String>> productImagesFuture = getProductImages(productId);
    CompletableFuture<Integer> productStockFuture = getProductStock(productId);
    CompletableFuture<List<Comment>> productCommentsFuture = getProductComments(productId);
    CompletableFuture<ShopInfo> shopInfoFuture = getShopInfo(shopId);

    // 使用 allOf 等待所有任务完成
    CompletableFuture<Void> allFuture = CompletableFuture.allOf(
            productInfoFuture,
            productImagesFuture,
            productStockFuture,
            productCommentsFuture,
            shopInfoFuture
    );

    // 等待所有任务完成,并获取结果
    return allFuture.thenApply(v -> {
        ProductInfo productInfo = productInfoFuture.join();
        List<String> productImages = productImagesFuture.join();
        Integer productStock = productStockFuture.join();
        List<Comment> productComments = productCommentsFuture.join();
        ShopInfo shopInfo = shopInfoFuture.join();

        return new ProductDetailPage(productInfo, productImages, productStock, productComments, shopInfo);
    }).join();
}

// 模拟数据类
class ProductInfo {
    long productId;
    String name;
    double price;
    String description;

    public ProductInfo(long productId, String name, double price, String description) {
        this.productId = productId;
        this.name = name;
        this.price = price;
        this.description = description;
    }

    // ... 省略 getter 和 setter
}

class Comment {
    String username;
    String content;

    public Comment(String username, String content) {
        this.username = username;
        this.content = content;
    }

    // ... 省略 getter 和 setter
}

class ShopInfo {
    long shopId;
    String shopName;

    public ShopInfo(long shopId, String shopName) {
        this.shopId = shopId;
        this.shopName = shopName;
    }

    // ... 省略 getter 和 setter
}

class ProductDetailPage {
    ProductInfo productInfo;
    List<String> productImages;
    Integer productStock;
    List<Comment> productComments;
    ShopInfo shopInfo;

    public ProductDetailPage(ProductInfo productInfo, List<String> productImages, Integer productStock, List<Comment> productComments, ShopInfo shopInfo) {
        this.productInfo = productInfo;
        this.productImages = productImages;
        this.productStock = productStock;
        this.productComments = productComments;
        this.shopInfo = shopInfo;
    }
    // ... 省略 getter 和 setter
}

class Product{
    long productId;
    String name;

    public Product(long productId, String name) {
        this.productId = productId;
        this.name = name;
    }
     // ... 省略 getter 和 setter
}

class Shop{

    long shopId;

    String name;
    // ... 省略 getter 和 setter

    public void setShopId(long shopId) {
        this.shopId = shopId;
    }

    public void setName(String name) {
        this.name = name;
    }

    public long getShopId() {
        return shopId;
    }

    public String getName() {
        return name;
    }
}

通过CompletableFuture,我们将商品详情页的多个加载任务并行化,显著减少了页面加载时间,提升了用户体验。假设原来串行加载需要 580ms (100+200+50+150+80),现在并行加载只需要 200ms(取决于耗时最长的任务)。

4. 微服务架构中的性能优化

在微服务架构中,服务之间的调用通常是同步阻塞的。如果一个服务调用另一个服务,而另一个服务响应缓慢,会导致调用方线程长时间阻塞,影响整个系统的性能。

CompletableFuture 可以帮助我们实现异步的服务调用,避免线程阻塞,提高系统的吞吐量。

例如,假设我们有一个订单服务,需要调用用户服务来获取用户信息。我们可以使用 CompletableFuture 异步调用用户服务:

// 模拟用户服务
CompletableFuture<UserInfo> getUserInfo(long userId) {
    return CompletableFuture.supplyAsync(() -> {
        // 模拟网络请求
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new UserInfo(userId, "用户名");
    });
}

// 订单服务
Order createOrder(long userId, long productId) {
    // 异步调用用户服务
    CompletableFuture<UserInfo> userInfoFuture = getUserInfo(userId);

    // 创建订单
    Order order = new Order(userId, productId);

    // 等待用户信息获取完成,并设置到订单中
    userInfoFuture.thenAccept(userInfo -> order.setUserInfo(userInfo));

     //thenAccept是阻塞的,这里只是为了演示。
    //实际上这里应该返回CompletableFuture<Order>,然后调用方如果需要结果可以进行join()
     return  order;
}

// 模拟数据类
class UserInfo {
    long userId;
    String username;

    public UserInfo(long userId, String username) {
        this.userId = userId;
        this.username = username;
    }
      // ... 省略 getter 和 setter
}

class Order {
    long userId;
    long productId;
    UserInfo userInfo;

    public Order(long userId, long productId) {
        this.userId = userId;
        this.productId = productId;
    }

    public void setUserInfo(UserInfo userInfo) {
        this.userInfo = userInfo;
    }
      // ... 省略 getter 和 setter
}

通过异步调用,订单服务无需等待用户服务返回结果,可以继续处理其他请求,提高了系统的吞吐量。

5. 异常处理

在异步任务中,异常处理非常重要。CompletableFuture 提供了多种处理异常的方法:

  • exceptionally(fn) 当任务执行过程中发生异常时,会调用给定的函数 fn,并将异常作为参数传递给该函数。exceptionally 方法返回一个新的 CompletableFuture 对象,其值为函数 fn 的返回值。

    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        throw new RuntimeException("Computation error!");
    }).exceptionally(ex -> {
        System.err.println("Error: " + ex.getMessage());
        return "Default Value";
    });
    
    System.out.println(future.join()); // 输出 Default Value
    
  • handle(fn) 无论任务是否成功完成,都会调用给定的函数 fn,并将任务的结果或异常作为参数传递给该函数。handle 方法返回一个新的 CompletableFuture 对象,其值为函数 fn 的返回值。

    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
            .handle((result, ex) -> {
                if (ex != null) {
                    System.err.println("Error: " + ex.getMessage());
                    return "Default Value";
                } else {
                    return result.toUpperCase();
                }
            });
    
    System.out.println(future.join()); // 输出 HELLO
    

6. 总结与注意事项

CompletableFuture 是 Java 并发编程的一大利器,它可以帮助我们编写高性能、高吞吐量的应用程序。在使用 CompletableFuture 时,需要注意以下几点:

  • 选择合适的线程池: 默认情况下,CompletableFuture 使用 ForkJoinPool.commonPool() 线程池。在某些情况下,我们可能需要自定义线程池,例如,为不同的任务分配不同的线程池,或者限制线程池的大小。
  • 避免过度使用: 虽然 CompletableFuture 很强大,但过度使用会导致代码复杂性增加,反而降低性能。我们应该根据实际情况,合理使用 CompletableFuture
  • **注意CompletableFuture的阻塞方法:**虽然CompletableFuture是异步编程的利器,但是其中仍然存在阻塞的方法,例如get()和join(),不正确的使用仍然会导致性能问题。
  • 异常处理: 异步任务中的异常处理非常重要。我们需要使用 exceptionallyhandle 方法来处理异常,避免程序崩溃。
  • 上下文传递: 在异步任务中,如果需要传递上下文信息(例如,用户信息、请求 ID 等),可以使用 ThreadLocal 或者自定义的上下文对象。

希望通过今天的分享,你能对 CompletableFuture 有更深入的了解,并在实际开发中灵活运用,打造高性能、高可用的 Java 应用!如果你有任何问题或者想了解更多关于 CompletableFuture 的知识,欢迎在评论区留言,我会尽力解答!

点评评价

captcha
健康