记一次调用第三方接口历程

61 阅读3分钟

这周接到一个需求,就是从某统一服务中心中拉取部门、用户信息到本地。其中获取部门信息是一个接口、根据部门拉取该部门下的用户信息是一个接口。中间遇到不少问题,记录一下解决历程。

接口开发

根据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分钟左右,速度提升还是很明显的。