標籤:Python程式設計語言 Python案例講解 Python基礎精講
在日常生活中,大家瞭解搜尋引擎如百度、360、搜狗、Google等,搜尋是大資料領域裡常見的需求。Splunk和ELK分別是該領域在非開源和開源領域裡的領導者。本文利用很少的Python代碼實現了一個基本的資料搜尋功能,試圖讓大家理解大資料搜尋的基本原理。
布隆過濾器(BloomFilter)
第一步我們先要實現一個布隆過濾器。
布隆過濾器是大資料領域的一個常見演算法,它的目的是過濾掉那些不是目標的元素。也就是說如果一個要搜尋的詞並不存在與我的資料中,那麼它可以以很快的速度返回目標不存在。
讓我們看看以下布隆過濾器的代碼:
classBloomfilter(object):
"""
ABloomfilterisaprobabilisticdata-structurethattradesspaceforaccuracy
whendeterminingifavalueisinaset.Itcantellyouifavaluewaspossibly
added,orifitwasdefinitelynotadded,butitcan‘ttellyouforcertainthat
itwasadded.
"""
definit(self,size):
"""SetuptheBFwiththeappropriatesize"""
self.values=[False]*size
self.size=size
defhash_value(self,value):
"""HashthevalueprovidedandscaleittofittheBFsize"""
returnhash(value)%self.size
defadd_value(self,value):
"""AddavaluetotheBF"""
h=self.hash_value(value)
self.values[h]=True
defmight_contain(self,value):
"""CheckifthevaluemightbeintheBF"""
h=self.hash_value(value)
returnself.values[h]
defprint_contents(self):
"""DumpthecontentsoftheBFfordebuggingpurposes"""
printself.values
基本的資料結構是個數組(實際上是個位元影像,用1/0來記錄資料是否存在),初始化是沒有任何內容,所以全部置False。實際的使用當中,該數組的長度是非常大的,以保證效率。
利用雜湊演算法來決定資料應該存在哪一位,也就是數組的索引
當一個資料被加入到布隆過濾器的時候,計算它的雜湊值然後把相應的位置為True
當檢查一個資料是否已經存在或者說被索引過的時候,只要檢查對應的雜湊值所在的位的True/Fasle
看到這裡,大家應該可以看出,如果布隆過濾器返回False,那麼資料一定是沒有索引過的,然而如果返回True,那也不能說資料一定就已經被索引過。在搜尋過程中使用布隆過濾器可以使得很多沒有命中的搜尋提前返回來提高效率。
我們看看這段code是如何啟動並執行:
bf=Bloomfilter(10)
bf.add_value(‘dog‘)
bf.add_value(‘fish‘)
bf.add_value(‘cat‘)
bf.print_contents
bf.add_value(‘bird‘)
bf.print_contents
#Note:contentsareunchangedafteraddingbird-itcollides
fortermin[‘dog‘,‘fish‘,‘cat‘,‘bird‘,‘duck‘,‘emu‘]:
print‘{}:{}{}‘.format(term,bf.hash_value(term),bf.might_contain(term))
結果:
[False,False,False,False,True,True,False,False,False,True]
[False,False,False,False,True,True,False,False,False,True]
dog:5True
fish:4True
cat:9True
bird:9True
duck:5True
emu:8False
首先建立了一個容量為10的的布隆過濾器
然後分別加入‘dog’,‘fish’,‘cat’三個對象,這時的布隆過濾器的內容如下:
然後加入‘bird’對象,布隆過濾器的內容並沒有改變,因為‘bird’和‘fish’恰好擁有相同的雜湊。
最後我們檢查一堆對象(’dog’,‘fish’,‘cat’,‘bird’,‘duck’,’emu’)是不是已經被索引了。結果發現‘duck’返回True,2而‘emu’返回False。因為‘duck’的雜湊恰好和‘dog’是一樣的。
分詞
下面一步我們要實現分詞。分詞的目的是要把我們的文本資料分割成可搜尋的最小單元,也就是詞。這裡我們主要針對英語,因為中文的分詞涉及到自然語言處理,比較複雜,而英文基本只要用標點符號就好了。
下面我們看看分詞的代碼:
defmajor_segments(s):
"""
Performmajorsegmentingonastring.Splitthestringbyallofthemajor
breaks,andreturnthesetofeverythingfound.Thebreaksinthisimplementation
aresinglecharacters,butinSplunkpropertheycanbemultiplecharacters.
Asetisusedbecauseorderingdoesn‘tmatter,andduplicatesarebad.
"""
major_breaks=‘‘
last=-1
results=set
#enumeratewillgiveus(0,s[0]),(1,s[1]),...
foridx,chinenumerate(s):
ifchinmajor_breaks:
segment=s[last+1:idx]
results.add(segment)
last=idx
#Thelastcharactermaynotbeabreaksoalwayscapture
#thelastsegment(whichmayendupbeing"",butyolo)
segment=s[last+1:]
results.add(segment)
returnresults
主要分割
主要分割使用空格來分詞,實際的分詞邏輯中,還會有其它的分隔字元。例如Splunk的預設分割符包括以下這些,使用者也可以定義自己的分割符。
]<>{}|!;,‘”*\n\r\s\t&?+%21%26%2526%3B%7C%20%2B%3D—%2520%5D%5B%3A%0A%2C%28%29
defminor_segments(s):
"""
Performminorsegmentingonastring.Thisislikemajor
segmenting,exceptitalsocapturesfromthestartofthe
inputtoeachbreak.
"""
minorbreaks=‘.‘
last=-1
results=set
foridx,chinenumerate(s):
ifchinminor_breaks:
segment=s[last+1:idx]
results.add(segment)
segment=s[:idx]
results.add(segment)
last=idx
segment=s[last+1:]
results.add(segment)
results.add(s)
returnresults
次要分割
次要分割和主要分割的邏輯類似,只是還會把從開始部分到當前分割的結果加入。例如“1.2.3.4”的次要分割會有1,2,3,4,1.2,1.2.3
defsegments(event):
"""Simplewrapperaroundmajor_segments/minor_segments"""
results=set
formajorinmajor_segments(event):
forminorinminor_segments(major):
results.add(minor)
returnresults
分詞的邏輯就是對文本先進行主要分割,對每一個主要分割在進行次要分割。然後把所有分出來的詞返回。
我們看看這段code是如何啟動並執行:
forterminsegments(‘src_ip=1.2.3.4‘):
printterm
src
1.2
1.2.3.4
src_ip
3
1
1.2.3
ip
2
=
4
搜尋
好了,有個分詞和布隆過濾器這兩個利器的支撐後,我們就可以來實現搜尋的功能了。上代碼:
classSplunk(object):
definit(self):
self.bf=Bloomfilter(64)
self.terms={}#Dictionaryoftermtosetofevents
self.events=
defadd_event(self,event):
"""Addsaneventtothisobject"""
#GenerateauniqueIDfortheevent,andsaveit
event_id=len(self.events)
self.events.append(event)
#Addeachtermtothebloomfilter,andtracktheeventbyeachterm
forterminsegments(event):
self.bf.add_value(term)
iftermnotinself.terms:
self.terms[term]=set
self.terms[term].add(event_id)
defsearch(self,term):
"""Searchforasingleterm,andyieldalltheeventsthatcontainit"""
#InSplunkthisrunsinO(1),andislikelytobeinfilesystemcache(memory)
ifnotself.bf.might_contain(term):
return
#InSplunkthisprobablyrunsinO(logN)whereNisthenumberoftermsinthetsidx
iftermnotinself.terms:
return
forevent_idinsorted(self.terms[term]):
yieldself.events[event_id]
Splunk代表一個擁有搜尋功能的索引集合
每一個集合中包含一個布隆過濾器,一個倒排詞表(字典),和一個儲存所有事件的數組
當一個事件被加入到索引的時候,會做以下的邏輯
為每一個事件產生一個unqieid,這裡就是序號
對事件進行分詞,把每一個詞加入到倒排詞表,也就是每一個詞對應的事件的id的映射結構,注意,一個詞可能對應多個事件,所以倒排表的的值是一個Set。倒排表是絕大部分搜尋引擎的核心功能。
當一個詞被搜尋的時候,會做以下的邏輯
檢查布隆過濾器,如果為假,直接返回
檢查詞表,如果被搜尋單詞不在詞表中,直接返回
在倒排表中找到所有對應的事件id,然後返回事件的內容
我們運行下看看把:
s=Splunk
s.add_event(‘src_ip=1.2.3.4‘)
s.add_event(‘src_ip=5.6.7.8‘)
s.add_event(‘dst_ip=1.2.3.4‘)
foreventins.search(‘1.2.3.4‘):
printevent
print‘-‘
foreventins.search(‘src_ip‘):
printevent
print‘-‘
foreventins.search(‘ip‘):
printevent
src_ip=1.2.3.4
dst_ip=1.2.3.4
-
src_ip=1.2.3.4
src_ip=5.6.7.8
-
src_ip=1.2.3.4
src_ip=5.6.7.8
dst_ip=1.2.3.4
是不是很贊!
更複雜的搜尋
更進一步,在搜尋過程中,我們想用And和Or來實現更複雜的搜尋邏輯。
上代碼:
classSplunkM(object):
definit(self):
self.bf=Bloomfilter(64)
self.terms={}#Dictionaryoftermtosetofevents
self.events=
defadd_event(self,event):
"""Addsaneventtothisobject"""
#GenerateauniqueIDfortheevent,andsaveit
event_id=len(self.events)
self.events.append(event)
#Addeachtermtothebloomfilter,andtracktheeventbyeachterm
forterminsegments(event):
self.bf.add_value(term)
iftermnotinself.terms:
self.terms[term]=set
self.terms[term].add(event_id)
defsearch_all(self,terms):
"""SearchforanANDofallterms"""
#Startwiththeuniverseofallevents...
results=set(range(len(self.events)))
forterminterms:
#Ifatermisn‘tpresentatallthenwecanstoplooking
ifnotself.bf.might_contain(term):
return
iftermnotinself.terms:
return
#Dropeventsthatdon‘tmatchfromourresults
results=results.intersection(self.terms[term])
forevent_idinsorted(results):
yieldself.events[event_id]
defsearch_any(self,terms):
"""SearchforanORofallterms"""
results=set
forterminterms:
#Ifatermisn‘tpresent,weskipit,butdon‘tstop
ifnotself.bf.might_contain(term):
continue
iftermnotinself.terms:
continue
#Addtheseeventstoourresults
results=results.union(self.terms[term])
forevent_idinsorted(results):
yieldself.events[event_id]
利用Python集合的intersection和union操作,可以很方便的支援And(求交集)和Or(求合集)的操作。
運行結果如下:
s=SplunkM
s.add_event(‘src_ip=1.2.3.4‘)
s.add_event(‘src_ip=5.6.7.8‘)
s.add_event(‘dst_ip=1.2.3.4‘)
foreventins.search_all([‘src_ip‘,‘5.6‘]):
printevent
print‘-‘
foreventins.search_any([‘src_ip‘,‘dst_ip‘]):
printevent
src_ip=5.6.7.8
-
src_ip=1.2.3.4
src_ip=5.6.7.8
dst_ip=1.2.3.4
用Python實現一個大資料搜尋及原始碼