普遍缓存技术
数据缓存:这里所说的数据缓存是指数据库查询缓存,每次访问页面的时候,都会先检测相应的缓存数据是否存在,
如果不存在,就连接数据库,得到数据,并把查询结果序列化后保存到文件中,以后同样的查询结果
就直接从缓存表或文件中获得。
用的最广的例子看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