高并发场景下的 HttpClient 优化方案,QPS 大大提升!

作者:仰望星空的尘埃

来源:blog.csdn.net/u010285974/article/details/85696239

HttpClient优化思路:

  1. 池化

  2. 长连接

  3. httpclient和httpget复用

  4. 合理的配置参数(最大并发请求数,各种超时时间,重试次数)

  5. 异步

  6. 多读源码


9584b6d0b24a60a0b34133fcb0f07f79.png

1.背景

8946ffd2444935ce0a74a94fe3edaca5.png

我们有个业务,会调用其他部门提供的一个基于http的服务,日调用量在千万级别。使用了httpclient来完成业务。之前因为qps上不去,就看了一下业务代码,并做了一些优化,记录在这里。

先对比前后:优化之前,平均执行时间是250ms;

优化之后,平均执行时间是80ms,降低了三分之二的消耗,容器不再动不动就报警线程耗尽了,清爽~


f57765ae80eeeb8d8d7a9eb202913329.png

2.分析

a317b6e1410d848703b607fdd983870c.png

项目的原实现比较粗略,就是每次请求时初始化一个httpclient,生成一个httpPost对象,执行,然后从返回结果取出entity,保存成一个字符串,最后显式关闭response和client。

我们一点点分析和优化:

2.1 httpclient反复创建开销

httpclient是一个线程安全的类,没有必要由每个线程在每次使用时创建,全局保留一个即可。

2.2 反复创建tcp连接的开销

tcp的三次握手与四次挥手两大裹脚布过程,对于高频次的请求来说,消耗实在太大。试想如果每次请求我们需要花费5ms用于协商过程,那么对于qps为100的单系统,1秒钟我们就要花500ms用于握手和挥手。又不是高级领导,我们程序员就不要搞这么大做派了,改成keep alive方式以实现连接复用!

2.3 重复缓存entity的开销

原本的逻辑里,使用了如下代码:

HttpEntity entity = httpResponse.getEntity();

String response = EntityUtils.toString(entity);

这里我们相当于额外复制了一份content到一个字符串里,而原本的httpResponse仍然保留了一份content,需要被consume掉,在高并发且content非常大的情况下,会消耗大量内存。并且,我们需要显式的关闭连接,ugly。


6fd7933c7d0506036b99b7181d706e9d.png

3.实现

f19c3637c112248171482166c728ef75.png

按上面的分析,我们主要要做三件事:一是单例的client,二是缓存的保活连接,三是更好的处理返回结果。一就不说了,来说说二。

提到连接缓存,很容易联想到数据库连接池。httpclient4提供了一个PoolingHttpClientConnectionManager 作为连接池。接下来我们通过以下步骤来优化:

3.1 定义一个keep alive strategy

关于keep-alive,本文不展开说明,只提一点,是否使用keep-alive要根据业务情况来定,它并不是灵丹妙药。还有一点,keep-alive和time_wait/close_wait之间也有不少故事。

在本业务场景里,我们相当于有少数固定客户端,长时间极高频次的访问服务器,启用keep-alive非常合适

再多提一嘴,http的keep-alive 和tcp的KEEPALIVE不是一个东西。回到正文,定义一个strategy如下:

ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {
    @Override
    public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
        HeaderElementIterator it = new BasicHeaderElementIterator
            (response.headerIterator(HTTP.CONN_KEEP_ALIVE));
        while (it.hasNext()) {
            HeaderElement he = it.nextElement();
            String param = he.getName();
            String value = he.getValue();
            if (value != null && param.equalsIgnoreCase
               ("timeout")) {
                return Long.parseLong(value) * 1000;
            }
        }
        return 60 * 1000;//如果没有约定,则默认定义时长为60s
    }
};
3.2 配置一个PoolingHttpClientConnectionManager
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(500);
connectionManager.setDefaultMaxPerRoute(50);//例如默认每路由最高50并发,具体依据业务来定

也可以针对每个路由设置并发数。

3.3 生成httpclient
httpClient = HttpClients.custom()
     .setConnectionManager(connectionManager)
     .setKeepAliveStrategy(kaStrategy)
     .setDefaultRequestConfig(RequestConfig.custom().setStaleConnectionCheckEnabled(true).build())
     .build();

注意:使用setStaleConnectionCheckEnabled方法来逐出已被关闭的链接不被推荐。更好的方式是手动启用一个线程,定时运行closeExpiredConnections 和closeIdleConnections方法,如下所示。

public static class IdleConnectionMonitorThread extends Thread {
    
    private final HttpClientConnectionManager connMgr;
    private volatile boolean shutdown;
    
    public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) {
        super();
        this.connMgr = connMgr;
    }
 
    @Override
    public void run() {
        try {
            while (!shutdown) {
                synchronized (this) {
                    wait(5000);
                    // Close expired connections
                    connMgr.closeExpiredConnections();
                    // Optionally, close connections
                    // that have been idle longer than 30 sec
                    connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
                }
            }
        } catch (InterruptedException ex) {
            // terminate
        }
    }
    
    public void shutdown() {
        shutdown = true;
        synchronized (this) {
            notifyAll();
        }
    }
    
}
3.4 使用httpclient执行method时降低开销

这里要注意的是,不要关闭connection。

一种可行的获取内容的方式类似于,把entity里的东西复制一份:

res = EntityUtils.toString(response.getEntity(),"UTF-8");
EntityUtils.consume(response1.getEntity());

但是,更推荐的方式是定义一个ResponseHandler,方便你我他,不再自己catch异常和关闭流。在此我们可以看一下相关的源码:

public <T> T execute(final HttpHost target, final HttpRequest request,
        final ResponseHandler<? extends T> responseHandler, final HttpContext context)
        throws IOException, ClientProtocolException {
    Args.notNull(responseHandler, "Response handler");

    final HttpResponse response = execute(target, request, context);

    final T result;
    try {
        result = responseHandler.handleResponse(response);
    } catch (final Exception t) {
        final HttpEntity entity = response.getEntity();
        try {
            EntityUtils.consume(entity);
        } catch (final Exception t2) {
            // Log this exception. The original exception is more
            // important and will be thrown to the caller.
            this.log.warn("Error consuming content after an exception.", t2);
        }
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        }
        if (t instanceof IOException) {
            throw (IOException) t;
        }
        throw new UndeclaredThrowableException(t);
    }

    // Handling the response was successful. Ensure that the content has
    // been fully consumed.
    final HttpEntity entity = response.getEntity();
    EntityUtils.consume(entity);//看这里看这里
    return result;
}

可以看到,如果我们使用resultHandler执行execute方法,会最终自动调用consume方法,而这个consume方法如下所示:

public static void consume(final HttpEntity entity) throws IOException {
    if (entity == null) {
        return;
    }
    if (entity.isStreaming()) {
        final InputStream instream = entity.getContent();
        if (instream != null) {
            instream.close();
        }
    }
}

可以看到最终它关闭了输入流。


2963fbd421d6ca5a099ffcad0de711a9.png

4.其他

3a7dddbb66eaea1454263f39e2377cc0.png

通过以上步骤,基本就完成了一个支持高并发的httpclient的写法,下面是一些额外的配置和提醒:

4.1 httpclient的一些超时配置

CONNECTION_TIMEOUT是连接超时时间,SO_TIMEOUT是socket超时时间,这两者是不同的。连接超时时间是发起请求前的等待时间;socket超时时间是等待数据的超时时间。

HttpParams params = new BasicHttpParams();
//设置连接超时时间
Integer CONNECTION_TIMEOUT = 2 * 1000; //设置请求超时2秒钟 根据业务调整
Integer SO_TIMEOUT = 2 * 1000; //设置等待数据超时时间2秒钟 根据业务调整
 
//定义了当从ClientConnectionManager中检索ManagedClientConnection实例时使用的毫秒级的超时时间
//这个参数期望得到一个java.lang.Long类型的值。如果这个参数没有被设置,默认等于CONNECTION_TIMEOUT,因此一定要设置。
Long CONN_MANAGER_TIMEOUT = 500L; //在httpclient4.2.3中我记得它被改成了一个对象导致直接用long会报错,后来又改回来了
 
params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, CONNECTION_TIMEOUT);
params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, SO_TIMEOUT);
params.setLongParameter(ClientPNames.CONN_MANAGER_TIMEOUT, CONN_MANAGER_TIMEOUT);
//在提交请求之前 测试连接是否可用
params.setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, true);
 
//另外设置http client的重试次数,默认是3次;当前是禁用掉(如果项目量不到,这个默认即可)
httpClient.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(0, false));
4.2 如果配置了nginx的话,nginx也要设置面向两端的keep-alive

现在的业务里,没有nginx的情况反而比较稀少。nginx默认和client端打开长连接而和server端使用短链接。

注意client端的keepalive_timeout和keepalive_requests参数,以及upstream端的keepalive参数设置,这三个参数的意义在此也不再赘述。

以上就是我的全部设置。通过这些设置,成功地将原本每次请求250ms的耗时降低到了80左右,效果显著。

JAR包如下:

<!-- httpclient -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.6</version>
</dependency>

代码如下:

//Basic认证
private static final CredentialsProvider credsProvider = new BasicCredentialsProvider();
//httpClient
private static final CloseableHttpClient httpclient;
//httpGet方法
private static final HttpGet httpget;
//
private static final RequestConfig reqestConfig;
//响应处理器
private static final ResponseHandler<String> responseHandler;
//jackson解析工具
private static final ObjectMapper mapper = new ObjectMapper();
static {
    System.setProperty("http.maxConnections","50");
    System.setProperty("http.keepAlive", "true");
    //设置basic校验
    credsProvider.setCredentials(
            new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM),
            new UsernamePasswordCredentials("", ""));
    //创建http客户端
    httpclient = HttpClients.custom()
            .useSystemProperties()
            .setRetryHandler(new DefaultHttpRequestRetryHandler(3,true))
            .setDefaultCredentialsProvider(credsProvider)
            .build();
    //初始化httpGet
    httpget = new HttpGet();
    //初始化HTTP请求配置
    reqestConfig = RequestConfig.custom()
            .setContentCompressionEnabled(true)
            .setSocketTimeout(100)
            .setAuthenticationEnabled(true)
            .setConnectionRequestTimeout(100)
            .setConnectTimeout(100).build();
    httpget.setConfig(reqestConfig);
    //初始化response解析器
    responseHandler = new BasicResponseHandler();
}
/*
 * 功能:返回响应
 * @author zhangdaquan
 * @param [url]
 * @return org.apache.http.client.methods.CloseableHttpResponse
 * @exception
 */
public static String getResponse(String url) throws IOException {
    HttpGet get = new HttpGet(url);
    String response = httpclient.execute(get,responseHandler);
    return response;
}
 
/*
 * 功能:发送http请求,并用net.sf.json工具解析
 * @author zhangdaquan
 * @param [url]
 * @return org.json.JSONObject
 * @exception
 */
public static JSONObject getUrl(String url) throws Exception{
    try {
        httpget.setURI(URI.create(url));
        String response = httpclient.execute(httpget,responseHandler);
        JSONObject json = JSONObject.fromObject(response);
        return json;
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}
/*
 * 功能:发送http请求,并用jackson工具解析
 * @author zhangdaquan
 * @param [url]
 * @return com.fasterxml.jackson.databind.JsonNode
 * @exception
 */
public static JsonNode getUrl2(String url){
    try {
        httpget.setURI(URI.create(url));
        String response = httpclient.execute(httpget,responseHandler);
        JsonNode node = mapper.readTree(response);
        return node;
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}
/*
 * 功能:发送http请求,并用fastjson工具解析
 * @author zhangdaquan
 * @param [url]
 * @return com.fasterxml.jackson.databind.JsonNode
 * @exception
 */
public static com.alibaba.fastjson.JSONObject getUrl3(String url){
    try {
        httpget.setURI(URI.create(url));
        String response = httpclient.execute(httpget,responseHandler);
        com.alibaba.fastjson.JSONObject jsonObject = com.alibaba.fastjson.JSONObject.parseObject(response);
        return jsonObject;
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第1部分概述 1 1 交易型系统设计的一些原则 2 1.1 高并发原则 3 1.1.1 无状态 3 1.1.2 拆分 3 1.1.3 服务化 4 1.1.4 消息队列 4 1.1.5 数据异构 6 1.1.6 缓存银弹 7 1.1.7 并发化 9 1.2 高可用原则 10 1.2.1 降级 10 1.2.2 限流 11 1.2.3 切流量 12 1.2.4 可回滚 12 1.3 业务设计原则 12 1.3.1 防重设计 13 1.3.2 幂等设计 13 1.3.3 流程可定义 13 1.3.4 状态与状态机 13 1.3.5 后台系统操作可反馈 14 1.3.6 后台系统审批化 14 1.3.7 文档和注释 14 1.3.8 备份 14 1.4 总结 14 第2部分高可用 17 2 负载均衡与反向代理 18 2.1 upstream配置 20 2.2 负载均衡算法 21 2.3 失败重试 23 2.4 健康检查 24 2.4.1 TCP心跳检查 24 2.4.2 HTTP心跳检查 25 2.5 其他配置 25 2.5.1 域名上游服务器 25 2.5.2 备份上游服务器 26 2.5.3 不可用上游服务器 26 2.6 长连接 26 2.7 HTTP反向代理示例 29 2.8 HTTP动态负载均衡 30 2.8.1 Consul+Consul-template 31 2.8.2 Consul+OpenResty 35 2.9 Nginx四层负载均衡 39 2.9.1 静态负载均衡 39 2.9.2 动态负载均衡 41 参考资料 42 3 隔离术 43 3.1 线程隔离 43 3.2 进程隔离 45 3.3 集群隔离 45 3.4 机房隔离 46 3.5 读写隔离 47 3.6 动静隔离 48 3.7 爬虫隔离 49 3.8 热点隔离 50 3.9 资源隔离 50 3.10 使用Hystrix实现隔离 51 3.10.1 Hystrix简介 51 3.10.2 隔离示例 52 3.11 基于Servlet 3实现请求隔离 56 3.11.1 请求解析和业务处理线程池分离 57 3.11.2 业务线程池隔离 58 3.11.3 业务线程池监控/运维/降级 58 3.11.4 如何使用Servlet 3异步化 59 3.11.5 一些Servlet 3异步化压测数据 64 4 限流详解 66 4.1 限流算法 67 4.1.1 令牌桶算法 67 4.1.2 漏桶算法 68 4.2 应用级限流 69 4.2.1 限流总并发/连接/请求数 69 4.2.2 限流总资源数 70 4.2.3 限流某个接口的总并发/请求数 70 4.2.4 限流某个接口的时间窗请求数 70 4.2.5 平滑限流某个接口的请求数 71 4.3 分布式限流 75 4.3.1 Redis+Lua实现 76 4.3.2 Nginx+Lua实现 77 4.4 接入层限流 78 4.4.1 ngx_http_limit_conn_module 78 4.4.2 ngx_http_limit_req_module 80 4.4.3 lua-resty-limit-traffic 88 4.5 节流 90 4.5.1 throttleFirst/throttleLast 90 4.5.2 throttleWithTimeout 91 参考资料 92 5 降级特技 93 5.1 降级预案 93 5.2 自动开关降级 95 5.2.1 超时降级 95 5.2.2 统计失败次数降级 95 5.2.3 故障降级 95 5.2.4 限流降级 95 5.3 人工开关降级 96 5.4 读服务降级 96 5.5 写服务降级 97 5.6 多级降级 98 5.7 配置中心 100 5.7.1 应用层API封装 100 5.7.2 配置文件实现开关配置 101 5.7.3 配置中心实现开关配置 102 5.8 使用Hystrix实现降级 106 5.9 使用Hystrix实现熔断 108 5.9.1 熔断机制实现 108 5.9.2 配置示例 112 5.9.3 采样统计 113 6 超时与重试机制 117 6.1 简介 117 6.2 代理层超时与重试 119 6.2.1 Nginx 119 6.2.2 Twemproxy 126 6.3 Web容器超时 127 6.4 中间件客户端超时与重试 127 6.5 数据库客户端超时 131 6.6 NoSQL客户端超时 134 6.7 业务超时 135 6.8 前端Ajax超时 135 6.9 总结 136 6.10 参考资料 137 7 回滚机制 139 7.1 事务回滚 139 7.2 代码库回滚 140 7.3 部署版本回滚 141 7.4 数据版本回滚 142 7.5 静态资源版本回滚 143 8 压测与预案 145 8.1 系统压测 145 8.1.1 线下压测 146 8.1.2 线上压测 146 8.2 系统优化和容灾 147 8.3 应急预案 148 第3部分高并发 153 9 应用级缓存 154 9.1 缓存简介 154 9.2 缓存命中率 155 9.3 缓存回收策略 155 9.3.1 基于空间 155 9.3.2 基于容量 155 9.3.3 基于时间 155 9.3.4 基于Java对象引用 156 9.3.5 回收算法 156 9.4 Java缓存类型 156 9.4.1 堆缓存 158 9.4.2 堆外缓存 162 9.4.3 磁盘缓存 162 9.4.4 分布式缓存 164 9.4.5 多级缓存 166 9.5 应用级缓存示例 167 9.5.1 多级缓存API封装 167 9.5.2 NULL Cache 170 9.5.3 强制获取最新数据 170 9.5.4 失败统计 171 9.5.5 延迟报警 171 9.6 缓存使用模式实践 172 9.6.1 Cache-Aside 173 9.6.2 Cache-As-SoR 174 9.6.3 Read-Through 174 9.6.4 Write-Through 176 9.6.5 Write-Behind 177 9.6.6 Copy Pattern 181 9.7 性能测试 181 9.8 参考资料 182 10 HTTP缓存 183 10.1 简介 183 10.2 HTTP缓存 184 10.2.1 Last-Modified 184 10.2.2 ETag 190 10.2.3 总结 192 10.3 HttpClient客户端缓存 192 10.3.1 主流程 195 10.3.2 清除无效缓存 195 10.3.3 查找缓存 196 10.3.4 缓存未命中 198 10.3.5 缓存命中 198 10.3.6 缓存内容陈旧需重新验证 202 10.3.7 缓存内容无效需重新执行请求 205 10.3.8 缓存响应 206 10.3.9 缓存头总结 207 10.4 Nginx HTTP缓存设置 208 10.4.1 expires 208 10.4.2 if-modified-since 209 10.4.3 nginx proxy_pass 209 10.5 Nginx代理层缓存 212 10.5.1 Nginx代理层缓存配置 212 10.5.2 清理缓存 215 10.6 一些经验 216 参考资料 217 11 多级缓存 218 11.1 多级缓存介绍 218 11.2 如何缓存数据 220 11.2.1 过期与不过期 220 11.2.2 维度化缓存与增量缓存 221 11.2.3 大Value缓存 221 11.2.4 热点缓存 221 11.3 分布式缓存与应用负载均衡 222 11.3.1 缓存分布式 222 11.3.2 应用负载均衡 222 11.4 热点数据与更新缓存 223 11.4.1 单机全量缓存+主从 223 11.4.2 分布式缓存+应用本地热点 224 11.5 更新缓存与原子性 225 11.6 缓存崩溃与快速修复 226 11.6.1 取模 226 11.6.2 一致性哈希 226 11.6.3 快速恢复 226 12 连接池线程池详解 227 12.1 数据库连接池 227 12.1.1 DBCP连接池配置 228 12.1.2 DBCP配置建议 233 12.1.3 数据库驱动超时实现 234 12.1.4 连接池使用的一些建议 235 12.2 HttpClient连接池 236 12.2.1 HttpClient 4.5.2配置 236 12.2.2 HttpClient连接池源码分析 240 12.2.3 HttpClient 4.2.3配置 241 12.2.4 问题示例 243 12.3 线程池 244 12.3.1 Java线程池 245 12.3.2 Tomcat线程池配置 248 13 异步并发实战 250 13.1 同步阻塞调用 251 13.2 异步Future 252 13.3 异步Callback 253 13.4 异步编排CompletableFuture 254 13.5 异步Web服务实现 257 13.6 请求缓存 259 13.7 请求合并 261 14 如何扩容 266 14.1 单体应用垂直扩容 267 14.2 单体应用水平扩容 267 14.3 应用拆分 268 14.4 数据库拆分 271 14.5 数据库分库分表示例 275 14.5.1 应用层还是中间件层 275 14.5.2 分库分表策略 277 14.5.3 使用sharding-jdbc分库分表 279 14.5.4 sharding-jdbc分库分表配置 279 14.5.5 使用sharding-jdbc读写分离 283 14.6 数据异构 284 14.6.1 查询维度异构 284 14.6.2 聚合数据异构 285 14.7 任务系统扩容 285 14.7.1 简单任务 285 14.7.2 分布式任务 287 14.7.3 Elastic-Job简介 287 14.7.4 Elastic-Job-Lite功能与架构 287 14.7.5 Elastic-Job-Lite示例 288 15 队列术 295 15.1 应用场景 295 15.2 缓冲队列 296 15.3 任务队列 297 15.4 消息队列 297 15.5 请求队列 299 15.6 数据总线队列 300 15.7 混合队列 301 15.8 其他队列 302 15.9 Disruptor+Redis队列 303 15.9.1 简介 303 15.9.2 XML配置 304 15.9.3 EventWorker 305 15.9.4 EventPublishThread 307 15.9.5 EventHandler 308 15.9.6 EventQueue 308 15.10 下单系统水平可扩展架构 311 15.10.1 下单服务 313 15.10.2 同步Worker 313 15.11 基于Canal实现数据异构 314 15.11.1 Mysql主从复制 315 15.11.2 Canal简介 316 15.11.3 Canal示例 318 第4部分案例 323 16 构建需求响应式亿级商品详情页 324 16.1 商品详情页是什么 324 16.2 商品详情页前端结构 325 16.3 我们的性能数据 327 16.4 单品页流量特点 327 16.5 单品页技术架构发展 327 16.5.1 架构1.0 328 16.5.2 架构2.0 328 16.5.3 架构3.0 330 16.6 详情页架构设计原则 332 16.6.1 数据闭环 332 16.6.2 数据维度化 333 16.6.3 拆分系统 334 16.6.4 Worker无状态化+任务化 334 16.6.5 异步化+并发化 335 16.6.6 多级缓存化 335 16.6.7 动态化 336 16.6.8 弹性化 336 16.6.9 降级开关 336 16.6.10 多机房多活 337 16.6.11 多种压测方案 338 16.7 遇到的一些坑和问题 339 16.7.1 SSD性能差 339 16.7.2 键值存储选型压测 340 16.7.3 数据量大时JIMDB同步不动 342 16.7.4 切换主从 342 16.7.5 分片配置 342 16.7.6 模板元数据存储HTML 342 16.7.7 库存接口访问量600w/分钟 343 16.7.8 微信接口调用量暴增 344 16.7.9 开启Nginx Proxy Cache性能不升反降 344 16.7.10 配送至读服务因依赖太多,响应时间偏慢 344 16.7.11 网络抖动时,返回502错误 346 16.7.12 机器流量太大 346 16.8 其他 347 17 京东商品详情页服务闭环实践 348 17.1 为什么需要统一服务 348 17.2 整体架构 349 17.3 一些架构思路和总结 350 17.3.1 两种读服务架构模式 351 17.3.2 本地缓存 352 17.3.3 多级缓存 353 17.3.4 统一入口/服务闭环 354 17.4 引入Nginx接入层 354 17.4.1 数据校验/过滤逻辑前置 354 17.4.2 缓存前置 355 17.4.3 业务逻辑前置 355 17.4.4 降级开关前置 355 17.4.5 AB测试 356 17.4.6 灰度发布/流量切换 356 17.4.7 监控服务质量 356 17.4.8 限流 356 17.5 前端业务逻辑后置 356 17.6 前端接口服务端聚合 357 17.7 服务隔离 359 18 使用OpenResty开发高性能Web应用 360 18.1 OpenResty简介 361 18.1.1 Nginx优点 361 18.1.2 Lua的优点 361 18.1.3 什么是ngx_lua 361 18.1.4 开发环境 362 18.1.5 OpenResty生态 362 18.1.6 场景 362 18.2 基于OpenResty的常用架构模式 363 18.2.1 负载均衡 363 18.2.2 单机闭环 364 18.2.3 分布式闭环 367 18.2.4 接入网关 368 18.2.5 核心接入Nginx功能 369 18.2.6 业务Nginx功能 369 18.2.7 Web应用 370 18.3 如何使用OpenResty开发Web应用 371 18.3.1 项目搭建 371 18.3.2 启停脚本 372 18.3.3 配置文件 372 18.3.4 nginx.conf配置文件 373 18.3.5 Nginx项目配置文件 373 18.3.6 业务代码 374 18.3.7 模板 374 18.3.8 公共Lua库 375 18.3.9 功能开发 375 18.4 基于OpenResty的常用功能总结 375 18.5 一些问题 376 19 应用数据静态化架构高性能单页Web应用 377 19.1 整体架构 378 19.1.1 CMS系统 379 19.1.2 前端展示系统 380 19.1.3 控制系统 380 19.2 数据和模板动态化 381 19.3 多版本机制 381 19.4 异常问题 382 20 使用OpenResty开发Web服务 383 20.1 架构 383 20.2 单DB架构 384 20.2.1 DB+Cache/数据库读写分离架构 384 20.2.2 OpenResty+Local Redis+Mysql集群架构 385 20.2.3 OpenResty+Redis集群+Mysql集群架构 386 20.3 实现 387 20.3.1 后台逻辑 388 20.3.2 前台逻辑 388 20.3.3 项目搭建 389 20.3.4 Redis+Twemproxy配置 389 20.3.5 Mysql+Atlas配置 390 20.3.6 Java+Tomcat安装 394 20.3.7 Java+Tomcat逻辑开发 395 20.3.8 Nginx+Lua逻辑开发 401 21 使用OpenResty开发商品详情页 405 21.1 技术选型 407 21.2 核心流程 408 21.3 项目搭建 408 21.4 数据存储实现 410 21.4.1 商品基本信息SSDB集群配置 410 21.4.2 商品介绍SSDB集群配置 413 21.4.3 其他信息Redis配置 417 21.4.4 集群测试 418 21.4.5 Twemproxy配置 419 21.5 动态服务实现 422 21.5.1 项目搭建 422 21.5.2 项目依赖 422 21.5.3 核心代码 423 21.5.4 基本信息服务 424 21.5.5 商品介绍服务 426 21.5.6 其他信息服务 426 21.5.7 辅助工具 427 21.5.8 web.xml配置 428 21.5.9 打WAR包 428 21.5.10 配置Tomcat 428 21.5.11 测试 429 21.5.12 Nginx配置 429 21.5.13 绑定hosts测试 430 21.6 前端展示实现 430 21.6.1 基础组件 430 21.6.2 商品介绍 432 21.6.4 前端展示 434 21.6.5 测试 442
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值