[转载]ORM对象缓存探讨 - robbin的自言自语

mikel阅读(1087)

[转载]ORM对象缓存探讨 – robbin的自言自语.

对象缓存和n+1问题分析

我们常见的OLTP类型的web应用,性能瓶颈往往是数据库查询,因为应用服务器层面可以水平扩展,但是数据库是单点的,很难水平扩展,当数据库服 务器发生磁盘IO,往往无法有效提高性能,因此如何有效降低数据库查询频率,减轻数据库磁盘IO压力,是web应用性能问题的根源。

对象缓存是所有缓存技术当中适用场景最广泛的,任何OLTP应用,即使实时性要求很高,你也可以使用对象缓存,而且好的ORM实现,对象缓存是完全透明的,完全不需要你的程序代码进行硬编码。

用不用对象缓存,怎么用对象缓存,不是一个简单的代码调优技巧,而是整个应用的架构问题。在你开发一个应用之前,你就要想清楚,这个应用最终的场景是什么?会有多大的用户量和数据量。你将采用什么方式来架构这个应用:

也许你偏好对SQL语句级别的优化,数据库设计当大表有很多冗余字段,会尽量消除大表之间的关联关系,当数据量很大以后,选择分库分表的优化方式, 这是目前业界常规做法。但是也可以选择使用ORM的对象缓存优化方式:数据库设计避免出现大表,比较多的表关联关系,通过ORM以对象化方式操作,利用对 象缓存提升性能。举个例子:

论坛的列表页面,需要显示topic的分页列表,topic作者的名字,topic最后回复帖子的作者,常规做法:

select ... from topic left join user left join post ..... 

你需要通过join user表来取得topic作者的名字,然后你还需要join post表取得最后回复的帖子,post再join user表取得最后回贴作者名字。也许你说,我可以设计表冗余,在topic里面增加username,在post里面增加username,所以通过大 表冗余字段,消除了复杂的表关联:

select ... from topic left join post... 

OK,且不说冗余字段的维护问题,现在仍然是两张大表的关联查询。然后让我们看看ORM怎么做?

select * from topic where ... --分页条件 

就这么一条SQL搞定,比上面的关联查询对数据库的压力小多了。 也许你说,不对阿,作者信息呢?回贴作者信息呢?这些难道不会发送SQL吗?如果发送SQL,这不就是臭名昭著的n+1条问题吗? 你说的对,最坏情况下,会有很多条SQL:

select * from user where id = topic_id...; .... select * from user where id = topic_id...; select * from post where id = last_topic_id...; .... select * from post where id = last_topic_id...; select * from user where id = post_id...; .... select * from user where id = post_id...; 

事实上何止n+1,根本就是3n+1条SQL了。那你怎么还说ORM性能高呢? 因为对象缓存在起作用,你可以观察到后面的3n条SQL语句全部都是基于主键的单表查询,这3n条语句在理想状况下(比较繁忙的web网站的热点数据), 全部都可以命中缓存。所以事实上只有一条SQL,就是:

select * from topic where ...--分页条件 

这条单表的条件查询和直接使用join查询SQL通过字段冗余简化过后的大表关联查询相比,当数据量大到一定程度以后对数据库磁盘IO的压力很小,这就是对象缓存的真正威力!

更进一步分析,使用ORM,我们不考虑缓存的情况,那么就是3n+1条SQL。但是这3n+1条SQL的执行速度一定比SQL的大表关联查询慢吗? 不一定!因为使用ORM的情况下,第一条SQL是单表的条件查询,在有索引的情况下,速度很快,后面的3n条SQL都是单表的主键查询,在繁忙的数据库系 统当中,3n条SQL几乎可以全部命中数据库的data buffer。但是使用SQL的大表关联查询,很可能会造成大范围的表扫描,造成频繁的数据库服务器磁盘IO,性能有可能是非常差的。

因此,即使不使用对象缓存,ORM的n+1条SQL性能仍然很有可能超过SQL的大表关联查询,而且对数据库磁盘IO造成的压力要小很多。这个结论貌似令人难以置信,但经过我的实践证明,就是事实。前提是数据量和访问量都要比较大,否则看不出来这种效果。

对象缓存的命中率

应用场景

是OLTP还是OLAP应用,即使是OLTP,也要看访问的频度,一个极少被访问到的缓存等于没有什么效果。一般来说,互联网网站是非常适合缓存应用的场景。

缓存的粒度

毫无疑问,缓存的粒度越小,命中率就越高,对象缓存是目前缓存粒度最小的,因此被命中的几率更高。举个例子来说吧:你访问当前这个页面,浏览帖子, 那么对于ORM来说,需要发送n条SQL,取各自帖子user的对象。很显然,如果这个user在其他帖子里面也跟贴了,那么在访问那个帖子的时候,就可 以直接从缓存里面取这个user对象了。

架构的设计

架构的设计对于缓存命中率也有至关重要的影响。例如你应该如何去尽量避免缓存失效的问题,如何尽量提供频繁访问数据的缓存问题,这些都是考验架构师 水平的地方。再举个例子来说,对于论坛,需要记录每个topic的浏览次数,所以每次有人访问这个topic,那么topic表就要update一次,这 意味着什么呢?对于topic的对象缓存是无效的,每次访问都要更新缓存。那么可以想一些办法,例如增加一个中间变量记录点击次数,每累计一定的点击,才 更新一次数据库,从而减低缓存失效的频率。

缓存的容量和缓存的有效期

缓存太小,造成频繁的LRU,也会降低命中率,缓存的有效期太短也会造成缓存命中率下降。

所以缓存命中率问题不能一概而论,一定说命中率很低或者命中率很高。但是如果你对于缓存的掌握很精通,有意识的去调整应用的架构,去分解缓存的粒度,总是会带来很高的命中率的。

[转载]Web应用的缓存设计模式 - robbin的自言自语

mikel阅读(1098)

[转载]Web应用的缓存设计模式 – robbin的自言自语.

ORM缓存引言

从10年前的2003年开始,在Web应用领域,ORM(对象-关系映射)框架就开始逐渐普及,并且流行开来,其中最广为人知的就是Java的开源 ORM框架Hibernate,后来Hibernate也成为了EJB3的实现框架;2005年以后,ORM开始普及到其他编程语言领域,其中最有名气的 是Ruby on rails框架的ORM - ActiveRecord。如今各种开源框架的ORM,乃至ODM(对象-文档关系映射,用在访问NoSQLDB)层出不穷,功能都十分强大,也很普及。

然而围绕ORM的性能问题,也一直有很多批评的声音。其实ORM的架构对插入缓存技术是非常容易的,我做的很多项目和产品,但凡使用ORM,缓存都 是标配,性能都非常好。而且我发现业界使用ORM的案例都忽视了缓存的运用,或者说没有意识到ORM缓存可以带来巨大的性能提升。

ORM缓存应用案例

我们去年有一个老产品重写的项目,这个产品有超过10年历史了,数据库的数据量很大,多个表都是上千万条记录,最大的表记录达到了9000万条,Web访问的请求数每天有300万左右。

老产品采用了传统的解决性能问题的方案:Web层采用了动态页面静态化技术,超过一定时间的文章生成静态HTML文件;对数据库进行分库分表,按年 拆表。动态页面静态化和分库分表是应对大访问量和大数据量的常规手段,本身也有效。但它的缺点也很多,比方说增加了代码复杂度和维护难度,跨库运算的困难 等等,这个产品的代码维护历来非常困难,导致bug很多。

进行产品重写的时候,我们放弃了动态页面静态化,采用了纯动态网页;放弃了分库分表,直接操作千万级,乃至近亿条记录的大表进行SQL查询;也没有 采取读写分离技术,全部查询都是在单台主数据库上进行;数据库访问全部使用ActiveRecord,进行了大量的ORM缓存。上线以后的效果非常好:单 台MySQL数据库服务器CPU的IO Wait低于5%;用单台1U服务器2颗4核至强CPU已经可以轻松支持每天350万动态请求量;最重要的是,插入缓存并不需要代码增加多少复杂度,可维 护性非常好。

总之,采用ORM缓存是Web应用提升性能一种有效的思路,这种思路和传统的提升性能的解决方案有很大的不同,但它在很多应用场景(包括高度动态化 的SNS类型应用)非常有效,而且不会显著增加代码复杂度,所以这也是我自己一直偏爱的方式。因此我一直很想写篇文章,结合示例代码介绍ORM缓存的编程 技巧。

今年春节前后,我开发自己的个人网站项目,有意识的大量使用了ORM缓存技巧。对一个没多少访问量的个人站点来说,有些过度设计了,但我也想借这个机会把常用的ORM缓存设计模式写成示例代码,提供给大家参考。我的个人网站源代码是开源的,托管在github上:robbin_site

ORM缓存的基本理念

我在2007年的时候写过一篇文章,分析ORM缓存的理念:ORM对象缓存探讨 ,所以这篇文章不展开详谈了,总结来说,ORM缓存的基本理念是:

  • 以减少数据库服务器磁盘IO为最终目的,而不是减少发送到数据库的SQL条数。实际上使用ORM,会显著增加SQL条数,有时候会成倍增加SQL。
  • 数据库schema设计的取向是尽量设计 细颗粒度 的表,表和表之间用外键关联,颗粒度越细,缓存对象的单位越小,缓存的应用场景越广泛
  • 尽量避免多表关联查询,尽量拆成多个表单独的主键查询,尽量多制造 n + 1 条查询,不要害怕“臭名昭著”的 n + 1 问题,实际上 n + 1 才能有效利用ORM缓存

利用表关联实现透明的对象缓存

在设计数据库的schema的时候,设计多个细颗粒度的表,用外键关联起来。当通过ORM访问关联对象的时候,ORM框架会将关联对象的访问转化成用主键查询关联表,发送 n + 1条SQL。而基于主键的查询可以直接利用对象缓存。

我们自己开发了一个基于ActiveRecord封装的对象缓存框架:second_level_cache ,从这个ruby插件的名称就可以看出,实现借鉴了Hibernate的二级缓存实现。这个对象缓存的配置和使用,可以看我写的ActiveRecord对象缓存配置

下面用一个实际例子来演示一下对象缓存起到的作用:访问我个人站点的首页。 这个页面的数据需要读取三张表:blogs表获取文章信息,blog_contents表获取文章内容,accounts表获取作者信息。三张表的model定义片段如下,完整代码请看models

class Account < ActiveRecord::Base acts_as_cached has_many :blogs end class Blog < ActiveRecord::Base acts_as_cached belongs_to :blog_content, :dependent => :destroy belongs_to :account, :counter_cache => true end class BlogContent < ActiveRecord::Base acts_as_cached end 

传统的做法是发送一条三表关联的查询语句,类似这样的:

SELECT blogs.*, blog_contents.content, account.name FROM blogs LEFT JOIN blog_contents ON blogs.blog_content_id = blog_contents.id LEFT JOIN accounts ON blogs.account_id = account.id 

往往单条SQL语句就搞定了,但是复杂SQL的带来的表扫描范围可能比较大,造成的数据库服务器磁盘IO会高很多,数据库实际IO负载往往无法得到有效缓解。

我的做法如下,完整代码请看home.rb

@blogs = Blog.order('id DESC').page(params[:page]) 

这是一条分页查询,实际发送的SQL如下:

SELECT * FROM blogs ORDER BY id DESC LIMIT 20 

转成了单表查询,磁盘IO会小很多。至于文章内容,则是通过blog.content的对象访问获得的,由于首页抓取20篇文章,所以实际上会多出来20条主键查询SQL访问blog_contents表。就像下面这样:

DEBUG - BlogContent Load (0.3ms) SELECT `blog_contents`.* FROM `blog_contents` WHERE `blog_contents`.`id` = 29 LIMIT 1 DEBUG - BlogContent Load (0.2ms) SELECT `blog_contents`.* FROM `blog_contents` WHERE `blog_contents`.`id` = 28 LIMIT 1 DEBUG - BlogContent Load (1.3ms) SELECT `blog_contents`.* FROM `blog_contents` WHERE `blog_contents`.`id` = 27 LIMIT 1 ...... DEBUG - BlogContent Load (0.9ms) SELECT `blog_contents`.* FROM `blog_contents` WHERE `blog_contents`.`id` = 10 LIMIT 1 

但是主键查询SQL不会造成表的扫描,而且往往已经被数据库buffer缓存,所以基本不会发生数据库服务器的磁盘IO,因而总体的数据库IO负载 会远远小于前者的多表联合查询。特别是当使用对象缓存之后,会缓存所有主键查询语句,这20条SQL语句往往并不会全部发生,特别是热点数据,缓存命中率 很高:

DEBUG - Cache read: robbin/blog/29/1 DEBUG - Cache read: robbin/account/1/0 DEBUG - Cache read: robbin/blogcontent/29/0 DEBUG - Cache read: robbin/account/1/0 DEBUG - Cache read: robbin/blog/28/1 ...... DEBUG - Cache read: robbin/blogcontent/11/0 DEBUG - Cache read: robbin/account/1/0 DEBUG - Cache read: robbin/blog/10/1 DEBUG - Cache read: robbin/blogcontent/10/0 DEBUG - Cache read: robbin/account/1/0 

拆分n+1条查询的方式,看起来似乎非常违反大家的直觉,但实际上这是真理,我实践经验证明:数据库服务器的瓶颈往往是磁盘IO,而不是SQL并发数量。因此 拆分n+1条查询本质上是以增加n条SQL语句为代价,简化复杂SQL,换取数据库服务器磁盘IO的降低 当然这样做以后,对于ORM来说,有额外的好处,就是可以高效的使用缓存了。

按照column拆表实现细粒度对象缓存

数据库的瓶颈往往在磁盘IO上,所以应该尽量避免对大表的扫描。传统的拆表是按照row去拆分,保持表的体积不会过大,但是缺点是造成应用代码复杂度很高;使用ORM缓存的办法,则是按照column进行拆表,原则一般是:

  • 将大字段拆分出来,放在一个单独的表里面,表只有主键和大字段,外键放在主表当中
  • 将不参与where条件和统计查询的字段拆分出来,放在独立的表中,外键放在主表当中

按照column拆表本质上是一个去关系化的过程。主表只保留参与关系运算的字段,将非关系型的字段剥离到关联表当中,关联表仅允许主键查询,以Key-Value DB的方式来访问。因此这种缓存设计模式本质上是一种SQLDB和NoSQLDB的混合架构设计

下面看一个实际的例子:文章的内容content字段是一个大字段,该字段不能放在blogs表中,否则会造成blogs表过大,表扫描造成较多的磁盘IO。我实际做法是创建blog_contents表,保存content字段,schema简化定义如下:

CREATE TABLE `blogs` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(255) NOT NULL, `blog_content_id` int(11) NOT NULL, `content_updated_at` datetime DEFAULT NULL, PRIMARY KEY (`id`), ); CREATE TABLE `blog_contents` ( `id` int(11) NOT NULL AUTO_INCREMENT, `content` mediumtext NOT NULL, PRIMARY KEY (`id`) ); 

blog_contents表只有content大字段,其外键保存到主表blogs的blog_content_id字段里面。

model定义和相关的封装如下:

class Blog < ActiveRecord::Base acts_as_cached delegate :content, :to => :blog_content, :allow_nil => true def content=(value) self.blog_content ||= BlogContent.new self.blog_content.content = value self.content_updated_at = Time.now end end class BlogContent < ActiveRecord::Base acts_as_cached validates :content, :presence => true end 

在Blog类上定义了虚拟属性content,当访问blog.content的时候,实际上会发生一条主键查询的SQL语句,获取blog_content.content内容。由于BlogContent上面定义了对象缓存acts_as_cached,只要被访问过一次,content内容就会被缓存到memcached里面。

这种缓存技术实际会非常有效,因为: 只要缓存足够大,所有文章内容可以全部被加载到缓存当中,无论文章内容表有多么大,你都不需要再访问数据库了 更进一步的是: 这张大表你永远都只需要通过主键进行访问,绝无可能出现表扫描的状况 为何当数据量大到9000万条记录以后,我们的系统仍然能够保持良好的性能,秘密就在于此。

还有一点非常重要: 使用以上两种对象缓存的设计模式,你除了需要添加一条缓存声明语句acts_as_cached以外,不需要显式编写一行代码 有效利用缓存的代价如此之低,何乐而不为呢?

以上两种缓存设计模式都不需要显式编写缓存代码,以下的缓存设计模式则需要编写少量的缓存代码,不过代码的增加量非常少。

写一致性缓存

写一致性缓存,叫做write-through cache,是一个CPU Cache借鉴过来的概念,意思是说,当数据库记录被修改以后,同时更新缓存,不必进行额外的缓存过期处理操作。但在应用系统中,我们需要一点技巧来实现写一致性缓存。来看一个例子:

我的网站文章原文是markdown格式的,当页面显示的时候,需要转换成html的页面,这个转换过程本身是非常消耗CPU的,我使用的是 Github的markdown的库。Github为了提高性能,用C写了转换库,但如果是非常大的文章,仍然是一个耗时的过程,Ruby应用服务器的负 载就会比较高。

我的解决办法是缓存markdown原文转换好的html页面的内容,这样当再次访问该页面的时候,就不必再次转换了,直接从缓存当中取出已经缓存好的页面内容即可,极大提升了系统性能。我的网站文章最终页的代码执行时间开销往往小于10ms,就是这个原因。代码如下:

def md_content  # cached markdown format blog content APP_CACHE.fetch(content_cache_key) { GitHub::Markdown.to_html(content, :gfm) } end 

这里存在一个如何进行缓存过期的问题,当文章内容被修改以后,应该更新缓存内容,让老的缓存过期,否则就会出现数据不一致的现象。进行缓存过期处理是比较麻烦的,我们可以利用一个技巧来实现自动缓存过期:

def content_cache_key "#{CACHE_PREFIX}/blog_content/#{self.id}/#{content_updated_at.to_i}" end 

当构造缓存对象的key的时候,我用文章内容被更新的时间来构造key值,这个文章内容更新时间用的是blogs表的 content_updated_at字段,当文章被更新的时候,blogs表会进行update,更新该字段。因此每当文章内容被更新,缓存的页面内容 的key就会改变,应用程序下次访问文章页面的时候,缓存就会失效,于是重新调用GitHub::Markdown.to_html(content, :gfm)生成新的页面内容。 而老的页面缓存内容再也不会被应用程序存取,根据memcached的LRU算法,当缓存填满之后,将被优先剔除。

除了文章内容缓存之外,文章的评论内容转换成html以后也使用了这种缓存设计模式。具体可以看相应的源代码:blog_comment.rb

片段缓存和过期处理

Web应用当中有大量的并非实时更新的数据,这些数据都可以使用缓存,避免每次存取的时候都进行数据库查询和运算。这种片段缓存的应用场景很多,例如:

  • 展示网站的Tag分类统计(只要没有更新文章分类,或者发布新文章,缓存一直有效)
  • 输出网站RSS(只要没有发新文章,缓存一直有效)
  • 网站右侧栏(如果没有新的评论或者发布新文章,则在一段时间例如一天内基本不需要更新)

以上应用场景都可以使用缓存,代码示例:

def self.cached_tag_cloud APP_CACHE.fetch("#{CACHE_PREFIX}/blog_tags/tag_cloud") do self.tag_counts.sort_by(&:count).reverse end end 

对全站文章的Tag云进行查询,对查询结果进行缓存

<% cache("#{CACHE_PREFIX}/layout/right", :expires_in => 1.day) do %> <div class="tag"> <% Blog.cached_tag_cloud.select {|t| t.count > 2}.each do |tag| %> <%= link_to "#{tag.name}<span>#{tag.count}</span>".html_safe, url(:blog, :tag, :name => tag.name) %> <% end %> </div> ...... <% end %> 

对全站右侧栏页面进行缓存,过期时间是1天。

缓存的过期处理往往是比较麻烦的事情,但在ORM框架当中,我们可以利用model对象的回调,很容易实现缓存过期处理。我们的缓存都是和文章,以 及评论相关的,所以可以直接注册Blog类和BlogComment类的回调接口,声明当对象被保存或者删除的时候调用删除方法:

class Blog < ActiveRecord::Base acts_as_cached after_save :clean_cache before_destroy :clean_cache def clean_cache APP_CACHE.delete("#{CACHE_PREFIX}/blog_tags/tag_cloud") # clean tag_cloud APP_CACHE.delete("#{CACHE_PREFIX}/rss/all") # clean rss cache APP_CACHE.delete("#{CACHE_PREFIX}/layout/right") # clean layout right column cache in _right.erb end end class BlogComment < ActiveRecord::Base acts_as_cached after_save :clean_cache before_destroy :clean_cache def clean_cache APP_CACHE.delete("#{CACHE_PREFIX}/layout/right") # clean layout right column cache in _right.erb end end 

在Blog对象的after_savebefore_destroy上注册clean_cache方法,当文章被修改或者删除的时候,删除以上缓存内容。总之,可以利用ORM对象的回调接口进行缓存过期处理,而不需要到处写缓存清理代码。

对象写入缓存

我们通常说到缓存,总是认为缓存是提升应用读取性能的,其实缓存也可以有效的提升应用的写入性能。我们看一个常见的应用场景:记录文章点击次数这个功能。

文章点击次数需要每次访问文章页面的时候,都要更新文章的点击次数字段view_count,然后文章必须实时显示文章的点击次数,因此常见的读缓存模式完全无效了。每次访问都必须更新数据库,当访问量很大以后数据库是吃不消的,因此我们必须同时做到两点:

  • 每次文章页面被访问,都要实时更新文章的点击次数,并且显示出来
  • 不能每次文章页面被访问,都更新数据库,否则数据库吃不消

对付这种应用场景,我们可以利用对象缓存的不一致,来实现对象写入缓存。原理就是每次页面展示的时候,只更新缓存中的对象,页面显示的时候优先读取缓存,但是不更新数据库,让缓存保持不一致,积累到n次,直接更新一次数据库,但绕过缓存过期操作。具体的做法可以参考blog.rb

# blog viewer hit counter def increment_view_count increment(:view_count) # add view_count += 1 write_second_level_cache # update cache per hit, but do not touch db # update db per 10 hits self.class.update_all({:view_count => view_count}, :id => id) if view_count % 10 == 0 end 

increment(:view_count)增加view_count计数,关键代码是第2行write_second_level_cache,更新view_count之后直接写入缓存,但不更新数据库。累计10次点击,再更新一次数据库相应的字段。另外还要注意,如果blog对象不是通过主键查询,而是通过查询语句构造的,要优先读取一次缓存,保证页面点击次数的显示一致性,因此 _blog.erb 这个页面模版文件开头有这样一段代码:

<% # read view_count from model cache if model has been cached. view_count = blog.view_count if b = Blog.read_second_level_cache(blog.id) view_count = b.view_count end %> 

采用对象写入缓存的设计模式,就可以非常容易的实现写入操作的缓存,在这个例子当中,我们仅仅增加了一行缓存写入代码,而这个时间开销大约是1ms,就可以实现文章实时点击计数功能,是不是非常简单和巧妙?实际上我们也可以使用这种设计模式实现很多数据库写入的缓存功能。

常用的ORM缓存设计模式就是以上的几种,本质上都是非常简单的编程技巧,代码的增加量和复杂度也非常低,只需要很少的代码就可以实现,但是在实际 应用当中,特别是当数据量很庞大,访问量很高的时候,可以发挥惊人的效果。我们实际的系统当中,缓存命中次数:SQL查询语句,一般都是5:1左右,即每 次向数据库查询一条SQL,都会在缓存当中命中5次,数据主要都是从缓存当中得到,而非来自于数据库了。

其他缓存的使用技巧

还有一些并非ORM特有的缓存设计模式,但是在Web应用当中也比较常见,简单提及一下:

用数据库来实现的缓存

在我这个网站当中,每篇文章都标记了若干tag,而tag关联关系都是保存到数据库里面的,如果每次显示文章,都需要额外查询关联表获取tag,显然会非常消耗数据库。在我使用的acts-as-taggable-on插件中,它在blogs表当中添加了一个cached_tag_list字段,保存了该文章标记的tag。当文章被修改的时候,会自动相应更新该字段,避免了每次显示文章的时候都需要去查询关联表的开销。

HTTP客户端缓存

基于资源协议实现的HTTP客户端缓存也是一种非常有效的缓存设计模式,我在2009年写过一篇文章详细的讲解了:基于资源的HTTP Cache的实现介绍 ,所以这里就不再复述了。

用缓存实现计数器功能

这种设计模式有点类似于对象写入缓存,利用缓存写入的低开销来实现高性能计数器。举一个例子:用户登录为了避免遭遇密码暴力破解,我限定了每小时每IP只能尝试登录5次,如果超过5次,拒绝该IP再次尝试登录。代码实现很简单,如下:

post :login, :map => '/login' do login_tries = APP_CACHE.read("#{CACHE_PREFIX}/login_counter/#{request.ip}") halt 403 if login_tries && login_tries.to_i > 5 # reject ip if login tries is over 5 times @account = Account.new(params[:account]) if login_account = Account.authenticate(@account.email, @account.password) session[:account_id] = login_account.id redirect url(:index) else # retry 5 times per one hour APP_CACHE.increment("#{CACHE_PREFIX}/login_counter/#{request.ip}", 1, :expires_in => 1.hour) render 'home/login' end end 

等用户POST提交登录信息之后,先从缓存当中取该IP尝试登录次数,如果大于5次,直接拒绝掉;如果不足5次,而且登录失败,计数加1,显示再次尝试登录页面。

以上相关代码可以从这里获取:robbin_site

[转载]VS发布Web时自动调用YUICompressor批量压缩JS、CSS - rentj - 博客园

mikel阅读(1103)

[转载]VS发布Web时自动调用YUICompressor批量压缩JS、CSS – rentj – 博客园.

在Visual Studio中通过修改发布配置文件,可以在发布Web时自动调用YUICompressor批量压缩项目中JS和CSS。这种方式的优点,一是不需要在 项目的js、css文件夹中单独建立Debug子文件夹来存放未经压缩的文件,二是使用Debug模式发布时不会进行压缩方便调试。具体方法如下:

1 安装JRE,下载YUICompressor,并解压(如:E:\工具\yuicompressor)

2 新建Compressor.bat文件内容为:

@echo off
if “%1” == “” goto exit
pushd “%1”
echo 正在压缩Css文件
for /r %%i in (*.css) do call “java.exe” -jar E:\工具\yuicompressor\yuicompressor.jar -o %%i %%i
echo 正在压缩js文件
for /r %%i in (*.js) do call “java.exe” -jar E:\工具\yuicompressor\yuicompressor.jar -o %%i %%i
:exit
exit
3 修改项目的发布配置文件, 项目的发布配置文件名为 <profilename>.pubxml,位于项目文件夹下的properties\PublishProfiles文件夹

增加下面的内容:

<Target Name=”YUICompressor” AfterTargets=”CopyAllFilesToSingleFolderForPackage” Condition=”‘$(ConfigurationName)’==’Release'”>
<Message Text=”调用YUICompressor压缩CSS、JS” Importance=”high” />
<Exec Command=”call E:\工具\Compressor.bat $(ProjectDir)obj\$(ConfigurationName)\Package\” />
</Target>

注: E:\工具\Compressor.bat路径根据需要替换。

完成上面的工作后在“解决方案资源管理器”中右击要发布的项目点击“发布”后就可以在项目发布文件夹中看到已经压缩过的CSS、JS了

[转载]Jquery打造可以上下移动行的表格 - Joe Zhou - 博客园

mikel阅读(1055)

[转载]Jquery打造可以上下移动行的表格 – Joe Zhou – 博客园.

 

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>表格</title>
<style type="text/css">
.editText
{
    border-width:1px;
    border-top-style:none;
    border-left-style:none;
    border-right-style:none;
    border-bottom-style:solid;
    border-color:#030;
    width:100%;
}
</style>
<script src="jquery.js" type="text/javascript">
</script>
<script type="text/javascript">
function moveUp(obj)
{
    var current=$(obj).parent().parent();
    var prev=current.prev();
    if(current.index()>1)
    {
        current.insertBefore(prev);
    }
}
function moveDown(obj)
{
    var current=$(obj).parent().parent();
    var next=current.next();
    if(next)
    {
        current.insertAfter(next);
    }
}
</script>
</head>
<body>
<table class="grid" width="100%" border="1" cellspacing="0" cellpadding="0">
  <tr>
    <td>字段英文名</td>
    <td>字段中文名</td>
    <td>字段数据类型</td>
    <td>列宽</td>
    <td>是否显示</td>
    <td>是否作为查询条件</td>
    <td>调整顺序</td>
  </tr>
  <tr>
    <td>1</td>
    <td><input type="text" name="textfield" class="editText" id="textfield"></td>
    <td> </td>
    <td><input type="text" name="textfield5" id="textfield5"></td>
    <td><input type="checkbox" name="checkbox" id="checkbox"></td>
    <td><input type="checkbox" name="checkbox5" id="checkbox5"></td>
    <td><a href="javascript:void(0)" onClick="moveUp(this)">上移</a><a href="javascript:void(0)" onClick="moveDown(this)">下移</a></td>
  </tr>
  <tr>
    <td>2</td>
    <td><input type="text" name="textfield2" id="textfield2"></td>
    <td> </td>
    <td><input type="text" name="textfield6" id="textfield6"></td>
    <td><input type="checkbox" name="checkbox2" id="checkbox2"></td>
    <td><input type="checkbox" name="checkbox6" id="checkbox6"></td>
    <td><a href="javascript:void(0)" onClick="moveUp(this)">上移</a><a href="javascript:void(0)" onClick="moveDown(this)">下移</a></td>
  </tr>
  <tr>
    <td>3</td>
    <td><input type="text" name="textfield3" id="textfield3"></td>
    <td> </td>
    <td><input type="text" name="textfield7" id="textfield7"></td>
    <td><input type="checkbox" name="checkbox3" id="checkbox3"></td>
    <td><input type="checkbox" name="checkbox7" id="checkbox7"></td>
    <td><a href="javascript:void(0)" onClick="moveUp(this)">上移</a><a href="javascript:void(0)" onClick="moveDown(this)">下移</a></td>
  </tr>
  <tr>
    <td>4</td>
    <td><input type="text" name="textfield4" id="textfield4"></td>
    <td> </td>
    <td><input type="text" name="textfield8" id="textfield8"></td>
    <td><input type="checkbox" name="checkbox4" id="checkbox4"></td>
    <td><input type="checkbox" name="checkbox8" id="checkbox8"></td>
    <td><a href="javascript:void(0)" onClick="moveUp(this)">上移</a><a href="javascript:void(0)" onClick="moveDown(this)">下移</a></td>
  </tr>
</table>
</body>
</html>

[转载]电子商务网站搜索架构方案 - 叶鹏 - 博客园

mikel阅读(999)

[转载]电子商务网站搜索架构方案 – 叶鹏 – 博客园.

    说是电子商务搜索架构方案,其实就是lucene.net的应用,公司庙小,人少,也就自己平时看看,以前做过一点例子,这样就被拉上去写架构方案了。 我这个懒惰的家伙,在网上疯狂的搜集搜索架构方面的东西,因为做做架构,暂时没写代码,每天就看人家博客,结果两个星期了才弄了个大概的草图,这不清明节 过后就要详细方案了,现在只能把我的草图分享一下,希望大家板砖伺候,闷在家里鼓捣比较郁闷啊,效率太低。

基于lucene的搜索方案

一、            Lucene 简介

Lucene是apache的一个顶级开源项目,由java实现的全文检索引擎,能基于各种文档格式的全文索引和检索,包括word、pdf,不包括图形类。

Lucene.net 是C#版的lucene 是由java的lucene翻译过来的,也被apache列为开源项目对外发布,功能和java的基本一样,但是由于缺乏良好的技术支持和社区活跃度,目前已被apache放入孵化器

Lucene写入:源文件经过analyzer处理,包括分词,权重处理、生成document记录,写入存储器(硬盘或者内存)。

Lucene 读出:对搜索关键词进行analyzer处理,包括分词、权重、范围匹配处理.源码结构图如下:

具体流程如下图:

数据流图如下:

二、常用推荐引擎算法问题

采用基于数据挖掘的算法来实现推荐引擎是各大电子商务网站、SNS社区最为常用的方法,推荐引擎常用Content-Based 推荐算法及协同过 滤算法(Item-Based 、User-based)。但从实际应用来看,对于大部分中小型企业来说,要在电子商务系统完整采用以上算法还有很大的难 度。

1)、相对成熟、完整、现成的开源解决方案较少

粗略分来,目前与数据挖掘及推荐引擎相关的开源项目主要有如下几类:

数据挖掘相关:主要包括Weka、R-Project、Knime、RapidMiner、Orange 等

文本挖掘相关:主要包括OpenNLP、LingPipe、FreeLing、GATE 、Carrot2 等,具体可以参考LingPipe’s Competition

推荐引擎相关:主要包括Apache Mahout、Duine framework、Singular Value Decomposition (SVD) ,其他包可以参考Open Source Collaborative Filtering Written in Java

搜索引擎相关:Lucene、Solr、Sphinx、Hibernate Search等

2)、常用推荐引擎算法相对复杂,入门门槛较高

3)、常用推荐引擎算法性能较低,并不适合海量数据挖掘

以上这些包或算法,除了Lucene/Sor相对成熟外,大部分都还处于学术研究使用,并不能直接应用于互联网规模的数据挖掘及推荐引擎引擎使用。

(以上都是基于java的,需要自己去研究实现,有很大难度)

备注:除了分类查找和主动搜索,推荐系统也是用户浏览商品的重要途径,能帮助用户发现类似并感兴趣的产品,增加商品的访问量,将访问者转化为购买者,引导用户购买。最终产生的价值是提升用户购物体验和用户粘度,提高订单量,如Amazon30%的订单来自推荐系统。

采用Lucene实现推荐引擎的优势

对很多众多的中小型网站而言,由于开发能力有限,如果有能够集成了搜索、推荐一体化的解决方案,这样的方案肯定大受欢迎。采用Lucene来实现推荐引擎具有如下优势:

1)、Lucene 入门门槛较低,大部分网站的站内搜索都采用了Lucene

2)、相对于协同过滤算法,Lucene性能较高

3)、Lucene对Text Mining、相似度计算等相关算法有很多现成方案

在开源的项目中,Mahout或者Duine Framework用于推荐引擎是相对完整的方案,尤其是Mahout 核心利用了Lucene,因此其架构很值得借鉴。只不过Mahout目前功能还不 是很完整,直接用其实现电子商务网站的推荐引擎尚不是很成熟。只不过从Mahout实现可以看出采用Lucene实现推荐引擎是一种可行方案。

3、采用Lucene实现推荐引擎需要解决的核心问题

Lucene对于Text Mining较为擅长,在contrib包中提供了MoreLikeThis功能,可以较为容易实现Content-Based的推荐,但对于涉及用户协 同过滤行为的结果(所谓的Relevance Feedback),Lucene目前并没有好的解决方案。需要在Lucene中内容相似算法中加入用户协同过滤行为对因素,将用户协同过滤行为结果转化 为Lucene所支持的模型。

推荐引擎的数据源

电子商务网站与推荐引擎相关典型的行为:

购买本商品的顾客还买过

浏览本商品的顾客还看过

浏览更多类似商品

喜欢此商品的人还喜欢

用户对此商品的平均打分

因此基于Lucene实现推荐引擎主要要处理如下两大类的数据

1)、内容相似度

例如:商品名称、作者/译者/制造商、商品类别、简介、评论、用户标签、系统标签

2)、用户协同行为相似度

例如:打标签、购买商品、点击流、搜索、推荐、收藏、打分、写评论、问答、页面停留时间、所在群组等等

5、实现方案

5.1、内容相似度 基于Lucene MoreLikeThis实现即可。

5.2、对用户协同行为的处理

1)、用户每一次协同行为都使用lucene来进行索引,每次行为一条记录

2)、索引记录中包含如下重要信息:

商品名、商品id、商品类别、商品简介、标签等重要特征值、用户关联行为的其他商品的特征元素、商品缩略图地址、协同行为类型(购买、点击、收藏、评分等)、Boost值(各协同行为在setBoost时候的权重值)

3)、对评分、收藏、点击等协同行为以商品特征值(标签、标题、概要信息)来表征

4)、不同的协同行为类型(例如购买、评分、点击)设置不同的值setBoost

5)、搜索时候采用Lucene MoreLikeThis算法,将用户协同转化为内容相似度

以上方案只是基于Lucene来实现推荐引擎最为简单的实现方案,方案的准确度及细化方案以后再细说。

更为精细的实现,可以参考Mahout的算法实现来优化。

其他搜索引擎开源工具推荐:Sphinx,目前是基于出自俄罗斯的开源全文搜索引擎软件Sphinx,单一索引最大可包含1亿条记 录,在1千万条记录情况下的查询速度为0.x秒(毫秒级)。Sphinx创建索引的速度为:创建100万条记录的索引只需3~4分钟,创建1000万条记 录的索引可以在50分钟内完成,而只包含最新10万条记录的增量索引,重建一次只需几十秒。

Sphinx 是一个基于 GPL 2 协议颁发的免费开源的全文搜索引擎.它是专门为更好的整合脚本语言和SQL数据库而设计的.当前内置的数据源支持直接从连接到 的 MySQL 或 PostgreSQL获取数据, 或者你可以使用 XML 通道结构(XML pipe mechanism , 一种基于 Sphinx 可识别的特殊xml格式的索引通道)

基于LAMP架构的应用很广泛,目前了解的商业应用有康盛的Discuz企业版。

三、手机之家的搜索方案(参考用)

手机之家目前的Lucene应用,采用的是Lucene 2.4.1 + JDK 1.6(64 bit)的组合,运行在8 CPU, 32G内存的机器上,数据量超过3300万条,原始数据文件超过14G,每天需要支持超过35万次的查询,高峰时期QPS超过20。单看这些数据可能并没 有大的亮点,但它的重建和更新都是自动化完成,而且两项任务可以同时运行,另一方面,在不影响服务可靠性的前提下,尽可能快地更新数据(如果两者发生冲 突,则优先保证可用性,延迟更新),其中的工作量还是非常大的

 

PPT连接 http://www.slideshare.net/tangfl/lucene-1752150

在大规模的应用中,Lucene更适合用于狭义的“搜索”,而不应当负责数据的存储。我们看看Lucene的源代码也可以知道,Document和 Field的存储效率是不够好看的。手机之家的团队也发现了这一点,他们的办法是,用Lucene存放索引,用Memcache + Berkeley DB(Java Edition)负责存储。这样有两个好处,一是减轻了Lucene的数据规模,提高了程序的效率;另一方面,这套系统也可以提供某些类似SQL的查询功 能。实际上,Lucene本身似乎也注意到了这个问题,在Store中新增了一个db的选项,其实也是利用的Berkeley DB。

在大规模应用中,Cache是非常重要的。PPT中也提到,可以在程序提供服务之前,进行几次”预热“搜索,填充Searcher的Cache。据 我们(银杏搜索)的经验,也可以在应用程序中,再提供针对Document的Cache,这样对性能有较大的改善。Lucene自己似乎也注意到了这个问 题,在2.4版本中提供了Cache,并提供了一个LRU Cache实现。不过据我们测试,在极端情况下,这个Cache可能会突破大小限制,一路膨胀最后吃光内存,甚至从网络上找的许多LRU Cache实现在极端条件下都有可能出现这样的问题,最终自己写了一个LRU Cache,并修改多次,目前来看是稳定的。

在编写Java服务程序的时候,记得设置退出的钩子函数(RunTime.getRunTime.addShutdownHook)是一个非常好的 习惯。许多Java程序员都没有这种意识,或者有,也只是写一个finalize函数,结果程序非正常退出时,可能造成某些外部资源的状态不稳定。拿 Lucene来说,之前的IndexWriter是默认autoCommit的,这样每添加一条记录,就提交一次,好处是如果中断,则之前添加的记录都是 可用的,坏处则是,索引的速度非常低。在新版本中autoCommit默认为False,速度提升明显(我们测试的结果是,提高了大约8倍),但如果中途 异常退出,则前功尽弃。如果我们添加了退出的钩子函数,捕获到退出信号则自动调用writer.close()方法,就可以避免这个问题。

目前的Lucene是兼容JDK 1.4的,它的binary版本也是JDK1.4编译的,如果对性能要求比较高,可以自行下载Lucene Source Code,用更新版本的JDK编译出.jar文件,据我测试,速度大约有30%的提升。

四、            XX网搜索方案

4.1 初步解决方案:

实现站内产品的分词搜索、推荐关键词和简单排序,定时自动更新,索引读写分离。

基于服务器的搜索压力大,用户的搜索体验不够有好,初步解决方案目标是解决服务器的搜索压力,实现初步的分词搜索,索引的自动定时维护。

4.1.1 数据库产品表分析:

l        大类基表

l        产品分类扩展基表

l        品牌基表,品牌系列表基表

l        产品基表(主表)

l        颜色基表

产品基表的数据大概在8万条左右,占用空间40m左右,单表数据量相对来书还是比较小的。

4.1.2  Lucene索引程序:

通过lucene的索引程序将库里的数据读入流,然后写入lucene自定义的索引文件,这个索引文件不进行搜索操作,需要完成后替换到搜索索引。 在建立索引的过程中进行分词处理,分词组件采用eaglet开发的盘古分词组件(已基于apache开源协议开源,进一步功能需要自己二次开发)。

4.1.3  Lucene索引库:

基表的索引文件大概在100m左右,分为写入时的库和搜索时用的库,写入库完成后并入搜索库,考虑到新索引合覆盖就索引的瞬间可能产生的索引程序错误或者索引文件损坏,在覆盖的同时通过程序控制让搜索程序读取写索引里的文件。

l        搜索处理services:基于产品库的的搜索,如品牌,分类,价格区间。搜索程序依赖于接口,基于数据库的搜索和基于文件的搜索要按需要随时切换。搜索的同时需要利用分词组件分词处理,对分词后的结果进行检索,数据库检索的暂时不做分词处理。

l        查询处理:查询前台程序使用mvc,实现产品的分词高亮显示,按照类别分类查询,品牌分类查询,价格区间查询。

 

4.2 第二步关键词统计:

搜索关键词的搜集和搜索的联合处理,实现简单的搜索推荐功能。主要是对前台的搜索关键字进行统计分析,并与搜索的排序进行关联,关键词的处理和与主表的关联索引方案等初步处理完成后再做完整解决方案。

 

4.3 第三步优化完善:

实现索引文件的基于消息的增量自动更新,权重计算,推荐产品计算研究,实时搜索的研究。权重计算,需要重新开发自己的向量算法引擎,考虑当中。

实时搜索目前在学习当中。

4.3.1 权重计算

权重计算方法会将前台用户的统计数据和产品库进行关联开发一套天天网产品的权重排序计算方法,以下算法流程图只是一个构思。

权重计算设计


4.3.2 索引自动化更新

             建立基于消息机制的一个索引更新与维护机制。

基于消息队列的索引生成程序

[转载]《实用技巧》让你的网站变成响应式的3个简单步骤 - 梦想天空(山边小溪) - 博客园

mikel阅读(999)

[转载]《实用技巧》——让你的网站变成响应式的3个简单步骤 – 梦想天空(山边小溪) – 博客园.

如今,一个网站只在桌面屏幕上好看是远远不够的,同时也要在平板电脑和智能手机中能够良好呈现。响应式的网站是指它能够适应客户端的屏幕尺寸, 自动响应客户端尺寸变化。在这篇文章中,我将向您展示如何通过3个简单的步骤轻松地使网站变成响应式(Responsive)。

您可能感兴趣的相关文章

 

1 – 布局

当创建一个响应式网站,或让现有的网站变成响应式的,首先要关注的元素的布局。我在建立响应式的网站,总是先创建一个非响应的布局,页面宽度固定大小。如果非响应版本完成得非常不错,我再添加媒体查询(Media Queries)和响应式代码。这种操作方式更容易实现响应式特性,在同一时间专注于一个任务。

当你已经完成了无响应的网站,做的第一件事是在你的 HTML 页面,粘贴下面的代码到<haed>和</head>标签之间。这将设置屏幕按1:1的尺寸显示,在 iPhone 和其他智能手机的浏览器提供网站全视图浏览,并允许用户缩放页面。

 

1
2
3
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="HandheldFriendly" content="true">

 

现在是时候添加一些媒体查询了。根据 W3C 网站,媒体查询由媒体类型和零个或多个媒体查询的条件表达式组成。通过使用媒体查询,外观呈现可以针对特定范围内的输出设备,而不需要改变内容本身。换句 话说,媒体查询让您的网站在各种各种显示器上看起来都很好,从小的智能手机到大的电脑屏幕等等。

媒体查询取决于你的网站布局,所以对我来说为您提供一个现成可以使用的代码片段有点困难。但是,下面的代码对于大多数网站都是一个很好的起点。在这个例子中,#primary 是主要内容区域,#secondary 是侧栏。

从代码中你可以看到,我定义了两种规格:首先有一个最大宽度为1060px,为平板电脑优化的横向显示。#primary 占在其父容器宽度的67%,#senondary 占30%,再加上3%的左外边距。 第二个规格是用于平板电脑和更小的屏幕尺寸。

由于智能手机的屏幕尺寸小,我决定给 #primary 设置100%的宽度,#secondary 也设置100%的宽度,他将在 #primary 下面。 正如我已经说过的,你可能必须要对这段代码位进行修改才能适应您的网站的具体需求。

 

1
2
3
4
5
6
7
8
9
10
11
/* Tablet Landscape */
@media screen and (max-width: 1060px) {
    #primary { width:67%; }
    #secondary { width:30%; margin-left:3%;} 
}
/* Tabled Portrait */
@media screen and (max-width: 768px) {
    #primary { width:100%; }
    #secondary { width:100%; margin:0; border:none; }
}

完成以后,让我们看看你的布局是如何响应的。要做到这一点,我用这 Matt Kersley 创建的一款非常的响应式测试工具

 

2 – 媒体

一个响应式的布局是实现响应网站的第一步。现在,让我们把注意力集中在另外一个现代化网站非常重要的方面:媒体,如视频或图像。 下面的 CSS 代码将确保您的图像将永远不会大于他们的父容器,代码非常简单,适用于大多数网站。请注意,IE6 等旧的浏览器不支持 max-width 指令。

 

1
img { max-width: 100%; }

 

虽然上述技术是有效的,有时你可能需要有更多的图像控制权,例如根据客户端的显示大小,显示不同的图像。

这是由 Nicolas Gallagher 发明的好方法。让我们看看 HTML:

 

1
<img src="image.jpg" data-src-600px="image-600px.jpg" data-src-800px="image-800px.jpg" alt="">

正如你可以看到,我们使用 data-* 属性来存储替换图像的 URL。现在,让我们使用强大的 CSS3 来为匹配 min-device-width 条件的媒体指定替换图像:

 

1
2
3
4
5
6
7
8
9
10
11
@media (min-device-width:600px) {
    img[data-src-600px] {
        content: attr(data-src-600px, url);
    }
}
@media (min-device-width:800px) {
    img[data-src-800px] {
        content: attr(data-src-800px, url);
    }
}

令人印象深刻,是不是?现在,让我们来看看另一个在今天的网站中非常重要的媒体——视频。由于大多数网站使用的视频来自第三方网站,我决定把重点放在 Nick La 的弹性视频技术,这种技术可让您嵌入的响应式的视频。

HTML:

1
2
3
<div class="video-container">
    <iframe src="http://player.vimeo.com/video/6284199?title=0&byline=0&portrait=0" width="800" height="450" frameborder="0"></iframe>
</div>

CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.video-container {
    position: relative;
    padding-bottom: 56.25%;
    padding-top: 30px;
    height: 0;
    overflow: hidden;
}
.video-container iframe, 
.video-container object, 
.video-container embed {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

在你的网站上应用了这些代码后,嵌入的视频也是响应式(Responsive)的了。

 

3 – 字体

本教程的最后一步绝对非常重要,但往往被网站开发人员忽视——字体。到现在为止,大多数开发人员(包括我自己)使用像素来定义字体的大小。虽然 像素在普通网站使用是OK的,但是对于响应式网站来说应该有响应式的字体。事实上,一个响应式的字体大小应关联它的父容器的宽度,这样它才可以适应客户端 的屏幕。

CSS3 规范引入了一个新的单位叫 rem,和 em 类相似,但相对于 HTML 元素来说, rem 更易于使用。

rem 是相对于 HTML 元素的,不要忘了重置 HTML 的字体大小:

 

1
html { font-size:100%; }

完成后,您可以定义响应式的字体大小,如下所示:

 

1
2
3
@media (min-width: 640px) { body {font-size:1rem;} }
@media (min-width:960px) { body {font-size:1.2rem;} }
@media (min-width:1100px) { body {font-size:1.5rem;} }

请注意,旧浏览器不支持 rem 单元,所以不要忘了实现一个替代。

这就是今天的所有内容了,希望你会喜欢这个教程!记得推荐和分享啊!

[转载]事实证明Ajax的世界更需要ASP.NET MVC - dudu - 博客园

mikel阅读(985)

[转载]事实证明Ajax的世界更需要ASP.NET MVC – dudu – 博客园.

真正走进ASP.NET MVC的世界,才知道它的精彩。

抛弃WebService,在.NET4中用 jQuery 调用 WCF”——原来抛弃WebService之后,还可以用JQuery调用ASP.NET MVC的Controller。

Ajax为主的应用不需要ASP.NET MVC”,原来Ajax的世界更需要ASP.NET MVC

曾经天真的想法,在实践中证明了它的天真,但在从天真到事实的过程中,得到的是成长。

下面就谈谈我是如何认识到这个的。相比于结论,其中的过程更重要。

还是以之前文章中的博客园站内短消息功能(显示当前用户短消息列表)为例,开始用的是JQuery插件Templates进行列表数据绑定,后来遇到了两个问题:

1) 在绑定时需要根据条件判断生成不同的元素,比如用户发过来的短消息,发件人显示为链接,如果是系统通知,则显示为文本。Templates对这样的操作处理起来不是很方便;

2) 绑定后的数据无法在服务端重用。有时从搜索引擎友好或者用户体验的角度,在页面第一次加载时,不用ajax,在页面加载后点击刷新或分页链接时,才使用ajax。这样就要在服务端与客户端分别维护数据绑定操作。

也就是说原来服务端返回的是实体类对象列表,现在要返回的是将数据与Html组装起来的字符串。

1. 开始我们考虑的是一种丑陋的方法,用StringBuilder进行字符串拼接生成数据绑定结果,服务器端WCF服务中的代码如下:

显然这种方法易出错,维护性差。

2. 接着我们考虑了第二种方法(参考自Render User Control as String Template),通过Web User Control生成字符串。WCF服务中的代码如下:

复制代码
Page page =new Page();
Control control
= page.LoadControl(~/Controls/MsgListControl.ascx);
((IRenderable
<List<SiteMsg>>)control).PopulateData(siteMsgList);
StringBuilder sb
=new StringBuilder();
using (StringWriter sw =new StringWriter(sb))
{
using (HtmlTextWriter htw =new HtmlTextWriter(sw))
{
control.RenderControl(htw);
return sb.ToString();
}
}
复制代码

由于MsgListControl.ascx的类型是动态编译生成的,所以无法通过强制类型转换将control转换为MsgListControl类型,然后传递数据给它。

这里需要通过一个另外的IRenderable<T>接口来实现数据的绑定,MsgListControl实现了这个接口,代码如下:

复制代码
publicpartialclass MsgListControl : UserControl, IRenderable<List<SiteMsg>>
{
publicvoid PopulateData(List<SiteMsg> siteMsgList)
{
rptMsgList.DataSource
= siteMsgList;
rptMsgList.DataBind();
}
}

publicinterface IRenderable<T>
{
void PopulateData(T data);
}

复制代码

在WCF服务中通过调用接口中的PopulateData方法进行数据的绑定。

这个方法增加了额外的接口,显得有些复杂。

3. 后来我们想到了ASP.NET MVC,虽然不熟悉,但要尝试一下,看能否更好地解决这个问题。

于是,上ASP.NET MVC 3,用Razor,咱们也MVC一把。

应用场景:在现有的VS2010 Web Site项目中应用ASP.NET MVC 3。MsgController收到请求后,由Inbox(一个Action)将包含短消息列表的整个页面视图返回给客户端;当用户点击页面的刷新或者分 页链接时,通过Ajax发起POST请求以获取短消息列表,MsgController收到请求后,由List(一个Action)将短消息列表的视图返 回给客户端。

期望的效果:短消息列表视图能重用,Inbox与List使用的是同一个视图。

一开始遇到了两个小问题:

a) MapRoute配置之后,访问出现” HTTP Error 404.0 – Not Found”错误。原因是访问的网址没有文件名,未走ASP.NET管线。解决方法是在web.config的system.webServer中加上以下的配置:

<validation validateIntegratedModeConfiguration=”false”/>
<modules runAllManagedModulesForAllRequests=”true”/> 

b) 继续访问,出现“The resource cannot be found.”错误。解决方法:由于用的是Web Site项目,要将Controllers文件夹移至App_Code。

然后进入MVC相关代码编写,先从Ajax调用部分开始。

Controller的代码如下:

 

复制代码
publicclass MsgController : Controller
{
[HttpPost]
public ActionResult List(SiteMsgQuery msgQuery)
{
List
<SiteMsg> siteMsgList = GetInboxMsgList(msgQuery);
return View(MsgList, siteMsgList);
}
}
复制代码

需要注意的就一个地方:[HttpPost],既然是Ajax调用,当然要响应POST请求。View的代码(MsgList.cshtml)如下:

复制代码
@using CNBlogs.UcHome.ExternalService.MsgWcfService
@model List
<SiteMsg>
@foreach(SiteMsg msg in Model){
<div class=”msg_item”>
<div class=”msg_sender”>@msg.SenderName</div>
<div class=”msg_title”><a href=’/msg/item/@msg.id/’>@msg.Subject</a></div>
<div class=”msg_sendtime”>@msg.SendTime.ToString(“yyyy-MM-dd HH:mm”)</div>
</div>
}
复制代码

比在.ascx中写起来方便多了。

客户端js调用代码如下:

复制代码
function GetMsgList(pageIndex, pageSize) {
var msgQuery = {}
msgQuery.PageIndex
= pageIndex;
msgQuery.PageSize
= pageSize;
$.ajaxSettings.dataType
=plain/text;//不要用json
$.ajaxSettings.url =/msg/list;
$.ajaxSettings.data
={“msgQuery”:+ JSON.stringify(msgQuery) +};
$.ajaxSettings.success
=function (data) {
$(
#msg_list).html(data);
};
$.ajax();
}
复制代码

需要注意的是两个地方(因为服务器端Controller返回的不是json格式的数据):

a) dataType不要用json,用JQuery默认的就行,如果指定的话,就用plain/text;

b) 返回数据就在data中,不要通过data.d获取。

这样,用ASP.NET MVC就轻松搞定Ajax调用,比之前的WCF, StringBuider, .ascx都要方便。

原来在ASP.NET MVC中使用Ajax如此方便,完全可以取代以前用的WCF中转站。

解决了Ajax的问题,接着处理整个页面的显示。

在页面的View中直接重用刚才Ajax所用的View就行了,示例代码如下:

View(Inbox.cshtml):

复制代码
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
@Html.Partial(“MsgList”)
</body>
</html>
复制代码

Control:

复制代码
publicclass MsgController : Controller
{
public ActionResult Inbox()
{
SiteMsgQuery msgQuery
=new SiteMsgQuery()
{
PageIndex
=1,
PageSize
=30
};
List
<SiteMsg> siteMsgList = GetInboxMsgList(msgQuery);
return View(Inbox, siteMsgList);
}
}
复制代码

搞定!真的很方便!想要的解决方案就是它–ASP.NET MVC!

在这里为我的错误观点“Ajax为主的应用不需要ASP.NET MVC”向大家道歉!请大家谅解!

好好学习,不进则退!

[转载]千呼万唤始出来,Iveely Search Engine 0.4.0 的发布 - Iveely Liu - 博客园

mikel阅读(839)

[转载]千呼万唤始出来,Iveely Search Engine 0.4.0 的发布 – Iveely Liu – 博客园.

  经过无数个夜晚的奋战,以及无数个夜晚的失眠,Iveely Search Engine 0.4.0 终于熬出来了,这其中的心酸只有自己知道!虽然Iveely Search Engine 在开发阶段已经经历了第四个版本,但是其中的不足依然数不胜数,功能也不尽完善,但是也希望大家能够理解,一是毕竟还在1.0版本之内,二是人手有限,如果你想为此做出点点努力,可以发送邮件或者微博私信。

介   绍:

好了,言归正传,开始介绍下Iveely Search Engine 0.4.0,本次版本主题是事件抽取。我们会从事件抽取慢慢过渡到知识提取。希望大家不要为没有达到知识提取而感到遗憾。

首先截图:

首页:

结果页:

   这个版本的调试重点,就在结果的第一条记录,那就是不断从数据中心分析出事件,当然准确率和性能是有待考验的,但是这是我们迈出知识提取的第一步,后期这依然是我们改进的重点。0.4.0版本的具体更改内容如下(由时间顺序):

  1.  改进分词算法,完全的去除了词库分词。

  2.  统一了隐马尔科夫模型,HMM同时为分词和词性分析所用。

  3.  添加了词性分析,词性分析语料库来自人民日报。

  4.  添加了事件抽取,分析与索引,并完整显示在结果中。(命中N条,但只显示权值最高的一条)

  5.  改进Internet信息服务,性能有一定的改善。

  6.  添加首页和结果页界面,放弃以前的浏览器Http请求。

  7.  其它细节调整。

  部署与安装及使用:

  第一步:首先,从开源主页下载发布的文件。

  第二步:解压文件,修改配置文件Iveely.config根据实际情况,调整配置信息。下面这个配置信息,是您一定要调整的。

      <add key=”Crawler” value=”http://yourwebsite.com/” />

  第三步:双击运行IveelySE.Run.Task.exe。

  第四步:打开浏览器,输入Http://localhost:8080,您就可以看到Iveely搜索首页,即前面两幅截图。

  此刻,您的部署和安装即实现,但是您可能会遇到如下的问题:

  第一:最开始搜索任何关键字都没有结果,是什么情况?

        最开始是从一个没有任何数据的搜索引擎开始,所以大概在运行20分钟后,在您的搜索框内才能数出结果。

      第二:您可能会一直搜索不到事件信息,是什么情况?

      事件信息分析与索引默认情况下,是关闭的。我之所以默认关闭它,是因为它会影响我们的性能,如果您需要这项,可以修改配置文件

      <add key=”EnableEvent” value=”1″/>

     其中,1表示启用,0表示不启用,建议您设置为0,因为性能和效果都在继续调整阶段,如果您对此没有特殊需求关闭最佳。后期我们算法调整到最佳          状态后,我们会将此默认打开。

  问题疑惑:

  很多网友给我发邮件,提了很多问题,我虽然一一回复了,但是我还是在这里总结一下:

1. 为什么没有分页?如果自己做分页如何做?

分页在后台做是非常麻烦而且是非常不必要的,分页在前端做比较好,实现全端和后端的完整分析。如果需要做分页,你可以修改Iveely.config配 置文件中的ResultCount,默认是10,即返回10条结果(最多11条,包括事件相关搜索结果)。当你修改为100条的时候,系统会为前端返回 100条结果,你可以在前端为这100条结果实现分页。

<add key=”ResultCount” value=”10″/>

2. 界面好丑,我们怎么去改进界面?

上一个版本的确是没有界面的,这一个版本是存在界面的,这个版本的界面只做了1个多小时,这是告诉大家,只要知道后端返回的数据格式后,完全可以自己控制 界面的显示。你们可以参考这个版本的界面调用方法,去实现自己的界面,当然把我们的logo换为您自己的也是没问题的。

3.   Iveely Search Engine 支持对多个网站进行抓取分析吗?

答案是可以的,修改配置文件Iveely.config中节点如下即可:

<add key=”Crawler” value=”http://news.cnblogs.com/,http://www.iveely.com,http://www.google.com” />

4.  我想利用IveelySE进行大规模站点的应用可以吗?

不想欺骗你们,这是不可以的。Iveely Search Engine 目前在很多方面是非常不足的。首先,IveelySE的数据存储目前是存在本地的;其次大规模的数据索引对本地计算机或服务器的要求也很高,这样直接导致 IveelySE性能不佳;最后,IveelySE还处于实验室阶段,无法保证很多性能与效果的问题。

5.  有相关开发文档参考吗?

这个版本中,我也一直在写文档,以前也有,但是都是零零散散,我争取早点把文档全部整理完毕,为我们共同学习和进步提供帮助。

总结与下一步:

接下来,我们依然会将主要的精力集中在知识提取上,一方面是性能,另一方面是准确率上。性能主要集中在伪分布式处理上,准确率依然是在算法上的调整。

真诚的感谢一直以来对IveelySE不断支持和厚爱的朋友,正是您们的支持,IveeluSE才走的更远,真诚的谢谢!

[转载]让IT工作者过度劳累的12个坏习惯_IT新闻_博客园

mikel阅读(824)

[转载]让IT工作者过度劳累的12个坏习惯_IT新闻_博客园.

  你的工作是不是总也做不完?是不是经常加班?有时候这一切很可能是由坏习惯造成的……

  1. QQ、MSN、Gtalk,一个都不少。

  由于聊天对象与聊天内容的不可控制性,使用即时通讯软件是降低工作效率的罪魁祸首。有调查显示,使用即时通讯软件,工作效率会降低 20%.

  2. “总想多看一点点”——忘记上网的目的。

  本想查找工作资料,结果在网页上发现《哈利波特7》出来了;再点进去,又在网页底端看到自己喜欢的明星跟某某又传出了绯闻……点着点着,就忘记自己要上网做什么了。

  3. 长期不擦拭电脑屏幕和鼠标。

  电脑屏幕已经糊了厚厚的一层灰尘,每次都要瞪大眼睛去看,费力去猜屏幕污点下面的字是什么;鼠标点起来已经非常费力了,反应也迟钝得像八十岁的老汉。这些都间接地影响了工作效率。

  4. 长期不清理电脑系统。 

  防火墙的防御力是有限的。长期不清理电脑系统的后果就是,内存被一些潜藏的垃圾程序给占用了,直接影响了电脑的运行速度。电脑运行慢了,也就降低了工作效率。

  5. 长期不整理办公桌胡文件。

  办公桌胡文件杂乱的后果就是,想找东西的时候却找不到。以前,有调查公司专门对美国 200 家大公司职员做了调查,发现公司职员每年都要把 6 周时间浪费在寻找乱放的东西上面。

  6. 长期不整理电子邮件胡通讯录。 

  想给客户发个邮件,却记不得E-mail 地址,于是在电子邮箱中一通乱找,却发现自己的邮箱早已被垃圾邮件搞得汪洋一片。想搜什么都变得很困难。再返回一堆堆名片中去找,就又陷入了第 5 个坏习惯。

  7. 不适时保存文件。

  尽管现在电脑的性能越来越高,死机现象越来越少,可是,意外地碰掉电脑插头、程序操作不当从而造成电脑关机、死机,总是不可避免的,如果不适时保存文件,那么文件就很可能会丢失,前功尽弃。

  8. “不磨刀误了砍柴工”。

  工作之前,不做充分的计划、准备。行动之后,才发现要么是工具准备得不充分,只得停下工作再去找文件、资料;要么工作做到一半,才发现偏离了预定的方向,只得重新开始,前功尽弃。

  9. 梦想电脑有“三头六臂”。 

  在同一时间,一个网页打不开,一个程序在等待,为了节约时间,就只好再打开其他网页。结果同一时间,十几个窗口同时打开着,电脑就变得“老态龙钟”,一动不动。什么都干不成。

  10. 不会充分利用等待时间。 

  打开一个软件,电脑迟迟没有反应,于是坐在电脑旁干等;打开一个网页,迟迟显示不出来,又打开新的网页,又是一阵干等;要打印文件,发现打印机里排着队的文件有好多,只好继续等待……一天的工作中,光是耗在电脑上的等待时间就很可观,如果不充分利用这些等待间隙的话,那自己的工作时间只好额外延长了。

  11. “耻于下问”。 

现在,电脑出现的病症越来越离奇。不是每个人都是电脑高手。身为 IT 圈中人,似乎像别人请教就显得自己太不专业了。于是就上网查找解决方案。结果,在垃圾信息的汪洋大海中奋力拼搏,折腾半天,才化解问题,浪费了工作的宝贵 时间。如果问问身边人,可能几秒钟就解决问题了。

  12. 过分崇拜科技。

IT 人很容易就会陷入科技崇拜。如果有新软件、新系统发布,IT 人一定是最早尝试的。不管自己的电脑能不能撑起 Windows 8,一定要给电脑装上。电脑负荷不了,只好不断罢工。工作也会因之延误。

[转载]游戏化改变年度计划 | GTDLife

mikel阅读(1017)

[转载]游戏化改变年度计划 | GTDLife.

你喜欢被“命令”去做某事吗?

你在“命令”自己做年度计划吗?

年度计划上写的是你“应该”做的事还是“想要”做的事?

我刚刚进入国企IT部门做程序员的时候总是想办法偷懒,到必须要提交代码的时候才匆匆糊弄一下(我糊弄的比较认真一点,哈哈),文档就更惨不忍睹了,身边的同事们也是这样做的,我没觉得有什么不对。

直到有一天我自己要做这个部门的项目管理时,从另外一面看同样的一种状态,立刻有了完全不同的看法:怎么让这些程序员更勤奋、更有计划性、质量更高?

和他们沟通之后印证了我的想法:拖延计划的原因是无趣和不喜欢被命令(程序员都很有性格哦~:))

我尝试用“不命令”的方式和他们一起完成项目计划

一共分三步:1、量化、可追踪。2、游戏化。3、视觉化,并在这个过程中保持沟通和检视

NewImage

我发现这对实现年度计划同样适用:

明确、量化、可追踪

“这个模块2个月的时间必须搞定,你制定个计划给我,然后按计划执行,否则会影响整个项目的进度,好好干!”

经理们经常这么说,但这一套很快就不管用了

因为没有把“你的计划”,变成“我们的计划”。

自己的年度计划也一样,是“我”和“真正的我”一起制定的计划吗

NewImage

这一点需要明确!

我过去的年度计划里面总是有一条:背英语单词,为什么要背?原来很模糊的认为我要阅读英文资料,所以要背单词,但其实根本没那回事

我是因为周围人都在背单词,所以我也想背单词。这并不是“真正的我”真正想做的事情,我是在强迫自己。

于是今年我改成“全年阅读50本书,并写读后感”

我从去年开始这种感觉特别强烈:你要经历某些事情所需要经验,书上或“三度人脉”里都能提供,2012年读了大概30本书,我决定今年加强它,所以制定了“全年阅读50本书并写读后感”的计划。

然后再把它厘清一些:比如系统思考系列、易经等传统文化系列、朋友推荐的商业书籍。

写读后感的目的除了总结外,主要是为了追踪起来方便,这个部分我的上一篇文章写的更详细一些(有视频哦~),这里就不再赘述。

接下来就是有趣的部分了,游戏化你的年度计划。

游戏化

游戏化的前提是它已经足够明确、量化、可追踪

我经常在《小强升职记》时间管理研习会上问大家:你在完成某个比较困难的任务后,有给自己奖励吗?

绝大部分的人都在摇头,他们说:“长呼一口气算不算?”

我和每个程序员一起制定了计划,并且设置了里程碑之后,接下来和他们聊的就是:到达里程碑之后如何奖励自己。

第一次和他们聊这个的时候他们睁大了眼睛看我说:“这个可以有吗?”,我回答:“这个真的可以有!”

渴望得到反馈,这是人的天性之一

还是拿我的读书计划举例,我比较俗:

  • 读到20本,买G6蓝牙键盘
  • 读到40本,换一台打印机
  • 读到50本,和家人吃大餐
这还只是“单机游戏”而已。
游戏化还可以局域网多人游戏(类似CS)或者互联网游戏(类似魔兽世界)
社群就是局域网游戏,因为某个人、某种理念将相同城市志趣相投的人聚合在一起,这就是社群。

这是一件神奇的事情,你要改变自己,就请先改变环境,你想成为什么样的人,就和什么样的人做朋友。

《大连接》这本书里也有写到一个案例:“你朋友的朋友的朋友是胖子,你就会变胖”

所以有些人会有这样的感觉,加入到某个社群之后,同事还有家人就会立刻感觉到你有点点不一样~这就是社群的力量。

这个局域网游戏怎么玩呢?

其实方法很多啦,我在西安建立的木立方成长俱乐部的玩法是:“群策群力的年度计划”:

  • 你把你今年想要完成的一件事情写下来,投到“梦想收集篮”里(比如你想学吉他)
  • 因为我们掌握每个人兴趣、特长、行业等信息,所以立即为你匹配到可以帮助你实现梦想的人(有人就是吉他高手,也有人认识教吉他的老师)
  • 介绍你们认识(不一定是一对一哦~)
  • 半年进行一次总结,奖励提供最多帮助的人。(其实不用奖励他也有最多礼物了)

一些社群里的读书会,就是读书的游戏化方式,一群人看同一本书,从不同角度和层次解读,就像玩七巧板,不是很有趣吗?

互联网游戏是什么呢?

或许你已经在玩了,比如微群里的晨型人打卡,比如知乎里的“感谢”,比如扇贝小组打卡……

这些都让你完成目标的过程更加有趣~为什么不运用到执行年度计划中来?

NewImage

视觉化

我做项目经理的时候把每个人的计划、进度、奖励都整合到一张图标上,挂在办公室(其实就是甘特图啦~)

大家一下子就更有干劲了(如果你说:“我们一直这么干的啊?没什么作用!”,那请重新参考前两部分。)

你有没有这样的感觉:一件事情记在脑袋里,写在A4纸上,画在A4纸上,写在大白板上,画在大白板上,都是不一样的~

视觉化有两个作用:

  1. 增强表象、记忆、与思维方面的反应强度
  2. 更容易达成共识(很多分歧是因为误解导致的 –心理建设师 四四)西方有一句话:“最有利的方式,就是让所有人的目光集中在一张纸上”  -《系统思考》 丹尼斯 舍伍德

NewImage

如果有色彩的话就更好了,所以我建议把你的年度计划视觉化呈现出来,比如@张冰easygo(再次推荐,hoho):

屏幕快照 2013 01 18 下午8 40 08

以上是我结合自己从程序员转型到项目管理的经历和大家分享“游戏化改变年度计划”的经验:

1、量化、追踪

2、游戏化

3、视觉化

并在这个过程中保持沟通和检视。

结语:

发自内心的年度计划才是容易实现的年度计划

NewImage

看到别人去西藏,你也制定一个去西藏的目标,看到别人穷游,你也制定一个穷游的目标,看到别人学习NLP,你也制定一个学习NLP的计划。这些“因 为别人而产生的年度计划”,很难和自己产生真正的共鸣,大多数情况下都只是一个小涟漪罢了~会被当做一件艺术品珍藏下来,心血来潮的时候把玩一下。

有空的话,请做一个属于自己的年度计划吧!它不是一个命令,而是一种呈现。

ps:我还有分享过《游戏改变拖延》的方法,大家可以在多贝网上观看视频