今天收到了金融学的结业证书,心中的一个结也解开了。两年前就选修这门课,可是由于时间因素,没有学完,心中一直耿耿于怀。今年抽空把这么课学完,并拿到了结业证书。

事实上,学完之后,很多知识又忘记了。可是上完这么课后,我知道了,股票是一个好东西。

金融学结业证书

联系作者

因为不会写MVC,所以只好使用模版,而在PHP中,一般使用Smarty.以下是自己在使用过程中,遇到的一些问题,以及需要注意的地方。
1.一般评论都是通过一个textarea输入,在显示的时候需要将换行幅\n替换成标签,当尝试使用replace : ‘\n’ : ‘
‘’时,一直不可行,后来才知道,原来有nl2br这个函数。

2.对于使用addslashes过滤的内容,则需要使用stripslashes将添加的\去掉。

3.对于left_delimiter和right_delimiter的选择,我的经验是{{和}}比较好,对于<{和}>最好不用,否则会遇到很多问题。使用判断语句如{{if}} {{elseif}} {{else}} {{/if}}时,千万不能在{{和关键字中留出空格,否则会出错。如写成{{ /if }} {{ else }}这些都会出错.

联系作者

经过三个月的时间,金融学公开课终于告一个段落了。两年之前就选修过这门课,可是当时由于时间问题,还是未能完成,一直心有不甘。现在工作之后,腾出周末的时间选修了这门课,所以想好好学完这门课。

这门课整体来说难度不大,只有到了统计部分才发现有点难度,这时也才知道以前学的数理统计还是有用的,可是我已经忘的差不多了。幸亏Gautam Kaul教导有方,对这部分总算有个了解。

我的感悟是,学习的过程最重要还是要做笔记和练习,否则太容易忘记了,即便是当时已经弄懂了,过了很久之后,还是会忘记当初的想法,就像现在,即使已经学完了,很多内容又忘的差不多了,真是可恶。可是有一点还是不能忘记的,那就是价值的创造是来自于伟大的想法,而金融运作并不创造价值。

下面是自己用Python写的一些函数,还是很实用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def pv(rate,nper,pmt,fv=0):
v = pmt * (( 1 + rate) ** nper - 1) / rate / ( (1 + rate) ** nper)
v += fv / ((1 + rate) ** nper)
return v
def pmt(rate,nper,pv,fv=0):
p = pv * ((1 + rate) ** nper ) * rate / ((1 + rate) ** nper - 1)
p += fv * rate / ((1 + rate) ** nper - 1)
return p
def fv(rate,nper,pmt,pv=0):
v = pmt * ((1 + rate) ** nper - 1) / rate
v += pv / ((1 + rate) ** nper)
return v
def paf(r,n,g):
return 1.0 / (r - g) * (1 - (1 + g) ** n / (1 + r) **n)

联系作者

因为项目需要用到分页功能,所以需要用到SetLimits函数,结果就出现了Error: searchd error: offset out of bounds (offset=9500, max_matches=1000)

于是找原因,发现配置文件中有max_matches这个选项,于是将它改为10000,可是依然出现Error: searchd error: offset out of bounds (offset=9500, max_matches=1000)错误,真是莫名其妙的错误,仔细看了SetLimits的函数说明以及SphinxClient.java,才知道使用SetLimits这个函数时,如果没有提供max_matches这个参数的值,则max_matches默认为1000,而9500超过了1000,所以溢出了。

现在终于明白原因,也就是说使用Sphinx一共可以在两个地方设置max_matches,一个是在searchd,也就是引擎端,提供给searchd的配置文件中进行设置;而在SphinxClient中,也就是客户端,如果在SetLimits函数中没有设置max_matches,则默认使用1000.这里有一点需要注意的是,客户端的max_matches一定要小于服务器端,否则会报错。而offset也一定要小于客户端的max_matches,这样offset才不会溢出。

联系作者

TCP建立连接时一般要发送三次包,也就是俗称的三次握手。首先客户端向服务器端发送一个建立连接请求,告诉服务器自己的序列号;服务器收到这个请求包后,进行确认,同时告诉客户端自己的序列号;之后客户端对这个包进行确认。如果一切正常,三次握手就已经完成。

在网络状况不好的时候,如果在发送的过程中任意一个包丢失会怎样呢?

首先来看看第一个包。这种情况显而易见,如果客户端建立连接的请求包丢失,那么服务器端根本不知道有这么一个请求,客户端只有重新发送这个包。

再来看看第二个包。这种情况也是显而易见,如果服务器的确认包丢失,那么客户端无法知道服务器是否收到这个请求,此时服务器端必须再次发送这个确认包。

最后来看看第三个包。这种情况就不是那么显而易见了。如果客户端的确认包丢失,一个明显的解决办法是客户端再次发送这个确认包,然而这是不可行的,因为即使再次发送,客户端依然不知道服务器端是否收到这个确认包。解决的办法是服务器端再次发送三次握手的第二个包。这样就可以说明服务器端没有收到客户端的确认包,所以它需要再次发送第二个包,当客户端再次收到这个包时,也就知道自己此前发送的确认包丢失了,于是再次发送确认包。

联系作者

有些时候,想让脚步运行在后台中,而且只存在一个这样的脚步,这时一种可行的方法是将脚步的运行的进程ID写在一个文件中,当再次运行这个脚步时,去读取这个文件,读出ID,如果这个ID有进程在运行,就退出。这时需要知道进程ID,而在shell中,它是一个特殊变量,也就是$$.当运行shell脚步时,$$就是输出进程ID。

而有些时候,在shell中会编写一些函数,并返回结果,这是需要一个变量保存函数运行结果。这个变量就是$?.

而又有些时候,在shell中想知道运行脚步的名字,这个变量就是$0.

还有许多很有用的特殊变量,只是我不知道还有哪些。google之后可以知道,只是很好奇的是,这些人是怎么知道这些变量的呢?

联系作者

很早之前,在使用Sphinx搭建搜索服务时,遇到这个问题,到Sphinx for Chinese的群里请教,没有得到满意的答案,于是将sql_query_info 这个选项注释掉,就没有报错了。今天正好有时间,于是着手找到这个问题的症结,也算是为Sphinx做点贡献。

打开源代码,才发现用的是匈牙利命名法,看得不爽。也许因为没有Lucene那么出名,只有两个人在维护这个项目,代码里到处充斥这Fix Me,还好结构还算精良,要不然真不知道从和看起。本来想用GDB调试的,还不太熟练,于是就只好用最原始的printf输出。经过缩小范围,找到了一些蛛丝马迹,
在search.cpp中 的第331附近,主要的查询工作就在这里完成的,跳转过去之后
if ( !pIndex->MultiQuery ( &tQuery, pResult, 1, &pTop, NULL ) )
锁定了到下面这个函数
在sphinx.cpp中 17301 if ( !sphCheckQueryHeight ( tParsed.m_pRoot, pResult->m_sError ) )
继续跳转,到了下面这行
在sphinx.cpp中 16404 int64_t iQueryStack = sphGetStackUsed() + iHeightSPH_EXTNODE_STACK_SIZE;
输出之后,发现问题出在sphGetStackUsed这个函数里
在sphinxstd.cpp 中 1218行 int64_t sphGetStackUsed()
继续跳转,
sphinxstd.cpp 中 1221行
BYTE cStack;
BYTE
pStackTop = (BYTE*)sphMyStack();
线程栈的使用大小就是上面两个值的差,继续查找
在sphinxstd.cpp return sphThreadGet ( g_tMyThreadStack );

这里用到了线程私有数据,看到私有数据的设置还是很正常,所以依然不知道哪里出了问题。于是索性将
int64_t iQueryStack = sphGetStackUsed() + iHeightSPH_EXTNODE_STACK_SIZE;
这行改成
int64_t iQueryStack = iHeight
SPH_EXTNODE_STACK_SIZE;
这样sql_query_info就可以使用了,也不会再报query too complex not enough stack错误。
可是这个自己查询得到的中文显示出来都是乱码,我认为是没有设置SET NAMES utf8的原因,但又无法在sql_query_info这里添加这句。虽然在sql_query_pre = SET NAMES utf8已经设置了,但是因为不是同一个查询连接,所以无效。

所以最终我得到解决这个错误的结论,那就是注释掉sql_query_info这个选项。最坑人的是,官方的示例中是开启这个选项的。

联系作者

之前说过用Sphinx给同事搭建搜索服务,可是他提了一个要求,也就是文本中有牛皮癣这个词,搜牛皮时也要能搜到牛皮癣,这个要求在经过分词后是不可以完成的。于是只好去寻求一元分词和二元分词的办法。
http://lutaf.com/157.htm 这里看到,“sphinx只要把min_word_len设置为1,并配置charset_table,默认就是单字切分 ”于是试着配置,结果不行。于是只好看文档,在文档中找到,默认情况下,Sphinx已经支持一元分词。
只需设置
charset_type = utf-8 ,
ngram_len = 1,
ngram_chars = U+3000..U+2FA1F
这样,再次搜牛皮时,就可以搜到牛皮癣了。

联系作者

在前面的介绍中,都没有处理更新和删除问题,这里有必要说说。在关于sphinx引擎的一些想法中说过公司所用的引擎中,处理更新和删除的办法是在索引中增加一个属性来标志这条记录是否失效,每次做增量时,就要去主索引和增量索引中更改相应id的属性值,这确实可以解决问题。不过并不是一个很好的解决办法,Sphinx的作者也说过这种方法既麻烦又容易出错。既然有更新和删除这个需求,必然会提供解决的办法,这个办法就是kilst。所谓的klist,就是kill list,按照字面理解,就是删除列表。我们只需要在增量索引中保存一个id列表,搜索时,如果在主索引中搜到相关文档,而文档的id存在于增量索引的id列表中,则这个文档将被丢弃。

这里有一个需要注意的是,当文章被删除时,仅仅通过增量抓取,在增量索引中并不能知道主索引中哪一个文档被删除了,所以这就必须在表中文档被删除时,能够记录下被删除的id,这就需要用到触发器,也需要建立一个辅助表来保存这些id。辅助表的建立如下:

1
2
3
4
create table sphinxklist(
        id integer not null,
        ts timestamp not null
);

触发器的建立如下:

1
2
3
4
5
6
7
8
DELIMITER //
CREATE TRIGGER sphinx_kill
AFTER DELETE ON wp_posts
FOR EACH ROW
BEGIN
        INSERT INTO sphinxklist VALUES (OLD.ID, NOW());

END
//

有了这些准备工作后,我们就可以使用klist了,事实上在之前的配置文件的基础上,只需要修改一点点内容就好了。首先修改主索引

1
2
3
4
5
6
7
8
9
10
11
12
source srcmain : base{
        sql_query_pre = SET NAMES utf8
        sql_query_pre = SET SESSION query_cache_type=OFF
        sql_query_pre = UPDATE sphinx_helper SET main_tmp_maxts=NOW() WHERE appid='blog_search';
        sql_query = \
                SELECT ID, post_title, post_content, UNIX_TIMESTAMP(post_modified) AS post_modified FROM wp_posts WHERE \
                        post_status='publish' AND post_modified < (SELECT main_tmp_maxts FROM sphinx_helper WHERE appid='blog_search');
        sql_query_post_index = UPDATE sphinx_helper SET main_maxts=main_tmp_maxts WHERE appid='blog_search';
        sql_query_post_index = DELETE FROM sphinxklist WHERE ts < (SELECT main_maxts FROM sphinx_helper WHERE appid='blog_search');
        sql_attr_timestamp = post_modified
        sql_field_string = post_title
}

可以看到,相对于之前的配置,这里只添加了一行

1
sql_query_post_index = DELETE FROM sphinxklist WHERE ts < (SELECT main_maxts FROM sphinx_helper WHERE appid='blog_search');

添加这行是为了防止之前运行引擎时留下的id再次被使用。
之后修改临时索引:

1
2
3
4
5
6
7
8
9
10
11
12
source srcdelta_temp : srcmain {
        sql_query_pre = SET NAMES utf8
        sql_query_pre = SET SESSION query_cache_type=OFF
        sql_query_pre = SET @maxtsdelta:=NOW();
        sql_query_pre = UPDATE sphinx_helper SET delta_tmp_maxts=@maxtsdelta WHERE appid='blog_search';
        sql_query = SELECT ID, post_title, post_content, UNIX_TIMESTAMP(post_modified) AS post_modified FROM wp_posts WHERE \
                post_status='publish' AND post_modified >= (SELECT main_maxts FROM sphinx_helper WHERE appid='blog_search')\
                AND post_modified < @maxtsdelta;
        sql_query_killlist = SELECT ID FROM wp_posts WHERE post_modified >= (SELECT main_maxts FROM sphinx_helper WHERE \
                appid='blog_search') AND post_modified < @maxtsdelta UNION SELECT id FROM sphinxklist;
        sql_query_post_index = UPDATE sphinx_helper SET delta_maxts=delta_tmp_maxts WHERE appid='blog_search';
}

也只是添加了一行,也就是将这次抓取的id与sphinxlist中的id合并。
之后还需要修改Shell脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
baseDir=/home/long/sphinxforchinese/blog_search
conf=$baseDir/etc/blog_search.conf
binDir=$baseDir/bin
cd $binDir
while [ true ]
do
        #./indexer -c $conf --merge-klists --rotate --merge delta deltaTemp
        ./indexer -c $conf  --merge-klists --rotate --merge delta delta_temp
        if [ "$?" -eq "0" ]; then
                cat $baseDir/script/post_merge.sql | mysql -u root --password=123456 blog
                ./indexer -c $conf --rotate delta_temp
        fi
        sleep 60
done

这个脚本相对于原来的只增加了–merge-klists这个参数,这个参数的意义是,将delta_temp合并到delta时,并不会删除delta的klist,而是将delta_temp的klist和delta的klist合并,这正是我们想要的。经过这样的变化,一个可以处理更新和删除的main+delta索引就建好了。

感谢Sphinx团队,感谢Sphinx-for-chinese团队,给我们提供了一个这么好用的开源引擎。

联系作者

上篇中,我们介绍了一种建立主索引和增量索引的方法,这种方法有一种不足之处就是会改变主索引,因为每次增量索引都会与主索引合并成新的主索引。为此,我们可以想出另一种解决的办法,每次只改变增量索引,这就需要另外再建立一个临时索引。

这里只需要改变少量地方,一个是增量索引,另外还需新增一个临时索引,具体配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
source srcdelta : srcmain{
        sql_query_pre = SET NAMES utf8
        sql_query_pre = SET SESSION query_cache_type=OFF
        sql_query = SELECT ID, post_title, post_content, UNIX_TIMESTAMP(post_modified) AS post_modified FROM wp_posts WHERE \
                post_status='publish' limit 0;
        sql_query_post_index =
}
source srcdelta_temp : srcmain {
        sql_query_pre = SET NAMES utf8
        sql_query_pre = SET SESSION query_cache_type=OFF
        sql_query_pre = SET @maxtsdelta:=NOW();
        sql_query_pre = UPDATE sphinx_helper SET delta_tmp_maxts=@maxtsdelta WHERE appid='blog_search';
        sql_query = SELECT ID, post_title, post_content, UNIX_TIMESTAMP(post_modified) AS post_modified FROM wp_posts WHERE \
                post_status='publish' AND post_modified >= (SELECT main_maxts FROM sphinx_helper WHERE appid='blog_search')\
                AND post_modified < @maxtsdelta;
        sql_query_post_index = UPDATE sphinx_helper SET delta_maxts=delta_tmp_maxts WHERE appid='blog_search';
}
index delta_temp : main{
        source = srcdelta_temp
        path = /home/long/sphinxforchinese/blog_search/var/data/delta_temp
}

实际上,我们是先建立一个空的增量索引,之后临时索引中的数据慢慢合并到增量索引中。在这里,增量索引很像上篇中的主索引,而临时索引则像上篇中的增量索引。
此时我们需要修改dist_blog_search,即增加临时索引

1
2
3
4
5
6
7
8
9
index dist_blog_search {
    type = distributed
    local = main
    local = delta
    local = delta_temp
    agent_connect_timeout = 1000
    agent_query_timeout = 3000

}

此后还需改变Shell脚本的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
baseDir=/home/long/sphinxforchinese/blog_search
conf=$baseDir/etc/main_delta_temp.conf
binDir=$baseDir/bin
cd $binDir
while [ true ]
do
        ./indexer -c $conf  --rotate --merge delta delta_temp
        if [ "$?" -eq "0" ]; then
                cat $baseDir/script/post_merge.sql | mysql -u root --password=123456 blog
                ./indexer -c $conf --rotate delta_temp
        fi
        sleep 60
done

事实上,改变的内容还是很少的。经过这样的改变,我们就无需再改变主索引了。第一次建立主索引后,就一直保持不变,变化的是增量索引。

联系作者