碰到问题
APNS推送的,用okhttp3(3.5.0)做APNS推送,发现APNS服务器的连接都连在苹果推送服务器的一个IP上,而且超时很多,另外链接发送的请求分配也很不均匀,国内到国外的网速也不稳定,几毫秒到几十秒都有。
主要方案:
1.租用云的VPC对等链接服务,加快每次调用时间
2.调用软件优化
苹果服务器給的性能指导里提到了,建多连接到不同的服务器节点。
Best Practices for Managing Connections
You can establish multiple connections to APNs servers to improve performance. When you send a large number of remote notifications, distribute them across connections to several server endpoints. This improves performance
现在就是要如何把请求更均匀的分配到每个链接,每个链接的请求并发请求数减少,实现链接尽量连向跟多多服务器。
OKHttp3框架分析
okhttp3的拦截器
ConnectionPool connectionPool=new ConnectionPool(50, 10, TimeUnit.MINUTES); OkHttpClient.Builder builder = new OkHttpClient .Builder() .addNetworkInterceptor(new ApnsNetworkInterceptor()) .connectionPool(connectionPool) .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .readTimeout(20,TimeUnit.SECONDS);
OkHttpClient.Builder builder给了2个接口
addInterceptor:应用的拦截,不能再处理链接相关的信息,已经去不到连接
addNetworkInterceptor:网络处理相关的连接器,可以处理connection
OKhttp内部拦截器链初始化:
RetryAndFollowUpInterceptor —>创建StreamAllocation对象,处理http的redirect,出错重试。对后续Interceptor的执行的影响:修改request及StreamAllocation。
BridgeInterceptor————–>补全缺失的一些http header。对后续Interceptor的执行的影响:修改request。
CacheInterceptor————–>处理http缓存。对后续Interceptor的执行的影响:若缓存中有所需请求的响应,则后续Interceptor不再执行。
ConnectInterceptor————>借助于前面分配的StreamAllocation对象建立与服务器之间的连接,并选定交互所用的协议是HTTP 1.1还是HTTP 2。对后续Interceptor的执行的影响:创建了httpStream和connection。
CallServerInterceptor———–>处理IO,与服务器进行数据交换。对后续Interceptor的执行的影响:为Interceptor链中的最后一个Interceptor,没有后续Interceptor。
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
链接池获取链接
/** Returns a recycled connection to {@code address}, or null if no such connection exists. */ RealConnection get(Address address, StreamAllocation streamAllocation) { assert (Thread.holdsLock(this)); for (RealConnection connection : connections) { if (connection.allocations.size() < connection.allocationLimit && address.equals(connection.route().address) && !connection.noNewStreams) { streamAllocation.acquire(connection); return connection; } } return null; }
拦截器实现
主要就是设置allocationLimit、让差的链接下次不会被使用。网络请求差距太大也没有好方法,而且OkHttp的扩展感觉不是很好,人笨没找到更好的方法。
/** * APNS推送的拦截器, * 1.设置链接最大并发请求参数 * 2.超时统计并关闭链接,根据平均值统计(1.超过花消阀值,2.接近最大平均时间的) * * @author shenwr * @version V1.0 * @since 2016-12-13 16:55 */ public class ApnsNetworkInterceptor implements Interceptor { public static final double took_time_precent = 0.9; private static Logger logger = LoggerFactory.getLogger(ApnsNetworkInterceptor.class); //花费时间阀值 private final long time_took_throttle = 20000; //花费时间阀值(最低阀值) private final long time_took_throttle_min = 5000; //一个链接到并发请求上限 private final int allocation_limit = 60; //统计次数 private final int stat_count = 20; //统计服务器数上限 private final int stat_server_limit = 100; //动态统计次数 private final int dynamic_stat_count = 1000; private ConcurrentHashMap<String, ServerPointStat> pointStat = new ConcurrentHashMap<String, ServerPointStat>( stat_server_limit); //最大平均花费时间 private long maxAvgTookTime = 0; //总调用次数 private AtomicLong invokeTotalCount = new AtomicLong(0); public ApnsNetworkInterceptor() { //启动清除任务 Timer timer = new Timer(); timer.schedule(new TimerTask() { public void run() { //10分钟内没有统计信息的,清楚统计 long expireTime = System.currentTimeMillis() - 600000; for (Map.Entry<String, ServerPointStat> stat : pointStat.entrySet()) { if (stat.getValue().getLastUsedTime() < expireTime) { pointStat.remove(stat.getKey()); } } } }, 60000, 60000); } @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); //设置一个链接的并发请求数上限 Connection connection = chain.connection(); RealConnection rc = null; if (connection != null && connection instanceof RealConnection) { rc = (RealConnection) connection; //只会对后面链接池里获取链接有效 rc.allocationLimit = allocation_limit; } long startNs = System.nanoTime(); Response response=null; try { //处理请求 response = chain.proceed(request); }catch (Exception e ){ logger.error("exception happened",e); throw e; }finally { long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs); try { if (rc == null) { return response; } //统计计数 String ip = rc.socket.getInetAddress().getHostAddress(); ServerPointStat stat = pointStat.get(ip); if (stat == null) { if (pointStat.size() > stat_server_limit) { //不再统计,直接返回 return response; } stat = new ServerPointStat(); pointStat.put(ip,stat); } //调用统计 long totalCount = invokeTotalCount.incrementAndGet(); if (totalCount < 0) { //溢出重置 invokeTotalCount.set(0); } //统计 stat.invokeStat(tookMs); //质量差的服务节点处理 long avgTime = stat.getAvgTime(); if (stat.getUseCount() > stat_count && avgTime > time_took_throttle && tookMs > avgTime) { //统计次数到了,平均花费时间超过阀值,认定为不好的服务器节点 //noNewStreams(chain); rc.allocationLimit = 0; } else if (maxAvgTookTime < time_took_throttle) { //如果请求处理时间都没有超过阀值 if (totalCount > dynamic_stat_count && avgTime > time_took_throttle_min && tookMs > avgTime && avgTime > Math.round(maxAvgTookTime * took_time_precent)) { //如果都没有超过超时阀值,并且接近平均最大时间,任务是不好的链接 //noNewStreams(chain); rc.allocationLimit = 0; } if (avgTime > maxAvgTookTime) { maxAvgTookTime = avgTime; } } } catch (Exception e) { logger.error("统计发送异常", e); } //返回请求结果 return response; } } private void noNewStreams(Chain chain) { /**这样处理像是有问题 if (chain instanceof RealInterceptorChain) { StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation(); if (streamAllocation.hasMoreRoutes()) { //该请求还有其他服务节点的时候,禁用用这个链接发送请求,等释放 streamAllocation.noNewStreams(); } } */ } private static class ServerPointStat { private long lastUsedTime; private AtomicLong useCount = new AtomicLong(0); private AtomicLong tookTotalTime = new AtomicLong(0); public void invokeStat(long tookTime) { tookTotalTime.addAndGet(tookTime); useCount.incrementAndGet(); lastUsedTime = System.currentTimeMillis(); if (tookTotalTime.get() < 0) { //溢出重置 tookTotalTime.set(0); useCount.set(0); } } public long getAvgTime() { return tookTotalTime.get() / useCount.get(); } /** * 获取 {@link #lastUsedTime} * * @return 返回 {@link #lastUsedTime} */ public long getLastUsedTime() { return lastUsedTime; } /** * 获取 {@link #useCount} * * @return 返回 {@link #useCount} */ public long getUseCount() { return useCount.get(); } /* * 获取 {@link #tookTotalTime} * * @return 返回 {@link #tookTotalTime} */ public long getTookTotalTime() { return tookTotalTime.get(); } } }
拦截器的调用关系
参考:http://w4lle.github.io/2016/12/06/OkHttp/