Google搜索引擎創建至今已經快20年了,以後全球各種大大小小相似的搜索引擎也陸續出現、消亡。國內目前以百度爲大,搜狗、360、必應等也勢在必爭。搜索引擎技術也發展的至關成熟,同時也就出現了不少開源的搜索引擎系統。好比,Solr、Lucene、Elasticsearch、Sphinx等。python
本文以sphinx search爲例來介紹如何打造本身的搜索引擎。該搜索引擎的架構大體以下:
mysql
Sphinx search 是俄羅斯人用C++寫的,速度很快,能夠很是容易的與SQL數據庫和腳本語言集成,內置MySQL和PostgreSQL 數據庫數據源的支持。其官方網站是: http://sphinxsearch.com/git
能夠說Sphinx支持包括英文、中文等全部語言的搜索。英文是以空格、標點符號來分割單詞的,很容易切分。而中文詞彙之間是沒有空格的,很難區分,因此纔有了天然語言處理中的「中文分詞」技術的研究。Sphinx默認把中文按字拆分的,但這樣就會產生搜索出不相干的內容來。好比,搜索「中國」,它會把同時包含「中」和「國」但不包含「中國」的文檔搜出來。所以,有人就給Sphinx打了中文分詞的補丁。github
若是沒有搞錯的話,最先添加中文分詞的是Coreseek,好像也是中文圈用得最廣的支持中文分詞的Sphinx,其它還有sphinx-for-chinese。然而這兩者基於的Sphinx版本都過低了,有好多年沒有更新。其中存在的一些Sphinx的bug也沒有解決。sql
github上有一個基於Sphinx 2.2.9版本的代碼庫添加了中文分詞: https://github.com/eric1688/sphinx 經測試,該版本穩定性和速度都要好於coreseek。固然它依然支持英文等其它語言的搜索,只是對中文搜索更加準確了。數據庫
git clone https://github.com/eric1688/sphinx cd sphinx #編譯(假設安裝到/usr/local/sphinx目錄,下文同) ./configure --prefix=/usr/local/sphinx # 說明: --prefix 指定安裝路徑 --with-mysql 編譯mysql支持 --with-pgsql 編譯pgsql支持 make sudo make install
安裝好後,在/usr/local/sphinx目錄下有如下幾個子目錄:
etc/ sphinx配置文件,不一樣的索引能夠寫不一樣的配置文件
bin/ sphinx程序,其中有創建索引的程序:indexer, 搜索守護進程:searchd
var/ 通常用了放置indexer索引好的文件網絡
MySQL數據庫表結構
從上面的架構圖能夠看出來,咱們要搜索的數據都存放在MySQL數據庫中。假設咱們的數據庫名稱叫blog_data,其中有個表叫article,表結構以下:架構
字段名 | 說明 |
---|---|
id | 文章惟一id(主鍵) |
title | 文章標題 |
content | 文章內容 |
created_time | 文章建立時間 |
該article表能夠是你自己網站的文本內容存放的表格,也能夠是你的網絡爬蟲抓取到的數據存儲表。python爬蟲
還有創建另一個表sph_counter用來存儲indexer已經索引的最大doc id
字段名 | 說明 |
---|---|
counter_id | 標記是對哪一個表作記錄 |
max_doc_id | 被索引表的最大ID |
note | 註釋,能夠是表名 |
update_at | 更新時間 |
創建索引配置文件:
新建或修改/usr/local/sphinx/etc/blog.conf 配置文件:
source blog_main { type = mysql sql_host = localhost sql_user = reader sql_pass = readerpassword sql_db = blog_data sql_port = 3306 sql_query_pre = SET NAMES utf8mb4 sql_query_pre = REPLACE INTO sph_counter SELECT 1, MAX(id), 'article', NOW() FROM article sql_query = SELECT id, title, content, \ UNIX_TIMESTAMP(created_time) AS ctime, \ FROM article \ WHERE id <= (SELECT max_doc_id from sph_counter WHERE counter_id=1) sql_attr_timestamp = ctime #從SQL讀取到的值必須爲整數,做爲時間屬性 } index blog_main { source = blog_main #對應的source名稱 path = /user/local/sphinx/var/data/blog_main docinfo = extern mlock = 0 morphology = none min_word_len = 1 html_strip = 0 charset_type = utf-8 chinese_dictionary = /user/local/sphinx/etc/xdict #中文分詞的詞典 ngram_len = 0 stopwords = /user/local/sphinx/etc/stop_words.utf8 } #全局index定義 indexer { mem_limit = 512M } #searchd服務定義 searchd { listen = 9900 listen = 9306:mysql41 # 實時索引監聽的端口 read_timeout = 5 max_children = 90 max_matches = 100000 max_packet_size = 32M read_buffer = 1M subtree_docs_cache = 8M subtree_hits_cache = 16M #workers = threads• dist_threads = 2 seamless_rotate = 0 preopen_indexes = 0 unlink_old = 1 pid_file = /usr/local/sphinx/var/log/blog_searchd_mysql.pid log = /usr/local/sphinx/var/log/blog_searchd_mysql.log query_log = /usr/local/sphinx/var/log/blog_query_mysql.log }
編輯好以上配置文件,就能夠開始創建索引了:
cd /usr/local/sphinx/bin ./indexer -c ../etc/blog.conf # 若是已經有searchd在運行了,就要加 --roate 來進行索引
索引創建後,就會在var/data/下面有名稱前綴爲blog_main.XXX的索引文件生成。
創建實時索引
上面的配置文件是創建一個靜態索引,把當時數據庫裏面的全部數據進行索引。可是,你的數據庫每每是不斷增長新數據的。爲了及時索引並搜索到最新加入的數據,就須要配置實時索引了。
index rt_weixin { type = rt path = /usr/local/sphinx/var/data/rt_blog rt_field = title rt_field = content rt_attr_timestamp = pubtime ngram_chars = U+3000..U+2FA1F #爲了支持中文 ngram_len = 1 }
該倉庫代碼的做者多是忘了給實時索引加中文分詞,若是不配置ngram_chars 參數就不能搜到中文,添加後搜索是按單字匹配的,可見做者確實是忘了給實時索引部分加中文分詞。
添加以上實時索引後並不能搜索到實時數據。實時索引的更新/添加只能經過SphinxQL(一個相似MySQL的協議),因此還要寫一個Python腳本,從數據庫讀取最新的數據並經過SphinxQL更新到實時索引。
import MySQLdb # 鏈接實時索引 db_rt = MySQLdb.connect( '127.0.0.1', 'nodb', # 對於實時索引來講,db,user,password都是不須要的,隨便寫。 'noname', 'nopass', port=9306, # 實時索引監聽的端口 ) # 向實時索引更新數據的函數 def into_rt(index_name, item): cursor = db_rt.cursor() fields = item.keys() values = item.values() fieldstr = ','.join(fields) valstr = ','.join(["'%s'"] * len(item)) for i in xrange(len(values)): if isinstance(values[i], unicode): values[i] = values[i].encode('utf8') elif isinstance(values[i], datetime): try: values[i] = int(time.mktime(values[i].timetuple())) except: traceback.print_exc() print values[i] values[i] = int(time.time()) sql = 'INSERT INTO %s (%s) VALUES(%s)' % (index_name, fieldstr, valstr) # print sql sql = sql % tuple(values) try: cursor.execute(sql) db_rt.commit() except Exception, e: if e[0] == 1064: # ignore duplicated id error pass else: traceback.print_exc() raise 'realtime index error' finally: cursor.close()
以上是及時創建實時索引的python程序的主要部分。能夠把它設置成後臺一直運行的守護程序,也能夠在crontab裏面配置每隔幾分鐘運行一次。
索引的更新
靜態的主索引若是隻創建一次,實時索引的數據量會越積越多,對實時索引的搜索帶來很大壓力,因此咱們要定時從新創建主索引,清理實時索引。
清理實時索引的程序能夠參考上面創建實時索引的python程序。
以上就是創建一個本身的搜索引擎的過程。更多配置細節可到官方網站參考文檔。