背景

之前打算做一个利用es实现商品搜索的功能,所以需要先把数据库的信息查询出来然后导入进去,而数据总量为八万条,但过程中发现数据丢失的问题,试了好几次都不行,并且还无法知道丢失了哪些数据

思路

我的想法是采用多线程并分页处理的方式来实现的

1.先得到结果总条数,比方每页查出200条,然后计算出    总共分页数=总条数/200(这里要记得向上取整,可利用Math的方法来实现)

2.然后利用CompletableFuture开启多个线程来查数据库得到每个线程对应的分页结果,这里我开了4个线程分别来查数据库表,每个线程查出200条,最后给封装到list里面

3.然后利用es批量导入的api来进行批量导入,最后记得要清空list的元素

代码如下图

这里其实就是开四个线程并且分别用feign远程调用其他服务的接口,并得到结果并一并封装到list

比方

       第一次循环

       a线程:查询   第一页   每页200条

       b线程:查询   第二页   每页200条

       c线程:查询   第三页   每页200条

       d线程:查询   第四页   每页200条

       第二次循环

       a线程:查询   第五页   每页200条

       b线程:查询   第六页   每页200条

       c线程:查询   第七页   每页200条

       d线程:查询   第八页   每页200条

      ..............

      ..............

以此类推,所以每一次循环的第一个线程肯定比上一次循环的线程多4页(比方第二次循环的a线程页码就比第一次循环的a线程多(5-1)=4),因此每次循环结束都要page=page+4

@Test
    public void testBulkRequest() throws Exception {
        Integer totalCount = itemClient.getTotalCount();

        double totalPage = Math.ceil(totalCount / 200);
        ArrayList<Item> items = new ArrayList<>();

        for (int page = 1; page <= totalPage; page=page+4) {
            final int start=page;
            CompletableFuture<PageDTO<Item>> aFuture= CompletableFuture.supplyAsync(() -> {
                return itemClient.itemPage(Long.parseLong(Integer.toString(start)), Long.parseLong(Integer.toString(200)));
            });
            CompletableFuture<PageDTO<Item>>  bFuture= CompletableFuture.supplyAsync(() -> {
                return itemClient.itemPage(Long.parseLong(Integer.toString(start+1)), Long.parseLong(Integer.toString(200)));
            });
            CompletableFuture<PageDTO<Item>>  cFuture= CompletableFuture.supplyAsync(() -> {
                return itemClient.itemPage(Long.parseLong(Integer.toString(start+2)), Long.parseLong(Integer.toString(200)));
            });
            CompletableFuture<PageDTO<Item>>  dFuture= CompletableFuture.supplyAsync(() -> {
                return itemClient.itemPage(Long.parseLong(Integer.toString(start+3)), Long.parseLong(Integer.toString(200)));
            });

            //这里会等到所有的线程执行完毕才会继续往下走
            CompletableFuture.allOf(aFuture, bFuture, cFuture, dFuture).join();

            PageDTO<Item> alist = aFuture.get();
            PageDTO<Item> blist = bFuture.get();
            PageDTO<Item> clist = cFuture.get();
            PageDTO<Item> dlist = dFuture.get();

            items.addAll(alist.getList());
            items.addAll(blist.getList());
            items.addAll(clist.getList());
            items.addAll(dlist.getList());

            insertToEs(items);//把批量导入的方法抽取出来(下一张图有详情)

            items.clear();
        }
public void insertToEs(List<Item> ItemList) throws IOException, InterruptedException {
        if(CollectionUtils.isNotEmpty(ItemList)){
            //创建批量操作请求对象
            BulkRequest bulkRequest = new BulkRequest();//底层集合缓存

            for(Item item:ItemList){
                ItemDoc itemDoc = new ItemDoc(item);

                //创建请求对象
                IndexRequest request = new IndexRequest("item2").id(itemDoc.getId().toString());
                //填充内容
                String json = objectMapper.writeValueAsString(itemDoc);
                request.source(json,XContentType.JSON);

                //把数据添加批量操作对象缓存中
                bulkRequest.add(request);
            }

            //一次性执行批量操作
            highLevelClient.bulk(bulkRequest,RequestOptions.DEFAULT);

        }
    }

数据丢失问题

因为我一开始是分5个线程的,然后一次往es插1000条,结果发现每次插入都会少200条,比方1000条数据最后只有800,2000条数据最后只有1600......当时我上网看了很多帖子都解决不了,再加上丢失数据也根本不会有报错信息,所以我就猜测嘛,会不会是我部署es的机子,只支持一次性插入不超于800呢,然后我就去试了一下,结果发现果然可以。所以问题就解决了。

总结

第一次写帖子,其实这个功能还有很多优化的地方(比方说频繁的新建线程销毁线程不太好),不足之处及时指出。有帮助的麻烦给个赞啦啦啦