原文出处: http://bbs.pconline.cn/topic-2065.html
R 系统白皮书
============
本文介绍R 系统的各种使用方法和背后的原因,是使用R系统前必须看明白的一份文档, 也是使用过程中的参考,同时也是HTTP调用各种问题的一个总结
是什么?
=======
R 系统是系统之间进行HTTP调用的一个统一的接口,封装了调用网络路由,HTTP调用和
结果缓存三大模块,是对于系统之间HTTP调用多年经验的一个整理总结的结果
为什么?
=======
在日常的开发中,系统之间进行HTTP调用是经常遇到的情况,但是要用好它并不是一件容
易的事情,因为实际情况中WEB 是一个[非可靠]的[分布式网络]系统,调用过程中要解决
各种不可控的情况需要经过深思熟虑才能做到,R 系统就是经过深思熟虑的一个设计
需求
====
经过慎密的分析,R 系统要解决一下问题:
- 端到端的调用路由和负载均衡,自动化的路由表更新
- 方便的调用方式和超时及出错/响应过慢处理
- 对结果的缓冲和并发及故障保护,支持过期缓存使用扩展
下面的使用场景说明会一步一步的说明各需求的应对情况
使用前准备
==========
R 系统需要JDK6的版本
resin/lib下需要放r-route-1.2.jar
应用的WEB-INF/lib下需要放r-1.2.jar
还要:
memcached客户端2.0.1以上
log4j-1.2.x以上
commons-codec-1.4以上
commons-logging-1.1.1以上
httpcore-4.1以上
httpclient-4.1.1以上
通常和spring一起使用
使用场景
========
- 路由配置
------------ +----------+ +-------| server-1 | ip:port +--------+ domain | +----------+ | client |----------+ ... +--------+ | +----------+ +-------| server-n | ip:port +----------+
上图是最基本的调用结构,需求1 主要解决基本的路由和负载均衡问题,R 系统使用 route 模块来解决相关的问题,route 会将域名调用对应到相关的ip:port 并进行轮询 来实现负载均衡,并采用扫描四层交换机的方式来自动更新路由表,对于用户来说,只要 使用适当的配置就可以解决需求1 的问题,下面我们说明一下路由相关配置方法
在resin 的配置文件中加入进行R 系统的生产环境路由配置:
------------------------------------------------------------------------------
<resource jndi-name="jca/pc_route" type="cn.pconline.r.route.PcRouteJNDI" >
<init>
<dnsAddr>192.168.238.75</dnsAddr>
<routeUri>http://192.168.237.61/route.txt</routeUri>
<routeOverwrite>
private.pcauto.com.cn=192.168.74.5:8888
</routeOverwrite>
</init>
</resource>
------------------------------------------------------------------------------
routeOverwrite用于指定特殊的路由映射,用于虚拟域名等星空
在resin 的配置文件中加入进行R 系统的开发、测试环境路由配置:
------------------------------------------------------------------------------
<resource jndi-name="jca/pc_route" type="cn.pconline.r.route.ProxyRoute" >
<init>
<proxy>192.168.11.90:8080</proxy>
<routes>
ks.pcauto.com.cn=192.168.74.10:8081,192.168.47.11:8081
bbs.pcauto.com.cn=192.168.74.5:8888
</routes>
</init>
</resource>
------------------------------------------------------------------------------
proxy 可以将对于域名的调用代理到公网,方便开发测试环境
routes用于指定要使用开发、测试环境的那些机器来进行测试
spring applicationContext.xml 配置:
------------------------------------------------------------------------------ <jee:jndi-lookup id="route" jndi-name="jca/pc_route"/> ------------------------------------------------------------------------------
- HTTP调用(无缓冲)
cn.pconline.r.client.SimpleHttpTemplate? 是HTTP调用的核心类,以模版方法的模式方便 的进行HTTP调用,通常需要配置为采用route 进行路由,有多种get 和post方法提供使用, 本模块会对于超时和错误进行合理的处理,用户配置好就可以方便使用
spring applicationContext.xml 配置:
------------------------------------------------------------------------------
<bean id="simpleHttpTemplate" class="cn.pconline.r.client.SimpleHttpTemplate"
init-method="init"
destroy-method="shutdown"
p:clientUri="http://myapp.pconline.com.cn"
p:connectTimeout="10000"
p:readTimeout="60000"
p:maxTotalConnections="300"
p:maxPerRoute="3"
p:route-ref="route"/>
------------------------------------------------------------------------------
五种get 方法:
public String get(String uri, String refererUri);
public String get(String uri, String refererUri, int readTimeout);
public <T>T get(String uri, String refererUri,
ResponseExtractor<T> responseExtractor);
public <T>T get(String uri, String refererUri,
RequestCallback requestCallback,
ResponseExtractor<T> responseExtractor);
public <T>T get(String uri, String refererUri,
RequestCallback requestCallback,
ResponseExtractor<T> responseExtractor, int readTimeout);
三种post方法:
public <T>T post(String uri, String refererUri,
ResponseExtractor<T> responseExtractor, HttpEntity entity);
public <T>T post(String uri, String refererUri,
RequestCallback requestCallback,
ResponseExtractor<T> responseExtractor, HttpEntity entity);
public <T>T post(String uri, String refererUri,
RequestCallback requestCallback,
ResponseExtractor<T> responseExtractor, HttpEntity entity,
int readTimeout);
- RClient 缓冲get 调用结果
---------------------------- +----------+ +-------| server-1 | Cache-Control: max-age=900 +--------+ domain | +----------+ | client |----+---------+ ... +--------+ | | +----------+ | +-------| server-n | Cache-Control: max-age=900 | +----------+ +-----+-----+ | memcached | +-----------+
服务端指定缓冲时间,RClient 按照缓冲时间用memcached 进行结果缓冲,并处理并发 调用相同uri 的情况
resin 配置:(注意线上环境)
---------------------------------------------------------------------------
<env-entry>
<description>MemCahcedClient config</description>
<env-entry-name>memcachedConfig</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>
servers=127.0.0.1:11211
initConn=20
minConn=10
maxConn=50
maintSleep=30
nagle=false
socketTO=3000
</env-entry-value>
</env-entry>
------------------------------------------------------------------------------
spring applicationContext.xml 配置:
------------------------------------------------------------------------------
<jee:jndi-lookup id="memcachedConfig"
jndi-name="java:comp/env/memcachedConfig"/>
<bean id="memcachedConfigFactory"
class="cn.pconline.r.util.MemCachedClientFactory"
p:config-ref="memcachedConfig"
p:poolName="r-test"
init-method="init"
destroy-method="shutdown"/>
<bean id="memcachedClient" class="com.danga.MemCached.MemCachedClient"
factory-bean="memcachedConfigFactory"
factory-method="getInstance"/>
<bean id="rClient" class="cn.pconline.r.client.RClient"
init-method="init"
destroy-method="shutdown"
p:clientUri="http://app.pconline.com.cn"
p:connectTimeout="10000"
p:soTimeout="60000"
p:cacheMillis="3600000"
p:errorCacheMillis="300000"
p:slowMillis="10000"
p:httpThreads="10"
p:maxPerRoute="3"
p:maxRetry="3"
p:route-ref="route"
p:memCachedClient-ref="memcachedClient"/>
------------------------------------------------------------------------------
public String get(final String uri, final String refererUri,
final int timeout, final TimeUnit timeUnit);
- 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 配置:
------------------------------------------------------------------------------
<bean id="rClientHelper" class="...">
<!-- mongodb config -->
...
</bean>
<bean id="rClient" class="cn.pconline.r.client.RClient"
...
p:helper-ref="rClientHelper"
...>
</bean>
------------------------------------------------------------------------------
RClient 调用get 方法时指定persistence 参数为true表示采用持久缓冲:
public String get(final String uri, final String refererUri,
final boolean persistence,
final int timeout, final TimeUnit timeUnit);
故障情景分析
============
以前我曾经将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的内部流程
本模块是对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|
+---------+
------------------------------------------------------------------------------
![(please configure the [header_logo] section in trac.ini)](http://www1.pconline.com.cn/hr/2009/global/images/logo.gif)