基于Sphinx+MySQL的千万级数据全文检索(搜索引擎)架构设计[原创]
[ 2008-7-27 22:29 | by 张宴 ]
[文章作者:张宴 本文版本:v1.0 最后修改:2008.07.27 转载请注明原文链接:http://blog.zyan.cc/post/360/]
前言:本文阐述的是一款经过生产环境检验的千万级数据全文检索(搜索引擎)架构。本文只列出前几章的内容节选,不提供全文内容。
在DELL PowerEdge 6850服务器(四颗64 位Inter Xeon MP 7110N处理器 / 8GB内存)、RedHat AS4 Linux操作系统、MySQL 5.1.26、MyISAM存储引擎、key_buffer=1024M环境下实测,单表1000万条记录的数据量(这张MySQL表拥有int、datetime、varchar、text等类型的10多个字段,只有主键,无其它索引),用主键(PRIMARY KEY)作为WHERE条件进行SQL查询,速度非常之快,只耗费0.01秒。
出自俄罗斯的开源全文搜索引擎软件Sphinx,单一索引最大可包含1亿条记录,在1千万条记录情况下的查询速度为0.x秒(毫秒级)。Sphinx创建索引的速度为:创建100万条记录的索引只需3~4分钟,创建1000万条记录的索引可以在50分钟内完成,而只包含最新10万条记录的增量索引,重建一次只需几十秒。
基于以上几点,我设计出了这套搜索引擎架构。在生产环境运行了一周,效果非常不错。有时间我会专为配合Sphinx搜索引擎,开发一个逻辑简单、速度快、占用内存低、非表锁的MySQL存储引擎插件,用来代替MyISAM引擎,以解决MyISAM存储引擎在频繁更新操作时的锁表延迟问题。另外,分布式搜索技术上已无任何问题。
一、搜索引擎架构设计:
1、搜索引擎架构图:
2、搜索引擎架构设计思路:
(1)、调用方式最简化:
尽量方便前端Web工程师,只需要一条简单的SQL语句“SELECT ... FROM myisam_table JOIN sphinx_table ON (sphinx_table.sphinx_id=myisam_table.id) WHERE query='...';”即可实现高效搜索。
(2)、创建索引、查询速度快:
①、Sphinx Search 是由俄罗斯人Andrew Aksyonoff 开发的高性能全文搜索软件包,在GPL与商业协议双许可协议下发行。
Sphinx的特征:
•Sphinx支持高速建立索引(可达10MB/秒,而Lucene建立索引的速度是1.8MB/秒)
•高性能搜索(在2-4 GB的文本上搜索,平均0.1秒内获得结果)
•高扩展性(实测最高可对100GB的文本建立索引,单一索引可包含1亿条记录)
•支持分布式检索
•支持基于短语和基于统计的复合结果排序机制
•支持任意数量的文件字段(数值属性或全文检索属性)
•支持不同的搜索模式(“完全匹配”,“短语匹配”和“任一匹配”)
•支持作为Mysql的存储引擎
②、通过国外《High Performance MySQL》专家组的测试可以看出,根据主键进行查询的类似“SELECT ... FROM ... WHERE id = ...”的SQL语句(其中id为PRIMARY KEY),每秒钟能够处理10000次以上的查询,而普通的SELECT查询每秒只能处理几十次到几百次:
③、Sphinx不负责文本字段的存储。假设将数据库的id、date、title、body字段,用sphinx建立搜索索引。根据关键字、时间、类别、范围等信息查询一下sphinx,sphinx只会将查询结果的ID号等非文本信息告诉我们。要显示title、body等信息,还需要根据此ID号去查询MySQL数据库,或者从Memcachedb等其他的存储中取得。安装SphinxSE作为MySQL的存储引擎,将MySQL与Sphinx结合起来,是一种便捷的方法。
创建一张Sphinx类型表,将MyISAM表的主键ID和Sphinx表的ID作一个JOIN联合查询。这样,对于MyISAM表来所,只相当于一个WHERE id=...的主键查询,WHERE后的条件都交给Sphinx去处理,可以充分发挥两者的优势,实现高速搜索查询。
(3)、按服务类型进行分离:
为了保证数据的一致性,我在配置Sphinx读取索引源的MySQL数据库时,进行了锁表。Sphinx读取索引源的过程会耗费一定时间,由于MyISAM存储引擎的读锁和写锁是互斥的,为了避免写操作被长时间阻塞,导致数据库同步落后跟不上,我将提供“搜索查询服务”的和提供“索引源服务”的MySQL数据库进行了分开。监听3306端口的MySQL提供“搜索查询服务”,监听3406端口的MySQL提供“索引源服务”。
(4)、“主索引+增量索引”更新方式:
一般网站的特征:信息发布较为频繁;刚发布完的信息被编辑、修改的可能性大;两天以前的老帖变动性较小。
基于这个特征,我设计了Sphinx主索引和增量索引。对于前天17:00之前的记录建立主索引,每天凌晨自动重建一次主索引;对于前天17:00之后到当前最新的记录,间隔3分钟自动重建一次增量索引。
(5)、“Ext3文件系统+tmpfs内存文件系统”相结合:
为了避免每3分钟重建增量索引导致磁盘IO较重,从而引起系统负载上升,我将主索引文件创建在磁盘,增量索引文件创建在tmpfs内存文件系统“/dev/shm/”内。“/dev/shm/”内的文件全部驻留在内存中,读写速度非常快。但是,重启服务器会导致“/dev/shm/”内的文件丢失,针对这个问题,我会在服务器开机时自动创建“/dev/shm/”内目录结构和Sphinx增量索引。
(6)、中文分词词库:
我根据“自整理的中文分词库”+“搜狗拼音输入法细胞词库”+“LibMMSeg高频字库”+... 综合整理成一份中文分词词库,出于某些考虑暂不提供。你可以使用LibMMSeg自带的中文分词词库。
前言:本文阐述的是一款经过生产环境检验的千万级数据全文检索(搜索引擎)架构。本文只列出前几章的内容节选,不提供全文内容。
在DELL PowerEdge 6850服务器(四颗64 位Inter Xeon MP 7110N处理器 / 8GB内存)、RedHat AS4 Linux操作系统、MySQL 5.1.26、MyISAM存储引擎、key_buffer=1024M环境下实测,单表1000万条记录的数据量(这张MySQL表拥有int、datetime、varchar、text等类型的10多个字段,只有主键,无其它索引),用主键(PRIMARY KEY)作为WHERE条件进行SQL查询,速度非常之快,只耗费0.01秒。
出自俄罗斯的开源全文搜索引擎软件Sphinx,单一索引最大可包含1亿条记录,在1千万条记录情况下的查询速度为0.x秒(毫秒级)。Sphinx创建索引的速度为:创建100万条记录的索引只需3~4分钟,创建1000万条记录的索引可以在50分钟内完成,而只包含最新10万条记录的增量索引,重建一次只需几十秒。
基于以上几点,我设计出了这套搜索引擎架构。在生产环境运行了一周,效果非常不错。有时间我会专为配合Sphinx搜索引擎,开发一个逻辑简单、速度快、占用内存低、非表锁的MySQL存储引擎插件,用来代替MyISAM引擎,以解决MyISAM存储引擎在频繁更新操作时的锁表延迟问题。另外,分布式搜索技术上已无任何问题。
一、搜索引擎架构设计:
1、搜索引擎架构图:
2、搜索引擎架构设计思路:
(1)、调用方式最简化:
尽量方便前端Web工程师,只需要一条简单的SQL语句“SELECT ... FROM myisam_table JOIN sphinx_table ON (sphinx_table.sphinx_id=myisam_table.id) WHERE query='...';”即可实现高效搜索。
(2)、创建索引、查询速度快:
①、Sphinx Search 是由俄罗斯人Andrew Aksyonoff 开发的高性能全文搜索软件包,在GPL与商业协议双许可协议下发行。
Sphinx的特征:
•Sphinx支持高速建立索引(可达10MB/秒,而Lucene建立索引的速度是1.8MB/秒)
•高性能搜索(在2-4 GB的文本上搜索,平均0.1秒内获得结果)
•高扩展性(实测最高可对100GB的文本建立索引,单一索引可包含1亿条记录)
•支持分布式检索
•支持基于短语和基于统计的复合结果排序机制
•支持任意数量的文件字段(数值属性或全文检索属性)
•支持不同的搜索模式(“完全匹配”,“短语匹配”和“任一匹配”)
•支持作为Mysql的存储引擎
②、通过国外《High Performance MySQL》专家组的测试可以看出,根据主键进行查询的类似“SELECT ... FROM ... WHERE id = ...”的SQL语句(其中id为PRIMARY KEY),每秒钟能够处理10000次以上的查询,而普通的SELECT查询每秒只能处理几十次到几百次:
③、Sphinx不负责文本字段的存储。假设将数据库的id、date、title、body字段,用sphinx建立搜索索引。根据关键字、时间、类别、范围等信息查询一下sphinx,sphinx只会将查询结果的ID号等非文本信息告诉我们。要显示title、body等信息,还需要根据此ID号去查询MySQL数据库,或者从Memcachedb等其他的存储中取得。安装SphinxSE作为MySQL的存储引擎,将MySQL与Sphinx结合起来,是一种便捷的方法。
创建一张Sphinx类型表,将MyISAM表的主键ID和Sphinx表的ID作一个JOIN联合查询。这样,对于MyISAM表来所,只相当于一个WHERE id=...的主键查询,WHERE后的条件都交给Sphinx去处理,可以充分发挥两者的优势,实现高速搜索查询。
(3)、按服务类型进行分离:
为了保证数据的一致性,我在配置Sphinx读取索引源的MySQL数据库时,进行了锁表。Sphinx读取索引源的过程会耗费一定时间,由于MyISAM存储引擎的读锁和写锁是互斥的,为了避免写操作被长时间阻塞,导致数据库同步落后跟不上,我将提供“搜索查询服务”的和提供“索引源服务”的MySQL数据库进行了分开。监听3306端口的MySQL提供“搜索查询服务”,监听3406端口的MySQL提供“索引源服务”。
(4)、“主索引+增量索引”更新方式:
一般网站的特征:信息发布较为频繁;刚发布完的信息被编辑、修改的可能性大;两天以前的老帖变动性较小。
基于这个特征,我设计了Sphinx主索引和增量索引。对于前天17:00之前的记录建立主索引,每天凌晨自动重建一次主索引;对于前天17:00之后到当前最新的记录,间隔3分钟自动重建一次增量索引。
(5)、“Ext3文件系统+tmpfs内存文件系统”相结合:
为了避免每3分钟重建增量索引导致磁盘IO较重,从而引起系统负载上升,我将主索引文件创建在磁盘,增量索引文件创建在tmpfs内存文件系统“/dev/shm/”内。“/dev/shm/”内的文件全部驻留在内存中,读写速度非常快。但是,重启服务器会导致“/dev/shm/”内的文件丢失,针对这个问题,我会在服务器开机时自动创建“/dev/shm/”内目录结构和Sphinx增量索引。
(6)、中文分词词库:
我根据“自整理的中文分词库”+“搜狗拼音输入法细胞词库”+“LibMMSeg高频字库”+... 综合整理成一份中文分词词库,出于某些考虑暂不提供。你可以使用LibMMSeg自带的中文分词词库。
快捷启动、停止、重启、杀死指定端口MySQL进程的脚本[原创]
[ 2008-7-24 08:37 | by 张宴 ]
[文章作者:张宴 本文版本:v1.0 最后修改:2008.07.24 转载请注明原文链接:http://blog.zyan.cc/post/359/]
写了一个shell脚本,可以在同一台Linux服务器的不同端口,运行多个MySQL服务的情况下,快捷启动、停止、重启、杀死指定端口的MySQL进程。
输入以下内容(因各服务器的MySQL配置不同,可能需要修改的部分已用红色标注):
赋予脚本可执行权限:
脚本执行方法:
写了一个shell脚本,可以在同一台Linux服务器的不同端口,运行多个MySQL服务的情况下,快捷启动、停止、重启、杀死指定端口的MySQL进程。
vi /usr/local/bin/mysql.sh
输入以下内容(因各服务器的MySQL配置不同,可能需要修改的部分已用红色标注):
#!/bin/sh
mysql_port=$2
mysql_username="root"
mysql_password="123456"
function_start_mysql()
{
printf "Starting MySQL...\n"
/bin/sh /usr/local/mysql/bin/mysqld_safe --defaults-file=/mysql/${mysql_port}/my.cnf 2>&1 > /dev/null &
}
function_stop_mysql()
{
printf "Stoping MySQL...\n"
/usr/local/mysql/bin/mysqladmin -u ${mysql_username} -p${mysql_password} -h localhost -P ${mysql_port} shutdown
}
function_restart_mysql()
{
printf "Restarting MySQL...\n"
function_stop_mysql
function_start_mysql
}
function_kill_mysql()
{
kill -9 $(ps -ef | grep 'bin/mysqld_safe' | grep ${mysql_port} | awk '{printf $2}')
kill -9 $(ps -ef | grep 'libexec/mysqld' | grep ${mysql_port} | awk '{printf $2}')
}
if [ "$1" = "start" ]; then
function_start_mysql
elif [ "$1" = "stop" ]; then
function_stop_mysql
elif [ "$1" = "restart" ]; then
function_restart_mysql
elif [ "$1" = "kill" ]; then
function_kill_mysql
else
printf "Usage: mysql.sh {start|stop|restart|kill}\n"
fi
mysql_port=$2
mysql_username="root"
mysql_password="123456"
function_start_mysql()
{
printf "Starting MySQL...\n"
/bin/sh /usr/local/mysql/bin/mysqld_safe --defaults-file=/mysql/${mysql_port}/my.cnf 2>&1 > /dev/null &
}
function_stop_mysql()
{
printf "Stoping MySQL...\n"
/usr/local/mysql/bin/mysqladmin -u ${mysql_username} -p${mysql_password} -h localhost -P ${mysql_port} shutdown
}
function_restart_mysql()
{
printf "Restarting MySQL...\n"
function_stop_mysql
function_start_mysql
}
function_kill_mysql()
{
kill -9 $(ps -ef | grep 'bin/mysqld_safe' | grep ${mysql_port} | awk '{printf $2}')
kill -9 $(ps -ef | grep 'libexec/mysqld' | grep ${mysql_port} | awk '{printf $2}')
}
if [ "$1" = "start" ]; then
function_start_mysql
elif [ "$1" = "stop" ]; then
function_stop_mysql
elif [ "$1" = "restart" ]; then
function_restart_mysql
elif [ "$1" = "kill" ]; then
function_kill_mysql
else
printf "Usage: mysql.sh {start|stop|restart|kill}\n"
fi
赋予脚本可执行权限:
chmod +x /usr/local/bin/mysql.sh
脚本执行方法:
mysql.sh start 3306
mysql.sh stop 3306
mysql.sh restart 3306
mysql.sh kill 3306
mysql.sh stop 3306
mysql.sh restart 3306
mysql.sh kill 3306