普遍缓存技术
数据缓存:这里所说的数据缓存是指数据库查询缓存,每次访问页面的时候,都会先检测相应的缓存数据是否存在, 如果不存在,就连接数据库,得到数据,并把查询结果序列化后保存到文件中,以后同样的查询结果 就直接从缓存表或文件中获得。 用的最广的例子看Discuz的搜索功能,把结果ID缓存到一个表中,下次搜索相同关键字时先搜索缓存表。 举个常用的方法,多表关联的时候,把附表中的内容生成数组保存到主表的一个字段中, 需要的时候数组分解一下,这样的好处是只读一个表,坏处就是两个数据同步会多不少步骤, 数据库永远是瓶颈,用硬盘换速度,是这个的关键点。
普遍缓存技术
每次访问页面的时候,都会先检测相应的缓存页面文件是否存在,如果不存在,就连接数据库,得到数据, 显示页面并同时生成缓存页面文件,这样下次访问的时候页面文件就发挥作用了。 (模板引擎和网上常见的一些缓存类通常有此功能)
时间触发缓存:
检查文件是否存在并且时间戳小于设置的过期时间,如果文件修改的时间戳比当前时间戳减去过期时间戳大, 那么就用缓存,否则更新缓存。
内容触发缓存:
当插入数据或更新数据时,强制更新缓存。
静态缓存:
这里所说的静态缓存是指静态化,直接生成HTML或XML等文本文件,有更新的时候重生成一次, 适合于不太变化的页面,这就不说了。 以上内容是代码级的解决方案,我直接CP别的框架,也懒得改,内容都差不多, 很容易就做到,而且会几种方式一起用,但下面的内容是服务器端的缓存方案, 非代码级的,要有多方的合作才能做到
内存缓存:
Memcached是高性能的,分布式的内存对象缓存系统,用于在动态应用中减少数据库负载, 提升访问速度。 这里说下Memcached的例子: <?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 = 'focalbook'; $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 $sql = 'SELECT * FROM users'; $key = md5($sql); //memcached 对象标识符 if ( !($datas = $mc->get($key)) ) { // 在 memcached 中未获取到缓存数据,则使用数据库查询获取记录集。 echo "n".str_pad('Read datas from MySQL.', 60, '_')."n"; $conn = mysql_connect('localhost', 'focalbook', 'focalbook'); mysql_select_db('focalbook'); $result = mysql_query($sql); while ($row = mysql_fetch_object($result)) $datas[] = $row; // 将数据库中获取到的结果集数据保存到 memcached 中,以供下次访问时使用。 $mc->add($key, $datas); } else { echo "n".str_pad('Read datas from memcached.', 60, '_')."n"; } var_dump($datas); ?>
php的缓冲器:
有eaccelerator, apc, phpa,xcache,这个这个就不说了吧,搜索一堆一堆的,自己看啦,知道有这玩意就OK
MYSQL缓存:
这也算非代码级的,经典的数据库就是用的这种方式,看下面的运行时间,0.09xxx之类的 我贴段根据蓝色那家伙修改后部分my.ini吧,2G的MYISAM表可以在0.05S左右,据说他前后改了有快一年 [client] …… default-character-set=gbk default-storage-engine=MYISAM max_connections=600 max_connect_errors=500 back_log=200 interactive_timeout=7200 query_cache_size=64M …… table_cache=512 …… myisam_max_sort_file_size=100G myisam_max_extra_sort_file_size=100G myisam_sort_buffer_size=128M key_buffer_size=1024M read_buffer_size=512M …… thread_concurrency=8
基于反向代理的Web缓存:
如Nginx,SQUID,mod_proxy(apache2以上又分为mod_proxy和mod_cache) NGINX的例子 <nginx.conf> #user nobody; worker_processes 4; error_log logs/error.log crit; pid logs/nginx.pid; worker_rlimit_nofile 10240; events { use epoll; worker_connections 51200; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; tcp_nodelay on; # server pool upstream bspfrontsvr { server 10.10.10.224:80 weight=1; server 10.10.10.221:80 weight=1; } upstream bspimgsvr { server 10.10.10.201:80 weight=1; } upstream bspstylesvr { server 10.10.10.202:80 weight=1; } upstream bsphelpsvr { server 10.10.10.204:80 weight=1; } upstream bspwsisvr { server 10.10.10.203:80 weight=1; } upstream bspadminsvr { server 10.10.10.222:80 weight=1; } upstream bspbuyersvr { server 10.10.10.223:80 weight=1; } upstream bspsellersvr { server 10.10.10.225:80 weight=1; } upstream bsploginsvr { server 10.10.10.220:443 weight=1; } upstream bspregistersvr { server 10.10.10.220:80 weight=1; } log_format jisuzw_com '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" "$http_user_agent" '; #-------------------------------------------------------------------- #img.jisuzw.com server { listen 10.10.10.230:80; server_name img.jisuzw.com; location / { proxy_pass http://bspimgsvr; include proxy_setting.conf; } access_log logs/img.log jisuzw_com; } #style.jisuzw.com server { listen 10.10.10.230:80; server_name style.jisuzw.com; location / { proxy_pass http://bspstylesvr; include proxy_setting.conf; } access_log logs/style.log jisuzw_com; } #help.jisuzw.com server { listen 10.10.10.230:80; server_name help.jisuzw.com; location / { proxy_pass http://bsphelpsvr; include proxy_setting.conf; } access_log logs/help.log jisuzw_com; } #admin.jisuzw.com server { listen 10.10.10.230:80; server_name admin.jisuzw.com; location / { proxy_pass http://bspadminsvr;
exit和_exit函数都是用来终止进程的。当程序执行到exit或_exit时,系统无条件的停止剩下所有操作,清除包括PCB在内的各种数据结构,并终止本进程的运行。但是,这两个函数是有区别的。
exit()函数的作用是:直接使用进程停止运行,清除其使用的内存空间,并清除其在内核中的各种数据结构;_exit()函数则在这一基础上做了一些包装。在执行退出之前加了若干道工序。exit()函数与_exit()函数最大区别就在于exit()函数在调用exit系统之前要检查文件的打开情况,把文件缓冲区的内容写回文件。
由于Linux的标准函数库中,有一种被称作“缓冲I/O”的操作,其特征就是对应每一个打开的文件,在内存中都有一片缓冲区。每次读文件时,会连续的读出若干条记录,这样在下次读文件时就可以直接从内存的缓冲区读取;同样,每次写文件的时候也仅仅是写入内存的缓冲区,等满足了一定的条件(如达到了一定数量或遇到特定字符等),再将缓冲区中的内容一次性写入文件。
这种技术大大增加了文件读写的速度,但也给编程代来了一点儿麻烦。比如有一些数据,认为已经写入了文件,实际上因为没有满足特定的条件,它们还只是保存在缓冲区内,这时用_exit()函数直接将进程关闭,缓冲区的数据就会丢失。因此,要想保证数据的完整性,就一定要使用exit()函数。
exit的函数声明在stdlib.h头文件中。
_exit的函数声明在unistd.h头文件当中。
下面的实例比较了这两个函数的区别。printf函数就是使用缓冲I/O的方式,该函数在遇到“\n”换行符时自动的从缓冲区中将记录读出。实例就是利用这个性质进行比较的。
exit.c源码
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
printf("Using exit...\n");
printf("This is the content in buffer");
exit(0);
}
输出信息:
Using exit...
This is the content in buffer
#include <unistd.h>
#include <stdio.h>
int main(void)
{
printf("Using exit...\n");
printf("This is the content in buffer");
_exit(0);
}
则只输出:
Using exit...
说明:在一个进程调用了exit之后,该进程并不会马上完全小时,而是留下一个称为僵尸进程(Zombie)的数据结构。僵尸进程是一种非常特殊的进程,它几乎已经放弃了所有的内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其它进程收集,除此之外,僵尸进程不再占有任何内存空间。
实现openfire消息记录通常有两种方式,修改服务端和添加消息记录插件。
今天,简单的说明一下修改服务端方式实现消息记录保存功能。
实现思路
修改前:
默认的,openfire只提供保存离线记录至ofOffline表中。当发送一条消息时,判断用户是否在线,若为true,不保存消息;若为fasle,保存消息至离线消息表中。
修改后:
仿照保存离线消息,用户每发送一条消息,将消息存放在ofHistory表中,ofHistory表结构同ofOffline
实现步骤:
1.修改初始数据库文件,路径src/database/openfire_sqlserver.sql
将ofOffline修改为ofHistory
CREATE TABLE ofHistory ( username NVARCHAR(64) NOT NULL, messageID INTEGER NOT NULL, creationDate NVARCHAR(64) NOT NULL, messageSize INTEGER NOT NULL, stanza NTEXT NOT NULL, CONSTRAINT ofHistory_pk PRIMARY KEY (username, messageID) ); CREATE TABLE ofOffline ( username NVARCHAR(64) NOT NULL, messageID INTEGER NOT NULL, creationDate CHAR(15) NOT NULL, messageSize INTEGER NOT NULL, stanza NTEXT NOT NULL, CONSTRAINT ofOffline_pk PRIMARY KEY (username, messageID) );
注:其他数据库修改方式雷同
2.添加保存消息方法
MessageRouter类中110行
try { // Deliver stanza to requested route routingTable.routePacket(recipientJID, packet, false); //保存消息记录dml@2013.4.15 OfflineMessageStore oms = new OfflineMessageStore(); oms.addMessage_toHistory(packet); } catch (Exception e) { log.error("Failed to route packet: " + packet.toXML(), e); routingFailed(recipientJID, packet); }
3.修改OfflineMessageStore类,添加保存消息记录方法
/** * $RCSfile$ * $Revision: 2911 $ * $Date: 2005-10-03 12:35:52 -0300 (Mon, 03 Oct 2005) $ * * Copyright (C) 2004-2008 Jive Software. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.openfire; import java.io.StringReader; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.jivesoftware.database.DbConnectionManager; import org.jivesoftware.database.SequenceManager; import org.jivesoftware.openfire.container.BasicModule; import org.jivesoftware.openfire.event.UserEventDispatcher; import org.jivesoftware.openfire.event.UserEventListener; import org.jivesoftware.openfire.user.User; import org.jivesoftware.openfire.user.UserManager; import org.jivesoftware.util.JiveConstants; import org.jivesoftware.util.LocaleUtils; import org.jivesoftware.util.StringUtils; import org.jivesoftware.util.XMPPDateTimeFormat; import org.jivesoftware.util.cache.Cache; import org.jivesoftware.util.cache.CacheFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xmpp.packet.JID; import org.xmpp.packet.Message; /** * Represents the user's offline message storage. A message store holds messages * that were sent to the user while they were unavailable. The user can retrieve * their messages by setting their presence to "available". The messages will * then be delivered normally. Offline message storage is optional, in which * case a null implementation is returned that always throws * UnauthorizedException when adding messages to the store. * * @author Iain Shigeoka */ public class OfflineMessageStore extends BasicModule implements UserEventListener { private static final Logger Log = LoggerFactory .getLogger(OfflineMessageStore.class); // 保存消息记录 dml@2013.4.16 private static final String INSERT_HISTORY = "INSERT INTO ofHistory (username, messageID, creationDate, messageSize, stanza) " + "VALUES (?, ?, ?, ?, ?)"; private static final String INSERT_OFFLINE = "INSERT INTO ofOffline (username, messageID, creationDate, messageSize, stanza) " + "VALUES (?, ?, ?, ?, ?)"; private static final String LOAD_OFFLINE = "SELECT stanza, creationDate FROM ofOffline WHERE username=?"; private static final String LOAD_OFFLINE_MESSAGE = "SELECT stanza FROM ofOffline WHERE username=? AND creationDate=?"; private static final String SELECT_SIZE_OFFLINE = "SELECT SUM(messageSize) FROM ofOffline WHERE username=?"; private static final String SELECT_SIZE_ALL_OFFLINE = "SELECT SUM(messageSize) FROM ofOffline"; private static final String DELETE_OFFLINE = "DELETE FROM ofOffline WHERE username=?"; private static final String DELETE_OFFLINE_MESSAGE = "DELETE FROM ofOffline WHERE username=? AND creationDate=?"; private static final int POOL_SIZE = 10; private Cache<String, Integer> sizeCache; /** * Pattern to use for detecting invalid XML characters. Invalid XML * characters will be removed from the stored offline messages. */ private Pattern pattern = Pattern.compile("&\\#[\\d]+;"); /** * Returns the instance of <tt>OfflineMessageStore</tt> being used by the * XMPPServer. * * @return the instance of <tt>OfflineMessageStore</tt> being used by the * XMPPServer. */ public static OfflineMessageStore getInstance() { return XMPPServer.getInstance().getOfflineMessageStore(); } /** * Pool of SAX Readers. SAXReader is not thread safe so we need to have a * pool of readers. */ private BlockingQueue<SAXReader> xmlReaders = new LinkedBlockingQueue<SAXReader>( POOL_SIZE); /** * Constructs a new offline message store. */ public OfflineMessageStore() { super("Offline Message Store"); sizeCache = CacheFactory.createCache("Offline Message Size"); } /** * Adds a message to this message store. Messages will be stored and made * available for later delivery. * * @param message * the message to store. */ public void addMessage(Message message) { if (message == null) { return; } // ignore empty bodied message (typically chat-state notifications). if (message.getBody() == null || message.getBody().length() == 0) { // allow empty pubsub messages (OF-191) if (message.getChildElement("event", "http://jabber.org/protocol/pubsub#event") == null) { return; } } JID recipient = message.getTo(); String username = recipient.getNode(); // If the username is null (such as when an anonymous user), don't // store. if (username == null || !UserManager.getInstance().isRegisteredUser(recipient)) { return; } else if (!XMPPServer.getInstance().getServerInfo().getXMPPDomain() .equals(recipient.getDomain())) { // Do not store messages sent to users of remote servers return; } long messageID = SequenceManager.nextID(JiveConstants.OFFLINE); // Get the message in XML format. String msgXML = message.getElement().asXML(); Connection con = null; PreparedStatement pstmt = null; try { con = DbConnectionManager.getConnection(); pstmt = con.prepareStatement(INSERT_OFFLINE); pstmt.setString(1, username); pstmt.setLong(2, messageID); pstmt.setString(3, StringUtils.dateToMillis(new java.util.Date())); // SimpleDateFormat df = new // SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // pstmt.setString(3, df.format(new Date()).toString()); pstmt.setInt(4, msgXML.length()); pstmt.setString(5, msgXML); pstmt.executeUpdate(); } catch (Exception e) { Log.error(LocaleUtils.getLocalizedString("admin.error"), e); } finally { DbConnectionManager.closeConnection(pstmt, con); } // Update the cached size if it exists. if (sizeCache.containsKey(username)) { int size = sizeCache.get(username); size += msgXML.length(); sizeCache.put(username, size); } } /** * 保存消息记录 * * @author dml * @param message */ public void addMessage_toHistory(Message message) { if (message == null) { return; } // ignore empty bodied message (typically chat-state notifications). if (message.getBody() == null || message.getBody().length() == 0) { // allow empty pubsub messages (OF-191) if (message.getChildElement("event", "http://jabber.org/protocol/pubsub#event") == null) { return; } } JID recipient = message.getTo(); String username = recipient.getNode(); // If the username is null (such as when an anonymous user), don't // store. if (username == null