効率的なパラレルインターフェイス重合SpringBoot

 

 

転送:juejin.im/post/5d064b90e51d45777540fda7

 

背景

バックエンドインターフェイスの開発は、最も一般的なシナリオの開発は、インタフェース安らかであるかもしれないですが、それは、RPCインタフェースの可能性がありインターフェースの開発は、多くの場合、どこからでもデータを削除し、その結果、特に一部のサービス・インターフェースに組み立て。 

どのように高性能インタフェースの迅速な開発を容易にするために考えなければならない課題です。

例えば、私は今のインターフェイスを実装する必要があり、ユーザは、ユーザのブログリスト情報+ +データ統合ユーザデータのファンの基礎を引っ張る、次の3つのインターフェイスは、すでに利用可能と仮定すると、ユーザーは、基本的な情報、ユーザーのブログのリストを取得するために使用されていますユーザデータのファン。

ユーザーの基本情報

@Service
public class UserServiceImpl implements UserService {
    @Override
    public User get(Long id) {
        try {Thread.sleep(1000L);} catch (InterruptedException e) {}
        /* mock a user*/
        User user = new User();
        user.setId(id);
        user.setEmail("[email protected]");
        user.setUsername("lvyahui8");
        return user;
    }
}

ユーザーのブログのリスト

@Service
public class PostServiceImpl implements PostService {
    @Override
    public List<Post> getPosts(Long userId) {
        try { Thread.sleep(1000L); } catch (InterruptedException e) {}
        Post post = new Post();
        post.setTitle("spring data aggregate example");
        post.setContent("No active profile set, falling back to default profiles");
        return Collections.singletonList(post);
    }
}

ユーザーデータのファン

@Service
public class FollowServiceImpl implements FollowService {
    @Override
    public List<User> getFollowers(Long userId) {
        try { Thread.sleep(1000L); } catch (InterruptedException e) {}
        int size = 10;
        List<User> users = new ArrayList<>(size);
        for(int i = 0 ; i < size; i++) {
            User user = new User();
            user.setUsername("name"+i);
            user.setEmail("email"+i+"@fox.com");
            user.setId((long) i);
            users.add(user);
        };
        return users;
    }
}

それぞれの方法は、時間のかかる業務をシミュレートするために、睡眠1秒であることに注意してください。

私たちは、3つ以上のデータ・インターフェースを組み立てるためのインタフェースをパッケージ化する必要があります。

PS:3つの要求に分割されていない理由は、このシーンは、実際の作業では一般的であり、多くの場合、私たちは一緒にデータを配置する必要があり、他の一つに行くための要求は、と思うかもしれないサードパーティのネットワークに転送されますか。? 

 

実際のクライアントのネットワークパフォーマンスの考慮事項については、多くの場合、ネットワーク要求で、もちろん、可能な限りのデータを送信し、前提は、データが大きすぎることができないということです、それ以外の場合は、時間のかかるレンダリングの伝送に影響を与えます。多くのAPPの家を、複雑なを見て、実際には1つのインタフェースのみ、1回限りのすべてのデータをプルダウン、クライアントの開発も簡単です。

シリアル実装

優れた性能インターフェース技術を書くすべてのバックエンドプログラマの追求だけでなく、ビジネスの基本的なニーズだけではありません。一般的に、より良い性能を確保するために、より頻繁にあなたが複雑なコードを記述する必要があります。

すべての犬は持っているが、不活性、したがって、私たちはしばしば、次のシリアルコールのようなコードを記述します

@Component
public class UserQueryFacade {
    @Autowired
    private FollowService followService;
    @Autowired
    private PostService postService;
    @Autowired
    private UserService userService;

    public User getUserData(Long userId) {
        User user = userService.get(userId);
        user.setPosts(postService.getPosts(userId));
        user.setFollowers(followService.getFollowers(userId));
        return user;
    }
}

明らかに、上記のコード、非効率的な、少なくとも3Sあなたが望む結果を得るために、使用するデータ・インタフェースと、我々はトラブルを再利用し、適切なサービスを注入する必要があります。

並列化

プログラマーの追求はすぐに考慮することができる複数の並列データ間得ることができ、何の依存性はありません、以下のように、達成するために、非同期スレッドたCountDownLatch +フューチャー+を通じて、まあ。

@Component
public class UserQueryFacade {
    @Autowired
    private FollowService followService;
    @Autowired
    private PostService postService;
    @Autowired
    private UserService userService;

    public User getUserDataByParallel(Long userId) throws InterruptedException, ExecutionException {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        CountDownLatch countDownLatch = new CountDownLatch(3);
        Future<User> userFuture = executorService.submit(() -> {
            try{
                return userService.get(userId);
            }finally {
                countDownLatch.countDown();
            }
        });
        Future<List<Post>> postsFuture = executorService.submit(() -> {
            try{
                return postService.getPosts(userId);
            }finally {
                countDownLatch.countDown();
            }
        });
        Future<List<User>> followersFuture = executorService.submit(() -> {
            try{
                return followService.getFollowers(userId);
            }finally {
                countDownLatch.countDown();
            }
        });
        countDownLatch.await();
        User user = userFuture.get();
        user.setFollowers(followersFuture.get());
        user.setPosts(postsFuture.get());
        return user;
    }
}

上面的代码, 将串行调用改为并行调用, 在有限并发级别下, 能极大提高性能. 但很明显, 它过于复杂, 如果每个接口都为了并行执行都写这样一段代码, 简直是噩梦.

优雅的注解实现

熟悉java的都知道, java有一种非常便利的特性 ~~ 注解. 简直是黑魔法. 往往只需要给类或者方法上添加一些注解, 便可以实现非常复杂的功能.

有了注解, 再结合Spring依赖自动注入的思想, 那么我们可不可以通过注解的方式, 自动注入依赖, 自动并行调用接口呢? 答案是肯定的.

首先, 我们先定义一个聚合接口 (当然也可以不定义聚合类, 所有代码写在原Service类中同样可以)

@Component
public class UserAggregate {
    @DataProvider("userFullData")
    public User userFullData(@DataConsumer("user") User user,
                             @DataConsumer("posts") List<Post> posts,
                             @DataConsumer("followers") List<User> followers) {
        user.setFollowers(followers);
        user.setPosts(posts);
        return user;
    }
}

其中

  • @DataProvider 表示这个方法是一个数据提供者, 数据Id为 userFullData

  • @DataConsumer 表示这个方法的参数, 需要消费数据, 数据Id分别为 user ,posts, followers.

当然, 原来的3个原子服务 用户基础信息 ,用户博客列表, 用户的粉丝数据, 也分别需要添加一些注解

@Service
public class UserServiceImpl implements UserService {
    @DataProvider("user")
    @Override
    public User get(@InvokeParameter("userId") Long id) {

 

@Service
public class PostServiceImpl implements PostService {
    @DataProvider("posts")
    @Override
    public List<Post> getPosts(@InvokeParameter("userId") Long userId) {

 

@Service
public class FollowServiceImpl implements FollowService {
    @DataProvider("followers")
    @Override
    public List<User> getFollowers(@InvokeParameter("userId") Long userId) {

其中

  • @DataProvider 与前面的含义相同, 表示这个方法是一个数据提供者

  • @InvokeParameter 表示方法执行时, 需要手动传入的参数

这里注意 @InvokeParameter 和 @DataConsumer的区别, 前者需要用户在最上层调用时手动传参; 而后者, 是由框架自动分析依赖, 并异步调用取得结果之后注入的.

最后, 仅仅只需要调用一个统一的门面(Facade)接口, 传递数据Id, Invoke Parameters,以及返回值类型. 剩下的并行处理, 依赖分析和注入, 完全由框架自动处理.

@Component
public class UserQueryFacade {
    @Autowired
    private DataBeanAggregateQueryFacade dataBeanAggregateQueryFacade;

    public User getUserFinal(Long userId) throws InterruptedException, 
                IllegalAccessException, InvocationTargetException {
        return dataBeanAggregateQueryFacade.get("userFullData",
                Collections.singletonMap("userId", userId), User.class);
    }
}

如何用在你的项目中

上面的功能, 笔者已经封装为一个spring boot starter, 并发布到maven中央仓库.

只需在你的项目引入依赖.

<dependency>
  <groupId>io.github.lvyahui8</groupId>
  <artifactId>spring-boot-data-aggregator-starter</artifactId>
  <version>1.0.2</version>
</dependency>

并在 application.properties 文件中声明注解的扫描路径.

# 替换成你需要扫描注解的包
io.github.lvyahui8.spring.base-packages=io.github.lvyahui8.spring.example

之后, 就可以使用如下注解和 Spring Bean 实现聚合查询

  • @DataProvider

  • @DataConsumer

  • @InvokeParameter

  • Spring Bean DataBeanAggregateQueryFacade

注意, @DataConsumer 和 @InvokeParameter 可以混合使用, 可以用在同一个方法的不同参数上. 且方法的所有参数必须有其中一个注解, 不能有没有注解的参数.

项目地址和上述示例代码:

https://github.com/lvyahui8/spring-boot-data-aggregator

おすすめ

転載: www.cnblogs.com/hahajava/p/11297623.html