Java调用MAC地址查询API的GET请求结果不稳定问题求助
MAC地址厂商查询API调用不稳定问题排查与解决
我开发了一款本地网络设备分析程序,核心功能之一是通过MAC地址调用macvendors.com的API获取设备厂商信息。读取MAC地址的逻辑正常,但批量循环调用API时结果不稳定,有时能返回正确厂商信息,有时返回“VENDOR NOT FOUND”。延长超时时间、添加请求参数都没用,但调试模式下每次都能成功。
相关代码
API调用方法
public static String getVendor(String macAddress) { String dataUrl = "https://api.macvendors.com/" + macAddress; HttpURLConnection connection = null; try { URL url = new URL(dataUrl); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setConnectTimeout(10000); connection.setReadTimeout(10000); if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) { BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); String inputLine; StringBuilder response = new StringBuilder(); while ((inputLine = in.readLine()) != null) { response.append(inputLine); } in.close(); return response.toString(); } } catch (Exception e) { e.printStackTrace(); } finally { if (connection != null) { connection.disconnect(); } } return "VENDOR NOT FOUND"; }
循环调用逻辑
public static void scanIP(InetAddress ip, NetworkInterface ni) { ConcurrentSkipListSet networkIps = scan(ip.toString().substring(1), 254); System.out.println("Devices connected to the network:"); networkIps.forEach(deviceIP -> { try { System.out.println(deviceIP); String MACaddress = getMacAddrHost(InetAddress.getByName((String)deviceIP)); System.out.println(MACaddress); System.out.println(getVendor(MACaddress)); System.out.println(); } catch (IOException | InterruptedException e) { throw new RuntimeException(e); } }); }
问题根源
调试模式正常但批量运行失败,核心原因是macvendors.com的API存在请求频率限制:
- 调试时单步执行,每个API请求间隔足够长,不会触发限流机制;
- 批量循环调用时,请求发送过于密集,API服务器返回非200状态码(比如429 Too Many Requests),代码直接返回“VENDOR NOT FOUND”,未处理这类限流响应。
解决方案
1. 添加请求间隔
在循环调用getVendor后添加短暂休眠,避免请求过于密集:
networkIps.forEach(deviceIP -> { try { System.out.println(deviceIP); String MACaddress = getMacAddrHost(InetAddress.getByName((String)deviceIP)); System.out.println(MACaddress); System.out.println(getVendor(MACaddress)); System.out.println(); // 添加1秒间隔,可根据API限制调整时长 Thread.sleep(1000); } catch (IOException | InterruptedException e) { throw new RuntimeException(e); } });
2. 实现重试机制
遇到非200响应时,重试几次再返回失败,处理临时限流或网络波动:
public static String getVendor(String macAddress) { int retryCount = 3; // 重试次数 long retryDelay = 1000; // 初始重试间隔(毫秒) String dataUrl = "https://api.macvendors.com/" + macAddress; for (int i = 0; i < retryCount; i++) { HttpURLConnection connection = null; try { URL url = new URL(dataUrl); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setConnectTimeout(10000); connection.setReadTimeout(10000); int responseCode = connection.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK) { BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); String inputLine; StringBuilder response = new StringBuilder(); while ((inputLine = in.readLine()) != null) { response.append(inputLine); } in.close(); return response.toString(); } else if (responseCode == 429) { // 触发限流,指数退避等待后重试 Thread.sleep(retryDelay); retryDelay *= 2; } else { // 其他非200状态,等待后重试 Thread.sleep(retryDelay); } } catch (Exception e) { e.printStackTrace(); try { Thread.sleep(retryDelay); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } } finally { if (connection != null) { connection.disconnect(); } } } return "VENDOR NOT FOUND"; }
3. 缓存查询结果
同一个MAC地址无需重复查询,用ConcurrentHashMap缓存结果,减少API调用次数:
private static final Map<String, String> MAC_VENDOR_CACHE = new ConcurrentHashMap<>(); public static String getVendor(String macAddress) { // 先从缓存获取,避免重复调用API if (MAC_VENDOR_CACHE.containsKey(macAddress)) { return MAC_VENDOR_CACHE.get(macAddress); } int retryCount = 3; long retryDelay = 1000; String dataUrl = "https://api.macvendors.com/" + macAddress; for (int i = 0; i < retryCount; i++) { HttpURLConnection connection = null; try { URL url = new URL(dataUrl); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setConnectTimeout(10000); connection.setReadTimeout(10000); int responseCode = connection.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK) { BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); String inputLine; StringBuilder response = new StringBuilder(); while ((inputLine = in.readLine()) != null) { response.append(inputLine); } in.close(); String vendor = response.toString(); MAC_VENDOR_CACHE.put(macAddress, vendor); // 存入缓存 return vendor; } else if (responseCode == 429) { Thread.sleep(retryDelay); retryDelay *= 2; } else { Thread.sleep(retryDelay); } } catch (Exception e) { e.printStackTrace(); try { Thread.sleep(retryDelay); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } } finally { if (connection != null) { connection.disconnect(); } } } MAC_VENDOR_CACHE.put(macAddress, "VENDOR NOT FOUND"); return "VENDOR NOT FOUND"; }
4. 复用HTTP连接池
HttpURLConnection默认支持连接复用,手动调用disconnect()会关闭连接,可去掉该语句让连接池复用连接;也可以替换为OkHttp等更高效的HTTP客户端,自动管理连接池。
内容的提问来源于stack exchange,提问作者Alexei Bobrik
相关产品推荐
相关产品推荐

