月度归档:2013年03月

题为“缓存的应用与设计模式”的技术分享活动准备文档

在公司举行的技术分享活动上, 这周轮到我进行一个主题的讲解了;在上周六开始在定下了“缓存” 为主题的内容,主要是有感于之前缓存在项目中的应用。主要思路就是在用户从浏览器发送请求,然后得到服务器的响应内容这样一个流程中,有哪些地方能够实现缓存的最大化利用,提高用户的体验以及服务器的响应速度。文档的主要构成是一些从网上找到的具体配置信息,概念性的内容;还有自己程序上的一些应用技巧,加上自己的理解;也借鉴了一些其他大牛的博客内容、理念等。

文稿如下:

在web应用的一个请求中,用户从浏览器发送请求,服务器接收到请求后进行数据的收集,根据参数信息调用对应的service或model,model从DB中查询检索到数据信息返回给服务器,服务器将得到的数据内容进行处理封装,响应发送给用户的浏览器。那在这个请求的流程中,缓存在不更改代码或少改代码情况下可以很好的提高服务器的响应处理速度,比如在服务器的缓存,内存层面的高速缓存,浏览器的本地缓存,程序应用上的缓存等。而有关php脚本优化以及源码编译后的opcode动态缓存的加速器如zend optimizer,eAccelerator等不在这次讨论范畴内。

服务器级缓存

Apache

mod_cache  mod_disk_cache  mod_mem_cache  mod_file_cache module的缓存实现

Apache 的缓存方式有两种,一种是基于硬盘文件的缓存,由mod_disk_cache实现,另一种是使用内存缓存,由mod_mem_cache实现,不过它们都是依赖mod_cache模块的,mod_cache模块提供了一些缓存配置的指令供它们使用,而mod_file_cache模块是搭配mod_mem_cache模块使用的

Mod_disk_cache以磁盘文件形式进行缓存

<IfModulecache_module>

CacheDefaultExpire  3600

CacheMaxExpire  86400

CacheLastModifiedFactor  0.1

<IfModuledisk_cache_module>

CacheEnable disk /

CacheRoot”D:\Program Files\Wamp\Apache2.2\cache”

CacheDirLevels 5

CacheDirLength 5

CacheMaxFileSize 1048576

CacheMinFileSize 10

</IfModule>

</IfModule>

使用缓存需要注意如下事项:

要使用缓存,必须使用指令CacheEnable启用它,目前可用的缓存类型为 disk 或mem,禁止缓存可以使用CacheDisable,如CacheDisable /private

待缓存的 URL 返回的状态值必须为: 200、203、300、301 或 410

URL 的请求方式必须是 GET 方式

发送请求时,头部中包含“Authorization: ”的字符串时,返回的内容将不会被缓存

URL 包含查询字符串,如问号?后的那些东西,除非返回的内容包含“Expires:”,否则不会被缓存

如果返回的状态值是 200,则返回的头部信息必须包含以下的一种才会被缓存:Etag、Last-Modified、Expires,除非设置了指令CacheIgnoreNoLastMod On

如果返回内容的头部信息“Cache-Control:”中包含“private”,除非设置了指令CacheStorePrivate On,否则不会被缓存

如果返回内容的头部信息“Cache-Control:”中包含“no-sotre”,除非设置了指令CacheStoreNoStore On,否则不会被缓存

如果返回内容的头部信息“Vary:”中包含了“*”,不会被缓存

配置完成后,重启apache后即可,测试一个脚本

<?php

Header(‘Last-Modified: ’. gmdate(‘D, d M Y H:i:s’, time()) . ’ GMT’);

Var_dump($_SERVER);

?>

在服务器指定的cachedir目录下面可以看到后缀为.data 与.header的文件;.data是当前请求页面的所有文档内容,而.header则是当前请求头信息。CacheDefaultExpire可以设定这些文件的过期时间。

Mod_file_cache缓存模块配合mod_mem_cache模块来使用

如果你的网站有几个文件的访问非常频繁而又不经常变动,则可以在 Apache 启动的时候就把它们的内容缓存到内存中(当然要启用内存缓存系统),使用的是mod_file_cache模块,具体如下:

有多个文件可以用空格格开

MMapFile  var/wwwml/index.html var/wwwml/articles/index.html

上面是缓存文件的内容到内存中,除此之外,还可以只缓存文件的打开句柄到内存中,当有请求进来的时候,Apache 直接从内存中获取文件的句柄,返回内容,和MMapFile指令很像,具体如下:

CacheFile var/wwwml/index.html var/wwwml/articles/index.html

上面两个指令所缓存的文件如果有修改的话,必须重启 Apache 或使用 graceful 之类的方式强制使 Apache 更新缓存数据,否则当用户访问的时候获取的不是最新的数据。

有时候需要根据某些特殊的头部信息来决定是否进行缓存,则可以使用如下指令:

当头部信息中包含 Set-Cookie 时则跳过不进行缓存操作

CacheIgnoreHeaders Set-Cookie

有时候需要缓存的时候跳过 URL 中的查询字符串?使用如下指令:

CacheIgnoreQueryString On

Apache 还有一个模块mod_expires,这个模块控制header中的cache-control参数的max-age指令

ExpiresActive On

#ExpiresDefault A600

ExpiresByType image/x-icon A2592000

ExpiresByType application/javascript A2592000

ExpiresByType text/css A604800

ExpiresByType image/gif A2592000

ExpiresByType image/png A2592000

ExpiresByType image/jpeg A2592000

ExpiresByType text/plain A86400

ExpiresByType application/x-shockwave-flash A2592000

ExpiresByType video/x-flv A2592000

ExpiresByType application/pdf A2592000

#ExpiresByType text/html A600

可以看到js文件

Cache-Control     max-age=2592000

Connection Keep-Alive

Date  Thu, 21 Mar 2013 07:21:31 GMT

Expires       Sat, 20 Apr 2013 07:21:31 GMT

Keep-Alive timeout=5, max=100

Server        Apache/2.2.19 (Win32) PHP/5.3.6

Vary  Accept-Encoding

X-Pad         avoid browser bug

自动加上了Expires过期时间了

扩展:

压缩模块

除了缓存服务器轻松配置一下mod_deflate gzip就能返回压缩后的数据,压缩率可以达到60%-75%之间。

扩展:

Nginx也有对应的fastcgi-cache /HttpHeadersModule /gzipmodule

内存级缓存

Memcache

Memcache是一个高性能的分布式的内存对象缓存系统,通过在内存里维护一个统一的巨大的hash表,它能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。简单的说就是将数据调用到内存中,然后从内存中读取,从而大大提高读取速度。

Memcache现在的版本是v1.4.15,使用memcache的网站一般流量较大;为了缓解数据库的压力,让memcache作为前端数据的一个缓存区域,将部分信息保存到内存中,让内存作为硬盘的高速缓存。因为单台服务器的memcache缓存容量是有限的,不过memcache是支持分布式的,所以我们可以根据实际情况进行数据的分布存储,比如社交类的网站以user为主,可以考虑把userid 为1开头的存放到编号为1的memcache服务器上,userid为2的放到编号为2的memcache服务器上。只是这样的话在程序业务上面就需要对userid进行判断了。

Memcache服务器端是直接通过客户端连接后操作的,没有进行任何的验证过程;在平时开发环境中可以这样,但是生产环境中就不能这样了。Memcache不能直接暴露在互联网上,否则很容易被人查看,或者服务器被入侵。所以我们可以以

1、   内网访问memcache服务器,服务器一般有两个网卡,一内一外;我们就让web服务器通过机房内网来访memcache。 Memcache的服务器上启动的时候就监听内网的IP地址和端口,内网间的访问能够有效阻止其他非法的访问。

# memcached -d -m 1024 -u root -l 192.168.0.200 -p 11211 -c 1024 -P /tmp/memcached.pid

Memcache服务器端设置监听通过内网的192.168.0.200的ip的11211端口,占用1024MB内存,并且允许最大1024个并发连接

2、   设置防火墙也是简单有效的方式,如果memcache需要通过外网ip来进行访问,那么我们可以设置服务器的iptables

# iptables -F

# iptables -P INPUT DROP

# iptables -A INPUT -p tcp -s 192.168.0.2 –dport 11211 -j ACCEPT

# iptables -A INPUT -p udp -s 192.168.0.2 –dport 11211 -j ACCEPT

这里的意思就是指定只允许来自192.168.0.2的服务器进行访问。

Redis

Redis是一种面向“键/值”对类型数据的分布式NoSQL数据库系统,特点是高性能,持久存储,适应高并发的应用场景。目前最新版本2.6.11

Redis是一个NOSQL数据库系统,支持各种数据存储类型,普通字符串,list,set,order sets;

那么redis与memcache都是内存级别的数据缓存,他们之间有什么区别呢?应该怎么选择呢。

1、memcache是一个高性能分布式的缓存系统,而redis是高性能分布式NOSQL数据库系统。

2、memcache数据常驻内存,memcached挂掉后数据消失;而redis数据内容也是常驻内存,且还可以定期的保存到磁盘中

3、memcache数据不可恢复,而redis数据丢失后可以通过aof恢复

4、redis有着更丰富的数据结构选择,在redis有着更丰富的操作;在memcache中只能拿到客户端中进行类似的操作在set进去,加大网络的IO次数

等等

Memcache与redis各有各的优势,memcache内存利用率高,数据存储维持简单搞笑的键值对模式,可以单纯的作为一个cache层,对于前后台的数据传输有着一个中转站的作用,而且有着成熟的分布式方案,同事各个语言都有着优秀的客户端。Redis是面向对象的键值对高性能分布式的数据库系统,适用于高并发,持久存储的场景,有着完善的数据存储类型且数据操作都是支持incr原子操作的,redis客户端也覆盖了各种语言,能很方便的使用。

对于数据同步以及持久化有很高要求的可以使用redis,对于仅仅是作为一个数据缓存层且不想维持复杂的数据类型结构那么可以选择memcache。

浏览器本地缓存

HTTP网页缓存

Cache-Control     max-age=2592000

Connection Keep-Alive

Date  Mon, 25 Mar 2013 07:31:50 GMT

Expires       Wed, 24 Apr 2013 07:31:50 GMT

一般一个http响应的header信息,包括如Etag  Last-modified Cache-control Expires 等这些信息,那么浏览器根据这些信息一并与网页内容进行本地缓存。

If-None-Match   “427fe7b6442f2096dff4f921548565486”

If-Modified-Since   Fri, 27 Jul 2012 01:35:39 GMT

浏览器再本地的缓存中发现last-modified信息,那么就以If-Modified-Since加入到header信息中发送至服务器,询问下服务器上面最后的一次访问更新是什么时候呢,那么apache服务器就会核对一下这个文件的最后更新时间,如果大于这个时间那么就会返回一个200的响应,重新下载这个文件。如果文件还是没有更新那么服务器就返回一个304 Not Modified,浏览器还是继续用本地缓存。

http的缓存,不仅可以节省服务器处理资源;还减少了网页的下载次数,节约网络带宽。但有时候我们经常是动态编程,比较少会整体缓存整个网页,不过对于网页所需要的附属内容如css、js、图片等内容,网站结构所需的图片js等内容我们可以缓存时间长点如30day,而用户图片数据内容可以进行5day的有效缓存。

如google这样的智能搜索引擎可以识别资源的状态信息,使用http cache之后,只有在文本内容真正变化后才会被爬取,那么我们可以直接对爬虫返回304 not modified,这样不但降低服务器负载与爬虫造成的网络带宽消耗,实际上也提高了google爬虫的效率。

历史帖子,一段时间后就很少人访问了;但是爬虫还是会每天记录访问造成服务器的高消耗,使用http cache后,爬虫爬过一次后,以后无论多少次爬取都可直接返回304了。

HTML5 DOM Storage本地存储API

在web storage api出现之前,服务器端需要存储客户端与浏览器之间交换的数据信息,如ajax请求的数据信息。而现在浏览器可以将需要重复请求的数据内容存储到web storage上,同时在关闭浏览器之后还可以恢复数据。

那同样是进行浏览器数据存储的cookie与web storage有什么区别?我们知道cookie是服务器端与浏览器进行文本传输的内置机制,我们可以用cookie来存储服务器生成当前连接的会话标识,可以用来实现购物网站的购物车,可以用来存储数据供接下来的页面使用。Cookie对于web开发程序确实有很大的用处,但是cookie也是有一些缺点的

1、   Cookie大小受限制,4KB数据

2、   只要有请求浏览器就会自动将cookie进行传输,cookie数据在网络上是可见的,存在安全风险;同时无论加载哪个url,cookie的数据都会消耗网络带宽。

而web storage目前可以存储高达5MB的数据内容,主流浏览器都支持。Web storage 有两种主要实现sessionstorage与localstorage,sessionstorage主要是用于会话的数据存储,关闭浏览器之后自动失效;而localstorage是永久存储在浏览器中的。

通过调用对象的setItem()与getItem()方法进行设置与获取的实现

window.sessionStorage.setItem(‘k’, ‘v’);

window.sessionStorage.getItem(‘k’);

window.sessionStorage.removeItem(‘k’);

也可以通过对象属性方式进行设置获取

window.sessionStorage.firstKey = ‘val’;

alert(window.sessionStorage.firstKey);

那么这样与普通的javascript数据对象有什么区别呢。最大的一个区别就是在于“作用域”普通的js对象在当前页面刷新后丢失已经不奇怪了,但是对于sessionstorage只要不关闭标签或浏览器,一个页面设置的sessionstorage值,只要是同源的网页中,其他页面可以依据Key获取到值。但是因为sessionstorage不能持久保存所以对于不是很重要的数据,但是短时间内存在的流程(比如对话框、向导),多个页面需要该数据的情况下可以存储在其中,而在以前这些数据需要用form或cookie进行来回传递的。

Localstorage代码使用上没有区别,但是要注意的是localstorage是持久化存储,除非用户删除是永远不会删除的。

扩展

IndexedDB

大容量索引型的数据结构存储可以看看IndexedDB,以K/V键值对形式进行数据存储。

程序应用层缓存

在应用开发过程中,需要了解缓存可以有效的提高程序的响应能力与处理能力。

那哪些数据写入缓存?

大量非实时更新的数据内容,如文章,站点信息,tag分类,rss list信息

Mpf魔炫网上面一个专题里面有着视频信息、专题信息、相关文章信息、微博信息等内容。

在项目上线运营之初没有考虑到具体的情况,后来分析发现在视频详细页与专题页是有着最高的访问量的,最高是达到了1万人同时在线访问,那段时间导致后台压力巨大,用户打开页面缓慢;之后做出调整,在视频、专题信息填写完成之后很少进行改动,所以我们决定把一些如视频信息,专题信息,相关文章信息等内容加入到缓存区,用户在进行访问是可以说服务器几乎不用查询mysql了。能够很大的提高用户的访问量,服务器数据能快速的返回。

ORM对象,大量的sql的查询主要作用于服务器磁盘的IO,在一定程度上mysql的反应速度体现在磁盘IO的速度上。那么在实现ORM对象缓存是,需要考虑的是

我们是以减少服务器磁盘IO为目的,不是为了减少发送到数据库的sql条数;使用ORM会增减sql语句条数。

既然决定采用ORM模式作为主要的数据读写操作,那么在数据库表结构的设计上尽量越细越好,经常查询访问的大数据量单独列表。

同时在构建sql语句是尽量避免多表关联查询,拆成多表的主键查询。(某个location附近的event)

保存缓存的数据同步

加入缓存后,就存在这样的问题;要保存缓存与数据库数据的一致性!否则数据不一致会加大程序排除问题的难度。那么数据一致性通常是在insert,update,delete时。可以以回调的方式实现缓存的同步。

Insert数据到数据库时,在insert成功后,将得到的最新last_insertid作为key,当前的数据内容作为value存入到缓存。数据在被调用时,直接就是获取缓存了。

Update 对于缓存的数据更新可以有两种方式。

1、   主动更新

2、   被动更新

主动更新就是update记录成功后,直接回调update缓存机制。

$cache->Delete($id);

$cache->set($id, $data);

先删除,后写入保持数据的最新。

被动更新则是,在用户访问这个页面时程序调用发现没有缓存,第一次去数据库读取内容返回,且写入缓存;用户第二次访问这个页面时则返回的是缓存数据内容了。

那么我个人推荐的是第一种,即在更新数据时直接同时回调将缓存数据内容也更新,这样避免在用户第一次访问时可能会得到一个较慢的响应。

案例:

某个网站的文章缓存是以文章id+文章的updatetime来作为key进行数据缓存的,在文章更新时updatetime改变,用户访问程序进行缓存查询时没有找到,进而查询数据库返回并且写入到cache中,注意这时原先的缓存并没有删除且也可能没有过期,那么就是一直存在其中缓存系统之中,直到缓存系统自动的垃圾回收。

1、   这样需要用户访问,被动式更新

2、   加重缓存系统的压力,需要管理更多的实际上对程序来说已经过期了的数据内容。

缓存的过期数据处理

缓存的过期数据处理的实现主要是两个方面的

1、数据库记录在删除时,同时回调删除缓存中的数据内容。

2、缓存对象系统的自动清除

建议是在数据库记录delete成功后回调清除cache中的记录信息,保持缓存系统中数据的清洁度。

Kohana中的缓存实现

Kohana内核框架只有一个简单的file cache方式,core.php里面的kohana:cache($key, $data, $lifetime = NULL)方法。实现方式就是当前时间与文件内容修改时间比对。

If($data === NULL){

If( (time() – fimemtime( $file)) < $lifetime ){

Return unserialize(file_get_content($file));

}else{

// delete expires cache file

}

Return NULL;

}

file_put_contents($file, serialize($data)); //写入

在项目复杂的业务逻辑中,这样的缓存机制肯定是不够用的了。所以kohana采用cache module用来专门的做缓存实现。且缓存实现在框架内核中多次用到,所以内核框架是有几个必须依赖的module的,cache就是其中之一。

今cache module实现的缓存驱动有apc,file,memcache,sqlite,wincache这几种,而我们常用的就是file,memcache。

Kohana中的缓存应用主要体现在

1、   类实例对象的缓存

2、   配置信息的缓存应用

分享总结

昨天讲解完毕之后,反响不咋地;你自己理解不一定能完整清晰的表达出来,有些问题模糊不清,自己也没弄明白,确实没有讲好;整体内容很全面,但是没有具体到哪一点,好像什么都概括了,但是没有侧重某一点深入讲开。

而且演讲的技巧要加强啊,整个过程就是自己个人概述没有一个活跃的气氛,让人昏昏欲睡。

总结下来就是几点

1、  整体内容比较多,较平。介绍用户一个请求过程中哪些地方可加入缓存机制,没有侧重某一点。

2、  内容概念多,实质涉及代码较少;需要把概念与代码结合起来。

3、  有些地方的问题较模糊,讲述不够清楚。

4、  事前对于提出需求的人想了解的内容,没有深入进行讲解。事先沟通没有做好。

5、  缓存应用的程序深度应用需要加强,内容较为浅显;案例分析代码讲解较少。

MongoDB实现mysql主键autoincrement

MongoDB新建表默认有一个_id字段来作为autoincrement自增实现,而这个_id字段类型是objectid类型(objectid 是12字节的BSON类型)。ObjectId的详细解释。

而现在因为数据库迁移,将项目中原mysql的一些locations表移植到mongodb上面。对于locations 使用mongodb 2d loc 索引能够更快的进行数据检索,同时避免原mysql需要经过复杂的经纬度查询。

那么数据迁移后主要的要求有

1、数据库表的迁移不会影响表与表之间的关系,继续保持表之间的主外键关系。

2、lat 与 lng 字段在mongodb 新表中合并为一个字段loc,同时对loc加以2d索引。

对于第二点主要影响是在代码之中,可以看看这里。就不做详细描述了。

那么现在主要要做的事情就是,维持表与表的关系与主外键关系。我们以event 与 对应的 event_locations为例

event中有一个外键location_id,location_id就是event_locations的主键id;而现在mongodb中新建event_locations表,同时要保持event_locations与event的主外键引用关系。

因为mongodb表中默认有着_id这样一个主键,那现在我们的数据迁移以及mongodb event_locations表结构构建有两种选择

1、event_locations 主键以_id,即将原mysql event_locations 的id 替换为 _id。也就是如下表结构


{ "_id" : ObjectId("51243bdb383ca83413000000"),  "topic_id" : 103874, "name" : "xixi aaahah", "address" : "xxxbbbb cccccc", "loc" : [ 30.11, 112.02 ], "created" : 1361329114, "updated" : 0 }

优缺点分析

优点就是 使用默认的_id主键值,速度快检索方便。_id自增不必维护,有mongodb自行完成。

缺点很明显 event中的location_id外键,原来类型是int,现在改用_id objectid类型作为主键id,location_id类型必须要更改为char(32)了。且程序代码还需要一定的更改。

最大的缺点就是目前项目已经上线,直接更改数据库表可能会带来不可预知的问题;对于运营维护可能带来很大压力。且对于目前event已有的location_id数据需要进行同步更新。

2、保持原来的event_locations表结构,使用id继续维持与event location_id的主外键关系。


{ "_id" : ObjectId("51243bdb383ca83413000000"), "id" : 2354, "topic_id" : 103874, "name" : "xixi aaahah", "address" : "xxxbbbb cccccc", "loc" : [ 30.11, 112.02 ], "created" : 1361329114, "updated" : 0 }

优缺点分析

优点 不必去更改event location_id的数据类型,且location_id的数据不必修改;只用将event_locations数据完整导入到新表中即可。

缺点 要维持id的自增属性,而mongodb默认是_id才有自增属性的,所以我们要用某种方法实现id的自增实现。_id会占用存储空间(默认的_id原本可以删除的,但考虑到以后可能会用到就保存了)。

我现在采用的是第2种方式,主要原因如下

1、项目已上线,且已经有了用户数据;对于直接更改数据表与操作数据会带来很大的风险。强烈不推荐。

2、event_locations数据几乎不用同步,只需要单向导入到mongodb 新表就好。

3、程序代码变动最小,目前程序代码更改就是新的loc(lng+lat)存储与id的自增实现。而第一种除了这些,还需要把所有的id替换为_id,数据类型也要从int变为string。

在确定了解决方法之后那么现在主要的难题就是怎么实现id的自增?

目前也有两种方法

1、传统实现,每次在event_locations insert之前,全表id desc排序得到最大的id值,对id值+1既得到了最新的id值从而实现了自增。

2、使用mongodb的findAndModify()方法实现,独立一个ids表用来记录所有可能需要id自增的值。

同样简要说明下这两种实现id自增的方法优劣。

第一种方法

优点 不用构建ids表(工具表),免于维护

缺点 每次都要进行全表索引排序且查找到最大id那条记录,在进行+1

第二种

优点 findandmodify() 原子操作(了解原子操作请查看这里),查找并修改,直接返回当前操作的这个文档内容。

缺点 要构建维护ids表,即项目中所有表可能需要自增的表都有一条记录存在于其中。

无论采用哪种方法都要修改程序代码,即id autoincrement部分需要程序实现。我采用的是第二种方法。

主要考虑还是性能问题,避免每次insert之前都要对event_locations进行全部的索引排序。而维护ids这样的表会很简单且性能上忽略不计,详细表结构如下


{ "_id" : ObjectId("5147ca1a08c2c1bc12abdb4c"), "id" : 2356, "tablename" : "topic" }

// _id 默认主键,mongodb自增

// id  表中自增主键值

// tablename 表名

获取下一个id的核心代码,继承了mongodb的ids Model。其中一个方法专门来做这个事情。


/**
* 构建ids用于存储所有表中需要自增的id值,结合findandmodify()来返回对应表最新的id
* @param string $tablename 表名
* @return int
*/
public function get_next_id($tablename = 'topic'){

// db.ids.findAndModify({update:{$inc: {id:1}},query:{tablename:'topic'},new:true});
$data = array(
'findAndModify' => 'ids',
'update' => array( '$inc' => array( 'id' => 1)),
'query' => array('tablename' => $tablename),
'new' => true
);
$idrecord = $this->db()->command( $data );
$newid = $idrecord['value'];

return $newid['id'];
}

// 等同于db.ids.findAndModify({query: {tablename: 'topic'}, update: {$inc: {id: 1}}});

这样在获取到最新的id值后,insert event_locations 时需要将id加入到sql语句中(原先id是autoincrement,id字段没有在sql中)。之后也就是同样的业务流程了。

扩展:

有时候可能会遇到一些奇怪的需求,比如一张表里面有两个自增id要实现。。。如此,上面的表结构就不能实现了。

<pre>// _id 默认主键,mongodb自增
// fieldname  表中自增字段名
// fieldval 表中自增id值
// tablename 表名</pre>

如此就行了哦

有时候也会碰到一些奇怪的需求,比如要从指定的值如200开始id自增。

那么其实用这样一句就可以了

db.ids.findAndModify({query: {tablename: 'topic'}, update: {id: 200,tablename: 'topic'}});
代码实现上就是注意要update时要把tablename也要传递进去了,因为update会更新所以的记录,如果不写上tablename那么返回的文档就只有一个id字段了。。。有需要的自己扩展下吧

需要注意的是event_locations id现在不是主键也没有任何索引,除非你加上了;所以这里还是强烈推荐加上索引

db.event_locations.ensureIndex({id: 1});

参考

在MongoDB中模拟Auto Increment

Create an Auto-Incrementing Sequence Field

findandmodify命令

“笔记”项目缓存应用

有感于Web应用的缓存设计模式这篇文章里面的缓存使用设计。

其实缓存方面确实可以大做文章,看看这位大牛的理念

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

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

这里我看到了这样的一个新概念,就是“数据库的瓶颈往往是磁盘IO,而不是SQL的并发数量”;这个说法我也不知道是否正确的,没有经历过百万千万数据级别的研究与维护;不过可以学习一下。

确实如同该博主所说,对于一个没有太多访问量的网站有些过度设计,不过如果用户或数据量突然飙升情况下可以以较少的代码更改,实现更高的承载力;当然了,在网站最初设计时,这些因素也应当考虑进去;之前我做的mpf就是没有考虑这些问题,导致高峰时期(1w+ 并发)都访问一个专题页面内容,而次次几乎都会读取数据库服务器,差点导致宕机,后来对于专题内呈现的非动态数据内容都进行了memcached缓存,这样应该好多了。

从数据读取已经ORM有效性来说,最小化schema表设计确实很有效果

1、主键查询mysql表会buffer数据缓存。

2、ORM关系很透明明了

3、ORM model对象可以直接进行数据缓存(目前公司有一个项目就是如此)

如一个微博系统,微博一张表,微博附属信息一张表,微博插入的音乐,图像,视频,评论登录都是独立一张表,全部用id与微博主表关联起来。

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

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

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

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

数据库设计分表这个应该很常见了,常用字段一个表,非常用字段可以另一个表;同时大数据字段单独一表。

数据一致性问题

使用缓存,必须要考虑数据一致性问题。写入保持同步即可,在读取时需要首先检索cache里面有没有这样的数据,有着读取,无则读取数据并写入缓存;

主要麻烦的地方就是在于update与delete,在原数据信息更新时,缓存数据也必须更新了,否则数据会不一致。不论是内存缓存还是文件脚本缓存或是html页面缓存都是这样的。区别在于是程序主动更新还是用户访问的被动更新。

有些开源cms 采用的就是用户被动访问更新,有人访问;采取cache中去读取更新。不过我更喜欢程序主动更新。如上文博主采用的以文章update更新时间+id作为key来存储缓存信息,在文章原数据update之后,update更新时间一定会被修改,那么读取时cache中就没有了这样的文章内容了,但是原来的文章数据缓存还是存在的,只能等待cache服务器进行自动抛弃更新。这样的话cache中就可能会多很多冗余数据了,对于cache服务器检索查询一定有影响吧。

我的选择就是在文章数据更新时直接update cache或者是不update cache 但是一定把原文章 cache数据清除掉。

在用户删除文章时,先对cache里面的数据进行清除,然后进行数据库的原数据删除。

今天抽空把daynote.sinaapp.com 中的markdown转换后的数据那一层进行缓存,缓存时间是7200s,对于此类非公开的私密日记。用户访问查看后一般都会选择离开,不会时时刻刻进行刷新数据请求,7200s数据缓存搓搓有余了。

原先的xhzd.sinaapp.com也是使用大量的memcache缓存,其中列表都使用了;但是考虑到sae上面的memcache额度,字详细那一块没有缓存。目前几乎没什么访问量,也就没有进行缓存操作了。如果访问量大,那么可以把几乎整个字典都存入了缓存了。

SAE上面使用memcache的方法请看这里

混沌

年复一年,唯一在变的就是时间了吧。

几年都是这样,永远对当前不满意;自我感觉总是憋屈,憋屈不谈,年纪长了却又要受婚姻催促之苦。真是麻烦啊。

 

对于相亲这种事情,在双方父母首先见面相谈甚欢之后总是以先入为主的思想;“恩,这样的亲家很好相处,你们要是搞成了会很好”,而我在见过一面之后就已经确定明白以及肯定的知道这事是不可能的,对于这样的女生,第一次我对你多好或多差,那么后几次也就是这样了,关系不会因为时间的改变而进一步,但是肯定会因为时间流逝而慢慢疏远到最后就是从此只是路人,我从来不相信男女之间有那种可以长远的友谊。说实话我是外貌协会,对于第一眼不喜欢的,之后怎样我也无法对目前的关系更进一步了。虽然我已经对妈这样说明以及强调了,但是她还是不停的说着“如果你们将来怎样了,xxxx”,“我看她妈的性格之后呢,就觉得这姑娘肯定跟我搞的来”,云云。说实话我很反感,只能继续强调了。但是另一方面却又碍于对方的情面以及父母的压力不得不去赴那种我根本不想搭理的约,所以果不其然既无聊又累人。

 

于是在各种不配合之后,老妈放狠话了,“你这也不喜欢,那也不喜欢你喜欢哪样的呢?”是啊,我喜欢哪样的,第一就是看上眼,不要求很漂亮,但是希望160cm要有吧,喜欢较瘦纤细一点的,如果是黑长直就更好了。第二性格善良一定要善良,一个不善良的女人不可能有贤惠,温柔,通情达理的气质。第三你一定是我喜欢的爱的,当然我也希望我也是你喜欢的爱的,虽然妈总是跟我说宁愿找一个女生喜欢你的,这样那个女生就会对你言听计从,至少会万事都向着你,但是对于这样的问题我的回答是“这样两个人根本是不平等的,双方没有在一个高度,一定会吵架争执”,也许妈是根据她与爸的这几十年婚姻得出的结论,尽管他们也经常吵架,但是我觉得他们之间还是很爱对方的,不过我还是坚持我的看法。在坚持我的看法之后,老妈再次放狠话“在不管你这些事了,看你怎么办等你象三舅那样(ps:三舅36才结婚)”对此我也是很是担心,三舅年轻时不乏最求者与被最求者,但是因为各种原因(主因还是三舅瞧对方不上)没有结成婚,以至于错过了,而现在三舅娶的三舅娘比他小十多岁,现在天天在家里吵闹。于是老妈总是依次为例来告诫我,我确实担心,但是担心的不是这个;我担心的是什么样的年纪做什么样的事情,在学生时没有打架,打篮球,初恋好好的做一个书呆子,却也没有得到对应的一个成绩就这样无为的度过了学生时代,在我的理解就是学也没学好,玩也没玩好;而现在确实到了婚嫁的年龄了,同学朋友同事都开始迈入婚姻了,我却还是独行一人。你只是参加他人婚礼,你的婚礼呢。这种境遇我确实不想遭遇,拿爸妈的一句话来说“你还不快点结婚,这几年人情都送了多少出去了;还有你送出去的,到时候结婚晚了,关系淡了别人哪还来啊”。这个确实是我在意的,我为什么老是落后于人呢?!奈何是个性使然么,不自信不果断。还记得初中时起外号对同学我自称“独来独往独大侠”,那时呈一时之快就脱口而出了,其实我一次都没用过也不喜欢这个外号。

 

其实我也理解父母的这种感受,他们不容易,而且现在也快50了;他们常言“你快点结婚,我帮你带几年孩子,你们也轻松些,也趁我们还带的动,过几年孩子大了就好了”,对此之前我总以为他们这是自私,这是为了早点完成自己的任务;但是后来我看到了知乎上的一个问答,中国的父母为什么有催孩子结婚的传统?

被赞同最多的答案里面确实很有道理

“所以我觉得除了情感因素,父母催婚还隐含着对中国国情的经济学考虑。目前中国家庭生活的国情,首先是中国一向有赡养父母的传统,不单单是经济上,还有身心 上的照顾,老人的身体就像是老花瓶,不断地破碎和修补,每一次修补后都变得更加脆弱。其次,中国的社保养老体系离完善还很远,前景具有很大不确定性。最 后,中国长期的独生子女政策将使未来几十年出现严重的人口结构老龄化问题,年轻人将承担非常大的负担。而早点生子在已定程度上可以缓解这项压力,或者说将 中年时养儿和养老的压力分散开,把养儿的压力提前些交给年轻力壮时来承担,且爷爷奶奶也可以帮忙分担一部分这方面的压力,而到了爷爷奶奶年老时,孙儿已经 长大成人,孙儿又可以分担一些赡养老人的压力。”

确实这些事情是我之前没有想到的,我在父母的羽翼下待的太久了。很多事情都是父母出面完成,而我只是坐享其成的。所以目前还是自己做好自己,然后加油吧。

还有就是“你看人家比你小的xx娃都已经会走了”言下之意我是听得出来的,父母也想早点抱孙子以不用羡他人,这点不论是在城市或者是在农村只要是有中国人的地方都会有着这种比较。前几天邻居家的小孩的婴儿车暂时放在我家里,一个已有孙子的太婆跑进来突然说了一句“抱孙子了啊”,妈没有搭理,我也没有搭理,那SB太婆无趣走了;明知故问,话里有话我也很不爽!我还是比较传统的对此我只能说QNMGEB,然后心里默默的喊加油。

 

但是如此如此,有时候又故意放纵自己;不去找女朋友,自己躲着撸。确实很无耻啊。混沌度日,感觉每天都是如此一成不变,对于路上遇见女生又不敢上前搭讪,总是希望有一天有一个美女突然从天而降砸到你头上,然后从此过上了快乐幸福的日子。不过这种概率应该比陨石解体砸到你头上的概率还要低吧。且人家花枝招展前凸后翘的美女岂会看上你这样无车无房无存款的屌丝呢?但是灰心几天后又开始打鸡血各种联系各种聊,对未来媳妇的出现充满信心,坚信桃花不是今日就是明日会开在你头上的,但是没过几天又开始心灰意冷放纵自己,过一天算一天那种心态,就是这样轮回过完了一年。然后等到年尾再次被各种亲属催促,各种朋友鄙视。

 

今年却真的不想这样了。对于不喜欢的早点完事直接表达不喜欢,不要让自己受累;伤人伤己不好。对于确定目标喜欢的,执着追求奋不顾身;安静时一个人好好的做好自己,喧嚣时冷静头脑遵从自己的内心选择。希望上表的菩萨保佑,麻烦您了。