兩組資料量相對大時,如何高效進行比對

linyb極客之路發表於2022-06-14

前言

前陣子專案因業務需要,要對接兄弟部門的使用者資料,因為兄弟部門並不提供增量使用者資料介面,每次只能從兄弟部門那邊同步全量使用者資料。全量的使用者資料大概有幾萬條。因為是全量資料,因此我們這邊要做資料比對(注: 使用者username是唯一),如果同步過來的資料,我們這邊沒有,就要做插入操作,如果我們這邊已經有,就要做更新操作。本文就來聊聊當資料量相對大時,如何進行對比

比對邏輯

因使用者username是唯一的,因此我們可以利用使用者username來進行比對匹配

比對實現

1、方案一:兩層巢狀迴圈比對

即: 將介面的全量資料和我們資料庫的全量資料進行迴圈比對

示例

  @Override
    public void compareAndSave(List<User> users, List<MockUser> mockUsers) {
        List<User> addUsers = new ArrayList<>();
        List<User> updateUsers = new ArrayList<>();
        for (MockUser mockUser : mockUsers) {
            for (User user : users) {
                if(mockUser.getUsername().equals(user.getUsername())){
                    int id = user.getId();
                    BeanUtils.copyProperties(mockUser,user);
                    user.setId(id);
                    updateUsers.add(user);
                }else{
                    User newUser = new User();
                    BeanUtils.copyProperties(mockUser,newUser);
                    addUsers.add(newUser);
                }
            }
        }

    }

用這種方法,我在測試環境壓了30萬條資料,比對資料等了大概20分鐘後,直接OOM

2、方案二:使用布隆過濾器

即: 比對開始前,先將我們這邊的資料壓入布隆過濾器,然後通過布隆過濾器來判定介面資料

示例

  @Override
    public void compareAndSave(List<User> users,List<MockUser> mockUsers){
        List<User> addUsers = new ArrayList<>();
        List<User> updateUsers = new ArrayList<>();
        BloomFilter<String> bloomFilter = getUserNameBloomFilter(users);
        for (MockUser mockUser : mockUsers) {
            boolean isExist = bloomFilter.mightContain(mockUser.getUsername());
            //更新
            if(isExist){
               User user = originUserMap.get(mockUser.getUsername());
               int id = user.getId();
               BeanUtils.copyProperties(mockUser,user);
               user.setId(id);
               updateUsers.add(user);
            }else{
                User user = new User();
                BeanUtils.copyProperties(mockUser,user);
                addUsers.add(user);
            }
        }

    }

用這種方法,我在測試環境壓了30萬條資料,比對耗時1秒左右

3、方案三:使用list + map比對

即:比對開始前,先將我們這邊資料存放到map中,map的key為username,value為使用者資料,然後遍歷介面資料,進行比對

示例

  @Override
    public void compareAndSave(List<User> users, List<MockUser> mockUsers) {
        Map<String,User> originUserMap = getOriginUserMap(users);
        List<User> addUsers = new ArrayList<>();
        List<User> updateUsers = new ArrayList<>();
        for (MockUser mockUser : mockUsers) {
             if(originUserMap.containsKey(mockUser.getUsername())){
                 User user = originUserMap.get(mockUser.getUsername());
                 int id = user.getId();
                 BeanUtils.copyProperties(mockUser,user);
                 user.setId(id);
                 updateUsers.add(user);
             }else{
                 User user = new User();
                 BeanUtils.copyProperties(mockUser,user);
                 addUsers.add(user);
             }
        }
    }

用這種方法,我在測試環境壓了30萬條資料,比對耗時350毫秒左右

總結

這三種方案,兩層迴圈效率是最低,而且隨著資料量增大會有OOM的風險。採用布隆過濾器,存在誤判的風險,為了降低誤判風險,只能降低誤判率,可以通過引數指定,但這也增加判斷時間。用map可以說是效率最好,他本質是將時間複雜度從O(n2)降低到O(n)。不過這種方案可能也不是最優方案,事後和朋友討論下,他說可以用啥雙向指標啥,因為我在演算法這方面沒有深入研究,因此本文就沒演示了

demo連結

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-comparedata

相關文章