AirJD 焦点
AirJD

没有录音文件
00:00/00:00
加收藏

高性能Web应用缓存架构设计浅谈 by Robbin Fan

发布者 ruby
发布于 1444870958040  浏览 6186 关键词 架构, 并发, Ruby 
分享到

第1页

高性能Web应用 缓存架构设计浅谈

Robbin Fan

Thursday, August 4, 11



第2页

高性能Web应用特征

• 大规模,高并发的访问请求 • 服务的高可用性 • 平滑的deployment • 良好的可伸缩性

Thursday, August 4, 11



第3页

特殊应用场景

• 长连接 • Web chat,Web game • 网络IO资源消耗型,非CPU消耗型 • EventMachine,Twisted, Node.js

Thursday, August 4, 11



第4页

高性能Web应用 是架构问题,不是

Rails框架问题

Thursday, August 4, 11



第5页

Constraint is liberty

Thursday, August 4, 11



第6页

JavaEye统计数据

• 3,000,000+ Rails dynamic requests per day • 15,000,000+ HTTP requests per day • 600+ HTTP requests/s on peak time • 1,000,000+ full-text search per day • 1400+ current connections/s on peak time • 600 average Memcached get operations • 150 average SQL queries per second

Thursday, August 4, 11



第7页

Web Server

lighttpd analytics

fastcgi(ruby)

memcached SearchServer

Thursday, August 4, 11



DB Server

mysql PDF Creating



第8页

Web Server



DB Server



CPU Peak < 60% IO Wait < 5%



CPU Peak < 25% IO Wait < 15%



Thursday, August 4, 11



第9页

Rails高性能Web之道

• 以REST的架构风格来编写Rails应用 • 从整体架构上设计各个层面缓存方案 • 消除架构上的各个单点性能瓶颈 • 多进程的分布式应用架构部署

Thursday, August 4, 11



第10页

REST

资源 + 操作 = REST

Thursday, August 4, 11



第11页

REST

URL 资源 + 操作 = REST

Thursday, August 4, 11



第12页

REST

URL + 资源 + 操作 = REST

Thursday, August 4, 11



第13页

REST

URL + HTTP 资源 + 操作 = REST

Thursday, August 4, 11



第14页

REST

URL + HTTP = 资源 + 操作 = REST

Thursday, August 4, 11



第15页

REST

URL + HTTP = Web 资源 + 操作 = REST

Thursday, August 4, 11



第16页

REST架构的设计准则

• 网络上的所有事物都被抽象为资源(resource)

• resource = data + representation

• 每个资源对应⼀一个唯⼀一的资源标识(resource identifier)

• resource identifier = URL

• 通过连接器(generic connector interface)对资源进行操作

• generic connector = HTTP Procotol (GET/POST/PUT/DELETE)

• 对资源的各种操作不会改变资源标识

• 对资源的GET操作具有幂等性,不改变资源的状态

• 所有的操作都是无状态的(stateless)

Thursday, August 4, 11



第17页

REST的实践用途

• 确保Web请求不会产生负作用 • 对内容型网站提供了最好的SEO效果 • 利用HTTP协议实现浏览器端缓存 • 大型系统的互操作标准接口,实现大型

系统的可扩展性和可伸缩性

Thursday, August 4, 11



第18页

HTTP语义被混淆

<a href=“/delete_user/1”>Delete User</a> <a href=“/users/1”>Delete User</a>

Thursday, August 4, 11



第19页

基于HTTP的资源缓存



/blog/123



Etag Last-Modified



"12523074“ Thu, 29 May 2010 09:43:46 GMT



/blog/123

If-Modified-Since Thu, 29 May 2010 09:43:46 GMT If-None-Match "12523074 "



304 Not Modified



Thursday, August 4, 11



第20页

JavaEye的资源

• 新闻文章 • 论坛主题贴 • 博客文章 • RSS订阅 • ...... (所有经常被访问的资源都可以缓存)

Thursday, August 4, 11



第21页

def news fresh_when(:last_modified => News.last.created_at, :etag => News.last)

end

@blogs = @blog_owner.last_blogs @hash = @blogs.collect{|b| {b.id => b.post.modified_at.to_i + b.posts_count}}.hash if stale?(:last_modified => (@blog_owner.last_blog.post.modified_at || @blog_owner.last_blog.post.created_at), :etag => @hash)

render :template => "blog" end

def board @topics = @forum.topics.paginate... if logged_in? || stale?(:last_modified =>

@topics[0].last_post.created_at, :etag => @topics.collect{|t| {t.id => t.posts_count}}.hash)

@announcements = (params[:page] || 1).to_i == 1 ? Topic.find :all, :conditions...

render :action => 'show' end end

Thursday, August 4, 11



第22页

def show @topic = Topic.find params[:id] user_session.update_....... if logged_in? Topic.increment_counter(...) if ...... @posts = @topic.post_by_page params[:page] posts_hash = @posts.collect{|p| {p.id => p.modified_at}}.hash topic_hash = @topic.forum_id + @topic.sys_tag_id.to_i + @topic.title.hash +

@topic.status_flag.hash ad_hash = ... (广告的hash算法,略) if logged_in? || stale?(:etag => [posts_hash, topic_hash, ad_hash]) render end

end

def topic @topic = Topic.find(params[:id]) unless logged_in? if request.not_modified?(5.days.ago) head :not_modified else response.last_modified = Time.now end end

end

Thursday, August 4, 11



第23页

Effect

• 30,000 304 status responses per day

Thursday, August 4, 11



第24页

基于Web的简单架构



浏览器



Web服务器



应用服务器



数据库



Thursday, August 4, 11



操作系统的文件系统



Web应用架构



存储设备



第25页

缓存系统的分层架构



• 操作系统磁盘缓存: 减少磁盘机械操作



• 数据库缓存



: 减少文件系统I/O



• 应用程序缓存



: 减少数据库查询



• Web服务器缓存 : 减少应用服务器请求



• 客户端浏览器缓存 : 减少对网站的访问



Thursday, August 4, 11



第26页

⼀一个误区

• n+1条SQL真的是低性能的吗? • 减少磁盘I/O才是数据库优化的终极之道

Thursday, August 4, 11



第27页

2007.02

• 把posts表的大字段剥离出来 • posts表的select count操作从80秒减少到

0.1秒

Thursday, August 4, 11



第28页

posts表

• posts(id, ..., body) • 磁盘存储空间6GB

Thursday, August 4, 11



第29页

剥离后posts表

• posts(id, post_text_id,...) 210MB • post_texts(id, body) 6GB

Thursday, August 4, 11



第30页

Thursday, August 4, 11



第31页

应用缓存概述

• 对象缓存 • 查询缓存 • 页面缓存 • 动态页面静态化(page cache) • 页面片段缓存(frgment cache) • 基于REST资源的缓存

Thursday, August 4, 11



第32页

对象缓存原则

• 数据库表的设计要细颗粒度 • 把有冗余字段的大表拆分为n个互相外键

关联的小表

• ORM的性能瓶颈不在于表关联,而在于 大表的全表扫描

• 尽量避免join查询,多制造n+1条SQL

Thursday, August 4, 11



第33页

对象缓存的意义

• Web应用很容易通过群集方式实现横向 扩展,系统的瓶颈往往出现在数据库

• 数据库的瓶颈往往出现在磁盘IO读写 • 因此要避免数据库的全表扫描和大表的

数据扫描操作

• 如何避免:拆表和臭名昭著的n+1条SQL

Thursday, August 4, 11



第34页

山寨cache plugin

• 基于Rails Cache的简单封装,仅60行代码 • 可以自动实现对象缓存的管理,n:1关系

的缓存,但不支持1:n集合缓存

• memcached缓存命中率超过96%

Thursday, August 4, 11



第35页

memcached统计信息

Thursday, August 4, 11



第36页

memcached统计信息

Thursday, August 4, 11



第37页

cache_money

• 出自twitter开发团队之手 • 可能是目前最强大的ruby cache框架 • 支持分页查询缓存,支持条件查询缓存

Thursday, August 4, 11



第38页

页面片段缓存

• JavaEye大量使用页面片段缓存 • 网站首页、新闻频道、个人博客左边导

航条等等

• 可以有效降低ruby应用的负载

Thursday, August 4, 11



第39页

JavaEye遇到的问题

• ruby负载远远超过db,Web Server的load 是DB的两倍

• 单纯对象缓存不能减轻ruby的负载

Thursday, August 4, 11



第40页

ruby的性能问题

• ruby的正则表达式运算性能很糟糕 • erb的性能也不好 • 大量字符串运算性能差 • 解决之道:用片段缓存降低ruby字符串

处理频率

Thursday, August 4, 11



第41页

不直接缓存post的内容,改为缓 存post内容生成的html片段

Thursday, August 4, 11



第42页

不直接缓存post的内容,改为缓 存post内容生成的html片段

Thursday, August 4, 11



第43页

JavaEye的缓存参考

• memcached缓存命中率96% • cache get : sql query = 4 : 1

Thursday, August 4, 11



第44页

在Rails之外寻求方案

• long-term request设置timeout • SQF Dispatcher (short queue first) • 用memcached实现计数器功能 • 用Redis的Set实现BlackList • 用Redis的List数据结构实现高性能队列 • crontab和 ``实现异步操作

Thursday, August 4, 11



第45页

`curl "http://blogsearch.google.com/ping? name=#{CGI::escape(@blog_owner.blog_name || @blog_owner.name + '的博客')} &url=#{CGI::escape blog_homepage_url(@blog_owner)} &changesURL=#{CGI::escape(blog_homepage_u rl(@blog_owner) + '/rss')}" > /dev/null 2>&1 &`

Thursday, August 4, 11



第46页

ip_counter = Rails.cache.increment(request.remote_ip) if !ip_counter

Rails.cache.write(request.remote_ip, 1, :expires_in => 30.minutes) elsif ip_counter > 2000

crawler_counter = Rails.cache.increment("crawler/ #{request.remote_ip}")

if !crawler_counter Rails.cache.write("crawler/#{request.remote_ip}", 1, :expires_in

=> 10.minutes) elsif crawler_counter > 50 BlackList.add(ip_sec) render :file => "#{RAILS_ROOT}/public/403.html", :status => 403

and return false end render :template => 'test', :status => 401 and return false

end

Thursday, August 4, 11



支持文件格式:*.pdf
上传最后阶段需要进行在线转换,可能需要1~2分钟,请耐心等待。