这周接到一个需求,就是从某统一服务中心中拉取部门、用户信息到本地。其中获取部门信息是一个接口、根据部门拉取该部门下的用户信息是一个接口。中间遇到不少问题,记录一下解决历程。
接口开发
根据API很快写好接口,伪代码如下
public List<OrganizationResponse> getOrganization(String organizationCode){
String url = baseUrl+"/api/v1/getOrganizationList";
//构建入参
LinkedHashMap<String,Object> paramMap = new LinkedHashMap<>();
paramMap.put("organizationCode",organizationCode);
//根据入参计算Header的值
LinkedHashMap<String,String> headerMap = getHeaderMap(paramMap);
String result = HttpUtil.doPost(url,headerMap,paramMap).getContent();
JSONObject jsonObject = JSONUtil.parseObj(result);
//如果返回成功,则解析数据
if(jsonObject.getInt("code") == 200){
JSONArray jsonArray = jsonObject.getJSONArray("data");
if(!CollectionUtils.isEmpty(jsonArray)){
return JSONUtil.toList(jsonObject.getJSONArray("data"),OrganizationResponse.class);
}
return new ArrayList<>();
} else {
throw new RuntimeException("组织查询出错");
}
}
将组织查询完毕之后开始查询组织下的人人员信息
public List<UserInfoResponse> getAllUserByOrg(String organizationCode){
List<UserInfoResponse> result = new ArrayList<UserInfoResponse>();
int pageNo = 1;
//该接口最多查100个人信息
int pageSize = 100;
while (true) {
final List<UserInfoResponse> userList = getUserInfo(organizationCode, pageNo, pageSize);
if (CollectionUtils.isNotEmpty(userList)) {
result.addAll(userList);
pageNo++;
}
if (CollectionUtils.isEmpty(userList) || userList.size() < pageSize) {
break;
}
}
return result;
}
public List<UserInfoResponse> getUserInfo(String organizationCode,int pageNo,int pageSize){
String url = baseUrl+"/api/v1/getUserList";
LinkedHashMap<String,Object> paramMap = new LinkedHashMap<>();
paramMap.put("organizationCode",organizationCode);
paramMap.put("pageNo",pageNo);
paramMap.put("pageSize",pageSize);
LinkedHashMap<String,String> headerMap = getHeaderMap(paramMap);
String result = HttpUtil.doPost(url,headerMap,paramMap).getContent();
JSONObject jsonObject = JSONUtil.parseObj(result);
if(jsonObject.getInt("code") == 200){
JSONArray jsonArray = jsonObject.getJSONObject("data").getJSONArray("list");
return JSONUtil.toList(jsonArray,UserInfoResponse.class);
}else {
throw new RuntimeException("用户查询出错");
}
}
}
接口重试
接口已经测试通过,开始拉取信息,因为数据量比较大,且我们一次只能查询一个部门,导致需要不间断查询。不知道是对方是不是限流了,还是接口存在问题,查询大概十分钟左右,就会报connection timeout
拒绝连接了。刚开始我认为应该是对方限流了,然后我想放慢速度查询,每次请求前停0.5s。
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
虽然解决了问题,但是查了一段时间,实在太慢了。不能用这么蠢的解决方法。后来想到可以用重试的方法来解决。所以加入了guava-retrying
重试。
引入jar包
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
全局定制重试策略
@Configuration
@Slf4j
public class RetryerUtils {
@Bean
public Retryer<String> retryer(){
return RetryerBuilder.<String>newBuilder()
.retryIfExceptionOfType(Exception.class)
/**
* retryIfException: retryIfException,抛出 runtime 异常、checked 异常时都会重试,但是抛出 error 不会重试。
* retryIfRuntimeException: retryIfRuntimeException 只会在抛 runtime 异常的时候才重试,checked 异常和error 都不重试。
* retryIfExceptionOfType: retryIfExceptionOfType 允许我们只在发生特定异常的时候才重试,比如NullPointerException 和 IllegalStateException 都属于 runtime 异常,也包括自定义的error。
*/
//.retryIfRuntimeException()
.withWaitStrategy(WaitStrategies.incrementingWait(2, TimeUnit.SECONDS, 2, TimeUnit.SECONDS))
.withStopStrategy(StopStrategies.stopAfterAttempt(5))
.withRetryListener(new RetryListener() {
@Override
public <V> void onRetry(Attempt<V> attempt) {
log.info("当前是第 {} 次重试",attempt.getAttemptNumber());
}
})
.build();
}
}
请求接口加入重试
@Autowired
private Retryer<String> retryer;
String result = retryer.call(()-> HttpUtil.doPost(url,headerMap,paramMap).getContent());
加入重试之后,问题也能解决,查看了一下日志,大概10-20分钟,会重试2次左右,问题初步解决。
多线程请求
虽然接口能正常请求了,但是速度还是太慢了,这样跑一次完全的数据,还要70-80分钟,跑过一次,不能忍受。想到用多线程解决。
//用于计次
AtomicInteger atomicInteger = new AtomicInteger(1);
private List<UserInfoResponse> getUserByOrg(List<OrganizationResponse> orgList){
List<UserInfoResponse> allUserList = new ArrayList<>();
if(!CollectionUtils.isEmpty(orgList)){
for(OrganizationResponse org:orgList){
log.info("根据部门查询用户信息,目前查询次数 : {}",atomicInteger.getAndIncrement());
List<UserInfoResponse> allUserByOrg = restClient.getAllUserByOrg(org.getOrganizationCode());
allUserList.addAll(allUserByOrg);
}
}
return allUserList;
}
public void getAllUser() throws InterruptedException {
List<UserInfoResponse> allUser = new ArrayList<>();
//启用6个线程跑
CountDownLatch countDownLatch = new CountDownLatch(6);
List<OrganizationResponse> orgList = getAllOrganization();
List<List<OrganizationResponse>> spliceList = ListUtil.splitAvg(orgList,6);
for(int i=0;i<6;i++){
int finalI = i;
new Thread(()->{
List<UserInfoResponse> userByOrg = getUserByOrg(spliceList.get(finalI));
allUser.addAll(userByOrg);
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
//数据处理
}
多线程处理之后,只需要7-8分钟左右,速度提升还是很明显的。