兩組數(shù)據(jù)量相對大時,如何高效進行比對
01 前言
前陣子項目因業(yè)務(wù)需要,要對接兄弟部門的用戶數(shù)據(jù),因為兄弟部門并不提供增量用戶數(shù)據(jù)接口,每次只能從兄弟部門那邊同步全量用戶數(shù)據(jù)。全量的用戶數(shù)據(jù)大概有幾萬條。因為是全量數(shù)據(jù),因此我們這邊要做數(shù)據(jù)比對( 注: 用戶username是唯一),如果同步過來的數(shù)據(jù),我們這邊沒有,就要做插入操作,如果我們這邊已經(jīng)有,就要做更新操作。本文就來聊聊當數(shù)據(jù)量相對大時,如何進行對比
02 比對邏輯
因用戶username是唯一的,因此我們可以利用用戶username來進行比對匹配
03 比對實現(xiàn)
1.方案一:兩層嵌套循環(huán)比對
即:將接口的全量數(shù)據(jù)和我們數(shù)據(jù)庫的全量數(shù)據(jù)進行循環(huán)比對
示例
@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);
}
}
}
}
用這種方法,我在測試環(huán)境壓了30萬條數(shù)據(jù),比對數(shù)據(jù)等了大概20分鐘后,直接OOM
2.方案二:使用布隆過濾器
即:比對開始前,先將我們這邊的數(shù)據(jù)壓入布隆過濾器,然后通過布隆過濾器來判定接口數(shù)據(jù)
示例
@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);
}
}
}
用這種方法,我在測試環(huán)境壓了30萬條數(shù)據(jù),比對耗時1秒左右
3.方案三:使用list + map比對
即:比對開始前,先將我們這邊數(shù)據(jù)存放到map中,map的key為username,value為用戶數(shù)據(jù),然后遍歷接口數(shù)據(jù),進行比對
示例
@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);
}
}
}
用這種方法,我在測試環(huán)境壓了30萬條數(shù)據(jù),比對耗時350毫秒左右
04 總結(jié)
這三種方案,兩層循環(huán)效率是最低,而且隨著數(shù)據(jù)量增大會有OOM的風險。采用布隆過濾器,存在誤判的風險,為了降低誤判風險,只能降低誤判率,可以通過參數(shù)指定,但這也增加判斷時間。用map可以說是效率最好,他本質(zhì)是將時間復雜度從O(n2)降低到O(n)。不過這種方案可能也不是最優(yōu)方案,事后和朋友討論下,他說可以用啥雙向指針啥,因為我在算法這方面沒有深入研究,因此本文就沒演示了
05 demo鏈接
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-comparedata。