Sphinx在电子商务网站中的应用(5)-总结及优化
这几篇blog,没想过写成手把手的教程,只是想把思路写出来,和大家交流,所以流水账了…代码都是些关键片段,不负责它正常运行!
基础方面的请到Sphinx官方站查看文档,非常详细!
谈几点优化方面的建议:
1.Sphinx自带PHP API和使用C写的PHP扩展相比,速度快了不少,应该是网络通讯方面的问题.觉得这应该是不正常的,但是问题我还没找到.也有可能是个别问题.
2.一个页面要是有多个Sphinx查询请求,请使用AddQuery方法,尽量收集,一次发送,一个接收.
3.AddQuery添加Query的条数有限制,应该是32条这样,我以前提到过.
4.做好cache!
file_get_contents获取网页可能会使CPU飙升至100%
在管理一台服务器时发现CPU经常跑满,排查至某php文件中利用file_get_contents获取网页的函数,服务器对目标地址的访问速度很差,使用file_get_contents获取目标地址时,常常卡住,这个时候CPU使用率就到了100%,还不能释放!!由于是多线程的应用,服务器负载跑到很高.
利用stream_context_create函数为file_get_contents加上了超时限制,也不管用,只有放弃file_get_contents…
自己用fsockopen写了个简单的模拟Get操作,就没有问题了,虽然对目标地址的访问速度还是很糟糕,但是对CPU的影响已经正常了,还得加上超时处理!
最后简单的换成curl系列函数,效率高,设置超时也方便:
$curlHandle = curl_init(); curl_setopt( $curlHandle , CURLOPT_URL, $url ); curl_setopt( $curlHandle , CURLOPT_RETURNTRANSFER, 1 ); curl_setopt( $curlHandle , CURLOPT_TIMEOUT, 30 ); $result= curl_exec( $curlHandle ); curl_close( $curlHandle );
另:google了下,php.net已经提交了这个bug,不过那是2005年的事儿…现在服务器上跑的是最新的PHP5.3.2 -_-! 杯具了
Sphinx在电子商务网站中的应用(4)-即时更新方案
5.即时更新方案
使用了Sphinx代替了Mysql进行查询后,最大的问题还在于数据的更新问题:我们在后台进行了产品的编辑,删除,添加操作,都需要尽快的反应到用户端.
说明:
(1).在Mysql中建立一个增量表,凡是产品更新操作(添加,修改,删除),都会将相关ID更新到到增量表;表结构很简单,只记录ID.
(2).修改Sphinx的配置文件
sql_query = SELECT id, name, price*100 AS price, 0 AS in_update FROM shop_product sql_attr_uint = in_update
增加了一个字段in_update,用来标记主索引的这条记录,是否在增量表中.
(3).产品的更新操作时,除了更新增量表,同时也利用Sphinx API的UpdateAttributes方法,将主索引中的相关记录的in_update属性设置成”1″
$this->Sphinx->UpdateAttributes( $index, array( 'in_update' ), array( $id => '1' ) );
代码中的$id指的是商品编号.
更新完了属性后,发出增量索引更新通知,可以是写队列,写文件等方式.
(4).在查询时,增加过滤器.
$Sphinx->SetFilter( 'in_update', array(0) );
这样,就不会使用到主索引中的处于增量索引中的doc,以免搜索到错误的编辑前的数据.
(5).守护进程接到增量索引更新通知,重建增量索引.
(6).每天某时段更新主索引,清空增量表,清空增量索引…
思路就是这样,下面的增量索引更新脚本是我用在测试环境的,生产环境的比较复杂,供参考.
#!/bin/bash
CHECK_FILE_PATH='/xxx/sphinx-check.txt'
SPHINX_COMMAND="/opt/sphinx/bin/indexer --config /opt/sphinx/etc/product.conf product_update_index --rotate"
while true
do
NOW_TIME=`date +'%Y-%m-%d %H:%M:%S'`
if [ -f $CHECK_FILE_PATH ];then
WORD_NUM=`cat ${CHECK_FILE_PATH} |wc -w`
if [ $WORD_NUM -gt 0 ];then
echo "bulid ${NOW_TIME}"
$SPHINX_COMMAND
> $CHECK_FILE_PATH
else
echo "skip ${NOW_TIME}"
fi
else
echo "nofile ${NOW_TIME}"
fi
sleep 30
done
后台操作时,简单的用写文件的方式通知索引更新脚本,索引更新完成后,将文件重置.
Sphinx在电子商务网站中的应用(3)–查询
Sphinx自带的PHP的API进行查询。
示例1:
编号为1,2,3,4,5的5个分类下的产品列表
$Sphinx->SetFilter( 'category_id', array(1,2,3,4,5) ); $Sphinx->Query( '', '*' );
示例2:
属性编号为1,2,3,4,5的下的产品列表(属性的交集)
$Sphinx->SetFilter( 'index_id', array(1) ); $Sphinx->SetFilter( 'index_id', array(2) ); $Sphinx->SetFilter( 'index_id', array(3) ); $Sphinx->SetFilter( 'index_id', array(4) ); $Sphinx->SetFilter( 'index_id', array(5) ); $Sphinx->Query( '', '*' );
示例3:
属性编号为1,2,3,4,5的下的产品列表(属性的交集)的价格分组,并且升序排列
$Sphinx->SetFilter( 'index_id', array(1) ); $Sphinx->SetFilter( 'index_id', array(2) ); $Sphinx->SetFilter( 'index_id', array(3) ); $Sphinx->SetFilter( 'index_id', array(4) ); $Sphinx->SetFilter( 'index_id', array(5) ); $Sphinx->SetGroupBy( 'price', SPH_GROUPBY_ATTR, '@group asc' ); $Sphinx->Query( '', '*' );
示例4:
属性编号为1,2,3,4,5的下的产品列表(属性的交集),分类编号是7,8,9
$Sphinx->SetFilter( 'category_id', array(7,8,9) ); $Sphinx->SetFilter( 'index_id', array(1) ); $Sphinx->SetFilter( 'index_id', array(2) ); $Sphinx->SetFilter( 'index_id', array(3) ); $Sphinx->SetFilter( 'index_id', array(4) ); $Sphinx->SetFilter( 'index_id', array(5) ); $Sphinx->SetGroupBy( 'price', SPH_GROUPBY_ATTR, '@group asc' ); $Sphinx->Query( '', '*' );
Sphinx的查询使用还是很方便的,需要注意的是,Filter传递的待筛选参数必须是数组;
关于MVA的使用,如果数组有多个成员:
$Sphinx->SetFilter( 'category_id', array(7,8,9) );
表示索引中的category_id满足数组中的任意一个值就会筛选出来.
$Sphinx->SetFilter( 'index_id', array(7) ); $Sphinx->SetFilter( 'index_id', array(8) ); $Sphinx->SetFilter( 'index_id', array(9) );
表示索引中的index_id包含7,8,9三个值才会被筛选出.
MVA也可以用来进行Group操作:
$Sphinx->SetGroupBy( 'index_value_id', SPH_GROUPBY_ATTR );
可以很方便的得出当前条件下的,各种属性(样式,品牌…)的数量.
Sphinx在电子商务网站中的应用(2)–索引结构配置
Sphinx配置文件
贴出主要部分,其他的手册上说的很清楚了。
source product_src
{
type = mysql
sql_host = 127.0.0.1
sql_user = root
sql_pass = 123
sql_db = shop
sql_port = 3306
sql_query_pre = SET NAMES utf8
sql_query = SELECT id, name, price*100 AS price FROM shop_product
sql_attr_uint = price
sql_attr_multi = uint index_id from query;SELECT product_id, index _id FROM product_index
sql_attr_multi = uint category_id from query;SELECT product_id, category_id FROM product_show
}
上面是source部分,项目的编码是UTF-8,所以首先得执行:“SET NAMES utf8”。product里面的price是以保留两位小数的形式存放的,在sql_query里面,将它乘以100换成整数,避免浮点精度带来的不必要的小数位。
关键的两条“sql_attr_multi”,配置了属性和分类的MVA,“from query”后面紧接着SQL,表示来自查询,product_id放第一个字段,后面跟着值字段,这样Sphinx内部才会进行处理。
Sphinx在电子商务网站中的应用(1)-分析与思路
实际项目的特点是:
1.数据量大
2.商品分类多
3.每个商品可以处于多个分类下
4.平均每个商品拥有2个以上的属性
要求可以做到:
1.按照商品名称搜索
2.按照商品属性筛选
3.按照分类筛选
4.按照价格分组筛选
5.按照价格、时间排序
MySQL基本数据结构:
product表
id
name
price
product_index表
index_id
product_id
product_show
category_id
product_id
说明:
product是基本产品表,id为产品编号,name是待搜索字段
product_index为产品属性表,index_id是属性编号,product_id为product表的id。如果编号为100的产品,有5个不同属性,在product_index表中就会记录product_id为100,index_id不相同的五条记录。
product_show的结构与product_index类似,记录的是产品于分类的联系,如果同一个产品同属5个不同的分类,在表中就会存在五条记录。
数据结构不复杂,但是实际应用中,使用Mysql的查询实现的搜索和检索,效率是比较低的。最消耗性能的地方是产品列表的索引导航,这是类似淘宝网产品列表的”按XXX浏览”:
SQL的实现不多说。
在Sphinx有个非常棒的特性:MVA(多值属性),MVA类似Mysql的set类型的字段,但是存放值不受预设的限制,使用MVA建立Sphinx的属性,用来存放每个产品的index_id和category_id,很方便就能实现筛选功能。
累了,明天再写第二部分….hihiyou
sphinx一次请求的最大query数限制
一般来说,在sphinx里面,如果一个应用,需要多条查询的话的,最好是使用AddQuery方法依次加入所有查询,然后再使用RunQueries方法返回结果;好处是节约了网络IO,而且sphinx也会在内部优化这些查询,效率上来讲,肯定要比多次Query要好很多.
在测试中发现,AddQuery的次数不是无限的,经过测试,最大查询条数应该是32条,也就是,只能用AddQuery加入不超过32条查询,超过这个数量,返回的结果就会出错了.
手册上我没有发现这点,也没有在配置文件发现有可以设置更大的同时query数.
Debian linux安装redmine流水笔记
开始装的是最新的0.9.x,按照官方教程,出了一些问题,google也无解,估计是环境的问题,毕竟对ruby还是很陌生的,换成0.8.7后就很顺利的完成了安装.
redmine基于ruby on rails开发,所以装之前,首先装好ROR的环境以及其他的一些工具包:
apt-get install rubygems gem install rails -v=2.3.5 apt-get install rake
然后下载redmine并且解压到放置指定位置(这里使用:/var/www/redmine):
wget http://rubyforge.org/frs/download.php/67144/redmine-0.8.7.tar.gz tar zxvf redmine-0.8.7.tar.gz cp -R redmine-0.8.7 /var/www/redmine
进入mysql,为redmine准备mysql用户和数据库:
create database redmine character set utf8; create user 'redmine'@'localhost' identified by 'my_password'; grant all privileges on redmine.* to 'redmine'@'localhost';
修改redmine的配置文件:
cd /var/www/redmine/config/ cp database.yml.example database.yml vim database.yml
将”production”代码块下的mysql相关信息修改成真实数据(冒号后面的空格请保留)
准备初始化redmine的数据:
cd /var/www/redmine RAILS_ENV=production rake config/initializers/session_store.rb RAILS_ENV=production rake db:migrate
第三条命令是初始化数据库表,如果遇到类似下面的错误:
error: Rake aborted! no such file to load -- net/https
安装ruby的ssl库,然后重复上一条命令
apt-get install libopenssl-ruby1.8
修改相关文件夹权限:
chmod -R 755 files log tmp public/plugin_assets
到此就完成安装了,可以按照下面方式运行:
cd /var/www/redmine ruby script/server webrick -e production
或者转入后台运行:
cd /var/www/redmine nohup ruby script/server webrick -e production &
一般还需要配置email发送:
cd /var/www/redmine/config/ cp email.yml.example email.yml vim email.yml
输入相关信息,重启redmine就行
比较特别的是gmail的smtp配置,我使用的是:
production:
delivery_method: :smtp
smtp_settings:
address: smtp.gmail.com
port: 587
domain: smtp.gmail.com
authentication: :login
user_name: xxx@xxx.com
password: xxx
tls: true
也是比较简单的,登陆redmine后,可以在后台测试email是否正常,出现问题的话,请尝试:
cd /var/www/redmine ruby script/plugin install git://github.com/collectiveidea/action_mailer_optional_tls.git --force
如果提示找不到git命令,那就得安装git包…
apt-get install git-core
看起来繁琐,熟练了也就这么回事…新手不用害怕…呵呵
IE6下JS动态插入的HTML代码中的图片无法显示
再次被IE6折服…娇羞的IE6…
当时情况是这样的,有链接:
<a href="javascript:void(0)" id="add_image">Add Image</a>
有JS(Jquery):
$('#add_image').click(function(){
$('#image_box').append('<div><img src="xxx.gif"></div>');
});
结果就是插入的这个xxx.gif ,死活不显示,除了IE6以外的浏览器都正常的不得了!!
后来我是翻来覆去的实验(这个过程我很抓狂),发现把href=”javascript:void(0)”改成href=”###”就正常了…-_- 无语
查资料,果然是IE6bug:
当a标签的href属性为”javascript:void(0);”时,IE6认为应该进行页面跳转,所以中断了异步请求的callback,并且中断了当前所有连接,这样新图片的加载就被阻止了
这个问题仿佛很久以前遇到过,只是没想到能引起图片的加载.
IE6赶紧着吧…
php的sphinx extension安装及测试
可以在pecl下载:
http://pecl.php.net/package/sphinx
安装也简单,解压,然后进入源文件目录,执行:
/opt/php/bin/phpize
然后
./configure –with-php-config=/opt/php/bin/php-config
make
make install
/opt/php为你的php安装目录.
完成后,将extendsion = ’sphinx.so’ 加入到php.ini(保证extensions_dir设置的目录无误)
phpinfo() 能看到sphinx的字样就成功了.
不过经过简单的测试,c扩展的sphinx获取数据的速度不如sphinx的php代码所写的api,大为不解
一段简单的代码(安装过eaccelerator进行加速)
php代码的api耗时 0.007秒
php扩展的api耗时 0.04秒
差距是很大的…找时间查查原因,暂时还是用php代码的api…




