前言
前陣子項目因業務需要,要對接兄弟部門的用戶數據,因為兄弟部門并不提供增量用戶數據接口,每次只能從兄弟部門那邊同步全量用戶數據。全量的用戶數據大概有幾萬條。因為是全量數據,因此我們這邊要做數據比對(注: 用戶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