感謝朋友支持本博客,歡迎共同探討交流,由於能力和時間有限,錯誤之處在所難免,歡迎指正!
如果轉載,請保留作者信息。
博客地址:http://blog.csdn.net/gaoxingnengjisuan
郵箱地址:[email protected]
PS:最近沒有登錄博客,很多朋友的留言沒有看見,這裏道歉!還有就是本人較少上QQ,可以郵件交流。
接續上一篇博客:
轉到2,來看方法update_deleted的實現:
if job['delete']:
self.run_pool.spawn(self.update_deleted, job)
def update_deleted(self, job):
"""
同步本分區下數據到遠程副本分區,並刪除本分區下對象數據;
1 獲取指定分區目錄下各個對象的suff----suffixes;
2 遍歷指定分區所有副本(除了本分區)節點,在每個副本節點上:
2.1 調用方法sync,實現通過rsync命令行實現同步本地分區下suffixes確定的若干對象數據到遠程節點相應的分區下;
注意:這裏並沒由冗餘複製數據的操作,因爲命令rsync可以自動跳過完全相同的文件只更新不同的文件,大大的減低了網絡傳輸負載;
2.2 通過REPLICATE方法獲取遠程節點相應的分區下對象相應的哈希值;
3 當本地分區到每個副本節點分區下的數據同步全部完成之後,則刪除本分區下的數據;
注:
這裏沒有進行本地分區數據和遠程副本數據的比較驗證工作,說明這個方法需要直接同步分區下所有數據到遠程副本節點;
應該適用於如下情形:
假設本分區所在設備節點號爲1,所有副本設備節點號爲1/2/3/4,當執行rebalance操作後,所有設備節點號爲2/3/4/5,在rebalance操作過程中,
1號設備上本分區則被標誌爲delete;此時,需要先同步1號設備本分區數據到2/3/4/5號設備上,然後刪除1號設備本分區下的數據;
在這裏雖然也執行了複製1號設備數據到2/3/4號設備,但是這裏並沒由進行冗餘複製操作,因爲命令rsync可以自動跳過完全相同的文件只更新不同的文件,大大的減低了網絡傳輸負載;
"""
def tpool_get_suffixes(path):
"""
獲取指定分區目錄下各個對象的suff;
path = job['path'] = /srv/node/local_dev['device']/objects/partition
"""
return [suff for suff in os.listdir(path)
if len(suff) == 3 and isdir(join(path, suff))]
self.replication_count += 1
self.logger.increment('partition.delete.count.%s' % (job['device'],))
begin = time.time()
try:
responses = []
# 獲取指定分區目錄下各個對象的suff;
# job['path'] = /srv/node/local_dev['device']/objects/partition
suffixes = tpool.execute(tpool_get_suffixes, job['path'])
if suffixes:
# job['nodes']:同一個分區相關副本除了本節點的其他的節點(所以可能有多個);
for node in job['nodes']:
# 通過rsync命令行實現同步本地分區下若干數據到遠程節點相應的分區下;
# 因爲在命令行的構成過程中,本地數據的地址在前作爲源數據地址,遠程數據地址在後作爲目標數據地址;
# 可以通過一條命令實現suffixes所指定的數據的同步,源數據地址有多個,目標數據地址有一個;
# 注:
# 這裏沒有進行本地和遠程數據的比較和驗證操作,而是直接把本地數據拷貝到遠程地址;
# 這裏並沒由冗餘複製數據的操作,因爲命令rsync可以自動跳過完全相同的文件只更新不同的文件,大大的減低了網絡傳輸負載;
success = self.sync(node, job, suffixes)
if success:
with Timeout(self.http_timeout):
# REPLICARE方法,對應sever裏面的RELICATE方法;
# REPLICATE方法就是獲取指定分區下的哈希值文件(可能有多個,因爲分區下可能映射了多個對象),用於判斷對象數據是否發生改變;
# 並獲取方法執行的響應信息,即遠程節點上副本的哈希值;
conn = http_connect(
node['replication_ip'],
node['replication_port'],
node['device'], job['partition'], 'REPLICATE',
'/' + '-'.join(suffixes), headers=self.headers)
conn.getresponse().read()
responses.append(success)
if self.handoff_delete:
# delete handoff if we have had handoff_delete successes
delete_handoff = len([resp for resp in responses if resp]) >= self.handoff_delete
else:
# delete handoff if all syncs were successful
delete_handoff = len(responses) == len(job['nodes']) and all(responses)
# suffixes爲空或請求的三個已經都響應成功後刪除本地partion下的文件;
if not suffixes or delete_handoff:
self.logger.info(_("Removing partition: %s"), job['path'])
tpool.execute(shutil.rmtree, job['path'], ignore_errors=True)
except (Exception, Timeout):
self.logger.exception(_("Error syncing handoff partition"))
finally:
self.partition_times.append(time.time() - begin)
self.logger.timing_since('partition.delete.timing', begin)
2.1.獲取指定分區目錄下各個對象的suff----suffixes;2.2.遍歷指定分區所有副本(除了本節點)節點,在每個副本節點上:
2.2.1.調用方法sync,實現通過rsync命令行實現同步本地分區下suffixes確定的若干對象數據到遠程節點相應的分區下;
注意:這裏並沒由冗餘複製數據的操作,因爲命令rsync可以自動跳過完全相同的文件只更新不同的文件,大大的減低了網絡傳輸負載;
2.2.2.通過REPLICATE方法獲取遠程節點相應的分區下對象相應的哈希值;
2.3.當本地分區到每個副本節點分區下的數據同步全部完成之後,則刪除本分區下的數據;
轉到2.2,來看方法sync的實現:
def sync(self, node, job, suffixes): # Just exists for doc anchor point
"""
通過rsync命令行實現同步本地分區下若干數據到遠程節點相應的分區下;
因爲在命令行的構成過程中,本地數據的地址在前作爲源數據地址,遠程數據地址在後作爲目標數據地址;
可以通過一條命令實現suffixes所指定的數據的同步,源數據地址有多個,目標數據地址有一個;
"""
# def rsync;
# 通過rsync命令行實現同步本地分區下若干數據到遠程節點相應的分區下;
# 因爲在命令行的構成過程中,本地數據的地址在前作爲源數據地址,遠程數據地址在後作爲目標數據地址;
# 可以通過一條命令實現suffixes所指定的數據的同步,源數據地址有多個,目標數據地址有一個;
return self.sync_method(node, job, suffixes)
注:這裏調用的方法是rsync;
def rsync(self, node, job, suffixes):
"""
通過rsync命令行實現同步本地分區下若干數據到遠程節點相應的分區下;
因爲在命令行的構成過程中,本地數據的地址在前作爲源數據地址,遠程數據地址在後作爲目標數據地址;
可以通過一條命令實現suffixes所指定的數據的同步,源數據地址有多個,目標數據地址有一個;
"""
if not os.path.exists(job['path']):
return False
# Rsync(remote synchronize)是一個遠程數據同步工具,
# 可通過LAN/WAN快速同步多臺主機間的文件。
# Rsync使用所謂的“Rsync算法”來使本地和遠程兩個主機之間的文件達到同步,
# 這個算法只傳送兩個文件的不同部分,而不是每次都整份傳送,因此速度相當快;
args = [
'rsync',
'--recursive',
'--whole-file',
'--human-readable',
'--xattrs',
'--itemize-changes',
'--ignore-existing',
'--timeout=%s' % self.rsync_io_timeout,
'--contimeout=%s' % self.rsync_io_timeout,
'--bwlimit=%s' % self.rsync_bwlimit,
]
# 獲取遠程節點的IP;
node_ip = rsync_ip(node['replication_ip'])
# rsync_module = node_ip::object
if self.vm_test_mode:
rsync_module = '%s::object%s' % (node_ip, node['replication_port'])
else:
rsync_module = '%s::object' % node_ip
had_any = False
# 遍歷suffixes,分別生成suffix的具體路徑,並加載到命令行變量args中;
# 如果不存在suffixes,則說明前面獲取損壞的對象數據的操作是錯誤的,則直接返回;
# 這裏也可以看到,命令rsync可以實現同時同步多個數據對象;
for suffix in suffixes:
# job['path'] = /srv/node/local_dev['device']/objects/partition
# spath = /srv/node/local_dev['device']/objects/partition/suffix
spath = join(job['path'], suffix)
if os.path.exists(spath):
args.append(spath)
had_any = True
if not had_any:
return False
# 添加遠程數據路徑到命令行變量args中;
# rsync_module = node_ip::object;
args.append(join(rsync_module, node['device'], 'objects', job['partition']))
# 實現同步本地分區下若干數據到遠程節點相應的分區下;
return self._rsync(args) == 0
2.2.1.遍歷suffixes,分別生成suffix的具體路徑(/srv/node/local_dev['device']/objects/partition/suffix),並加載到命令行變量args中;2.2.2.添加遠程數據路徑到命令行變量args中;
2.2.3.調用方法_rsync實現同步本地分區下若干數據到遠程節點相應的分區下;
轉到2.2.3,來看方法_rsync的實現:
def _rsync(self, args):
"""
實現同步本地分區下若干數據到遠程節點相應的分區下
"""
start_time = time.time()
ret_val = None
try:
with Timeout(self.rsync_timeout):
# 此處即爲同步操作了,推送模式;
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
results = proc.stdout.read()
ret_val = proc.wait()
except Timeout:
self.logger.error(_("Killing long-running rsync: %s"), str(args))
proc.kill()
return 1 # failure response code
total_time = time.time() - start_time
for result in results.split('\n'):
if result == '':
continue
if result.startswith('cd+'):
continue
if not ret_val:
self.logger.info(result)
else:
self.logger.error(result)
if ret_val:
error_line = _('Bad rsync return code: %(ret)d <- %(args)s') % {'args': str(args), 'ret': ret_val}
if self.rsync_error_log_line_length:
error_line = error_line[:self.rsync_error_log_line_length]
self.logger.error(error_line)
elif results:
self.logger.info(_("Successful rsync of %(src)s at %(dst)s (%(time).03f)"), {'src': args[-2], 'dst': args[-1], 'time': total_time})
else:
self.logger.debug(_("Successful rsync of %(src)s at %(dst)s (%(time).03f)"), {'src': args[-2], 'dst': args[-1], 'time': total_time})
return ret_val
轉到3,來看方法update的實現:
def update(self, job):
"""
實現複製一個分區的高級方法;
對於遠程副本節點,循環執行,針對每一個節點實現以下操作:
1 通過http連接遠程節點,通過REPLICATE方法實現獲取job['partition']下所有對象的哈希值;
2 找出本地分區下哈希值中後綴和遠程分區下哈希值中後綴不同的,說明分區下的某些對象文件數據發生了變化;
3 針對發生變化的數據,調用sync方法,通過rsync命令行實現同步本地分區下若干數據到遠程節點相應的分區下;
"""
self.replication_count += 1
self.logger.increment('partition.update.count.%s' % (job['device'],))
begin = time.time()
try:
# 方法get_hashes從具體的分區(具體的object)的哈希值文件hashes.pkl獲取hashes值並更新,獲取本地的hashes;
# job[path]爲job_path = join(obj_path, partition) = /srv/node/local_dev['device']/objects/partition;
# local_hash爲hashes.pkl中的反序列化回來的內容;
# hashed爲改變的數目;
hashed, local_hash = tpool_reraise(
get_hashes,
job['path'], # job['path'] = /srv/node/local_dev['device']/objects/partition
do_listdir=(self.replication_count % 10) == 0,
reclaim_age=self.reclaim_age)
self.suffix_hash += hashed
self.logger.update_stats('suffix.hashes', hashed)
# 其他副本對應的節點數目;
# 此時attempts_left 爲2 若果replica爲3;
attempts_left = len(job['nodes'])
# 此時的nodes爲除去本節點外的所有節點;
# 因爲job['nodes]不包含本地節點,
# get_more_nodes(int(job['partition']))能獲得除去本partion所對應節點外的其他所有節點;
nodes = itertools.chain(
job['nodes'],
# get_more_nodes:這個方法實現了獲取其他副本的節點;
# 這個方法說明了三副本帶來的高可用性;
# 如果replicator進程檢測到對遠程node執行同步操作失敗;
# 那麼它就會通過ring類提供的get_more_nodes接口來獲得其他副本存放的node進行同步;
self.object_ring.get_more_nodes(int(job['partition'])))
# 其他副本對應的節點數目;
# 此時attempts_left 爲2 若果replica爲3;
# 對於遠程副本節點,循環執行,針對每一個節點實現以下操作:
# 通過http連接遠程節點,通過REPLICATE方法實現獲取job['partition']下所有對象的哈希值;
# 找出本地分區下哈希值中後綴和遠程分區下哈希值中後綴不同的,說明分區下的某些對象文件數據發生了變化;
# 針對發生變化的數據,調用sync方法,通過rsync命令行實現同步本地分區下若干數據到遠程節點相應的分區下;
while attempts_left > 0:
# If this throws StopIterator it will be caught way below
node = next(nodes)
attempts_left -= 1
try:
with Timeout(self.http_timeout):
# REPLICARE方法,對應sever裏面的RELICATE方法;
# REPLICATE方法就是獲取指定分區下的哈希值文件(可能有多個,因爲分區下可能映射了多個對象),用於判斷對象數據是否發生改變;
#並獲取方法執行的響應信息,即遠程節點上副本的哈希值;
resp = http_connect(
node['replication_ip'], node['replication_port'],
node['device'], job['partition'], 'REPLICATE',
'', headers=self.headers).getresponse()
if resp.status == HTTP_INSUFFICIENT_STORAGE:
self.logger.error(_('%(ip)s/%(device)s responded as unmounted'), node)
attempts_left += 1
continue
if resp.status != HTTP_OK:
self.logger.error(_("Invalid response %(resp)s from %(ip)s"), {'resp': resp.status, 'ip': node['replication_ip']})
continue
# 獲取遠程節點上分區的哈希值;
remote_hash = pickle.loads(resp.read())
del resp
# 找出本地分區下哈希值中後綴和遠程分區下哈希值中後綴不同的;
# 如果分區下某些對象數據發生改變,其對應的哈希值文件也會發生改變;
# 如果有不同,說明分區下的某些對象文件數據發生了變化;
# 示例:
# 假如 local_hash 爲 123 321 122 remote_hash 123 321 124 則 122爲變化的
# 文件路徑hash值後三位會不會重複
suffixes = [suffix for suffix in local_hash if
local_hash[suffix] !=
remote_hash.get(suffix, -1)]
# 如果沒有不同,說明對象數據都沒有變化,則繼續請求下一個節點;
if not suffixes:
continue
# 針對那些和遠程節點分區上不同的哈希值,這裏進行重新計算;
# 然後再一次和遠程節點分區上的哈希值進行比較;
# 這樣做的目的是確保篩選的完全準確性;
hashed, recalc_hash = tpool_reraise(
get_hashes,
job['path'], recalculate=suffixes,
reclaim_age=self.reclaim_age)
self.logger.update_stats('suffix.hashes', hashed)
local_hash = recalc_hash
suffixes = [suffix for suffix in local_hash if
local_hash[suffix] !=
remote_hash.get(suffix, -1)]
# sync方法:
# 通過rsync命令行實現同步本地分區下若干數據到遠程節點相應的分區下;
# 因爲在命令行的構成過程中,本地數據的地址在前作爲源數據地址,遠程數據地址在後作爲目標數據地址;
# 可以通過一條命令實現suffixes所指定的數據的同步,源數據地址有多個,目標數據地址有一個;
self.sync(node, job, suffixes)
with Timeout(self.http_timeout):
conn = http_connect(node['replication_ip'], node['replication_port'], node['device'], job['partition'], 'REPLICATE',
'/' + '-'.join(suffixes), headers=self.headers)
conn.getresponse().read()
self.suffix_sync += len(suffixes)
self.logger.update_stats('suffix.syncs', len(suffixes))
except (Exception, Timeout):
self.logger.exception(_("Error syncing with node: %s") % node)
self.suffix_count += len(local_hash)
except (Exception, Timeout):
self.logger.exception(_("Error syncing partition"))
finally:
self.partition_times.append(time.time() - begin)
self.logger.timing_since('partition.update.timing', begin)
3.1.通過http連接遠程節點,通過REPLICATE方法實現獲取job['partition']下所有對象的哈希值;3.2.找出本地分區下哈希值中後綴和遠程分區下哈希值中後綴不同的,說明分區下的某些對象文件數據發生了變化;
3.3.針對發生變化的數據,調用sync方法,通過rsync命令行實現同步本地分區下若干數據到遠程節點相應的分區下;
注:方法sync的解析,前面已經完成,這裏不再進行贅述;