OKHTTP3 APNS推送时通过拦截器修改一些优化连接

碰到问题

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/

转载于:https://my.oschina.net/greki/blog/810108

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值