unload事件,刷新,顺序,不靠谱

window的unload事件,用起来不靠谱,为什么呢?

我做个测试来说明,在页面unload事件中做一个同步ajax请求,然后刷新页面,我来比较刷新时unload事件和get新页面请求发生的时间顺序,结果如下:

FF:
16:13:48 445766772 get
16:13:48 445766787 unload

unload在get之后

 

Chrome:
16:36:13 447111240 get
16:36:13 447111274 unload

unload在get之后

 

IE:
16:33:08 446926865 get
16:33:08 446926842 unload

unload在get之前

 

Safari:
16:38:08 447226350 get
16:38:08 447226386 unload

unload在get之后

 

Opera:
16:39:39 447317584 get

没有unload事件

 

结果是FF,Safari,Chrome竟然unload请求在get请求之后。就只IE符合正常逻辑unload完了再get。Opera索性不支持unload事件。

通过HTTP抓包分析FF是先获得新页面的html然后执行上页面的unload事件,然后开始请求新页面的资源。

如果某些处理依赖unload事件而又讲究顺序的话,那就可能对我们的程序造成问题,所以,unload事件还是尽量不用为好,不靠谱啊!

 

beforeubload也存在这个情况。

Continue reading unload事件,刷新,顺序,不靠谱

【转】Google OAUTH + OpenID解决方案

同系列文章,转载自 Google OAUTH + OpenID解决方案, 蓝色字是我加的注解或是着重提示。

 

       在前面已经介绍过OAuth与OpenID,这两种服务,Google都实现了。我们可以通过Google OAuth服务为Google 用户的资源进行授权,如用户通过第三方软件调用Google Open API操作用户的资源时,就需要用户对第三方软件授权;通过Google OpenID服务可以打通Google与其他支持OpenID服务网站之间的用户体系。现在假如有另外一个网站,也想开放自己的Open API服务,但是又不想实现OAuth服务(毕竟实现OAUTH服务还是需要一些成本的),那该怎么办?它可不可以使用Google提供的OAuth服务,授权认证交给Google来处理?可以!但是OAuth授权也是基于用户登录来实现的,Google OAuth用户体系只是Google的用户体系,那又怎么办了?OpenID!对,将网站的用户体系与Google用户体系打通,并且使用Google OAuth服务来实现授权,即Google提出的OpenID + OAUTH的解决方案。

一、 OAUTH与OpenID

前面两篇文章对OAUTH与OpenID均做过介绍,且Google均提供了这两种服务,在此我们先简要的回顾这两种服务,具体介绍请参见相关文章。

OAUTH是一种开放的,基于用户登录的授权认证方式。如当用户使用第三方软件调用Google Open API去操作自己的Google服务资源时,用户就要先对该软件授权。授权过程中,第三方软件会引导用户登录Google,进行用户鉴权,用户通过Google身份鉴权后才能对第三方软件授权。显然,Google OAUTH只能对Google用户进行鉴权,其他用户体系的用户不能直接鉴权。

OpenID是一种开放的,去用户中心的,用于打通各网站之间的用户体系的服务。在支持OpenID的网站间,你可以使用任何一个网站的帐号或者Open ID去登录任何一个网站。OpenID提供了类似单点登录的用户体验,并且用户无需在各个网站上注册就可以使用该网站的资源,将用户从繁重的帐号注册与管理工作中解脱出来。当用户使用OpenID登录没注册的网站过程中,网站会引导用户登录OP(用户OpenID注册的网站),请求OP对用户身份鉴权,用户通过OP鉴权,网站才会容许用户登录。

若将OpenID与Google OAUTH结合,OpenID将第三方网站的用户体系与Google用户体系打通后,第三方网站便可使用Google OAUTH服务,对自己的用户进行授权!交互示意图如下图所示:

二、 Google OAUTH + OpenID解决方案

Google提出了OpenID + OAUTH的解决方案,将两者揉合在一起,具体流程如下图所示:

1. Web应用请求用户登录;

2. 用户选择使用Google OpenID进行登录;

3. Web应用请求发现Google认证服务URL;

4. Google向Web应用返回XRDS信息,其中包含Google认证服务URL;

5. Web应用请求用户登录Google服务,通过请求用户授权;

6. Google引导用户登录;

7. 用户输入用户名密码进行登录,同时确认是否对第三方软件授权;

8. Google认证中心返回用户ID与授权的Request Token给Web应用;

9. 用户可以访问受保护的资源,同时可以继续第七部中Oauth认证余下的环节;

从上面的流程第五步可以看出,Google解决方案中,将OAUTH与OpenID的登录操作合并在一起、并且在登录的同时向Google发送Oauth请求,请求用户授权。这样一来,在第五步中,用户登录Google(OpenID中Google对用户鉴权),同时请求对用户授权(OAUTH中对用户授权,同时无需再次登陆,因为前面OpenID已经登录过了)。

三、Google OAUTH+OpenID Demo

Google提供了OAUTH + OpenID的DEMO,Demo演示地址如下:http://googlecodesamples.com/hybrid/

刚开始,用户既没OpenID登录也没OAUTH授权,如下图所示:

接着,点击上图中login按钮请求以Google提供的OpenID登录,如下图所示:

输入用户名与密码登录后,Google提醒您即将登陆到外部网站,外部网站申请对您的资源进行授权,您是否同意,如下图所示:

点击继续登录后,登录成功,并且返回授权的Token,如下图所示:

 

参见:

http://kazge.com/archives/765.html

http://kazge.com/archives/496.html

Continue reading 【转】Google OAUTH + OpenID解决方案

【转】OAUTH协议简介

好文,忍不住转载了,转自http://blog.csdn.net/hereweare2009/article/details/3968582 ,蓝色字是我加的注解或是着重提示。

 摘要:OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是OAUTH的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此OAUTH是安全的。同时,任何第三方都可以使用OAUTH认证服务,任何服务提供商都可以实现自身的OAUTH认证服务,因而OAUTH是开放的。业界提供了OAUTH的多种实现如PHP,JavaScript,Java,Ruby等各种语言开发包,大大节约了程序员的时间,因而OAUTH是简易的。目前互联网很多服务如Open API,很多大头公司如Google,Yahoo,Microsoft等都提供了OAUTH认证服务,这些都足以说明OAUTH标准逐渐成为开放资源授权的标准。

一、OAUTH产生的背景

    典型案例:如果一个用户拥有两项服务:一项服务是图片在线存储服务A,另一个是图片在线打印服务B。如下图所示。由于服务A与服务B是由两家不同的服务提供商提供的,所以用户在这两家服务提供商的网站上各自注册了两个用户,假设这两个用户名各不相同,密码也各不相同。当用户要使用服务B打印存储在服务A上的图片时,用户该如何处理?法一:用户可能先将待打印的图片从服务A上下载下来并上传到服务B上打印,这种方式安全但处理比较繁琐,效率低下;法二:用户将在服务A上注册的用户名与密码提供给服务B,服务B使用用户的帐号再去服务A处下载待打印的图片,这种方式效率是提高了,但是安全性大大降低了,服务B可以使用用户的用户名与密码去服务A上查看甚至篡改用户的资源。

    很多公司和个人都尝试解决这类问题,包括Google、Yahoo、Microsoft,这也促使OAUTH项目组的产生。OAuth是由Blaine Cook、Chris Messina、Larry Halff 及David Recordon共同发起的,目的在于为API访问授权提供一个开放的标准。OAuth规范的1.0版于2007年12月4日发布。通过官方网址:http://oauth.net可以阅读更多的相关信息。

二、OAUTH简介

    在官方网站的首页,可以看到下面这段简介:

    An open protocol to allow secure API authorization in a simple and standard method from desktop and web applications.

    大概意思是说OAUTH是一种开放的协议,为桌面程序或者基于BS的web应用提供了一种简单的,标准的方式去访问需要用户授权的API服务。OAUTH类似于Flickr Auth、Google's AuthSub、Yahoo's BBAuth、 Facebook Auth等。OAUTH认证授权具有以下特点:

1. 简单:不管是OAUTH服务提供者还是应用开发者,都很容易于理解与使用;

2. 安全:没有涉及到用户密钥等信息,更安全更灵活;

3. 开放:任何服务提供商都可以实现OAUTH,任何软件开发商都可以使用OAUTH;

 三、OAUTH相关术语

    在弄清楚OAUTH流程之前,我们先了解下OAUTH的一些术语的定义:

  • OAUTH相关的三个URL
    • Request Token URL: 获取未授权的Request Token服务地址;
    • User Authorization URL: 获取用户授权的Request Token服务地址;
    • Access Token URL: 用授权的Request Token换取Access Token的服务地址;

  • OAUTH相关的参数定义:
    • oauth_consumer_key: 使用者的ID,OAUTH服务的直接使用者是开发者开发出来的应用。所以该参数值的获取一般是要去OAUTH服务提供商处注册一个应用,再获取该应用的oauth_consumer_key。如Yahoo该值的注册地址为:https://developer.yahoo.com/dashboard/
    • oauth_consumer_secret:oauth_consumer_key对应的密钥。
    • oauth_signature_method: 请求串的签名方法,应用每次向OAUTH三个服务地址发送请求时,必须对请求进行签名。签名的方法有:HMAC-SHA1、RSA-SHA1与PLAINTEXT等三种。
    • oauth_signature: 用上面的签名方法对请求的签名。
    • oauth_timestamp: 发起请求的时间戳,其值是距1970 00:00:00 GMT的秒数,必须是大于0的整数。本次请求的时间戳必须大于或者等于上次的时间戳。
    • oauth_nonce: 随机生成的字符串,用于防止请求的重放,防止外界的非法攻击。
    • oauth_version: OAUTH的版本号,可选,其值必须为1.0。

  OAUTH HTTP响应代码:

  • HTTP 400 Bad Request 请求错误
    • Unsupported parameter 参数错误
    • Unsupported signature method 签名方法错误
    • Missing required parameter 参数丢失
    • Duplicated OAuth Protocol Parameter 参数重复
  • HTTP 401 Unauthorized 未授权
    • Invalid Consumer Key 非法key
    • Invalid / expired Token 失效或者非法的token
    • Invalid signature 签名非法
    • Invalid / used nonce 非法的nonce

四、OAUTH认证授权流程

    在弄清楚了OAUTH的术语后,我们可以对OAUTH认证授权的流程进行初步认识。其实,简单的来说,OAUTH认证授权就三个步骤,三句话可以概括:

1. 获取未授权的Request Token

2. 获取用户授权的Request Token

3. 用授权的Request Token换取Access Token

    当应用拿到Access Token后,就可以有权访问用户授权的资源了。大家肯能看出来了,这三个步骤不就是对应OAUTH的三个URL服务地址嘛。一点没错,上面的三个步骤中,每个步骤分别请求一个URL,并且收到相关信息,并且拿到上步的相关信息去请求接下来的URL直到拿到Access Token。具体的步骤如下图所示:

 

具体每步执行信息如下:

A. 使用者(第三方软件)向OAUTH服务提供商请求未授权的Request Token。向Request Token URL发起请求,请求需要带上的参数见上图。

B. OAUTH服务提供商同意使用者的请求,并向其颁发未经用户授权的oauth_token与对应的oauth_token_secret,并返回给使用者。

C. 使用者向OAUTH服务提供商请求用户授权的Request Token。向User Authorization URL发起请求,请求带上上步拿到的未授权的token与其密钥。

D. OAUTH服务提供商将引导用户授权。该过程可能会提示用户,你想将哪些受保护的资源授权给该应用。此步可能会返回授权的Request Token也可能不返回。如Yahoo OAUTH就不会返回任何信息给使用者。

E. Request Token 授权后,使用者将向Access Token URL发起请求,将上步授权的Request Token换取成Access Token。请求的参数见上图,这个比第一步A多了一个参数就是Request Token。

F. OAUTH服务提供商同意使用者的请求,并向其颁发Access Token与对应的密钥,并返回给使用者。

G. 使用者以后就可以使用上步返回的Access Token访问用户授权的资源。

    从上面的步骤可以看出,用户始终没有将其用户名与密码等信息提供给使用者(第三方软件),从而更安全。用OAUTH实现背景一节中的典型案例:当服务B(打印服务)要访问用户的服务A(图片服务)时,通过OAUTH机制,服务B向服务A请求未经用户授权的Request Token后,服务A将引导用户在服务A的网站上登录,并询问用户是否将图片服务授权给服务B。用户同意后,服务B就可以访问用户在服务A上的图片服务。整个过程服务B没有触及到用户在服务A的帐号信息。如下图所示,图中的字母对应OAUTH流程中的字母:

 

五、OAUTH服务提供商

    OAUTH标准提出到现在不到两年,但取得了很大成功。不仅提供了各种语言的版本库,甚至Google,Yahoo,Microsoft等等互联网大头都实现了OAUTH协议。由于OAUTH的client包有很多,所以我们就没有必要在去自己写,避免重复造轮子,直接拿过来用就行了。我使用了这些库去访问Yahoo OAUTH服务,很不错哦!下面就贴出一些图片跟大家一起分享下!

    下图是OAUTH服务提供商引导用户登录(若用户开始没有登录)

  

    下图是提示用户将要授权给第三方应用,是否同意授权的页面

 

    下图提示用户已授权成功的信息

 

    一些服务提供商不仅仅仅实现了OAUTH协议上的功能,还提供了一些更友好的服务,比如管理第三方软件的授权服务。下图就是YAHOO管理软件授权的页面,用户可以取消都某些应用的授权。

参见:

http://kazge.com/archives/765.html

http://kazge.com/archives/496.html

Continue reading 【转】OAUTH协议简介

【转】memcache在大型网站的应用策略

style="width: 858px; height: 64px" />

转自 http://blog.sina.com.cn/s/blog_5f53615f0100qdxc.html ,原文不知出处。

蓝色字是我的加重或是注解。

今天看完了日本人mixi写的“memcached全面剖析”的系列文章,结合我在项目中使用memcache的经验,再谈谈memcache在大型网站中的应用策略。

【部署策略】

基于memcached的slab和dump的内存管理方式,它产生的内存碎片比较少,不需要OS去做繁杂的内存回收,所以它对CPU的占用率那是相当的低。所以建议将它跟占用CPU较高的WEB服务器一起使用来节省成本。当然如果你有大量的廉价PC,那用来专门做memcached服务器也不错。由于32位操作系统中,每个进程最多只能使用2GB内存,所以如果你有大内存的话,可以以daemon的方式启动两个以上的memcached服务,或者干脆用64位的CPU和OS。

【服务监控】

memcached支持分布式机制,php通过memcache::addserver来实现该机制,它实现了如果服务器列表中的一台down掉的时候在参数retry_interval指定的时间就不会再连接该服务器的机制。所以只要你在该时间段内重启或者修复了该服务器,则不会对前端造成太大的影响。当然了,如果你一直不去重启该服务器的话,我测试过addserver再次连接一台down掉的服务器的时间将比平时延长了1秒的时间。可以通过addserver的最后一个参数failure_callback定义一个回调函数来实现邮件通知或者短信通知管理员的功能。

如果是手工重启,我建议将该值设置为20分钟。不过对于memcached进程死掉的服务器,只要重新启动memcached,就可以正常运行,所以采用了监视memcached进程并自动启动的方法的效果最好。mixi推荐的工具:nagios,daemontool

【应用策略】

memcached主要的作用是为减轻大访问量对数据库的冲击,所以一般的逻辑是首先从memcached中读取数据,如果没有就从数据库中读取数据写入到memcache中,等下一次读取的时候就可以从memcached中读取了。但在项目中的具体应用策略(也就是哪些数据应该缓存?怎么样缓存?过期策略?)就是个问题了。它的一个总原则是将经常需要从数据库读取的数据缓存在memcached中。这些数据也分为几类:

一、经常被读取并且实时性要求不强可以等到自动过期的数据。例如网站首页最新文章列表、某某排行等数据。也就是虽然新数据产生了,但对用户体验不会产生任何影响的场景。

这类数据就使用典型的缓存策略,设置一过合理的过期时间,当数据过期以后再从数据库中读取。当然你得制定一个缓存清除策略,便于编辑或者其它人员能马上看到效果。

二、经常被读取并且实时性要求强的数据。比如用户的好友列表,用户文章列表,用户阅读记录等。

这类数据首先被载入到memcached中,当发生更改(添加、修改、删除)时就清除缓存。在缓存的时候,我将查询的SQL语句md5()得到它的hash值作为key,结果数组作为值写入memcached,并且将该SQL涉及的table_name以及hash值配对存入memcached中。当更改了这个表时,我就将与此表相配对的key的缓存全部删除。

三、统计类缓存,比如文章浏览数、网站PV等

此类缓存是将在数据库的中来累加的数据放在memcached来累加。获取也通过memcached来获取。但这样就产生了一个问题,如果memcached服务器down掉的话这些数据就有可能丢失,所以一般使用memcached的永固性存储,这方面我们新浪使用memcachedb。[PS:这是nosql的地盘了]

四、活跃用户的基本信息或者某篇热门文章。

此类数据的一个特点就是数据都是一行,也就是一个一维数组,当数据被update时(比如修改昵称、文章的评论数),在更改数据库数据的同时,使用Memcache::replace替换掉缓存里的数据。这样就有效了避免了再次查询数据库。

[PS:第五点哪去了?]

六、session数据

使用memcached来存储session的效率是最高的。memcached本身也是非常稳定的,不太用担心它会突然down掉引起session数据的丢失,即使丢失就重新登录了,也没啥。见[多服务器session共享之memcache共享]

【总结】

通过以上的策略数据库的压力将会被大大减轻。检验你使用memcached是否得当的方法是查看memcached的命中率。有些策略好的网站的命中率可以到达到90%以上。

【后记】

一、memcached暂时还不支持故障转移,希望在以后的版本中能支持该功能。当然你可以使用日本人平林幹雄的Tokyo Tyrant 来代替memcached,它支持memcached协议,支持永固性存储和故障转移[这个似乎并不是memcached本身语义该做的事情吧]。但我没有在生产环境中使用过,也没有相关的性能测试数据。以后会试用一下这个东东。

二、memcached不支持检索的功能,在实际使用中比如我们想把一个IP表放在memcached中,来检索某一个IP属于那个地区的话,有了检索功能就方便多了。希望他在以后的版本中能提供该功能。暂时可以通过排序存储和二分法在客户端实现。

三、memcached1.2.2版本确实有不稳定的情况,有时候会出现DOWN机,强烈建议升级至1.2.6的版本。见官方说明:Version 1.2.6 released. Major crash fixes, DTrace support, minor updates. If you have stability issues with any previous release, please upgrade to this one.

Continue reading 【转】memcache在大型网站的应用策略

eclipse outline视图对于javascript的bug

现在我一个javascript文件里面写的代码一般就有一千多行,原因主要是用闭包啊,不想搞太多的变量空间引用麻烦。一般来说eclipse的outline视图很好用,将所有函数都列出来了,但是有几次原本好好地outline结构什么都没有了,让我切换函数非常麻烦,查找一下原因,原来对于函数中return 一个json对象的写法造成outline失效了:

function a(){
    return {}; //这里会造成outline问题
}

解决办法:

/>

function a(){
var o = {};
return o;

}

这个问题对EXTJS的源码很头疼,因为里面一堆的return  {};

估计他们不是用eclipse开发的吧,也许牛人既不需要outline视图,LOL.

Continue reading eclipse outline视图对于javascript的bug

【转】Memcached Java Client API详解

转自 http://blog.csdn.net/qqiabc521/article/details/6438429 ,蓝色字是我加的加重或是注解

Memcached Java Client API详解

针对Memcached官方网站提供的java_memcached-release_2.0.1版本进行阅读分析,Memcached Java客户端lib库主要提供的调用类是SockIOPool和MemCachedClient?,关键类及方法整理说明如下。

SockIOPool

这个类用来创建管理客户端和服务器通讯连接池,客户端主要的工作包括数据通讯、服务器定位、hash码生成等都是由这个类完成的。

  • public static SockIOPool getInstance()
    • 获得连接池的单态方法。这个方法有一个重载方法getInstance( String poolName ),每个poolName只构造一个SockIOPool实例。缺省构造的poolName是default。
    • 如果在客户端配置多个memcached服务,一定要显式声明poolName。
  • public void setServers( String[] servers )
    • 设置连接池可用的cache服务器列表,server的构成形式是IP:PORT(如:127.0.0.1:11211)
  • public void setWeights( Integer[] weights )
    • 设置连接池可用cache服务器的权重,和server数组的位置一一对应
    • 其实现方法是通过根据每个权重在连接池的bucket中放置同样数目的server(如下代码所示),因此所有权重的最大公约数应该是1,不然会引起bucket资源的浪费。
for ( int i = 0; i < servers.length; i+/+ ) { if ( this.weights /!= null &&this.weights.length > i ) { for ( int k = 0; k < this.weights[i].intValue(); k+/+ ) { this.buckets.add( servers[i] ); if ( log.isDebugEnabled() ) log.debug("++++ added " + servers[i] + " to server bucket" ); } }

这个文档上举例:

Lets say you have 3 servers.  Server 1 and server 2 have 3GB of space

and server 3 has 2GB of space for cache.  Here is how I would set up

my client.

如果有三个server,分别有3GB,3GB,2GB的缓存空间,那么权重对应应该是3:3:2

 

  • public void setInitConn( int initConn )
    • 设置开始时每个cache服务器的可用连接数
  • public void setMinConn( int minConn )
    • 设置每个服务器最少可用连接数
  • public void setMaxConn( int maxConn )
    • 设置每个服务器最大可用连接数
  • public void setMaxIdle( long maxIdle )
    • 设置可用连接池的最长等待时间
  • public void setMaintSleep( long maintSleep )
    • 设置连接池维护线程的睡眠时间
    • 设置为0,维护线程不启动
    • 维护线程主要通过log输出socket的运行状况,监测连接数目及空闲等待时间等参数以控制连接创建和关闭。
  • public void setNagle( boolean nagle )
    • 设置是否使用Nagle算法,因为我们的通讯数据量通常都比较大(相对TCP控制数据)而且要求响应及时,因此该值需要设置为false(默认是true)

Nagle算法是用于对缓冲区内的一定数量的消息进行自动连接。该处理过程(称为Nagling),通过减少必须发送的封包的数量,提高了网络应用程序系统的效率。这个该不该禁用还需的具体分析,

参见http://www.blogjava.net/killme2008/archive/2011/10/25/353441.html 这个也是与memcached相关的经验。

  • ublic void setSocketTO( int socketTO )
    • 设置socket的读取等待超时值
  • public void setSocketConnectTO( int socketConnectTO )
    • 设置socket的连接等待超时值
  • public void setAliveCheck( boolean aliveCheck )
    • 设置连接心跳监测开关。
    • 设为true则每次通信都要进行连接是否有效的监测,造成通信次数倍增,加大网络负载,因此该参数应该在对HA要求比较高的场合设为TRUE,默认状态是false。
  • public void setFailback( boolean failback )
    • 设置连接失败恢复开关
    • 设置为TRUE,当宕机的服务器启动或中断的网络连接后,这个socket连接还可继续使用,否则将不再使用,默认状态是true,建议保持默认。
  • public void setFailover( boolean failover )
    • 设置容错开关
    • 设置为TRUE,当当前socket不可用时,程序会自动查找可用连接并返回,否则返回NULL,默认状态是true,建议保持默认。

      这里我补充一下,当这个设置为true时,写操作会对此server池中所有的server都进行,例如set(‘1’,’abc’);则每个server都存储了1=abc,当设置为false时,则把所有server作为一个整体,那么上一个set操作可能就放置在server1,而下一个set操作可能就放置在server2.

  • public void setHashingAlg( int alg )
    • 设置hash算法
      • alg=0 使用String.hashCode()获得hash code,该方法依赖JDK,可能和其他客户端不兼容,建议不使用
      • alg=1 使用original 兼容hash算法,兼容其他客户端
      • alg=2 使用CRC32兼容hash算法,兼容其他客户端,性能优于original算法
      • alg=3 使用MD5 hash算法
    • 采用前三种hash算法的时候,查找cache服务器使用余数方法。采用最后一种hash算法查找cache服务时使用consistent方法。
  • public void initialize()
    • 设置完pool参数后最后调用该方法,启动pool。

MemCachedClient?

  • public void setCompressEnable( boolean compressEnable )
    • 设定是否压缩放入cache中的数据
    • 默认值是ture
    • 如果设定该值为true,需要设定CompressThreshold?
  • public void setCompressThreshold( long compressThreshold )
    • 设定需要压缩的cache数据的阈值
    • 默认值是30k
  • public void setPrimitiveAsString( boolean primitiveAsString )
    • 设置cache数据的原始类型是String
    • 默认值是false
    • 只有在确定cache的数据类型是string的情况下才设为true,这样可以加快处理速度。
  • public void setDefaultEncoding( String defaultEncoding )
    • 当primitiveAsString为true时使用的编码转化格式
    • 默认值是utf-8
    • 如果确认主要写入数据是中文等非ASCII编码字符,建议采用GBK等更短的编码格式
  • cache数据写入操作方法
    • set方法
      • 将数据保存到cache服务器,如果保存成功则返回true
      • 如果cache服务器存在同样的key,则替换之
      • set有5个重载方法,key和value是必须的参数,还有过期时间,hash码,value是否字符串三个可选参数
    • add方法
      • 将数据添加到cache服务器,如果保存成功则返回true
      • 如果cache服务器存在同样key,则返回false
      • add有4个重载方法,key和value是必须的参数,还有过期时间,hash码两个可选参数
    • replace方法
      • 将数据替换cache服务器中相同的key,如果保存成功则返回true
      • 如果cache服务器不存在同样key,则返回false
      • replace有4个重载方法,key和value是必须的参数,还有过期时间,hash码两个可选参数
    • 建议分析key的规律,如果呈现某种规律有序,则自己构造hash码,提高存储效率
  • cache数据读取操作方法
    • 使用get方法从cache服务器获取一个数据
      • 如果写入时是压缩的或序列化的,则get的返回会自动解压缩及反序列化
      • get方法有3个重载方法,key是必须的参数,hash码和value是否字符串是可选参数
    • 使用getMulti方法从cache服务器获取一组数据
      • get方法的数组实现,输入参数keys是一个key数组
      • 返回是一个map
  • 通过cache使用计数器
    • 使用storeCounter方法初始化一个计数器
    • 使用incr方法对计数器增量操作
    • 使用decr对计数器减量操作

Memcached Client API 优化(草)

实现memcached的遍历操作

有些应用情况下,需要遍历memcached服务器中所有被cache的数据,目前memcached client API不支持遍历操作,需要进行扩展。

实现get时刷新数据过期时间(应用于session,可能需要修改服务器端程序)

当memcached被用作session服务器的时候,需要支持session的access方法,根据最近访问时间刷新过期时间,目前memcached也不支持该操作,需要进行扩展。

Continue reading 【转】Memcached Java Client API详解

Memcached-Java-Client使用

官网是:https://github.com/gwhalin/Memcached-Java-Client/

Memcached-Java-Client是memcahed的java客户端库,依据其源码文档doc/howto.txt 介绍,是meetup网站目前使用的客户端。

可参见 http://developer.51cto.com/art/201106/271749.htm

从各方面参考,这个是目前最靠谱的java client了.参见

http://kazge.com/archives/752.html

http://www.infoq.com/cn/articles/memcached-java

http://lveyo.iteye.com/blog/240146

 http://www.iteye.com/topic/154767

2.5版之后较之前有较大性能提升,所以网上许多怀疑其性能问题的一般都是在2011年之前的文章。

它的doc很简单,在源码目录doc/howto.txt 中,这位仁兄整理了较新的中文版:

http://kazge.com/archives/754.html

其实也可以理解,协议就那几个。

其源码中MemcachedBench是压力测试的例子。

如下是我测试的结果(客户端和服务器不是一台机子,服务端10m缓存空间,其他默认):

100000 sets: 73047ms
100000 gets: 49594ms
100000 getMulti: 30485ms
100000 deletes: 43281ms

10000 sets: 7579ms
10000 gets: 7328ms
10000 getMulti: 4015ms
10000 deletes: 4172ms

1000 sets: 906ms
1000 gets: 781ms
1000 getMulti: 375ms
1000 deletes: 437ms

100 sets: 109ms
100 gets: 109ms
100 getMulti: 47ms
100 deletes: 47ms

这说明不了什么,仅供参考。

Continue reading Memcached-Java-Client使用

【转】JAVA客户端调用memcached比较

style="width: 902px; height: 88px" />

转自http://blog.chinaunix.net/space.php?uid=20787846&do=blog&id=1841943 ,

有趣的是这篇文章测试结果显示上篇转载的ali client性能有问题,那篇文章还说是优化了whalin,结果反而不如它,不知怎么回事!

蓝色字是博主加的加重标记或是注解。

 

1.memcached client for java客户端API:memcached client for java 

网址:http://www.whalin.com/memcached 

最新版本:java_memcached-release_2.0.1 

操作示例: 

Java代码
  1. import com.danga.MemCached.*;  
  2. import org.apache.log4j.*;  
  3. public class TestMemcached {  
  4.     public static void main(String[] args) {  
  5.         /*初始化SockIOPool,管理memcached的连接池*/ 
  6.         String[] servers = { "192.168.1.20:12111" };  
  7.         SockIOPool pool = SockIOPool.getInstance();  
  8.         pool.setServers(servers);  
  9.         pool.setFailover(true);  
  10.         pool.setInitConn(10);  
  11.         pool.setMinConn(5);  
  12.         pool.setMaxConn(250);  
  13.         pool.setMaintSleep(30);  
  14.         pool.setNagle(false);  
  15.         pool.setSocketTO(3000);  
  16.         pool.setAliveCheck(true);  
  17.         pool.initialize();  
  18.         /*建立MemcachedClient实例*/ 
  19.         MemCachedClient memCachedClient = new MemCachedClient();  
  20.         for (int i = 0; i < 10; i++) {  
  21.             /*将对象加入到memcached缓存*/ 
  22.             boolean success = memCachedClient.set("" + i, "Hello!");  
  23.             /*从memcached缓存中按key值取对象*/ 
  24.             String result = (String) memCachedClient.get("" + i);  
  25.             System.out.println(String.format("set( %d ): %s", i, success));  
  26.             System.out.println(String.format("get( %d ): %s", i, result));  
  27.         }  
  28.     }  

2.spymemcached客户端API:spymemcached client 

网址:http://code.google.com/p/spymemcached/ 

最新版本:memcached-2.1.jar 

操作示例: 

用spymemcached将对象存入缓存 

Java代码
  1. import java.net.InetSocketAddress;  
  2. import java.util.concurrent.Future;  
  3.  
  4. import net.spy.memcached.MemcachedClient;  
  5.  
  6. public class MClient {  
  7.       
  8.     public static void main(String[] args){  
  9.         try{  
  10.             /*建立MemcachedClient 实例,并指定memcached服务的IP地址和端口号*/ 
  11.             MemcachedClient mc = new MemcachedClient(new InetSocketAddress("192.168.1.20", 12111));  
  12.             Future<Boolean> b = null;  
  13.             /*将key值,过期时间(秒)和要缓存的对象set到memcached中*/ 
  14.             b = mc.set("neea:testDaF:ksIdno", 900, "someObject");  
  15.             if(b.get().booleanValue()==true){  
  16.                 mc.shutdown();  
  17.             }  
  18.         }  
  19.         catch(Exception ex){  
  20.             ex.printStackTrace();  
  21.         }  
  22.     }  

用spymemcached从缓存中取得对象 

Java代码
  1. import java.net.InetSocketAddress;  
  2. import java.util.concurrent.Future;  
  3.  
  4. import net.spy.memcached.MemcachedClient;  
  5.  
  6. public class MClient {  
  7.       
  8.     public static void main(String[] args){  
  9.         try{  
  10.             /*建立MemcachedClient 实例,并指定memcached服务的IP地址和端口号*/ 
  11.             MemcachedClient mc = new MemcachedClient(new InetSocketAddress("192.168.1.20", 12111));  
  12.             /*按照key值从memcached中查找缓存,不存在则返回null */ 
  13. Object b = mc.get("neea:testDaF:ksIdno ");  
  14.             mc.shutdown();  
  15.         }  
  16.         catch(Exception ex){  
  17.             ex.printStackTrace();  
  18.         }  
  19.     }  
 
3.alisoft-xplatform-asf-cache-2.4
这是国产的memcached client.我花时间测试过,配置简单,使用也简单,有很详细的中文文档和英文文档.

网址:http://code.google.com/p/memcache-client-forjava/

最新版本: alisoft-xplatform-asf-cache-2.5.2.jar

使用例子
memcached.xml
=========================
<?xml version="1.0" encoding="UTF-8"?>

<memcached>

    

    <client name="mclient0" compressEnable="true" defaultEncoding="UTF-8" socketpool="pool0">

        <errorHandler>com.moit.xplatform.asf.cache.memcached.MemcachedErrorHandler</errorHandler>

    </client>

    

    <client name="mclient1" compressEnable="true" defaultEncoding="UTF-8" socketpool="pool1">

        <errorHandler>com.moit.xplatform.asf.cache.memcached.MemcachedErrorHandler</errorHandler>

    </client>

    

    <client name="mclient2" compressEnable="true" defaultEncoding="UTF-8" socketpool="pool2">

        <errorHandler>com.moit.xplatform.asf.cache.memcached.MemcachedErrorHandler</errorHandler>

    </client>

    

    <socketpool name="pool0" failover="true" initConn="5" minConn="5" maxConn="250" maintSleep="0"

        nagle="false" socketTO="3000" aliveCheck="true">

        <servers>192.168.3.251:11211,192.168.3.251:11211</servers>

    </socketpool>

    

    <socketpool name="pool1" failover="true" initConn="5" minConn="5" maxConn="250" maintSleep="0"

        nagle="false" socketTO="3000" aliveCheck="true">

        <servers>192.168.3.251:11211,192.168.3.251:11211</servers>

    </socketpool>

    

    <socketpool name="pool2" failover="true" initConn="5" minConn="5" maxConn="250" maintSleep="0"

        nagle="false" socketTO="3000" aliveCheck="true">

        <servers>192.168.3.251:11211,192.168.3.251:11211</servers>

    </socketpool>

    <cluster name="cluster1">

        <memCachedClients>mclient1,mclient2</memCachedClients>

    </cluster>

</memcached>
 
  //用containsKey方法判断cache服务器按指定的key值是否存在。

  System.out.println("是否包含了key的数据="+cache.containsKey("key"));

  if(cache.containsKey("key"))

  {

    System.out.println("包含了key的数据");

    System.out.println("从cache服务器获得key值");

  }

  else

  {

    System.out.println("没有包含了key的数据");

    System.out.println("cache服务器,没有数据,则去取数据库数据!");

  }

  

  例子:

  static ICacheManager<IMemcachedCache> manager;

 
  /**

   * 测试MemCached

   * @return

   */

  public String memcache()

  { 
    manager = CacheUtil.getCacheManager(IMemcachedCache.class,

    MemcachedCacheManager.class.getName());

    manager.start();

    try

    {

      IMemcachedCache cache = manager.getCache("mclient0");

      //根据key得到缓存数据

      String a =(String)cache.get("key");

      //用containsKey方法判断cache服务器按指定的key值是否存在。

      System.out.println("是否包含了key的数据="+cache.containsKey("key"));

      if(cache.containsKey("key"))

      {

        System.out.println("包含了key的数据");

        System.out.println("从cache服务器获得key值");

      }

      else

      {

        System.out.println("没有包含了key的数据");

        System.out.println("cache服务器,没有数据,则去取数据库数据!");

      }

      //根据key删除服务器上的对应的缓存数据

      cache.remove("key");

      //根据key保存数据到服务器上

      cache.put("key", "你好!"); 
      //设置带有过期时间的例子

      //过30分钟

      Calendar calendar = Calendar.getInstance();//当前日期

        calendar.setTime(new Date());

        calendar.add(Calendar.MINUTE, 30);//

        cache.remove("keytime");

        cache.put("keytime", "30分钟后过期",calendar.getTime());

        System.out.println("30分钟后过期=keytime="+cache.get("keytime"));

        System.out.println("cache服务器getTime="+calendar.getTime());  
  
  }finally{ manager.stop();}

     //jsp 使用请参考test.jsp文件

     return "testmempage";

  }

总结:三种API比较 

memcached client for java:较早推出的memcached JAVA客户端API,应用广泛,运行比较稳定。 [注:这个是meetup正在使用的]

spymemcached:A simple, asynchronous, single-threaded memcached client written in java. 支持异步,单线程的memcached客户端,用到了java1.5版本的concurrent和nio,存取速度会高于前者,但是稳定性不好,测试中常报timeOut等相关异常。

alisoft-xplatform-asf-cache-2.4 我开始采用,后来发现性能很差,尤其是调用频繁时特别容易出问题.我后来还是采用第一个.现在还没发现什么问题.

由于memcached client for java发布了新版本,性能上有所提高,并且运行稳定,所以建议使用memcached client for java。

Continue reading 【转】JAVA客户端调用memcached比较

【转】Memcached深度分析

此文写得好不得不收藏, 转自 http://kb.cnblogs.com/page/42776/ ,蓝色字是我加的评注或是对原文的加重标记。

Memcached是danga.com(运营LiveJournal的技术团队)开发的一套分布式内存对象缓存系统,用于在动态系统中减少数据库负载, 提升性能。关于这个东西,相信很多人都用过,本文意在通过对memcached的实现及代码分析,获得对这个出色的开源软件更深入的了解,并可以根据我们 的需要对其进行更进一步的优化。末了将通过对BSM_Memcache扩展的分析,加深对memcached的使用方式理解。


本文的部分内容可能需要比较好的数学基础作为辅助。



◎Memcached是什么



在 阐述这个问题之前,我们首先要清楚它“不是什么”。很多人把它当作和SharedMemory那种形式的存储载体来使用,虽然memcached使用了同 样的“Key=>Value”方式组织数据,但是它和共享内存、APC等本地缓存有非常大的区别。Memcached是分布式的,也就是说它不是本地的。它基于网络连接[PS:这个说法容易产生误解,我澄清一下:memcache主要是提供服务端和api,需要客户端配合运行,那么一客一服可以说是分布式了吧,客户服同在一机器也算是分布式,其实分布是最初的意思就是客户机服务器,然后才是扩展:我爱钻牛角尖!](当然它也可以使用localhost)方式完成服务,本身它是一个独立于应用的程序或守护进程(Daemon方式)。



Memcached 使用libevent库实现网络连接服务,理论上可以处理无限多的连接,但是它和Apache不同,它更多的时候是面向稳定的持续连接的,所以它实际的并发能力是有限制的。在保守情况下memcached的最大同时连接数为200,这和Linux线程能力有关系,这个数值是可以调整的。关于 libevent可以参考相关文档。 Memcached内存使用方式也和APC不同。APC是基于共享内存和MMAP的,memcachd有自己的内存分配算法和管理方式,它和共享内存没有 关系,也没有共享内存的限制,通常情况下,每个memcached进程可以管理2GB的内存空间,如果需要更多的空间,可以增加进程数。 



◎Memcached适合什么场合



在很多时候,memcached都被滥用了,这当然少不了对它的抱怨。我经常在论坛上看见有人发贴,类似于“如何提高效率”,回复是“用memcached”,至于怎么用,用在哪里,用来干什么一句没有。memcached不是万能的,它也不是适用在所有场合。



Memcached 是“分布式”的内存对象缓存系统,那么就是说,那些不需要“分布”的,不需要共享的,或者干脆规模小到只有一台服务器的应用,memcached不会带来 任何好处,相反还会拖慢系统效率,因为网络连接同样需要资源,即使是UNIX本地连接也一样。 在我之前的测试数据中显示,memcached本地读写速度要比直接PHP内存数组慢几十倍,而APC、共享内存方式都和直接数组差不多。可见,如果只是 本地级缓存,使用memcached是非常不划算的。



Memcached在很多时候都是作为数据库前端cache使用的。因为它比数据库 少了很多SQL解析、磁盘操作等开销,而且它是使用内存来管理数据的,所以它可以提供比直接读取数据库更好的性能,在大型系统中,访问同样的数据是很频繁的,memcached可以大大降低数据库压力,使系统执行效率提升。另外,memcached也经常作为服务器之间数据共享的存储媒介,例如在SSO系统中保存系统单点登陆状态的数据就可以保存在memcached中,被多个应用共享。



需要注意的是,memcached使用内存管理数 据,所以它是易失的,当服务器重启,或者memcached进程中止,数据便会丢失,所以memcached不能用来持久保存数据。很多人的错误理 解,memcached的性能非常好,好到了内存和硬盘的对比程度,其实memcached使用内存并不会得到成百上千的读写速度提高,它的实际瓶颈在于网络连接,它和使用磁盘的数据库系统相比,好处在于它本身非常“轻”,因为没有过多的开销和直接的读写方式,它可以轻松应付非常大的数据交换量,所以经常 会出现两条千兆网络带宽都满负荷了,memcached进程本身并不占用多少CPU资源的情况。



◎Memcached的工作方式 [这部分还是看Memcached 原理和使用详解(PPT/PDF)较好理解]


以下的部分中,读者最好能准备一份memcached的源代码。



Memcached是传统的网络服务程序,如果启动的时候使用了-d参数,它会以守护进程的方式执行。创建守护进程由daemon.c完成,这个程序只有一个daemon函数,这个函数很简单(如无特殊说明,代码以1.2.1为准):

CODE:
#include <fcntl.h>

#include <stdlib.h>


#include <unistd.h>



int


daemon(nochdir, noclose)


    int nochdir, noclose;


{


    int fd; 



    switch (fork()) {


    case -1:


        return (-1);


    case 0: 


        break;  
    default:


        _exit(0);


    }



    if (setsid() == -1)


        return (-1);



    if (!nochdir)


        (void)chdir("/");



    if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) {


        (void)dup2(fd, STDIN_FILENO);


        (void)dup2(fd, STDOUT_FILENO);


        (void)dup2(fd, STDERR_FILENO);


        if (fd > STDERR_FILENO)


            (void)close(fd);


    }


    return (0);


}

这个函数 fork 了整个进程之后,父进程就退出,接着重新定位 STDIN 、 STDOUT 、 STDERR 到空设备, daemon 就建立成功了。 



Memcached 本身的启动过程,在 memcached.c 的 main 函数中顺序如下: 



1 、调用 settings_init() 设定初始化参数


2 、从启动命令中读取参数来设置 setting 值


3 、设定 LIMIT 参数


4 、开始网络 socket 监听(如果非 socketpath 存在)( 1.2 之后支持 UDP 方式)


5 、检查用户身份( Memcached 不允许 root 身份启动)


6 、如果有 socketpath 存在,开启 UNIX 本地连接(Sock 管道)


7 、如果以 -d 方式启动,创建守护进程(如上调用 daemon 函数)


8 、初始化 item 、 event 、状态信息、 hash 、连接、 slab


9 、如设置中 managed 生效,创建 bucket 数组


10 、检查是否需要锁定内存页


11 、初始化信号、连接、删除队列


12 、如果 daemon 方式,处理进程 ID


13 、event 开始,启动过程结束, main 函数进入循环。 



在 daemon 方式中,因为 stderr 已经被定向到黑洞,所以不会反馈执行中的可见错误信息。 



memcached.c 的主循环函数是 drive_machine ,传入参数是指向当前的连接的结构指针,根据 state 成员的状态来决定动作。 



Memcached 使用一套自定义的协议完成数据交换,它的 protocol 文档可以参考: http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt



在API中,换行符号统一为\r\n



◎Memcached的内存管理方式



Memcached有一个很有特色的内存管理方式,为了提高效率,它使用预申请和分组的方式管理内存空间,而并不是每次需要写入数据的时候去malloc,删除数据的时候free一个指针。Memcached使用slab->chunk的组织方式管理内存。



1.1和1.2的slabs.c中的slab空间划分算法有一些不同,后面会分别介绍。



Slab 可以理解为一个内存块,一个slab是memcached一次申请内存的最小单位,在memcached中,一个slab的大小默认为1048576字节 (1MB),所以memcached都是整MB的使用内存。每一个slab被划分为若干个chunk,每个chunk里保存一个item,每个item同 时包含了item结构体、key和value(注意在memcached中的value是只有字符串的)。slab按照自己的id分别组成链表,这些链表 又按id挂在一个slabclass数组上,整个结构看起来有点像二维数组。slabclass的长度在1.1中是21,在1.2中是200。



slab有一个初始chunk大小,1.1中是1字节,1.2中是80字节,1.2中有一个factor值,默认为1.25



在 1.1中,chunk大小表示为初始大小*2^n,n为classid,即:id为0的slab,每chunk大小1字节,id为1的slab,每 chunk大小2字节,id为2的slab,每chunk大小4字节……id为20的slab,每chunk大小为1MB,就是说id为20的slab里 只有一个chunk:

CODE:
void slabs_init(size_t limit) {

    int i;


    int size=1;



    mem_limit = limit;


    for(i=0; i<=POWER_LARGEST; i++, size*=2) {


        slabclass[i].size = size;


        slabclass[i].perslab = POWER_BLOCK / size;


        slabclass[i].slots = 0;


        slabclass[i].sl_curr = slabclass[i].sl_total = slabclass[i].slabs = 0;


        slabclass[i].end_page_ptr = 0;


        slabclass[i].end_page_free = 0;


        slabclass[i].slab_list = 0;


        slabclass[i].list_size = 0;


        slabclass[i].killing = 0;


    }



    /* for the test suite:  faking of how much we've already malloc'd */


    {


        char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");


        if (t_initial_malloc) {


            mem_malloced = atol(getenv("T_MEMD_INITIAL_MALLOC"));


        }


    }



    /* pre-allocate slabs by default, unless the environment variable


       for testing is set to something non-zero */


    {


        char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");


        if (!pre_alloc || atoi(pre_alloc)) {


            slabs_preallocate(limit / POWER_BLOCK);


        }


    }


}

在1.2中,chunk大小表示为初始大小*f^n,f为factor,在memcached.c中定义,n为classid,同时,201个头不是全部 都要初始化的,因为factor可变,初始化只循环到计算出的大小达到slab大小的一半为止,而且它是从id1开始的,即:id为1的slab,每 chunk大小80字节,id为2的slab,每chunk大小80*f,id为3的slab,每chunk大小80*f^2,初始化大小有一个修正值 CHUNK_ALIGN_BYTES,用来保证n-byte排列 (保证结果是CHUNK_ALIGN_BYTES的整倍数)。这样,在标准情况下,memcached1.2会初始化到id40,这个slab中每个 chunk大小为504692,每个slab中有两个chunk。最后,slab_init函数会在最后补足一个id41,它是整块的,也就是这个 slab中只有一个1MB大的chunk:

CODE:
void slabs_init(size_t limit, double factor) {

    int i = POWER_SMALLEST - 1;


    unsigned int size = sizeof(item) + settings.chunk_size;



    /* Factor of 2.0 means use the default memcached behavior */


    if (factor == 2.0 && size < 128)


        size = 128;



    mem_limit = limit;


    memset(slabclass, 0, sizeof(slabclass));



    while (++i < POWER_LARGEST && size <= POWER_BLOCK / 2) {


        /* Make sure items are always n-byte aligned */


        if (size % CHUNK_ALIGN_BYTES)


            size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);



        slabclass[i].size = size; 


        slabclass[i].perslab = POWER_BLOCK / slabclass[i].size;


        size *= factor; 


        if (settings.verbose > 1) {


            fprintf(stderr, "slab class %3d: chunk size %6d perslab %5d\n",


                    i, slabclass[i].size, slabclass[i].perslab);


        }       


    }



    power_largest = i;


    slabclass[power_largest].size = POWER_BLOCK;


    slabclass[power_largest].perslab = 1;



    /* for the test suite:  faking of how much we've already malloc'd */


    {


        char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");


        if (t_initial_malloc) {


            mem_malloced = atol(getenv("T_MEMD_INITIAL_MALLOC"));


        }       



    }



#ifndef DONT_PREALLOC_SLABS


    {


        char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");


        if (!pre_alloc || atoi(pre_alloc)) {


            slabs_preallocate(limit / POWER_BLOCK);


        }


    }


#endif


}

由上可以看出,memcached的内存分配是有冗余的,当一个slab不能被它所拥有的chunk大小整除时,slab尾部剩余的空间就被丢弃了,如id40中,两个chunk占用了1009384字节,这个slab一共有1MB,那么就有39192字节被浪费了。


Memcached 使用这种方式来分配内存,是为了可以快速的通过item长度定位出slab的classid,有一点类似hash,因为item的长度是可以计算的,比如 一个item的长度是300字节,在1.2中就可以得到它应该保存在id7的slab中,因为按照上面的计算方法,id6的chunk大小是252字 节,id7的chunk大小是316字节,id8的chunk大小是396字节,表示所有252到316字节的item都应该保存在id7中。同理,在 1.1中,也可以计算得到它出于256和512之间,应该放在chunk_size为512的id9中(32位系统)。



Memcached 初始化的时候,会初始化slab(前面可以看到,在main函数中调用了slabs_init())。它会在slabs_init()中检查一个常量 DONT_PREALLOC_SLABS,如果这个没有被定义,说明使用预分配内存方式初始化slab,这样在所有已经定义过的slabclass中,每 一个id创建一个slab。这样就表示,1.2在默认的环境中启动进程后要分配41MB的slab空间,在这个过程里,memcached的第二个内存冗 余发生了,因为有可能一个id根本没有被使用过,但是它也默认申请了一个slab,每个slab会用掉1MB内存



当一个slab用光后,又有新的item要插入这个id,那么它就会重新申请新的slab,申请新的slab时,对应id的slab链表就要增长,这个链表是成倍增长的,在函数grow_slab_list函数中,这个链的长度从1变成2,从2变成4,从4变成8……:

CODE:
static int grow_slab_list (unsigned int id) {

    slabclass_t *p = &slabclass[id];


    if (p->slabs == p->list_size) {


        size_t new_size =  p->list_size ? p->list_size * 2 : 16; 


        void *new_list = realloc(p->slab_list, new_size*sizeof(void*));


        if (new_list == 0) return 0;


        p->list_size = new_size;


        p->slab_list = new_list;


    }


    return 1;


}

在定位item时,都是使用slabs_clsid函数,传入参数为item大小,返回值为classid,由这个过程可以看出,memcached的第 三个内存冗余发生在保存item的过程中,item总是小于或等于chunk大小的,当item小于chunk大小时,就又发生了空间浪费。


◎Memcached的NewHash算法



Memcached 的item保存基于一个大的hash表,它的实际地址就是slab中的chunk偏移,但是它的定位是依靠对key做hash的结果,在 primary_hashtable中找到的。在assoc.c和items.c中定义了所有的hash和item操作。



Memcached使用了一个叫做NewHash的算法,它的效果很好,效率也很高。1.1和1.2的NewHash有一些不同,主要的实现方式还是一样的,1.2的hash函数是经过整理优化的,适应性更好一些。



NewHash的原型参考:http://burtleburtle.net/bob/hash/evahash.html。数学家总是有点奇怪,呵呵~



为了变换方便,定义了u4和u1两种数据类型,u4就是无符号的长整形,u1就是无符号char(0-255)。



具体代码可以参考1.1和1.2源码包。



注 意这里的hashtable长度,1.1和1.2也是有区别的,1.1中定义了HASHPOWER常量为20,hashtable表长为 hashsize(HASHPOWER),就是4MB(hashsize是一个宏,表示1右移n位),1.2中是变量16,即hashtable表长 65536:

CODE:
typedef  unsigned long  int  ub4;   /* unsigned 4-byte quantities */

typedef  unsigned       char ub1;   /* unsigned 1-byte quantities */



#define hashsize(n) ((ub4)1<<(n))


#define hashmask(n) (hashsize(n)-1)

在assoc_init()中,会对primary_hashtable做初始化,对应的hash操作包括:assoc_find()、 assoc_expand()、assoc_move_next_bucket()、assoc_insert()、assoc_delete(),对应 于item的读写操作。其中assoc_find()是根据key和key长寻找对应的item地址的函数(注意在C中,很多时候都是同时直接传入字符串 和字符串长度,而不是在函数内部做strlen),返回的是item结构指针,它的数据地址在slab中的某个chunk上。


items.c是数据项的操作程序,每一个完整的item包括几个部分,在item_make_header()中定义为:



key:键


nkey:键长


flags:用户定义的flag(其实这个flag在memcached中没有启用)


nbytes:值长(包括换行符号\r\n)


suffix:后缀Buffer


nsuffix:后缀长



一个完整的item长度是键长+值长+后缀长+item结构大小(32字节),item操作就是根据这个长度来计算slab的classid的。



hashtable 中的每一个桶上挂着一个双链表,item_init()的时候已经初始化了heads、tails、sizes三个数组为0,这三个数组的大小都为常量 LARGEST_ID(默认为255,这个值需要配合factor来修改),在每次item_assoc()的时候,它会首先尝试从slab中获取一块空 闲的chunk,如果没有可用的chunk,会在链表中扫描50次,以得到一个被LRU踢掉的item,将它unlink,然后将需要插入的item插入 链表中。



注意item的refcount成员。item被unlink之后只是从链表上摘掉,不是立刻就被free的,只是将它放到删除队列中(item_unlink_q()函数)。



item对应一些读写操作,包括remove、update、replace,当然最重要的就是alloc操作。



item 还有一个特性就是它有过期时间,这是memcached的一个很有用的特性,很多应用都是依赖于memcached的item过期,比如session存 储、操作锁等。item_flush_expired()函数就是扫描表中的item,对过期的item执行unlink操作,当然这只是一个回收动作, 实际上在get的时候还要进行时间判断:

CODE:
/* expires items that are more recent than the oldest_live setting. */

void item_flush_expired() {


    int i;  
    item *iter, *next;


    if (! settings.oldest_live)


        return; 


    for (i = 0; i < LARGEST_ID; i++) {


        /* The LRU is sorted in decreasing time order, and an item's timestamp


         * is never newer than its last access time, so we only need to walk


         * back until we hit an item older than the oldest_live time.


         * The oldest_live checking will auto-expire the remaining items.


         */


        for (iter = heads[i]; iter != NULL; iter = next) { 


            if (iter->time >= settings.oldest_live) {


                next = iter->next;


                if ((iter->it_flags & ITEM_SLABBED) == 0) { 


                    item_unlink(iter);


                }       


            } else {


                /* We've hit the first old item. Continue to the next queue. */


                break;  
            }       


        }       


    }


}

 

CODE:
/* wrapper around assoc_find which does the lazy expiration/deletion logic */

item *get_item_notedeleted(char *key, size_t nkey, int *delete_locked) {


    item *it = assoc_find(key, nkey);


    if (delete_locked) *delete_locked = 0;


    if (it && (it->it_flags & ITEM_DELETED)) {


        /* it's flagged as delete-locked.  let's see if that condition


           is past due, and the 5-second delete_timer just hasn't


           gotten to it yet... */


        if (! item_delete_lock_over(it)) {


            if (delete_locked) *delete_locked = 1;


            it = 0; 


        }       


    }


    if (it && settings.oldest_live && settings.oldest_live <= current_time &&


        it->time <= settings.oldest_live) {


        item_unlink(it);


        it = 0; 


    }


    if (it && it->exptime && it->exptime <= current_time) {


        item_unlink(it);


        it = 0; 


    }


    return it;


}

Memcached的内存管理方式是非常精巧和高效的,它很大程度上减少了直接alloc系统内存的次数,降低函数开销和内存碎片产生几率,虽然这种方式会造成一些冗余浪费,但是这种浪费在大型系统应用中是微不足道的。



◎Memcached的理论参数计算方式



影响 memcached 工作的几个参数有: 



常量REALTIME_MAXDELTA 60*60*24*30


最大30天的过期时间



conn_init()中的freetotal(=200)


最大同时连接数



常量KEY_MAX_LENGTH 250


最大键长



settings.factor(=1.25)


factor将影响chunk的步进大小



settings.maxconns(=1024)


最大软连接



settings.chunk_size(=48)


一个保守估计的key+value长度,用来生成id1中的chunk长度(1.2)。id1的chunk长度等于这个数值加上item结构体的长度(32),即默认的80字节。



常量POWER_SMALLEST 1


最小classid(1.2)



常量POWER_LARGEST 200


最大classid(1.2)



常量POWER_BLOCK 1048576


默认slab大小



常量CHUNK_ALIGN_BYTES (sizeof(void *))


保证chunk大小是这个数值的整数倍,防止越界(void *的长度在不同系统上不一样,在标准32位系统上是4)



常量ITEM_UPDATE_INTERVAL 60


队列刷新间隔



常量LARGEST_ID 255


最大item链表数(这个值不能比最大的classid小)



变量hashpower(在1.1中是常量HASHPOWER)


决定hashtable的大小



根据上面介绍的内容及参数设定,可以计算出的一些结果:



1、在memcached中可以保存的item个数是没有软件上限的,之前我的100万的说法是错误的。

2、假设NewHash算法碰撞均匀,查找item的循环次数是item总数除以hashtable大小(由hashpower决定),是线性的。


3、Memcached限制了可以接受的最大item是1MB,大于1MB的数据不予理会。


4、Memcached的空间利用率和数据特性有很大的关系,又与DONT_PREALLOC_SLABS常量有关。 在最差情况下,有198个slab会被浪费(所有item都集中在一个slab中,199个id全部分配满)。
 



◎Memcached的定长优化



根据上面几节的描述,多少对memcached有了一个比较深入的认识。在深入认识的基础上才好对它进行优化。



Memcached 本身是为变长数据设计的,根据数据特性,可以说它是“面向大众”的设计,但是很多时候,我们的数据并不是这样的“普遍”,典型的情况中,一种是非均匀分 布,即数据长度集中在几个区域内(如保存用户 Session);另一种更极端的状态是等长数据(如定长键值,定长数据,多见于访问、在线统计或执行锁)。



这里主要研究一下定长数据的优化方案(1.2),集中分布的变长数据仅供参考,实现起来也很容易。



解决定长数据,首先需要解决的是slab的分配问题,第一个需要确认的是我们不需要那么多不同chunk长度的slab,为了最大限度地利用资源,最好chunk和item等长,所以首先要计算item长度。



在之前已经有了计算item长度的算法,需要注意的是,除了字符串长度外,还要加上item结构的长度32字节。



假设我们已经计算出需要保存200字节的等长数据。



接下来是要修改slab的classid和chunk长度的关系。在原始版本中,chunk长度和classid是有对应关系的,现在如果把所有的 chunk都定为200个字节,那么这个关系就不存在了,我们需要重新确定这二者的关系。一种方法是,整个存储结构只使用一个固定的id,即只使用199 个槽中的1个,在这种条件下,就一定要定义DONT_PREALLOC_SLABS来避免另外的预分配浪费。另一种方法是建立一个hash关系,来从 item确定classid,不能使用长度来做键,可以使用key的NewHash结果等不定数据,或者直接根据key来做hash(定长数据的key也 一定等长)。这里简单起见,选择第一种方法,这种方法的不足之处在于只使用一个id,在数据量非常大的情况下,slab链会很长(因为所有数据都挤在一条 链上了),遍历起来的代价比较高。



前面介绍了三种空间冗余,设置chunk长度等于item长度,解决了第一种空间浪费问题,不预申请空 间解决了第二种空间浪费问题,那么对于第一种问题(slab内剩余)如何解决呢,这就需要修改POWER_BLOCK常量,使得每一个slab大小正好等 于chunk长度的整数倍,这样一个slab就可以正好划分成n个chunk。这个数值应该比较接近1MB,过大的话同样会造成冗余,过小的话会造成次数 过多的alloc,根据chunk长度为200,选择1000000作为POWER_BLOCK的值,这样一个slab就是100万字节,不是 1048576。三个冗余问题都解决了,空间利用率会大大提升。



修改 slabs_clsid 函数,让它直接返回一个定值(比如 1 ):

CODE:
unsigned int slabs_clsid(size_t size) {

        return 1;


}

修改slabs_init函数,去掉循环创建所有classid属性的部分,直接添加slabclass[1]:

CODE:
slabclass[1].size = 200;                //每chunk200字节

slabclass[1].perslab = 5000;        //1000000/200

◎Memcached客户端



Memcached是一个服务程序,使用的时候可以根据它的协议,连接到 memcached服务器上,发送命令给服务进程,就可以操作上面的数据。为了方便使用,memcached有很多个客户端程序可以使用,对应于各种语 言,有各种语言的客户端。基于C语言的有libmemcache、APR_Memcache;基于Perl的有Cache::Memcached;另外还 有Python、Ruby、Java、C#等语言的支持。PHP的客户端是最多的,不光有mcache和PECL memcache两个扩展,还有大把的由PHP编写的封装类,下面介绍一下在PHP中使用memcached的方法:



mcache扩展是 基于libmemcache再封装的。libmemcache一直没有发布stable版本,目前版本是1.4.0-rc2,可以在这里找到。 libmemcache有一个很不好的特性,就是会向stderr写很多错误信息,一般的,作为lib使用的时候,stderr一般都会被定向到其它地 方,比如Apache的错误日志,而且libmemcache会自杀,可能会导致异常,不过它的性能还是很好的。



mcache扩展最后更 新到1.2.0-beta10,作者大概是离职了,不光停止更新,连网站也打不开了(~_~),只能到其它地方去获取这个不负责的扩展了。解压后安装方法 如常:phpize & configure & make & make install,一定要先安装libmemcache。使用这个扩展很简单:

CODE:
<?php

$mc
= memcache();    // 创建一个memcache连接对象,注意这里不是用new!

$mc->add_server('localhost', 11211);    // 添加一个服务进程

$mc->add_server('localhost', 11212);    // 添加第二个服务进程

$mc->set('key1', 'Hello');    // 写入key1 => Hello

$mc->set('key2', 'World', 10);    // 写入key2 => World,10秒过期

$mc->set('arr1', array('Hello', 'World'));    // 写入一个数组

$key1 = $mc->get('key1');    // 获取'key1'的值,赋给$key1

$key2 = $mc->get('key2');    // 获取'key2'的值,赋给$key2,如果超过10秒,就取不到了

$arr1 = $mc->get('arr1');    // 获取'arr1'数组

$mc->delete('arr1');    // 删除'arr1'

$mc->flush_all();    // 删掉所有数据

$stats = $mc->stats();    // 获取服务器信息

var_dump($stats);    // 服务器信息是一个数组

?>

这个扩展的好处是可以很方便地实现分布式存储和负载均衡,因为它可以添加多个服务地址,数据在保存的时候是会根据hash结果定位到某台服务器上的,这也 是libmemcache的特性。libmemcache支持集中hash方式,包括CRC32、ELF和Perl hash。


PECL memcache是PECL发布的扩展,目前最新版本是2.1.0,可以在pecl网站得到。memcache扩展的使用方法可以在新一些的PHP手册中找到,它和mcache很像,真的很像:

CODE:
<?php


$memcache = new Memcache;

$memcache->connect('localhost', 11211) or die ("Could not connect");


$version = $memcache->getVersion();

echo
"Server's version: ".$version."n";


$tmp_object = new stdClass;

$tmp_object->str_attr = 'test';

$tmp_object->int_attr = 123;


$memcache->set('key', $tmp_object, false, 10) or die ("Failed to save data at the server");

echo
"Store data in the cache (data will expire in 10 seconds)n";


$get_result = $memcache->get('key');

echo
"Data from the cache:n";


var_dump($get_result);


?>

这个扩展是使用php的stream直接连接memcached服务器并通过socket发送命令的。它不像libmemcache那样完善,也不支持 add_server这种分布操作,但是因为它不依赖其它的外界程序,兼容性要好一些,也比较稳定。至于效率,差别不是很大。


另外,有很多的PHP class可以使用,比如MemcacheClient.inc.php,phpclasses.org上可以找到很多,一般都是对perl client API的再封装,使用方式很像。



◎BSM_Memcache



从 C client来说,APR_Memcache是一个很成熟很稳定的client程序,支持线程锁和原子级操作,保证运行的稳定性。不过它是基于APR的 (APR将在最后一节介绍),没有libmemcache的应用范围广,目前也没有很多基于它开发的程序,现有的多是一些Apache Module,因为它不能脱离APR环境运行。但是APR倒是可以脱离Apache单独安装的,在APR网站上可以下载APR和APR-util,不需要 有Apache,可以直接安装,而且它是跨平台的。



BSM_Memcache是我在BS.Magic项目中开发的一个基于APR_Memcache的PHP扩展,说起来有点拗口,至少它把APR扯进了PHP扩展中。这个程序很简单,也没做太多的功能,只是一种形式的尝试,它支持服务器分组。



和mcache扩展支持多服务器分布存储不同,BSM_Memcache支持多组服务器,每一组内的服务器还是按照hash方式来分布保存数据,但是两个组中保存的数据是一样的,也就是实现了热备,它不会因为一台服务器发生单点故障导致数据无法获取,除非所有的服务器组都损坏(例如机房停电)。当然实现这个功能的代价就是性能上的牺牲,在每次添加删除数据的时候都要扫描所有的组,在get数据的时候会随机选择一组服务器开始轮询,一直到找到数据为止,正常情 况下一次就可以获取得到。



BSM_Memcache只支持这几个函数:

CODE:
zend_function_entry bsm_memcache_functions[] =

{


    PHP_FE(mc_get,          NULL)


    PHP_FE(mc_set,          NULL)


    PHP_FE(mc_del,          NULL)


    PHP_FE(mc_add_group,    NULL)


    PHP_FE(mc_add_server,   NULL)


    PHP_FE(mc_shutdown,     NULL)


    {NULL, NULL, NULL}


};

mc_add_group函数返回一个整形(其实应该是一个object,我偷懒了~_~)作为组ID,mc_add_server的时候要提供两个参数,一个是组ID,一个是服务器地址(ADDRORT)。

CODE:
/**

* Add a server group


*/


PHP_FUNCTION(mc_add_group)


{


    apr_int32_t group_id;


    apr_status_t rv;



    if (0 != ZEND_NUM_ARGS())


    {


        WRONG_PARAM_COUNT;


        RETURN_NULL();


    }



    group_id = free_group_id();


    if (-1 == group_id)


    {


        RETURN_FALSE;


    }



    apr_memcache_t *mc;


    rv = apr_memcache_create(p, MAX_G_SERVER, 0, &mc);



    add_group(group_id, mc);



    RETURN_DOUBLE(group_id);


}

 

CODE:
/**

* Add a server into group


*/


PHP_FUNCTION(mc_add_server)


{


    apr_status_t rv;


    apr_int32_t group_id;


    double g;


    char *srv_str;


    int srv_str_l;



    if (2 != ZEND_NUM_ARGS())


    {


        WRONG_PARAM_COUNT;


    }



    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ds", &g, &srv_str, &srv_str_l) == FAILURE)


    {


        RETURN_FALSE;


    }



    group_id = (apr_int32_t) g;



    if (-1 == is_validate_group(group_id))


    {


        RETURN_FALSE;


    }



    char *host, *scope;


    apr_port_t port;



    rv = apr_parse_addr_port(&host, &scope, &port, srv_str, p);


    if (APR_SUCCESS == rv)


    {


        // Create this server object


        apr_memcache_server_t *st;


        rv = apr_memcache_server_create(p, host, port, 0, 64, 1024, 600, &st);


        if (APR_SUCCESS == rv)


        {


            if (NULL == mc_groups[group_id])


            {


                RETURN_FALSE;


            }



            // Add server


            rv = apr_memcache_add_server(mc_groups[group_id], st);



            if (APR_SUCCESS == rv)


            {


                RETURN_TRUE;


            }


        }


    }



    RETURN_FALSE;


}

在set和del数据的时候,要循环所有的组:

CODE:
/**

* Store item into all groups


*/


PHP_FUNCTION(mc_set)


{


    char *key, *value;


    int key_l, value_l;


    double ttl = 0;


    double set_ct = 0;



    if (2 != ZEND_NUM_ARGS())


    {


        WRONG_PARAM_COUNT;


    }



    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|d", &key, &key_l, &value, &value_l, ttl) == FAILURE)


    {


        RETURN_FALSE;


    }



    // Write data into every object


    apr_int32_t i = 0;


    if (ttl < 0)


    {


        ttl = 0;


    }



    apr_status_t rv;



    for (i = 0; i < MAX_GROUP; i++)


    {


        if (0 == is_validate_group(i))


        {


            // Write it!


            rv = apr_memcache_add(mc_groups[i], key, value, value_l, (apr_uint32_t) ttl, 0);


            if (APR_SUCCESS == rv)


            {


                set_ct++;


            }


        }


    }



    RETURN_DOUBLE(set_ct);


}

在mc_get中,首先要随机选择一个组,然后从这个组开始轮询:

CODE:
/**

* Fetch a item from a random group


*/


PHP_FUNCTION(mc_get)


{               
    char *key, *value = NULL;


    int key_l;


    apr_size_t value_l;



    if (1 != ZEND_NUM_ARGS())


    {


        WRONG_PARAM_COUNT;


    }



    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key, &key_l) == FAILURE)


    {


        RETURN_MULL();


    }


    


    // I will try ...


    // Random read


    apr_int32_t curr_group_id = random_group();


    apr_int32_t i = 0;


    apr_int32_t try = 0;


    apr_uint32_t flag;


    apr_memcache_t *oper;


    apr_status_t rv;



    for (i = 0; i < MAX_GROUP; i++)


    {


        try = i + curr_group_id;


        try = try % MAX_GROUP;


        if (0 == is_validate_group(try))


        {


            // Get a value


            oper = mc_groups[try];


            rv = apr_memcache_getp(mc_groups[try], p, (const char *) key, &value, &value_l, 0);


            if (APR_SUCCESS == rv)


            {


                RETURN_STRING(value, 1);


            }


        }


    }



    RETURN_FALSE;


}

 

CODE:
/**

* Random group id


* For mc_get()


*/


apr_int32_t random_group()


{


    struct timeval tv;


    struct timezone tz;


    int usec;



    gettimeofday(&tv, &tz);



    usec = tv.tv_usec;



    int curr = usec % count_group();



    return (apr_int32_t) curr;


}

BSM_Memcache的使用方式和其它的client类似:

CODE:
<?php

$g1
= mc_add_group();    // 添加第一个组

$g2 = mc_add_group();    // 添加第二个组

mc_add_server($g1, 'localhost:11211');    // 在第一个组中添加第一台服务器

mc_add_server($g1, 'localhost:11212');    // 在第一个组中添加第二台服务器

mc_add_server($g2, '10.0.0.16:11211');    // 在第二个组中添加第一台服务器

mc_add_server($g2, '10.0.0.17:11211');    // 在第二个组中添加第二台服务器


mc_set('key', 'Hello');    // 写入数据

$key = mc_get('key');    // 读出数据

mc_del('key');    // 删除数据

mc_shutdown();    // 关闭所有组

?>

APR_Memcache的相关资料可以在这里找到,BSM_Memcache可以在本站下载。


◎APR环境介绍



APR 的全称:Apache Portable Runtime。它是Apache软件基金会创建并维持的一套跨平台的C语言库。它从Apache httpd1.x中抽取出来并独立于httpd之外,Apache httpd2.x就是建立在APR上。APR提供了很多方便的API接口可供使用,包括如内存池、字符串操作、网络、数组、hash表等实用的功能。开发 Apache2 Module要接触很多APR函数,当然APR可以独立安装独立使用,可以用来写自己的应用程序,不一定是Apache httpd的相关开发。



◎后记



这是我在农历丙戌年(我的本命年)的最后一篇文章,由于Memcached的内涵很多,仓促整理一定有很多遗漏和错误。感谢新浪网提供的研究机会,感谢部门同事的帮助。



NP博士 02-13-2007

Continue reading 【转】Memcached深度分析

Pagination


Total views.

© 2013 - 2019. All rights reserved.

Powered by Hydejack v6.6.1