前言
前陣子專案因業務需要,要對接兄弟部門的使用者資料,因為兄弟部門並不提供增量使用者資料介面,每次只能從兄弟部門那邊同步全量使用者資料。全量的使用者資料大概有幾萬條。因為是全量資料,因此我們這邊要做資料比對(注: 使用者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