原文出处:http://bbs.pconline.cn/topic-2065.html R 系统白皮书[[br]] ============ 本文介绍R 系统的各种使用方法和背后的原因,是使用R系统前必须看明白的一份文档, 也是使用过程中的参考,同时也是HTTP调用各种问题的一个总结 是什么?[[br]] =======[[br]] R 系统是系统之间进行HTTP调用的一个统一的接口,封装了调用网络路由,HTTP调用和 结果缓存三大模块,是对于系统之间HTTP调用多年经验的一个整理总结的结果 为什么?[[br]] =======[[br]] 在日常的开发中,系统之间进行HTTP调用是经常遇到的情况,但是要用好它并不是一件容 易的事情,因为实际情况中WEB 是一个[非可靠]的[分布式网络]系统,调用过程中要解决 各种不可控的情况需要经过深思熟虑才能做到,R 系统就是经过深思熟虑的一个设计 需求[[br]] ====[[br]] 经过慎密的分析,R 系统要解决一下问题: 1. 端到端的调用路由和负载均衡,自动化的路由表更新 2. 方便的调用方式和超时及出错/响应过慢处理 3. 对结果的缓冲和并发及故障保护,支持过期缓存使用扩展 下面的使用场景说明会一步一步的说明各需求的应对情况 使用前准备[[br]] ==========[[br]] R 系统需要JDK6的版本[[br]] resin/lib下需要放r-route-1.2.jar 应用的WEB-INF/lib下需要放r-1.2.jar[[br]] 还要:[[br]] memcached客户端2.0.1以上[[br]] log4j-1.2.x以上[[br]] commons-codec-1.4以上[[br]] commons-logging-1.1.1以上[[br]] httpcore-4.1以上[[br]] httpclient-4.1.1以上[[br]] 通常和spring一起使用 使用场景[[br]] ========[[br]] 1. 路由配置[[br]] {{{ ------------ +----------+ +-------| server-1 | ip:port +--------+ domain | +----------+ | client |----------+ ... +--------+ | +----------+ +-------| server-n | ip:port +----------+ }}} 上图是最基本的调用结构,需求1 主要解决基本的路由和负载均衡问题,R 系统使用 route 模块来解决相关的问题,route 会将域名调用对应到相关的ip:port 并进行轮询 来实现负载均衡,并采用扫描四层交换机的方式来自动更新路由表,对于用户来说,只要 使用适当的配置就可以解决需求1 的问题,下面我们说明一下路由相关配置方法 在resin 的配置文件中加入进行R 系统的生产环境路由配置: {{{ ------------------------------------------------------------------------------ 192.168.238.75 http://192.168.237.61/route.txt private.pcauto.com.cn=192.168.74.5:8888 ------------------------------------------------------------------------------ }}} routeOverwrite用于指定特殊的路由映射,用于虚拟域名等星空 在resin 的配置文件中加入进行R 系统的开发、测试环境路由配置: {{{ ------------------------------------------------------------------------------ 192.168.11.90:8080 ks.pcauto.com.cn=192.168.74.10:8081,192.168.47.11:8081 bbs.pcauto.com.cn=192.168.74.5:8888 ------------------------------------------------------------------------------ }}} proxy 可以将对于域名的调用代理到公网,方便开发测试环境[[br]] routes用于指定要使用开发、测试环境的那些机器来进行测试 spring applicationContext.xml 配置: {{{ ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ }}} 2. HTTP调用(无缓冲)[[br]] cn.pconline.r.client.SimpleHttpTemplate 是HTTP调用的核心类,以模版方法的模式方便 的进行HTTP调用,通常需要配置为采用route 进行路由,有多种get 和post方法提供使用, 本模块会对于超时和错误进行合理的处理,用户配置好就可以方便使用 spring applicationContext.xml 配置: {{{ ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ }}} 五种get 方法: {{{ public String get(String uri, String refererUri); public String get(String uri, String refererUri, int readTimeout); public T get(String uri, String refererUri, ResponseExtractor responseExtractor); public T get(String uri, String refererUri, RequestCallback requestCallback, ResponseExtractor responseExtractor); public T get(String uri, String refererUri, RequestCallback requestCallback, ResponseExtractor responseExtractor, int readTimeout); }}} 三种post方法: {{{ public T post(String uri, String refererUri, ResponseExtractor responseExtractor, HttpEntity entity); public T post(String uri, String refererUri, RequestCallback requestCallback, ResponseExtractor responseExtractor, HttpEntity entity); public T post(String uri, String refererUri, RequestCallback requestCallback, ResponseExtractor responseExtractor, HttpEntity entity, int readTimeout); }}} 3. RClient 缓冲get 调用结果 {{{ ---------------------------- +----------+ +-------| server-1 | Cache-Control: max-age=900 +--------+ domain | +----------+ | client |----+---------+ ... +--------+ | | +----------+ | +-------| server-n | Cache-Control: max-age=900 | +----------+ +-----+-----+ | memcached | +-----------+ }}} 服务端指定缓冲时间,RClient 按照缓冲时间用memcached 进行结果缓冲,并处理并发 调用相同uri 的情况 resin 配置:(注意线上环境) {{{ --------------------------------------------------------------------------- MemCahcedClient config memcachedConfig java.lang.String servers=127.0.0.1:11211 initConn=20 minConn=10 maxConn=50 maintSleep=30 nagle=false socketTO=3000 ------------------------------------------------------------------------------ }}} spring applicationContext.xml 配置: {{{ ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ public String get(final String uri, final String refererUri, final int timeout, final TimeUnit timeUnit); }}} 4. RClient 持久化缓冲(后端故障时使用过期缓冲内容) {{{ ---------------------------------------------------- +----------+ | mongodb | 持久化缓冲KV存储 +----------+ | +----------+ | +-------| server-1 | +--------+ | domain | +----------+ | client |----+---------+ ... +--------+ | | +----------+ | +-------| server-n | | +----------+ +-----+-----+ | memcached | +-----------+ }}} 对于某些比较重要的内容,当后端失败时需要采用最近的旧数据提供服务,可以使用持久 化缓冲的方式进行 实现RClientHelper 扩展进行持久化缓冲 {{{ ------------------------------------------------------------------------------ public interface RClientHelper { void update(String uri, String key, String content, long resourceTimeMillis, long cacheLifeMillis); R get(String uri, String key); } ------------------------------------------------------------------------------ }}} spring applicationContext.xml 配置: {{{ ------------------------------------------------------------------------------ ... ------------------------------------------------------------------------------ }}} RClient 调用get 方法时指定persistence 参数为true表示采用持久缓冲: {{{ public String get(final String uri, final String refererUri, final boolean persistence, final int timeout, final TimeUnit timeUnit); }}} 故障情景分析[[br]] ============[[br]] 以前我曾经将HTTP服务的状态分为四种情况:死、慢、错、对,只有第四种才是好的服务 状态,前三种都属于故障,下面我们分析一下R 系统是怎么应对这三种故障状态的 我们还是按照SimpleHttpTemplate和RClient 两种情况进行分析会清楚一点 {{{ +----------+ +-------| server-1 | ip:port +--------+ domain | +----------+ | client |----------+ ... +--------+ | +----------+ +-------| server-n | ip:port +----------+ }}} 对于SimpleHttpTemplate,当全部后端死掉时,将返回最后一台访问状态,否则,返回 成功请求的结果 情况表 {{{ ----------------------------+------+------+------+------+------+------+ | all | some | all | some | all | some | Back failed status | die | die | slow | slow | err | err | --------------------+-------+------+------+------+------+------+------+ | GET | fail | OK | fail | OK | fail | OK | SimpleHttpTemplate +-------+------+------+------+------+------+------+ | POST | fail | OK | fail | O/F | fail | O/F | --------------------+-------+------+------+------+------+------+------+ }}} O/F: 轮询时如果碰到好的就OK,碰到有问题的就失败 要和大家再集体商量一次,最终做个了断 {{{ +----------+ | mongodb | 持久化缓冲KV存储 +----------+ | +----------+ | +-------| server-1 | +--------+ | domain | +----------+ | client |----+---------+ ... +--------+ | | +----------+ | +-------| server-n | | +----------+ +-----+-----+ | memcached | +-----------+ }}} 对于RClient, 当后端全部死掉时,还要看缓存的情况,有没有持久缓存也不同 情况表 {{{ ------------------------------------+-----------+-----------+ Back server failed status | all fail | some fail | ------------------------+-----------+-----------+-----------+ memcached |persistence|-----------------------| ------------------------+-----------+-----------+-----------+ mc valid | | OK | OK | ------------------------+ +-----------+-----------+ mc expiried | no | BLANK | OK | ------------------------+ +-----------+-----------+ mc LRU out | | BLANK | OK | ------------------------+-----------+-----------+-----------+ mc valid | | OK | OK | ------------------------+ +-----------+-----------+ mc expiried | yes | OK/OLD | OK | ------------------------+ +-----------+-----------+ mc LRU out | | OK/OLD | OK | ------------------------+-----------+-----------+-----------+ }}} OK/OLD: 如果缓存没有过期就是OK,已经过期就是旧的 内部流程 ======== 分别说明SimpleHttpTemplate和RClient的内部流程 SimpleHttpTemplate ------------------ 本模块是对Apache HttpClient 的简单封装,没有使用另外的线程进行异步处理,整个 调用对于应用来说是同步的,下面的配置除了clientUri外,都是直接的设置 {{{ p:clientUri="http://myapp.pconline.com.cn" p:connectTimeout="10000" p:readTimeout="60000" p:maxTotalConnections="300" p:maxPerRoute="3" }}} 实际的流程是: 1 用户请求uri 2 模块根据uri获得后端服务器的列表 3 向一个轮询出的服务器请求 4 请求成功返回结果 4.1 请求不成功,判断是否请求下一个服务器 4.1.1不需要请求下一个,则返回成功结果 4.1.2需要请求下一个,则从列表中拿出下一个,到第4布进行请求 * 是否请求下一个服务器的标准按照前文的情况表决定 RClient ------- 本模块采用异步的方式,有专门的线程进行异步的HTTP请求,对于请求的结果,采用 memcached 进行缓冲,必要时采用mongodb 之类的KV存储对结果进行持久化存储, 对于相同的uri 请求进行并发请求标志保护,同一个uri过期时理论上只有一个HTTP请求 会到后端去请求,配置如下,前四与simpleHttpTemplate 相同,都是直接设置 Apache HttpClient, 最后三个用于缓冲时间设置 {{{ p:clientUri="http://app.pconline.com.cn" p:connectTimeout="10000" p:soTimeout="60000" p:maxPerRoute="3" p:httpThreads="10" p:maxRetry="3" p:cacheMillis="3600000" p:errorCacheMillis="300000" p:slowMillis="10000" }}} 流程: 细节看代码好点... 1 请求memcached,有结果返回 2 请求mongodb,结果未过期返回,同时设置memcached 3 异步请求后端,正常时返回,同时设置memcached和mongodb 后端访问为轮询(maxRetry会限制轮询的次数), 遇到正常结果终止轮询,或者返回最后的结果 {{{ +-------+ |mongodb| +---+---+ | +---+-------------------------------+ +--------+ | | RClient | +------|server-1| +------+ | | +------+ | | +--------+ |client|--------+---+--$|Thread| +----------+ | | +------+ | | | Pool |------|HttpClient|--+----+ ... | | +------+ +----------+ | | | | | | +--------+ +---+-------------------------------+ +------|server-n| | +--------+ +---+-----+ |memcached| +---------+ ------------------------------------------------------------------------------ }}}