| | 1 | 原文出处:http://bbs.pconline.cn/topic-2065.html |
| | 2 | |
| | 3 | R 系统白皮书 |
| | 4 | ============ |
| | 5 | |
| | 6 | 本文介绍R 系统的各种使用方法和背后的原因,是使用R系统前必须看明白的一份文档, |
| | 7 | 也是使用过程中的参考,同时也是HTTP调用各种问题的一个总结 |
| | 8 | |
| | 9 | |
| | 10 | 是什么? |
| | 11 | ======= |
| | 12 | R 系统是系统之间进行HTTP调用的一个统一的接口,封装了调用网络路由,HTTP调用和 |
| | 13 | 结果缓存三大模块,是对于系统之间HTTP调用多年经验的一个整理总结的结果 |
| | 14 | |
| | 15 | |
| | 16 | 为什么? |
| | 17 | ======= |
| | 18 | 在日常的开发中,系统之间进行HTTP调用是经常遇到的情况,但是要用好它并不是一件容 |
| | 19 | 易的事情,因为实际情况中WEB 是一个[非可靠]的[分布式网络]系统,调用过程中要解决 |
| | 20 | 各种不可控的情况需要经过深思熟虑才能做到,R 系统就是经过深思熟虑的一个设计 |
| | 21 | |
| | 22 | |
| | 23 | 需求 |
| | 24 | ==== |
| | 25 | 经过慎密的分析,R 系统要解决一下问题: |
| | 26 | 1. 端到端的调用路由和负载均衡,自动化的路由表更新 |
| | 27 | 2. 方便的调用方式和超时及出错/响应过慢处理 |
| | 28 | 3. 对结果的缓冲和并发及故障保护,支持过期缓存使用扩展 |
| | 29 | 下面的使用场景说明会一步一步的说明各需求的应对情况 |
| | 30 | |
| | 31 | 使用前准备 |
| | 32 | ========== |
| | 33 | R 系统需要JDK6的版本 |
| | 34 | resin/lib下需要放r-route-1.2.jar |
| | 35 | |
| | 36 | 应用的WEB-INF/lib下需要放r-1.2.jar |
| | 37 | 还要: |
| | 38 | memcached客户端2.0.1以上 |
| | 39 | log4j-1.2.x以上 |
| | 40 | commons-codec-1.4以上 |
| | 41 | commons-logging-1.1.1以上 |
| | 42 | httpcore-4.1以上 |
| | 43 | httpclient-4.1.1以上 |
| | 44 | |
| | 45 | 通常和spring一起使用 |
| | 46 | |
| | 47 | |
| | 48 | 使用场景 |
| | 49 | ======== |
| | 50 | |
| | 51 | 1. 路由配置 |
| | 52 | ------------ |
| | 53 | {{{ |
| | 54 | +----------+ |
| | 55 | +-------| server-1 | ip:port |
| | 56 | +--------+ domain | +----------+ |
| | 57 | | client |----------+ ... |
| | 58 | +--------+ | +----------+ |
| | 59 | +-------| server-n | ip:port |
| | 60 | +----------+ |
| | 61 | }}} |
| | 62 | |
| | 63 | 上图是最基本的调用结构,需求1 主要解决基本的路由和负载均衡问题,R 系统使用 |
| | 64 | route 模块来解决相关的问题,route 会将域名调用对应到相关的ip:port 并进行轮询 |
| | 65 | 来实现负载均衡,并采用扫描四层交换机的方式来自动更新路由表,对于用户来说,只要 |
| | 66 | 使用适当的配置就可以解决需求1 的问题,下面我们说明一下路由相关配置方法 |
| | 67 | |
| | 68 | 在resin 的配置文件中加入进行R 系统的生产环境路由配置: |
| | 69 | {{{ |
| | 70 | ------------------------------------------------------------------------------ |
| | 71 | <resource jndi-name="jca/pc_route" type="cn.pconline.r.route.PcRouteJNDI" > |
| | 72 | <init> |
| | 73 | <dnsAddr>192.168.238.75</dnsAddr> |
| | 74 | <routeUri>http://192.168.237.61/route.txt</routeUri> |
| | 75 | <routeOverwrite> |
| | 76 | private.pcauto.com.cn=192.168.74.5:8888 |
| | 77 | </routeOverwrite> |
| | 78 | </init> |
| | 79 | </resource> |
| | 80 | ------------------------------------------------------------------------------ |
| | 81 | }}} |
| | 82 | routeOverwrite用于指定特殊的路由映射,用于虚拟域名等星空 |
| | 83 | |
| | 84 | |
| | 85 | 在resin 的配置文件中加入进行R 系统的开发、测试环境路由配置: |
| | 86 | {{{ |
| | 87 | ------------------------------------------------------------------------------ |
| | 88 | <resource jndi-name="jca/pc_route" type="cn.pconline.r.route.ProxyRoute" > |
| | 89 | <init> |
| | 90 | <proxy>192.168.11.90:8080</proxy> |
| | 91 | <routes> |
| | 92 | ks.pcauto.com.cn=192.168.74.10:8081,192.168.47.11:8081 |
| | 93 | bbs.pcauto.com.cn=192.168.74.5:8888 |
| | 94 | </routes> |
| | 95 | </init> |
| | 96 | </resource> |
| | 97 | ------------------------------------------------------------------------------ |
| | 98 | }}} |
| | 99 | proxy 可以将对于域名的调用代理到公网,方便开发测试环境 |
| | 100 | routes用于指定要使用开发、测试环境的那些机器来进行测试 |
| | 101 | |
| | 102 | |
| | 103 | spring applicationContext.xml 配置: |
| | 104 | {{{ |
| | 105 | ------------------------------------------------------------------------------ |
| | 106 | <jee:jndi-lookup id="route" jndi-name="jca/pc_route"/> |
| | 107 | ------------------------------------------------------------------------------ |
| | 108 | }}} |
| | 109 | |
| | 110 | |
| | 111 | 2. HTTP调用(无缓冲) |
| | 112 | --------------------- |
| | 113 | cn.pconline.r.client.SimpleHttpTemplate 是HTTP调用的核心类,以模版方法的模式方便 |
| | 114 | 的进行HTTP调用,通常需要配置为采用route 进行路由,有多种get 和post方法提供使用, |
| | 115 | 本模块会对于超时和错误进行合理的处理,用户配置好就可以方便使用 |
| | 116 | |
| | 117 | |
| | 118 | spring applicationContext.xml 配置: |
| | 119 | {{{ |
| | 120 | ------------------------------------------------------------------------------ |
| | 121 | <bean id="simpleHttpTemplate" class="cn.pconline.r.client.SimpleHttpTemplate" |
| | 122 | init-method="init" |
| | 123 | destroy-method="shutdown" |
| | 124 | p:clientUri="http://myapp.pconline.com.cn" |
| | 125 | p:connectTimeout="10000" |
| | 126 | p:readTimeout="60000" |
| | 127 | p:maxTotalConnections="300" |
| | 128 | p:maxPerRoute="3" |
| | 129 | p:route-ref="route"/> |
| | 130 | ------------------------------------------------------------------------------ |
| | 131 | }}} |
| | 132 | |
| | 133 | 五种get 方法: |
| | 134 | {{{ |
| | 135 | public String get(String uri, String refererUri); |
| | 136 | public String get(String uri, String refererUri, int readTimeout); |
| | 137 | public <T>T get(String uri, String refererUri, |
| | 138 | ResponseExtractor<T> responseExtractor); |
| | 139 | public <T>T get(String uri, String refererUri, |
| | 140 | RequestCallback requestCallback, |
| | 141 | ResponseExtractor<T> responseExtractor); |
| | 142 | public <T>T get(String uri, String refererUri, |
| | 143 | RequestCallback requestCallback, |
| | 144 | ResponseExtractor<T> responseExtractor, int readTimeout); |
| | 145 | }}} |
| | 146 | |
| | 147 | 三种post方法: |
| | 148 | {{{ |
| | 149 | public <T>T post(String uri, String refererUri, |
| | 150 | ResponseExtractor<T> responseExtractor, HttpEntity entity); |
| | 151 | public <T>T post(String uri, String refererUri, |
| | 152 | RequestCallback requestCallback, |
| | 153 | ResponseExtractor<T> responseExtractor, HttpEntity entity); |
| | 154 | public <T>T post(String uri, String refererUri, |
| | 155 | RequestCallback requestCallback, |
| | 156 | ResponseExtractor<T> responseExtractor, HttpEntity entity, |
| | 157 | int readTimeout); |
| | 158 | }}} |
| | 159 | |
| | 160 | 3. RClient 缓冲get 调用结果 |
| | 161 | {{{ |
| | 162 | ---------------------------- |
| | 163 | |
| | 164 | +----------+ |
| | 165 | +-------| server-1 | Cache-Control: max-age=900 |
| | 166 | +--------+ domain | +----------+ |
| | 167 | | client |----+---------+ ... |
| | 168 | +--------+ | | +----------+ |
| | 169 | | +-------| server-n | Cache-Control: max-age=900 |
| | 170 | | +----------+ |
| | 171 | +-----+-----+ |
| | 172 | | memcached | |
| | 173 | +-----------+ |
| | 174 | }}} |
| | 175 | |
| | 176 | 服务端指定缓冲时间,RClient 按照缓冲时间用memcached 进行结果缓冲,并处理并发 |
| | 177 | 调用相同uri 的情况 |
| | 178 | |
| | 179 | |
| | 180 | |
| | 181 | resin 配置:(注意线上环境) |
| | 182 | {{{ |
| | 183 | --------------------------------------------------------------------------- |
| | 184 | <env-entry> |
| | 185 | <description>MemCahcedClient config</description> |
| | 186 | <env-entry-name>memcachedConfig</env-entry-name> |
| | 187 | <env-entry-type>java.lang.String</env-entry-type> |
| | 188 | <env-entry-value> |
| | 189 | servers=127.0.0.1:11211 |
| | 190 | initConn=20 |
| | 191 | minConn=10 |
| | 192 | maxConn=50 |
| | 193 | maintSleep=30 |
| | 194 | nagle=false |
| | 195 | socketTO=3000 |
| | 196 | </env-entry-value> |
| | 197 | </env-entry> |
| | 198 | ------------------------------------------------------------------------------ |
| | 199 | }}} |
| | 200 | |
| | 201 | spring applicationContext.xml 配置: |
| | 202 | {{{ |
| | 203 | ------------------------------------------------------------------------------ |
| | 204 | <jee:jndi-lookup id="memcachedConfig" |
| | 205 | jndi-name="java:comp/env/memcachedConfig"/> |
| | 206 | <bean id="memcachedConfigFactory" |
| | 207 | class="cn.pconline.r.util.MemCachedClientFactory" |
| | 208 | p:config-ref="memcachedConfig" |
| | 209 | p:poolName="r-test" |
| | 210 | init-method="init" |
| | 211 | destroy-method="shutdown"/> |
| | 212 | <bean id="memcachedClient" class="com.danga.MemCached.MemCachedClient" |
| | 213 | factory-bean="memcachedConfigFactory" |
| | 214 | factory-method="getInstance"/> |
| | 215 | |
| | 216 | <bean id="rClient" class="cn.pconline.r.client.RClient" |
| | 217 | init-method="init" |
| | 218 | destroy-method="shutdown" |
| | 219 | p:clientUri="http://app.pconline.com.cn" |
| | 220 | p:connectTimeout="10000" |
| | 221 | p:soTimeout="60000" |
| | 222 | p:cacheMillis="3600000" |
| | 223 | p:errorCacheMillis="300000" |
| | 224 | p:slowMillis="10000" |
| | 225 | p:httpThreads="10" |
| | 226 | p:maxPerRoute="3" |
| | 227 | p:maxRetry="3" |
| | 228 | p:route-ref="route" |
| | 229 | p:memCachedClient-ref="memcachedClient"/> |
| | 230 | ------------------------------------------------------------------------------ |
| | 231 | |
| | 232 | public String get(final String uri, final String refererUri, |
| | 233 | final int timeout, final TimeUnit timeUnit); |
| | 234 | }}} |
| | 235 | |
| | 236 | |
| | 237 | 4. RClient 持久化缓冲(后端故障时使用过期缓冲内容) |
| | 238 | {{{ |
| | 239 | ---------------------------------------------------- |
| | 240 | |
| | 241 | +----------+ |
| | 242 | | mongodb | 持久化缓冲KV存储 |
| | 243 | +----------+ |
| | 244 | | +----------+ |
| | 245 | | +-------| server-1 | |
| | 246 | +--------+ | domain | +----------+ |
| | 247 | | client |----+---------+ ... |
| | 248 | +--------+ | | +----------+ |
| | 249 | | +-------| server-n | |
| | 250 | | +----------+ |
| | 251 | +-----+-----+ |
| | 252 | | memcached | |
| | 253 | +-----------+ |
| | 254 | }}} |
| | 255 | |
| | 256 | 对于某些比较重要的内容,当后端失败时需要采用最近的旧数据提供服务,可以使用持久 |
| | 257 | 化缓冲的方式进行 |
| | 258 | |
| | 259 | |
| | 260 | 实现RClientHelper 扩展进行持久化缓冲 |
| | 261 | {{{ |
| | 262 | ------------------------------------------------------------------------------ |
| | 263 | public interface RClientHelper { |
| | 264 | |
| | 265 | void update(String uri, String key, String content, |
| | 266 | long resourceTimeMillis, long cacheLifeMillis); |
| | 267 | |
| | 268 | R get(String uri, String key); |
| | 269 | |
| | 270 | } |
| | 271 | ------------------------------------------------------------------------------ |
| | 272 | }}} |
| | 273 | |
| | 274 | spring applicationContext.xml 配置: |
| | 275 | {{{ |
| | 276 | ------------------------------------------------------------------------------ |
| | 277 | <bean id="rClientHelper" class="..."> |
| | 278 | <!-- mongodb config --> |
| | 279 | ... |
| | 280 | </bean> |
| | 281 | |
| | 282 | <bean id="rClient" class="cn.pconline.r.client.RClient" |
| | 283 | ... |
| | 284 | p:helper-ref="rClientHelper" |
| | 285 | ...> |
| | 286 | </bean> |
| | 287 | ------------------------------------------------------------------------------ |
| | 288 | }}} |
| | 289 | |
| | 290 | RClient 调用get 方法时指定persistence 参数为true表示采用持久缓冲: |
| | 291 | {{{ |
| | 292 | public String get(final String uri, final String refererUri, |
| | 293 | final boolean persistence, |
| | 294 | final int timeout, final TimeUnit timeUnit); |
| | 295 | }}} |
| | 296 | |
| | 297 | |
| | 298 | 故障情景分析 |
| | 299 | ============ |
| | 300 | 以前我曾经将HTTP服务的状态分为四种情况:死、慢、错、对,只有第四种才是好的服务 |
| | 301 | 状态,前三种都属于故障,下面我们分析一下R 系统是怎么应对这三种故障状态的 |
| | 302 | |
| | 303 | 我们还是按照SimpleHttpTemplate和RClient 两种情况进行分析会清楚一点 |
| | 304 | {{{ |
| | 305 | |
| | 306 | |
| | 307 | +----------+ |
| | 308 | +-------| server-1 | ip:port |
| | 309 | +--------+ domain | +----------+ |
| | 310 | | client |----------+ ... |
| | 311 | +--------+ | +----------+ |
| | 312 | +-------| server-n | ip:port |
| | 313 | +----------+ |
| | 314 | }}} |
| | 315 | |
| | 316 | 对于SimpleHttpTemplate,当全部后端死掉时,将返回最后一台访问状态,否则,返回 |
| | 317 | 成功请求的结果 |
| | 318 | |
| | 319 | |
| | 320 | 情况表 |
| | 321 | {{{ |
| | 322 | ----------------------------+------+------+------+------+------+------+ |
| | 323 | | all | some | all | some | all | some | |
| | 324 | Back failed status | die | die | slow | slow | err | err | |
| | 325 | --------------------+-------+------+------+------+------+------+------+ |
| | 326 | | GET | fail | OK | fail | OK | fail | OK | |
| | 327 | SimpleHttpTemplate +-------+------+------+------+------+------+------+ |
| | 328 | | POST | fail | OK | fail | O/F | fail | O/F | |
| | 329 | --------------------+-------+------+------+------+------+------+------+ |
| | 330 | }}} |
| | 331 | O/F: 轮询时如果碰到好的就OK,碰到有问题的就失败 |
| | 332 | 要和大家再集体商量一次,最终做个了断 |
| | 333 | |
| | 334 | |
| | 335 | |
| | 336 | |
| | 337 | |
| | 338 | {{{ |
| | 339 | +----------+ |
| | 340 | | mongodb | 持久化缓冲KV存储 |
| | 341 | +----------+ |
| | 342 | | +----------+ |
| | 343 | | +-------| server-1 | |
| | 344 | +--------+ | domain | +----------+ |
| | 345 | | client |----+---------+ ... |
| | 346 | +--------+ | | +----------+ |
| | 347 | | +-------| server-n | |
| | 348 | | +----------+ |
| | 349 | +-----+-----+ |
| | 350 | | memcached | |
| | 351 | +-----------+ |
| | 352 | }}} |
| | 353 | 对于RClient, 当后端全部死掉时,还要看缓存的情况,有没有持久缓存也不同 |
| | 354 | |
| | 355 | 情况表 |
| | 356 | {{{ |
| | 357 | ------------------------------------+-----------+-----------+ |
| | 358 | Back server failed status | all fail | some fail | |
| | 359 | ------------------------+-----------+-----------+-----------+ |
| | 360 | memcached |persistence|-----------------------| |
| | 361 | ------------------------+-----------+-----------+-----------+ |
| | 362 | mc valid | | OK | OK | |
| | 363 | ------------------------+ +-----------+-----------+ |
| | 364 | mc expiried | no | BLANK | OK | |
| | 365 | ------------------------+ +-----------+-----------+ |
| | 366 | mc LRU out | | BLANK | OK | |
| | 367 | ------------------------+-----------+-----------+-----------+ |
| | 368 | mc valid | | OK | OK | |
| | 369 | ------------------------+ +-----------+-----------+ |
| | 370 | mc expiried | yes | OK/OLD | OK | |
| | 371 | ------------------------+ +-----------+-----------+ |
| | 372 | mc LRU out | | OK/OLD | OK | |
| | 373 | ------------------------+-----------+-----------+-----------+ |
| | 374 | }}} |
| | 375 | OK/OLD: 如果缓存没有过期就是OK,已经过期就是旧的 |
| | 376 | |
| | 377 | |
| | 378 | |
| | 379 | 内部流程 |
| | 380 | ======== |
| | 381 | |
| | 382 | 分别说明SimpleHttpTemplate和RClient的内部流程 |
| | 383 | |
| | 384 | SimpleHttpTemplate |
| | 385 | ------------------ |
| | 386 | 本模块是对Apache HttpClient 的简单封装,没有使用另外的线程进行异步处理,整个 |
| | 387 | 调用对于应用来说是同步的,下面的配置除了clientUri外,都是直接的设置 |
| | 388 | {{{ |
| | 389 | p:clientUri="http://myapp.pconline.com.cn" |
| | 390 | p:connectTimeout="10000" |
| | 391 | p:readTimeout="60000" |
| | 392 | p:maxTotalConnections="300" |
| | 393 | p:maxPerRoute="3" |
| | 394 | }}} |
| | 395 | 实际的流程是: |
| | 396 | 1 用户请求uri |
| | 397 | 2 模块根据uri获得后端服务器的列表 |
| | 398 | 3 向一个轮询出的服务器请求 |
| | 399 | 4 请求成功返回结果 |
| | 400 | 4.1 请求不成功,判断是否请求下一个服务器 |
| | 401 | 4.1.1不需要请求下一个,则返回成功结果 |
| | 402 | 4.1.2需要请求下一个,则从列表中拿出下一个,到第4布进行请求 |
| | 403 | |
| | 404 | * 是否请求下一个服务器的标准按照前文的情况表决定 |
| | 405 | |
| | 406 | |
| | 407 | RClient |
| | 408 | ------- |
| | 409 | 本模块采用异步的方式,有专门的线程进行异步的HTTP请求,对于请求的结果,采用 |
| | 410 | memcached 进行缓冲,必要时采用mongodb 之类的KV存储对结果进行持久化存储, |
| | 411 | 对于相同的uri 请求进行并发请求标志保护,同一个uri过期时理论上只有一个HTTP请求 |
| | 412 | 会到后端去请求,配置如下,前四与simpleHttpTemplate 相同,都是直接设置 |
| | 413 | Apache HttpClient, 最后三个用于缓冲时间设置 |
| | 414 | {{{ |
| | 415 | p:clientUri="http://app.pconline.com.cn" |
| | 416 | p:connectTimeout="10000" |
| | 417 | p:soTimeout="60000" |
| | 418 | p:maxPerRoute="3" |
| | 419 | p:httpThreads="10" |
| | 420 | p:maxRetry="3" |
| | 421 | p:cacheMillis="3600000" |
| | 422 | p:errorCacheMillis="300000" |
| | 423 | p:slowMillis="10000" |
| | 424 | }}} |
| | 425 | 流程: |
| | 426 | 细节看代码好点... |
| | 427 | 1 请求memcached,有结果返回 |
| | 428 | 2 请求mongodb,结果未过期返回,同时设置memcached |
| | 429 | 3 异步请求后端,正常时返回,同时设置memcached和mongodb |
| | 430 | |
| | 431 | 后端访问为轮询(maxRetry会限制轮询的次数), |
| | 432 | 遇到正常结果终止轮询,或者返回最后的结果 |
| | 433 | |
| | 434 | {{{ |
| | 435 | +-------+ |
| | 436 | |mongodb| |
| | 437 | +---+---+ |
| | 438 | | |
| | 439 | +---+-------------------------------+ +--------+ |
| | 440 | | | RClient | +------|server-1| |
| | 441 | +------+ | | +------+ | | +--------+ |
| | 442 | |client|--------+---+--$|Thread| +----------+ | | |
| | 443 | +------+ | | | Pool |------|HttpClient|--+----+ ... |
| | 444 | | | +------+ +----------+ | | |
| | 445 | | | | | +--------+ |
| | 446 | +---+-------------------------------+ +------|server-n| |
| | 447 | | +--------+ |
| | 448 | +---+-----+ |
| | 449 | |memcached| |
| | 450 | +---------+ |
| | 451 | |
| | 452 | ------------------------------------------------------------------------------ |
| | 453 | }}} |