Swift源碼分析----swift-object-updater

感謝朋友支持本博客,歡迎共同探討交流,由於能力和時間有限,錯誤之處在所難免,歡迎指正!

如果轉載,請保留作者信息。
博客地址:http://blog.csdn.net/gaoxingnengjisuan
郵箱地址:[email protected]

PS:最近沒有登錄博客,很多朋友的留言沒有看見,這裏道歉!還有就是本人較少上QQ,可以郵件交流。


概述部分:

對象更新守護進程適用於這種情況:
在系統故障或者是服務器高負荷的情況下,賬戶或容器的數據可能不會立即被更新或者出現數據更新失敗的情況;
如果出現這種情況,該次更新將會在本地文件系統上被加入隊列,然後更新器將會繼續處理這些隊列中的更新工作;
對象更新守護進程就是用於處理隊列中對象更新任務操作;
注:
1 這些更新任務存儲在本地目錄/srv/node/device1(設備)/async_pending/下面,這裏存儲了每一個設備下所有屬於這個設備的要執行的更新操作;
2 當然這個目錄下面每個對象的更新任務都是獨立的一個文件,如/srv/node/device1/async_pending/prefix****/update****/****;

這個守護進程的總體流程如下:
1 遍歷本地節點上的所有設備,獲取每一個設備;
2 針對每一個設備,嵌套遍歷到目錄/srv/node/device1/async_pending/prefix****/update****層次,需要執行的更新任務就存儲在此;
  調用方法process_object_update執行以下的操作:
  2.1 獲取一個對象的副本節點和分區號;
  2.2 遍歷一個對象的所有副本節點,針對每一個節點調用object_update方法,通過POST或DELETE方法實現對象的更新操作;
3 當執行完/srv/node/device1/async_pending/prefix****/update****指定的對象更新任務,刪除這個文件;


源碼解析部分:

下面是這部分代碼的主要執行流程,代碼中較重要的部分已經進行了相關的註釋;

from swift.obj.updater import ObjectUpdater
from swift.common.utils import parse_options
from swift.common.daemon import run_daemon

if __name__ == '__main__':
    conf_file, options = parse_options(once=True)
    run_daemon(ObjectUpdater, conf_file, **options)

def run_once(self, *args, **kwargs):
    """
    運行一次更新;
    """
    self.logger.info(_('Begin object update single threaded sweep'))
    begin = time.time()
    self.successes = 0
    self.failures = 0
        
    # self.devices = /srv/node
    # ls /srv/node/
    # account.builder  backups            container.ring.gz  object.builder
    # account.ring.gz  container.builder  device1            object.ring.gz
    for device in os.listdir(self.devices):
        if self.mount_check and not ismount(os.path.join(self.devices, device)):
            self.logger.increment('errors')
            self.logger.warn(_('Skipping %s as it is not mounted'), device)
            continue
            
        # object_sweep:掃描device上的async pendings目錄,遍歷每一個prefix目錄並執行升級;
        # self.devices = /srv/node
        # /srv/node/device1
        # /srv/node/device2
        self.object_sweep(os.path.join(self.devices, device))
    elapsed = time.time() - begin
    self.logger.info(_('Object update single threaded sweep completed: '
                       '%(elapsed).02fs, %(success)s successes, %(fail)s failures'),
                       {'elapsed': elapsed, 'success': self.successes, 'fail': self.failures})
    dump_recon_cache({'object_updater_sweep': elapsed}, self.rcache, self.logger)
遍歷每一個設備,調用方法object_sweep對其目錄下的對象實現更新操作;

來看方法object_sweep的具體實現:

def object_sweep(self, device):
    """
    掃描device上的async pendings目錄,遍歷每一個prefix目錄並執行更新;
    """
    start_time = time.time()
    # async_pending = /srv/node/device1/async_pending
    async_pending = os.path.join(device, ASYNCDIR)
    if not os.path.isdir(async_pending):
        return
        
    for prefix in os.listdir(async_pending):
        # 獲取/srv/node/device1/async_pending/每一個目錄的路徑;
        prefix_path = os.path.join(async_pending, prefix)
        if not os.path.isdir(prefix_path):
            continue
            
        last_obj_hash = None
        # 按照從大到小的順序遍歷prefix_path下的文件夾;
        for update in sorted(os.listdir(prefix_path), reverse=True):
            # update_path = prefix_path/update
            update_path = os.path.join(prefix_path, update)
            if not os.path.isfile(update_path):
                continue
                
            try:
                obj_hash, timestamp = update.split('-')
            except ValueError:
                self.logger.increment('errors')
                self.logger.error(_('ERROR async pending file with unexpected name %s') % (update_path))
                continue
                
            # 如果obj_hash = last_obj_hash,則刪除update_path;
            if obj_hash == last_obj_hash:
                self.logger.increment("unlinks")
                os.unlink(update_path)
                    
            else:
                # process_object_update:執行更新object;
                # device = /srv/node/device1
                # update_path = /srv/node/device1/async_pending/prefix****/update****
                self.process_object_update(update_path, device)
                last_obj_hash = obj_hash
            time.sleep(self.slowdown)
        try:
            os.rmdir(prefix_path)
        except OSError:
            pass
    self.logger.timing_since('timing', start_time)
update_path = /srv/node/device1/async_pending/prefix/update
嵌套遍歷指定的設備device下的文件,遍歷所有形如update_path = /srv/node/device1/async_pending/prefix****/update****的文件;
針對每一個update_path文件,調用方法process_object_update,實現爲每一個object進行更新操作;

來看方法process_object_update的實現:

def process_object_update(self, update_path, device):
    """
    執行更新object;       
    # device = /srv/node/device1
    # update_path = /srv/node/device1/async_pending/prefix****/update****
    """
    try:
        update = pickle.load(open(update_path, 'rb'))
    except Exception:
        self.logger.exception(_('ERROR Pickle problem, quarantining %s'), update_path)
        self.logger.increment('quarantines')
        renamer(update_path, os.path.join(device, 'quarantined', 'objects', os.path.basename(update_path)))
        return
        
    successes = update.get('successes', [])
    # get_container_ring:獲取或建立container的環,且加載它;
    # get_nodes:獲取account/container/object所對應的分區號和節點(可能是多個,因爲分區副本有多個,可能位於不同的節點上);
    # 返回元組(分區,節點信息列表);
    # 在節點信息列表中至少包含id、weight、zone、ip、port、device、meta;
    part, nodes = self.get_container_ring().get_nodes(update['account'], update['container'])
    # 明確當前對象的層次關係;
    obj = '/%s/%s/%s' % (update['account'], update['container'], update['obj'])
        
    success = True
    new_successes = False
        
    # 遍歷對象的所有副本節點;
    for node in nodes:
        # 如果某個節點上的對象沒有執行更新操作,則調用方法object_update執行更新操作;
        if node['id'] not in successes:
            # object_update:通過POST或DELETE方法執行container中的object的更新;
            # update['op']:對象更新操作具體調用的方法(如POST或DELETE);
            # 更新操作的相關信息保存在update['headers']中;
            status = self.object_update(node, part, update['op'], obj, update['headers'])
                
            if not is_success(status) and status != HTTP_NOT_FOUND:
                success = False
            else:
                successes.append(node['id'])
                new_successes = True
        
    if success:
        self.successes += 1
        self.logger.increment('successes')
        self.logger.debug(_('Update sent for %(obj)s %(path)s'), {'obj': obj, 'path': update_path})
        self.logger.increment("unlinks")
        os.unlink(update_path)
    else:
        self.failures += 1
        self.logger.increment('failures')
        self.logger.debug(_('Update failed for %(obj)s %(path)s'), {'obj': obj, 'path': update_path})
        if new_successes:
            update['successes'] = successes
            write_pickle(update, update_path, os.path.join(device, 'tmp'))
1.讀取指定的對象數據更新文件update_path = /srv/node/device1/async_pending/prefix****/update****;
  注:每個update_path文件記錄了一個object要執行的更新信息;
  在系統故障或者是服務器高負荷的情況下,賬戶或容器的數據可能不會立即被更新或者出現數據更新失敗的情況;
  如果出現這種情況,該次更新將會在本地文件系統上被加入隊列,然後更新器將會繼續處理這些隊列中的更新工作;
  這些沒有及時執行的更新操作,就是存儲在這些update_path文件中,每個update_path文件對應一個object的更新操作;
2.根據讀取的對象數據更新文件獲取對象所屬的賬戶(account)和容器(container),並獲取指定容器的分區號和所有副本節點;
3.針對每個容器的副本節點,調用方法object_update實現通過POST或DELETE方法執行container中的object的更新;
  注:在方法object_update的調用傳遞參數中,指定了對象更新操作具體調用的方法(如POST或DELETE),也是由前面讀取的數據更新文件中獲取的;


轉到3,來看方法object_update的具體實現:

def object_update(self, node, part, op, obj, headers):
     """
     執行container中的object的更新;
     """
    headers_out = headers.copy()
    headers_out['user-agent'] = 'obj-updater %s' % os.getpid()
    try:
        # 通過POST或DELETE方法實現對象的更新操作;
        with ConnectionTimeout(self.conn_timeout):
            conn = http_connect(node['ip'], node['port'], node['device'], part, op, obj, headers_out)
        with Timeout(self.node_timeout):
            resp = conn.getresponse()
            resp.read()
            return resp.status
    except (Exception, Timeout):
        self.logger.exception(_('ERROR with remote server %(ip)s:%(port)s/%(device)s'), node)
    return HTTP_INTERNAL_SERVER_ERROR

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章