Skip to content

Recent Articles

4

Sphinx在电子商务网站中的应用(5)-总结及优化

这几篇blog,没想过写成手把手的教程,只是想把思路写出来,和大家交流,所以流水账了…代码都是些关键片段,不负责它正常运行!
基础方面的请到Sphinx官方站查看文档,非常详细!

谈几点优化方面的建议:

1.Sphinx自带PHP API和使用C写的PHP扩展相比,速度快了不少,应该是网络通讯方面的问题.觉得这应该是不正常的,但是问题我还没找到.也有可能是个别问题.

2.一个页面要是有多个Sphinx查询请求,请使用AddQuery方法,尽量收集,一次发送,一个接收.

3.AddQuery添加Query的条数有限制,应该是32条这样,我以前提到过.

4.做好cache!

17

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 -_-! 杯具了

4

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

后台操作时,简单的用写文件的方式通知索引更新脚本,索引更新完成后,将文件重置.

3

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 );

可以很方便的得出当前条件下的,各种属性(样式,品牌…)的数量.

3

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内部才会进行处理。

建立索引后,结构可以看成下图所示:

2

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

27

sphinx一次请求的最大query数限制

一般来说,在sphinx里面,如果一个应用,需要多条查询的话的,最好是使用AddQuery方法依次加入所有查询,然后再使用RunQueries方法返回结果;好处是节约了网络IO,而且sphinx也会在内部优化这些查询,效率上来讲,肯定要比多次Query要好很多.

在测试中发现,AddQuery的次数不是无限的,经过测试,最大查询条数应该是32条,也就是,只能用AddQuery加入不超过32条查询,超过这个数量,返回的结果就会出错了.

手册上我没有发现这点,也没有在配置文件发现有可以设置更大的同时query数.

21

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

看起来繁琐,熟练了也就这么回事…新手不用害怕…呵呵

17

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赶紧着吧…

16

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…