Redis運維實踐(二)——python多線程方式快速遍歷集羣

Redis單實例的遍歷我們都知道,使用scan就可以了。但是對於集羣,遍歷功能的支持就不是這麼友好了,所以本次我就想到用單實例scan+多線程的形式去實現集羣的快速遍歷。


今日內容預覽

scan功能簡介


redis全庫遍歷key使用的是keys 命令,而keys命令會造成阻塞,所以出現了scan增量式迭代的命令來支持非阻塞遍歷。
對於SCAN這類增量式迭代命令來說,有可能在增量迭代過程中,集合元素被修改,對返回值無法提供完全準確的保證。
SCAN命令是一個基於遊標的迭代器。這意味着命令每次被調用都需要使用上一次這個調用返回的遊標作爲該次調用的遊標參數,以此來延續之前的迭代過程

當SCAN命令的遊標參數被設置爲 0 時, 服務器將開始一次新的迭代, 而當服務器向用戶返回值爲 0 的遊標時, 表示迭代已結束。
SCAN命令的返回值 是一個包含兩個元素的數組, 第一個數組元素是用於進行下一次迭代的新遊標, 而第二個數組元素則是一個數組, 這個數組中包含了所有被迭代的元素。

在第二次調用 SCAN 命令時, 命令返回了遊標 0 , 這表示迭代已經結束, 整個數據集已經被完整遍歷過了。
full iteration :以 0 作爲遊標開始一次新的迭代, 一直調用 SCAN 命令, 直到命令返回遊標 0 , 我們稱這個過程爲一次完整遍歷。

scan的兩個絕對:

1、從完整遍歷開始直到完整遍歷結束期間, 一直存在於數據集內的所有元素都會被完整遍歷返回; 這意味着, 如果有一個元素, 它從遍歷開始直到遍歷結束期間都存在於被遍歷的數據集當中, 那麼 SCAN 命令總會在某次迭代中將這個元素返回給用戶。
2、同樣,如果一個元素在開始遍歷之前被移出集合,並且在遍歷開始直到遍歷結束期間都沒有再加入,那麼在遍歷返回的元素集中就不會出現該元素。

兩個不足

1、同一個元素可能會被返回多次。 處理重複元素的工作交由應用程序負責, 比如說, 可以考慮將迭代返回的元素僅僅用於可以安全地重複執行多次的操作上。
2、如果一個元素是在迭代過程中被添加到數據集的, 又或者是在迭代過程中從數據集中被刪除的, 那麼這個元素可能會被返回, 也可能不會。

類似於KEYS 命令,增量式迭代命令通過給定 MATCH 參數的方式實現了通過提供一個 glob 風格的模式參數, 讓命令只返回和給定模式相匹配的元素。

MATCH功能對元素的模式匹配工作是在命令從數據集中取出元素後和向客戶端返回元素前的這段時間內進行的, 所以如果被迭代的數據集中只有少量元素和模式相匹配, 那麼迭代命令或許會在多次執行中都不返回任何元素。

python多線程簡介


setDaemon(True)將線程聲明爲守護線程,默認情況下(其實就是setDaemon(False)),主線程執行完自己的任務以後,就退出了,此時子線程會繼續執行自己的任務,直到自己的任務結束。當我們使用setDaemon(True)方法,設置子線程爲守護線程時,主線程一旦執行結束,則全部線程全部被終止執行,可能出現的情況就是,子線程的任務還沒有完全執行結束,就被迫停止。
此時join的作用就凸顯出來了,join所完成的工作就是線程同步,即主線程任務結束之後,進入阻塞狀態,一直等待其他的子線程執行結束之後,主線程在終止
start()開始線程活動。
join()的作用是,在子線程完成運行之前,這個子線程的父線程將一直被阻塞。
注意: join()方法的位置是在for循環外的,也就是說必須等待for循環裏的兩個進程都結束後,纔去執行主進程。
join有一個timeout參數:

當設置守護線程時,含義是主線程對於子線程等待timeout的時間將會殺死該子線程,最後退出程序。所以說,如果有10個子線程,全部的等待時間就是每個timeout的累加和。簡單的來說,就是給每個子線程一個timeout的時間,讓他去執行,時間一到,不管任務有沒有完成,直接殺死。
沒有設置守護線程時,主線程將會等待timeout的累加和這樣的一段時間,時間一到,主線程結束,但是並沒有殺死子線程,子線程依然可以繼續執行,直到子線程全部結束,程序退出。
class threading.Thread()說明:

class threading.Thread(group=None, target=None, name=None, args=(), kwargs={})

This constructor should always be called with keyword arguments. Arguments are:

  group should be None; reserved for future extension when a ThreadGroup class is implemented.

  target is the callable object to be invoked by the run() method. Defaults to None, meaning nothing is called.

  name is the thread name. By default, a unique name is constructed of the form “Thread-N” where N is a small decimal number.

  args is the argument tuple for the target invocation. Defaults to ().

  kwargs is a dictionary of keyword arguments for the target invocation. Defaults to {}.

If the subclass overrides the constructor, it must make sure to invoke the base class constructor (Thread.__init__()) before doing 

anything else to the thread.

scan+多線程集羣遍歷實現


實現集羣遍歷,field_1值爲多少,那麼field_2的值便增多少功能,同時記錄field_2的增量值及結果值。

#encoding: utf-8
import redis
import sys
import datetime
import time
import threading
import os
all_instances = ["127.0.0.1_6379","127.0.0.1_6370","127.0.0.1_6371"]

def main_process(rip, rport):
    print 'thread %s is running...\n' % threading.current_thread().name
    rpool = redis.ConnectionPool(host = rip, port = rport, db = 0, password = 'test')
    redis_cli = redis.Redis(connection_pool=rpool)
    redis_pip = redis_cli.pipeline(transaction=False)
    role = redis_cli.info()["role"]
    if role == "slave":
        return
    log_file_name = "test_%s.csv.%s" % (threading.current_thread().name, time.strftime("%Y%m%d_%H", time.localtime()))
    log_file_name = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "Logs", log_file_name)
    key_list = []
    pipe_len = 0
    for key in redis_cli.scan_iter(match='*',count=1000):
        redis_pip.hget(key, 'field_1')
        key_list.append(key)
        pipe_len += 1
        if pipe_len % 1000 == 0:
            log_file_fd = open(log_file_name, 'a')
            pip_result = redis_pip.execute()
            key_val_list = []
            for index in range(len(pip_result)):
                if pip_result[index] and int(pip_result[index]) > 0:
                    redis_pip.hincrby(key_list[index], 'field_2', int(pip_result[index]))
                    key_val_list.append([key_list[index], int(pip_result[index])])
            pip_result = redis_pip.execute()
            for index in range(len(pip_result)):
                log_file_fd.write("%s,%d,%s\n" % (key_val_list[index][0], key_val_list[index][1], pip_result[index]))
            key_list = []
            log_file_fd.close()
        
    log_file_fd = open(log_file_name, 'a')
    pip_result = redis_pip.execute()
    key_val_list = []
    for index in range(len(pip_result)):
        if pip_result[index] and int(pip_result[index]) > 0:
            redis_pip.hincrby(key_list[index], 'field_2', int(pip_result[index]))
            key_val_list.append([key_list[index], int(pip_result[index])])
    pip_result = redis_pip.execute()
    for index in range(len(pip_result)):
        log_file_fd.write("%s,%d,%s\n" % (key_val_list[index][0], key_val_list[index][1], pip_result[index]))
    log_file_fd.close()

if __name__ == "__main__":
    start_time = time.time()
    print 'thread %s is running...\n' % threading.current_thread().name
    threads = []
    for instance in all_instances:
        instance_ip, instance_port = instance.split('_')
        cur_thread = threading.Thread(target=main_process, args=(instance_ip, instance_port), name=instance)
        threads.append(cur_thread)
    for eve_t in threads:
        eve_t.setDaemon(True)
        eve_t.start()
    for eve_t in threads:
        eve_t.join()
    print '主線程-%s結束了!用時:%d\n' % (threading.current_thread().name, time.time()-start_time)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章