Gnocchi 8、sacks基础

Sacks介绍
1 Sacks的生成
为了便于并行处理数据,incoming数据被放置在不同的sacks中。
storage扮演了从这些方法中处理新的数据:
gnocchi.incoming.IncomingDriver#_store_new_measures(metric_id, data) or gnocchi.incoming.IncomingDriver#add_measures_batch(metrics_and_measures)。
In normal circumstances the latter one calls under-the-hood the former one except for Redis and Ceph storage that implement it independently.
尽管如此,它们都时用了sacks。例如,如果后端是一个本地文件系统,sacks会被认为是工作目录下的路径。Gnocchi将它们放知道一个带有监控项measures的目录下,正如下面的测试代码:

def should_build_correct_sack_name(self):
  file_storage = incoming.file.FileStorage(Conf())

  sack_path = file_storage._sack_path(7)

  # as you can see, the sack path is a concatenation of:
  # "incoming" + number of sacks (defined in the gnocchi-config file under "sacks" key) + sack number
  self.assertEqual('/incoming30-7', sack_path)

  def should_store_new_measures_in_appropriate_metric_sack_in_different_files(self):
  sacks_path = '/tmp/incoming30-4/00000000-0000-0000-0000-000000000004'
  try:
      os.makedirs(sacks_path)
  except OSError as exc:
      if exc.errno == errno.EEXIST and os.path.isdir(sacks_path):
          shutil.rmtree(sacks_path)

  file_storage = incoming.file.FileStorage(Conf())
  file_storage.basepath_tmp = '/tmp/'

  for i in range(0, 5):
      file_storage.add_measures(uuid.UUID(int=4), [])
      time.sleep(5)

  # As you can see, at least for local filesystem storage, new measures are
  # added as separate files to the sacks directory
  # Globally the directory looks like here:
  # bartosz:/tmp$ tree incoming30-4/00000000-0000-0000-0000-000000000004/
  #    incoming30-4/00000000-0000-0000-0000-000000000004/
  #    ├── 449d66be-d6c5-485d-97f1-4cc2311d918f_20180425_13:19:02
  #    ├── 8304a8e1-c1bd-44f2-9f82-66872c52b61e_20180425_13:19:07
  #    ├── 86ac1386-2998-4c7e-8185-965a972d7fd3_20180425_13:18:47
  #    ├── aa74f624-dbc5-438f-a9bd-f32af4574c55_20180425_13:18:52
  #    └── d1bb81b4-7c23-4270-ba3e-aecad3724306_20180425_13:18:57
  # 0 directories, 5 files

  measures = [measure_file for measure_file in listdir(sacks_path)
              if isfile(join(sacks_path, measure_file))]
  self.assertEqual(5, len(measures))


正如你在测试用例中所看到的,新的measures被直接写入到新的事件戳文件中。sack 编号对应着指定的metric,这些metricd是在
gnocchi.incoming.IncomingDriver#sack_for_metric(metric_id)

中计算出来的,并且它在下层的storage中是独立存在的。

def sack_for_metric(self, metric_id):
    return metric_id.int % self.NUM_SACKS


其他的storage引擎性为类似。因为它并不容易通过测试用例来演示,
让我们通过分析Redis的源码来展示这种说法。

def add_measures_batch(self, metrics_and_measures):
  notified_sacks = set()
  pipe = self._client.pipeline(transaction=False)
  for metric_id, measures in six.iteritems(metrics_and_measures):
      sack_name = self.get_sack_name(self.sack_for_metric(metric_id))
      path = self._build_measure_path_with_sack(metric_id, sack_name)
      pipe.rpush(path, self._encode_measures(measures))
      if self.greedy and sack_name not in notified_sacks:
          # value has no meaning, we just use this for notification
          pipe.setnx(sack_name, 1)
          notified_sacks.add(sack_name)
  pipe.execute()


正如你所看到的,sack的概念和metric路径都展示在这儿了。
不同之处在于路径在Redis中被当作一个key存储,新的measures被追加到这个key对应的列表中。

2 Sacks在聚合计算中的使用
sacks被聚合服务metricd所处理。它们中的每一个都会运行在不同的结点上并且都属于集群中相同的组别。多亏有了membership,它们可以分担不同sacks的职责(这个细节在这篇文章中:
https://www.waitingforcode.com/time-series/horizontal-scalability-gnocchi/read
)。每个聚合daemon进程知道属于该daemon进程的sacks的个数。
在处理过程中所做的第一件事情就是发现需要处理的监控项metrics。
为了这样做,daemon查询了位置,该位置主除了在之前的section
中所展示的sack路径。让我们继续以本地文件系统为例,通过下面两个方法说明是如何进行发现的。

def list_metric_with_measures_to_process(self, sack):
  return set(self._list_target(self._sack_path(sack)))
 
@staticmethod
def _list_target(target):
  try:
    return os.listdir(target)
  except OSError as e:
    # Some other process treated this one, then do nothing
    if e.errno == errno.ENOENT:
      return []
    raise
 
# And this method returns new measures 
@contextlib.contextmanager
def process_measure_for_metric(self, metric_id):
  files = self._list_measures_container_for_metric(metric_id)
  measures = self._make_measures_array() # creates numpy.array([], dtype=TIMESERIES_ARRAY_DTYPE)
  for f in files:
    abspath = self._build_measure_path(metric_id, f)
    with open(abspath, "rb") as e:
        measures = numpy.concatenate((
            measures, self._unserialize_measures(f, e.read())))
 
  yield measures
 
  self._delete_measures_files_for_metric(metric_id, files)


代码采用了一个经典路径,该经典路径是待处理的measures所存在的地方:具体如下,
从metric目录读取,帆序列化,将measures数组和处理它们之后metricd返回的。
在处理结束后,所有文件都被删除了。它是一个很好的经典模式,但是一个前在的问题应该是measures的顺序。没有人能确保measures文件,由一个宿即的uuid和生成事件所组成的名称是有序的。
然而,在这一阶段它并不是一个问题,因为它取决于计算聚合的方法去排序并返回measures:

# gnocchi.storage.StorageDriver#_compute_and_store_timeseries
def _compute_and_store_timeseries(self, metric, measures):
  # NOTE(mnaser): The metric could have been handled by
  #               another worker, ignore if no measures.
  if len(measures) == 0:
      LOG.debug("Skipping %s (already processed)", metric)
      return
 
  measures = numpy.sort(measures, order='timestamps')
 
  agg_methods = list(metric.archive_policy.aggregation_methods)
  block_size = metric.archive_policy.max_block_size
  back_window = metric.archive_policy.back_window
  # ...


因此,对于每一个处理任务,gnocchi聚合的daemon进程检索出所有给定metric最近注册的measures,将它们连接到一个单独的数组中并返回给负责生成最终聚合结果的方法。

3 配置
Gnocchi的文档给了一些关于sacks的输入。首先它提供了一个方法来计算sacks number的初始值:
Sacks = number of active metrics / 300
在上述的公式中,我们可以替换300,视情况而定,500(如果我们不期待添加新的metrics) 或者100(如果我们仅仅开始,active 监控项的数量是期待会增长的)。这个公式也带来另一个问题-如何改变已经启动系统中的metrics个数?不幸的是, it doesn't come without the downtime。这个操作包括调用 
gnocchi-change-sack-size $number_of_sacks
命令,该命令将会在没有measures需要处理的时候执行。因此,我们需要在做出改变之前确保先停止input 服务并等待metricd完成它的工作。在此之后,我们可以重启所有服务来修改sacks的大小。
To reduce the downtime an alternative version exists. 它包括应用
gnocchi-change-sack-size到一个新的storage中。在这个action之后,我们必须使得所有input服务发送measures到新的storage。最后,当所有metricd daemons完成从老的storage处理数据后。switch also them to consume measures from new location.
Gnocchi使用了sacks的概念来表示并行但源。正如测试用例中所展示的,这个概念在所有存储引擎中是类似的,它为每隔sacks构建了一个唯一的路径,并将所有新的待处理的measures放到相关联的metrics中。所有sacks的大小强烈依赖于active metrics。通过上述我们避免了无规律的分发。

以上翻译自:
https://www.waitingforcode.com/time-series/sacks-data-parallelization-unit-gnocchi/read

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值