wiki:Intro

原文出处: http://bbs.pconline.cn/topic-2065.html

R 系统白皮书
============

本文介绍R 系统的各种使用方法和背后的原因,是使用R系统前必须看明白的一份文档, 也是使用过程中的参考,同时也是HTTP调用各种问题的一个总结

是什么?
=======
R 系统是系统之间进行HTTP调用的一个统一的接口,封装了调用网络路由,HTTP调用和 结果缓存三大模块,是对于系统之间HTTP调用多年经验的一个整理总结的结果

为什么?
=======
在日常的开发中,系统之间进行HTTP调用是经常遇到的情况,但是要用好它并不是一件容 易的事情,因为实际情况中WEB 是一个[非可靠]的[分布式网络]系统,调用过程中要解决 各种不可控的情况需要经过深思熟虑才能做到,R 系统就是经过深思熟虑的一个设计

需求
====
经过慎密的分析,R 系统要解决一下问题:

  1. 端到端的调用路由和负载均衡,自动化的路由表更新
  2. 方便的调用方式和超时及出错/响应过慢处理
  3. 对结果的缓冲和并发及故障保护,支持过期缓存使用扩展

下面的使用场景说明会一步一步的说明各需求的应对情况

使用前准备
==========
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一起使用

使用场景
========

  1. 路由配置
    ------------
    
                                +----------+
                        +-------| 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"/>
------------------------------------------------------------------------------
  1. 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);
  1. 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);
  1. 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的内部流程

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|
                +---------+

------------------------------------------------------------------------------