上一篇:使用Nginx+Lua(OpenResty)开发高性能Web应用

下一篇:京东商品详情页服务闭环实践

京东商品详情页碎碎念

在之前的两篇文章《构建需求响应式亿级商品详情页》和《京东商品详情页服务闭环实践》已经详细介绍了整个系统的架构设计和实现思路。本篇将介绍下杂七杂八的一些实践:

  • 静态化
  • 突发流量
  • 恶意访问
  • 托底数据
  • 超时时间/重试
  • CDN回源
  • 监控和报警
  • 日志

静态化

我个人总结为:数据静态化、页面片段静态化、页面静态化;数据静态化即把相关数据聚合为一个大数据,这样比如获取数据时只需要一次获取即可,好处是减少多次获取带来的性能开销;页面片段静态化即整个大页面按照不同的维度进行分拆,生成一些页面片段,然后通过SSI(服务端包含技术)将多个页面片段合并输出,比如商品详情页可以分为主框架、面包屑、商家信息、商品介绍、规格参数等页面片段,可以存储到磁盘或者分布式文件系统,存储到磁盘需要注意小文件造成的随机读写变多了和一些文件系统对inode数的限制;而页面静态化是把整个大页面生成静态页,缺点是页面一部分变更需要更新整个页面;其中数据静态化最灵活,可以实现数据和模板的分离,模板可以随意变化,适合经常变更的业务。

突发流量

对于突发流量,我这边系统的一般解决方案:高效缓存、自动降级、减少回源量;高效缓存可以参考《构建需求响应式亿级商品详情页》和《京东商品详情页服务闭环实践》中的缓存实践;而自动降级也在这两篇文章中讲到了,不再重复。

减少回源量可以通过一致性哈希、非阻塞锁、304响应来实现;通过一致性哈希可以将相同的请求打到相同的服务器,提升缓存命中率,而轮训会造成缓存命中率低;非阻塞锁即比如100个人同时请求一个页面时,首先让一个回源到后端系统进行处理,假设超时时间是100ms,如果100ms内返回了内容,则这100个人直接返回这一份内容即可,如果100ms后端系统没能返回,这100个请求会全部打到后端系统,也就是说如果后端系统响应快则回源量减少,否则回源量不会变少。

对于304可以通过两种机制实现,一种是业务系统提供变更时间,然后去比对是否变更,如果没有变更则直接返回304,这样只需要对比变更时间即可;另一个是设置一个容忍时间,比如10s,那么10s内的请求都返回304,不调用业务系统。对于秒杀业务设置10s的容忍时间会减少很多回源量,减少突发流量打挂后端业务系统。

恶意访问

对于恶意方案可以通过:提升缓存命中率,减少回源量、限流、导流到隔离的分组、N页以后的请求做特殊处理,如降低请求速度。提升缓存命中率或者所有数据都缓存/SSD,这样只要预估好流量,一般就不怕刷;另外可以减少回源量,比如使用多级缓存、爬虫做特殊处理等;对于一些请求还是需要做限流,限流可以按照连接数、请求数、流量、用户进行限流,对于缓存未命中的需要回源查数据库之类的我们需要限流,而查询缓存的请求可以根据实际情况进行限流处理;对于一些恶意的请求可能是通过小区宽带过来的,我们不能完全拒绝掉该IP,可以考虑将这些流量导到隔离的分组,比如进行慢速处理;还有一些列表页相关的应用,可以考虑对N页以后的请求做特殊处理,比如慢速处理或者根据实际调用量返回一些过期缓存数据。

托底数据

所谓托底数据即假设正常业务处理失败了,不能返回给用户一个503错误页,可以进行有损降级处理,比如根据场景返回一些快过期或已过期数据;另外可以进行部分有损服务而不是所有。比如请求一个用户推荐时,假设用户推荐没出来我们可以返回默认的推荐数据或者历史的推荐数据。还有如CDN缓存的,可以在出问题时返回历史数据;或者使用html5 localstorage存储;还可以通过两个分离的域名,一个失败后,自动调用另一个拿托底数据。

超时时间/重试

对于一个系统来说,超时时间是必须设置的,主要有如TCP连接/读/写超时时间、连接池超时时间,还有相关的重试时机/次数;对于一个网络服务,需要根据实际情况设置TCP连接/读/写超时时间,如果不设置很可能会在网络出问题时服务直接挂断,比如我们连接Redis都设置在150ms以内;还有如果使用Nginx,还需要考虑Nginx超时时间、upstream超时时间和业务超时时间,假设Nginx超时时间是5s、upstream超时时间是6s,而业务的超时时间是8s,那么假设业务处理了7s,那么upstream超时时间到了需要进行下一个upstream的重试,但是Nginx总的超时时间是5s,所以就无法重试了;连接池也需要设置获取连接的等待时间,如果不设置会有很多线程一直在等待,之前写自己实现连接池时,会去发现是否网络错误,如果网络错误就没必要等待连接了,而立即失败即可;还需要考虑服务失败重试时机,比如是立即重试,还是指数式重试;还有重试次数,是一次性重试三次还是指数式等待时间后进行一次次的重试;比如HttpClient(DefaultHttpRequestRetryHandler)默认会立即进行三次重试。 

CDN回源

对于CDN减少回源的策略有非阻塞锁、爬虫不回源,返回历史数据、版本化等;对于版本化的意思是对于一些内容可以通过版本化机制设置更长的超时时间,而且固定URL顺序,这样相同的URL请求只要版本不变就会更有效的命中CDN数据;版本可以通过推送机制通知相关系统;比如评价数据的版本化,可以每隔几分钟推送变更了版本 的商品更新版本号。

监控和报警

对于监控我们一般监控CPU/内存/磁盘、应用实例存活、调用量/响应时间/可用率这些信息,可以根据系统状况设置一些参数的阀值,比如可用率(系统异常)小于80%则告警,这样可以及时发现系统问题并解决。

日志

对于日志我们一般需要经常收集记录和查看Nginx日志(访问日志、错误日志)、应用日志(业务日志、异常日志、OOM堆/堆栈日志)、监控日志(调用量、响应时间、可用率)、系统日志。Nginx访问日志可以记录IP、UA、Referer等信息以便发现恶意访问的一些线索,记录request_time、response_time来发现是否存在一些慢速请求;而Nginx错误日志查看是否存在超时或者一些服务出问题了;应用业务日志用来排查业务逻辑是否正确的,而应用异常日志用来发现什么时间、什么位置、哪个类的哪个方法出现了什么问题,以便故障排查,还有如Java应用OOM时的堆/堆栈日志也要存好了;监控日志用来记录服务的一些吞吐量和性能数据;另外需要经常查看系统日志,如之前遇到过应用大量timeout情况,查看dmsg发现存在nf_conntrack: table full, dropping packet,让运维把防火墙关掉即可。

其他

为了发现是哪台服务器出现了问题,我们都要在响应响应头记录服务器真实IP,这样出问题后,直接定位到哪台服务器出问题了;对于多机房业务尽量使用智能DNS,即一个机房的服务尽量调用一个机房的服务,减少跨机房调用;在没有进行Docker容器化之前,我们还可以减少跨交换机、跨机柜调用;对于整个系统我们无法保证整个系统不出现脏数据,所以需要提供刷脏数据的接口,以便出现问题时进行数据的更新或删除。

上一篇:使用Nginx+Lua(OpenResty)开发高性能Web应用

下一篇:京东商品详情页服务闭环实践