Coldstar

Coldstar 查看完整档案

北京编辑  |  填写毕业院校  |  填写所在公司/组织 github.com/go-baa 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

Coldstar 赞了文章 · 2020-03-03

Kubeflow 1.0:适合所有人的云原生ML

我们荣幸地代表整个社区宣布我们的第一个主要版本Kubeflow 1.0。 Kubeflow于2017年12月在美国Kubecon开源,并且在过去两年中,Kubeflow项目的增长超出了我们的期望。现在有来自30多个参与组织的数百名贡献者。在1.0版本中,我们将逐步提供一组稳定的应用程序,这些应用程序是在Kubernetes上有效开发,构建,训练和部署模型所需的。

Kubeflow的目标是使机器学习(ML)工程师和数据科学家能够轻松地利用云资产(公有云或本地机房)来处理ML工作负载。您可以在任何符合Kubernetes的集群上使用Kubeflow。

在1.0版本中,我们将逐步提供一组稳定的应用程序,这些应用程序是在Kubernetes上有效开发,构建,训练和部署模型所需的。

毕业应用包括:

使用Kubeflow开发,构建,训练和部署

m-01.png

使用Kubeflow 1.0,用户可以使用Jupyter开发模型。然后,他们可以使用fairing(Kubeflow的python SDK)之类的Kubeflow工具来构建容器,并创建Kubernetes资源来训练他们的模型。一旦有了模型,他们就可以使用KFServing创建和部署用于推理的服务器。

Kubernetes上的ML入门

Kubernetes是一个出色的平台,可用于基础架构(无论是在公共云还是本地),但是部署针对ML优化并与您的云集成的Kubernetes并非易事。在1.0中,我们提供了CLI和配置文件,因此您可以使用一个命令来部署Kubeflow:

kfctl apply -f kfctl_gcp_iap.v1.0.0.yaml
kfctl apply -f kfctl_k8s_istio.v1.0.0.yaml
kfctl apply -f kfctl_aws_cognito.v1.0.0.yaml
kfctl apply -f kfctl_ibm.v1.0.0.yaml

Kubernetes上的Jupyter

在Kubeflow的用户调查中,数据科学家一直在表达Jupyter notebooks的重要性。此外,他们需要能够将孤立的Jupyter notebooks与Kubernetes on Cloud的效率集成在一起,以使用GPU训练更大的模型并并行运行多个实验。 Kubeflow使利用Kubernetes进行资源管理变得容易,并将数据中心的全部功能置于数据科学家的掌握之中。

借助Kubeflow,可以为每个数据科学家或团队提供各自的命名空间,以在其中运行其工作负载。命名空间提供安全性和资源隔离。使用Kubernetes资源配额,平台管理员可以轻松限制个人或团队可以消耗多少资源,以确保公平的调度。

部署Kubeflow之后,用户可以利用Kubeflow的中央仪表板来启动notebooks:

m-02.png

在Kubeflow UI中,用户可以通过选择Jupyter的预构建docker镜像之一或输入自定义镜像的URL来轻松启动新notebook。接下来,用户可以设置要连接到notebook的CPU和GPU数量。notebook还可以包含配置和密码参数,以简化对外部存储库和数据库的访问。

m-03.png

通过分布式训练更快地训练模型

分布式训练是Google的规范,也是TensorFlow和PyTorch等深度学习框架最令人兴奋和要求最高的功能之一。

当我们启动Kubeflow时,我们的主要动机之一就是利用Kubernetes简化分布式训练。 Kubeflow提供Kubernetes自定义资源,这些资源使使用TensorFlow和PyTorch进行分布式训练变得简单。用户需要做的就是定义一个TFJob或PyTorch资源,如下图所示。定制控制器负责扩展和管理所有单个进程,并将它们配置为彼此对话:

apiVersion: kubeflow.org/v1
kind: TFJob
metadata:
  name: mnist-train
spec:
  tfReplicaSpecs:
    Chief:
      replicas: 1
        spec:
          containers:
            image: gcr.io/alice-dev/fairing-job/mnist
            name: tensorflow
    Ps:
      replicas: 1
      template:
        spec:
          containers:
            image: gcr.io/alice-dev/fairing-job/mnist
            name: tensorflow
    Worker:
      replicas: 10      
        spec:
          containers:
            image: gcr.io/alice-dev/fairing-job/mnist
            name: tensorflow

使用TensorBoard监控模型训练

为了训练高质量的模型,数据科学家需要使用Tensorboard之类的工具调试和监控训练过程。使用Kubernetes和Kubeflow,用户可以通过创建如下所示的YAML文件轻松地在他们的Kubernetes集群上部署TensorBoard。在Kubeflow上部署TensorBoard时,用户可以利用Kubeflow的AuthN和AuthZ集成来安全地访问Kubeflow进入公共云后的TensorBoard:

// On GCP: 
https://${KFNAME}.endpoints.${PROJECT}.cloud.goog/mnist/kubeflow-mnist/tensorboard/

// On AWS:
http://8fb34ebe-istiosystem-istio-2af2-925939634.us-west-2.elb.amazonaws.com/mnist/anonymous/tensorboard/

无需使用Kubectl port-forward转发到各个Pod。

部署模型

KFServing是基于Knative的自定义资源,用于部署和管理ML模型。 KFServing提供了以下功能:

  1. Deploy your model using out-of-the-box model servers (no need to write your own flask app)
  2. Auto-scaling based on load, even for models served on GPUs
  3. Safe, controlled model rollout
  4. Explainability (alpha)
  5. Payload logging (alpha)

以下是一个KFServing规范的示例,该示例显示了如何部署模型。用户要做的就是使用storageUri提供其模型文件的URI:

apiVersion: "serving.kubeflow.org/v1alpha2"
kind: "InferenceService"
metadata:
  name: "sklearn-iris"
spec:
  default:
    predictor:
      sklearn:
        storageUri: "gs://kfserving-samples/models/sklearn/iris"

查看示例以了解如何使用上述功能。

解决方案不仅仅是模型

为了使ML正常工作,您通常需要将该模型整合到应用程序中-无论是Web应用程序,移动应用程序还是某些后端报告管道的一部分。

诸如flask和bootstrap之类的框架使数据科学家可以轻松地创建丰富的,具有视觉吸引力的Web应用程序,从而使他们的模型能够正常工作。下面是我们为Kubeflow的mnist示例构建的UI的屏幕截图。

借助Kubeflow,数据科学家无需学习新的概念或平台即可部署其应用程序,也无需处理入口,网络证书等。他们可以像TensorBoard一样部署其应用程序。唯一更改的是Docker镜像和标志。

m-04.png

如果这听起来像您要找的东西,我们建议:

1.访问我们的文档,以了解如何在公共或私有云上部署Kubeflow。

2.逐步完成mnist教程,亲自尝试我们的核心应用程序。

Kubeflow其他的功能

除了我们在本博文中介绍的内容外,Kubeflow还有很多其他功能。除了此处列出的应用程序,我们还在开发许多应用程序:

  • Pipelines(beta) for defining complex ML workflows
  • Metadata(beta) for tracking datasets, jobs, and models,
  • Katib(beta) for hyper-parameter tuning
  • Distributed operators for other frameworks like xgboost

在将来的版本中,我们会将这些应用程序升级到1.0。

查看原文

赞 4 收藏 1 评论 0

Coldstar 赞了文章 · 2020-02-11

Octarine为Kubernetes发布开源安全扫描工具

d-01.jpg

有助于自动化Kubernetes工作负载安全性的初创公司Octarine今天发布了开源扫描工具。该工具称为Kube-scan,旨在帮助开发人员了解其Kubernetes集群中的安全风险级别。

该公司还开放了另一个名为The Kubernetes Common Configuration Scoring System的工具,简称为KCCSS,该工具是Kube-scan中使用的基础配置框架。

正如Ocatrine产品负责人Julien Sobrier指出的那样,Kubernetes中有30种安全设置,而Kube-scan可以帮助您查看其中任何一种的脆弱性,范围为0-10,其中10是最脆弱的。

Kubernetes为开发人员提供了很大的灵活性和强大的功能。 Sobrier告诉TechCrunch,目前有30多种安全设置,并了解它们之间的交互方式,哪些设置会使安全性变得更差,哪些变得更好,并且每个选择的影响都不容易衡量或解释。

Octarine希望为这两个开源工具提供帮助。它从构建KCCSS开始,这是一个基于行业标准通用漏洞评分系统(CVSS)的漏洞模型,旨在为Kube-scan提供风险评估框架。

“我们采用了这种CVSS模型,并将其应用于Kubernetes。这有助于向用户说明哪些安全设置引起了风险?从群集的可用性,群集的完整性和群集的机密性来看,对工作负载造成的危险是什么,” Sobrier解释说。这为开发人员和运营人员提供了一个通用的系统,以了解群集的安全状况,并使他们更容易决定风险是否可以接受。

risk-expanded.png

然后,他们采用了KCCSS框架并构建了Kube-scan。这将采用KCCSS中定义的设置并应用分数,该分数可衡量您在其上运行的Kubernetes集群中每个设置的风险等级。 “ Kube-scan基本上是KCCSS框架的实现。因此,它将是一种软件,一个容器,它将在您的群集上运行,并向您显示所有[设置]的风险,范围从零(无风险)到10(高度风险),然后为您提供有关等级的所有详细信息是的,以及您可以采取的补救措施,”他说。

虽然它显然可以与Octarine自己的安全工具配合使用,但商业化副总裁Rafael Feitelberg表示,该项目更多地是在帮助公司了解其Kubernetes集群风险水平,并为他们提供解决所发现问题的信息。他说:“通过调整Kubernetes的配置,可以对很多事情进行补救,您可以明确看到如何解决Kube-scan中的(问题)。”

Feitelberg说Octarine是独立的东西,旨在帮助您自动执行安全设置。他说:“我们的商业产品更多地是关于流程的自动化,并且要连续不断地进行,因此它是您的CI/CD(管道)和DevOps流程的一部分。”

两种开源工具今天都可以在GitHub上使用。

查看原文

赞 2 收藏 0 评论 0

Coldstar 关注了用户 · 2018-09-06

bali @bali_5a0a92fba16aa

在此大家可以加我的WX:psk12221。
添加微信进微信群免费领取资料

群里专注分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。

大家也可以关注我的公众号:《Java烂猪皮》 每天准时会分享Java,架构,微服务,面试等经典案例文章。

关注 6830

Coldstar 收藏了文章 · 2018-07-26

推荐系统技术之文本相似性计算(一)

推荐系统技术 --- 文本相似性计算(模型化 上)

1. 前言

推荐系统分为两种,一种是基于用户的,根据某个用户的特性推荐一些东西,还有一种是根据内容,推荐一些相似的内容,或者是两种的结合,任何推荐系统,仔细分析下来,都属于这两种情况的组合。

今天我们说一下基于内容推荐中的一个分支,也是使用得比较多的内容推荐方式,那就是基于文本相似性的推荐,我们说文本相似性的计算,文本相似性应用范围是比较广的:

  • 普通的阅读文章,底下的相关文章推荐可以用

  • 论文查重也可以用

  • 过滤相似度很高的新闻,或者网页去重

本系列我们会写三篇。

  • 前两篇是模型化,分为上和下,这两篇是重点,开篇会用说人话的方式说说计算文本相似性的一些思想和套路,对文本相似性有个感性的了解,然后会把人话变成数学化的东西,只有数学化了才能计算,才有计算机能做的文本相似性,当然不会出现大量数学公式,只会有一些数学概念。

  • 第三篇是工程化,这篇是非重点,会介绍一下相关的工程的工具包或者代码片段,相当于局部实战吧,没有理解第一篇说的直接看这篇没什么含义。

2. 直观理解

假如我们有以下这么些篇文档

  • 用Golang写一个搜索引擎

  • 搜索引擎的实现

  • 推荐系统的技术要点

  • 常用的推荐算法总结

  • 广告系统是一个搜索引擎和推荐引擎的组合

  • 计算广告中都有哪些数学原理

有个一个小朋友,他叫小明,但是他还在上学前班,你让他来说这些文章中哪几篇比较相似,他现在字都认不全,更别说理解这些文章中的概念了,如果他特别聪明,那么他很可能说1和5比较相似,因为都有个长得一样的东西一个搜索引擎

又过了一些年,小明已经读初中了,你再让他来看哪几篇比较相似,他可能会告诉你1和2比较相似,然后和5也比较相似,因为他现在已经学会了主谓宾定状补的语法,知道第一条的主要表述的是搜索引擎,第二篇主要表述的也是搜索引擎,第五篇虽然主要不是说的搜索引擎,但宾语的定语也是搜索引擎,也是相关的。所以会给出1和2相似,5和他们也比较相似。

再过一些年,小明已经大学毕业了,学的就是计算机专业,这时候你再来看,他会给出6个都比较相似,因为他已经知道了搜索,推荐,广告三个领域的基础技术都差不多,如果对一个感兴趣,那么对另外的主题也会比较感兴趣,只不过相似性有高有低而已。

上面的三个阶段,实际上也是文本相似性计算发展的三个阶段,从最开始的字面的匹配相似,到第二阶段的词汇的匹配相似,再到第三阶段的语义的相似,我们一个一个来说说每个阶段使用的数学方法和原理,每个阶段都会有数学原理,但我们对数学公式不做深入讨论,感兴趣的可以自己查阅具体的数学原理。

下面,我们再用计算机和数学的思想来看看计算机如何在上述三个阶段中进行文本相似性的计算的。

4. 前期准备

在开始三个阶段之前,我们先准备一些必要的知识。

4.1 分词

分词也叫切词,因为文档的最小单位是词,所以我们默认都是讨论分词过的情况,为了方便,我们把每个词都分配一个唯一id,我们叫这个词的token。后面出现token这个概念,就是表示切词后的唯一id

4.2 词袋模型

维基百科解释 :Bag-of-words model是个在自然语言处理和信息检索下被简化的表达模型。此模型下,像是句子或是文件这样的文字可以用一个袋子装着这些词的方式表现,这种表现方式不考虑文法以及词的顺序。

通俗的说就是把一个文档分词得到的一堆token放到一个袋子里,用这个袋子来表示这个文档,这是一种简化的文本描述方法。

5. 学前班阶段

学前班阶段也叫直接计算相似的阶段,我们其实不关心这篇文章到底讲什么,用计算机的理解就是,分词完成以后,我们找到一种方法,来计算各个token集合之间的相似性就行了。

5.1 JaccardSimilarity方法

分词以后,我们得到的一堆token,按照学前班的小明的思想,找到两两之间相似性即可,JaccardSimilarity方法可以满足这个条件,JaccardSimilarity说起来非常简单,容易实现,实际上就是两个集合的交集除以两个集合的并集,所得的就是两个集合的相似度,直观的看就是下面这个图。
图片描述

数学表达式是:
图片描述

很明显,我们可以很容易的把上面的那几个文档两两进行上述计算,然后得到每两个文档的相似性,再一排,就知道每个文档和其他每个文档的相似性了。

即便新来一个文档,按照上面的公式计算一下,就知道它和每个文档的相似性了,完全没有难度,当然,你会发现算出来真的就像个学前班的学生弄出来的,完全没有可用性。

6. 初中阶段

学前班阶段实在是太Low了,我们看看初中阶段都出现了一些什么新东西?

6.1 数学化

要将表达意思的文本变成可计算相似度的东西,首先,必须将文本数字化,并且数字化以后还能保留文本的一些基本信息,只有数字化以后才有可计算性,只有保留了基本信息,这个可计算性才有可信度。

线性代数给我们提供了一个数学工具叫向量,向量看上去特别简单,就是一串数字,别看它看上去非常简单,但却是非常强大的数学工具,有多强大呢?我们从侧面来说说,我们知道无论哪个编程语言,都有一个最基本的数据结构,是内嵌在语言中的,那就是数组,而数组就是向量,数组有多强大不用我说了吧?谁敢说他没用过?它都已经强大到我们感觉不到他的强大了,就像空气一样,重要到我们不觉得他重要了(北京除外,呵呵)。

如果我们能将一个文本变成一个向量,那么我们就将一篇复杂的文章变成了一个可以用数组描述的数学概念了。

啰嗦了这么多,如果有一个向量了会怎么样?再往上一步,线性代数还给了我们一个概念,就是空间,任何向量都可以表示为某一个空间上的一个点。

所以说,先有了文本,文本变成了向量,再有了空间,向量变成了空间的点,那么我们通过求两个点之间的距离,就求得了两个文档的相似性。

至此,数学化完成了,文本相似性的计算就变成了空间中两个点的距离的计算,就像下图一样。

6.2 向量化

6.2.1 最简单的向量化

我们先来看看如何进行向量化,前期准备部分我们已经说了,每个词都可以表示为一个唯一的token,那么最简单的向量化,我们拿这个token来向量化,比如下面两个文档,每个词用一个id表示(搜索引擎这个词重复出现了,所以id一样,都是5)

文档内容token集合
用/Golang/写/一个/搜索引擎/1,2,3,4,5
搜索引擎/的/实现/5,6,7

这两个向量不一样长,不好映射到同一个空间中,于是我们这么处理一下,编号1到7为所有的token,用数组的下标表示,如果这个编号上有词,那么设为1,否则设为0,这样一来,两个文档向量化以后就变成了

用Golang写一个搜索引擎  ===>  [1,1,1,1,1,0,0]
搜索引擎的实现          ===>  [0,0,0,0,1,1,1]

这样,两个文档就都向量化了,虽然这种向量化是最简单的,但不管怎样,我们至少把文本变成了数学符号了。

6.2.2 TF-IDF向量化

文本处理中,还有一种非常常见的向量化方法,就是TF-IDF方法,关于TF-IDF方法,可以参见我之前的一篇文章,已经说得比较清楚了,这里就不赘述了,可以点击链接打开看。

总之,通过TF-IDF的向量化方法,我们可以将每个词向量化成一个表示权重的小数,而不是上面的0,1向量了,它已经带有了文本的信息了,通过TF-IDF计算,两个文档向量化以后就变成了下面这样

用Golang写一个搜索引擎  ===>  [0.5, 0.8, 0.2, 0.15, 0.9, 0,   0]
搜索引擎的实现          ===>  [0,   0,   0,   0,    0.8, 0.4, 0.3]

这样向量化以后,每个词都带上了TF-IDF信息了,而TF-IDF的作用就是保留词在文档中的权重信息,这就相当于保留了文本的信息,于是我们通过token的概念和TF-IDF方法,就把一个文本向量化了,并且向量化完了以后还保留了文本本身的信息,每一个向量就是一个前面提到的词袋。

6.3 向量空间模型

向量化完了以后,需要提供一个空间来进行计算,我们把这个叫做向量空间(VSM),这没啥好说的,比如向量是一个二维向量,那么空间就是一个平面,如果是个三维向量,那么空间就是一个立体空间,上文中的向量是一个7维向量,那么空间就是一个七维空间了。

这样,每一篇文档向量化以后都是一个7维向量,都可以表述为这个向量空间中的一个点了。

6.4 向量相似度计算

有了向量空间和向量本身了,计算两个向量的相似度就简单了,一般有两种方法

6.4.1 欧式距离

不是说每个向量就是这个空间中的一个点么?那么相似性就是直接计算这两个点的欧式距离,欧式距离公式初中就学了哦

图片描述

把上面那两个向量用这个距离公式一带入,就求出两篇文档的相似度了。

6.4.2 余弦相似度距离

除了欧式距离,还有一种方法求相似度,就是求两个向量之间的夹角,这个叫余弦相似性,这也是初中数学的内容,不过初中我们学的是二维向量,如果是N维呢?是一样的,假设两个向量是A和B,那么公式是,n表示维度

图片描述

照样带入,就能求出两个文档相似度了。

7. 中学毕业

至此,文本相似性计算的最基本的概念和模型都介绍完了,中学已经毕业了,你可以按照上面的方法自己试着计算计算文档的相似性,应该不会太离谱,后面一篇会介绍一些更加高级的东西,但是整体的思想不会有太大的变化,还是向量化文档,然后计算向量间的相似度来表述为文本之间的相似度。

这篇我们看到的东西都还是浅层的文本相似性计算,但是其实一个TF-IDF向量化模型,一个余弦相似性夹角计算已经可以处理一大部分的文本相似性计算了,而且效果还凑合吧,但后面出来的各种语义模型才是文本推荐的未来。


欢迎关注我的公众号,主要聊聊搜索,推荐,广告技术,还有瞎扯。。文章会在这里首先发出来:)扫描或者搜索微信号XJJ267或者搜索西加加语言就行

图片描述

查看原文

Coldstar 收藏了文章 · 2018-07-26

推荐系统技术 --- 文本相似性计算(二)

第一篇地址:https://segmentfault.com/a/1190000005270047
上一篇中我们的小明已经中学毕业了,今天这一篇继续文本相似性的计算。
首先前一篇不能解决的问题是因为我们只是机械的计算了词的向量,并没有任何上下文的关系,所以思想还停留在机器层面,还没有到更高的层次上来,正因为这样才有了自然语言处理这门课程了。

今天我们稍微说说这个吧,后台留言很多朋友对这方面感兴趣,因为自然语言处理实在不是一篇文章就能说清的,而且我水平也非常有限,我本身是个工程人员,比较在行的是系统设计和架构,我14年10月左右的时候我老大要我补一下这方面的理论才开始看这方面的东西,后来由于换工作的原因也断了一截,不是时时刻刻都在学这个,还有很多其他工作要做,但也还算比较连续吧,而且有一些朋友同事可以请教,不是一个人瞎搞,这已经比很多人好了。但即使是这样只能说还没有入门,或者说刚刚看到门,还没找到钥匙。

下面我说说我尝试过的模型吧,其他更高深的东西我也没用过,要写也能喷一下,但我觉得那就没意义了。

1. 主题模型(Topic Model)

主题模型是目前也比较流行的文本分类的方法了,他主要解决的是文章的分类问题,就是这篇文章属于哪个类别。

如何来对文章进行分类呢?如果按照之前的算法,我们可以把每篇文章的关键词都提取出来,然后按照关键词进行分类,把文章分到每个类别中,但是,那样显得不太高端,我们来想想这么一个情况,就是你是如何写这篇文章的?

比如我目前写的这篇文章文本相似性的计算,一般的思路是这样的。

  • 首先,你想好题目以后会想一些提纲,比如我想我会写一下主题模型,然后写词向量,这两个就是我的主题了。

  • 然后,我开始写了,写主题模型的时候,我的一些词语都是和主题模型相关的,比如LDA分类主题概率啊等等,然后写词向量的时候也会有这么一些词,这些就是主题下的词语。

  • 最后,我就是按照上面的两条规则把文章写完了。

如何让以计算机的思维来按这个规则写作呢?

  • 首先,定两个主题,然后把每个词都分到某一个主题下

  • 开始写作的时候就是先找个主题,然后在主题下找个词,然后写出来

  • 循环到上一步,就是这样么一直写,然后一篇文章就写好了

这样每个词都属于两个主题中的一个。

这样当然写不出东西来,但是我们现在需要的分类,如果反过来想想,我已经有一篇文章了,如果我知道这篇文章的每个词都属于哪个分类,那么我其实就知道了这篇文章都属于哪些个主题了,那么有相似主题的文章,很可能是比较相似的。

于是,现在的关键问题就是找到主题主题下的词了,把这两个东西当成一个模型,新来一篇文章就在这个模型里面过一遍,然后就知道这篇文章的主题了。

主题主题下的词怎么找呢?最简单的就是靠人拍啊,人为的设定一些主题和这个主题下的词以及词出现的概率,那不就行了,人怎么拍呢?靠经验啊,我今年30多了,看了二十多年的书,少说有1000本吧,我知道哪些词应该在哪些分类下啊。OK,靠经验估计出来的,我靠,是不是有种似曾相识的赶脚啊?这不是机器学习最喜欢干的事情么?给一堆文章给他『看』,然后自己估算出一个主题模型出来。

好吧,其实主题模型就是这么干的。LDA模型的数学表达比较复杂,涉及到好几个分布函数,还有采样函数等,这篇文章必然讲不清楚,如果感兴趣的,我后面列了几篇文章,可以看看他的数学原理是什么。这个东西我理解了很久很久才算明白,我现在用说人话的方式来说说整个过程,作为一个抛砖引玉吧,当然,和实际的算法还是有比较大出入的,如果真感兴趣可以看后面的推荐文章。

我们先定义下一个场景,有3篇文档,每个文档有2个词,设定有2个主题,这三篇文档和词分别是:

1 :你好 世界
2 :你好 中国
3 :搜索 你好

那么词就是:你好,世界,中国,搜索 四个
主题定义为:T1,T2

下方这些人话是重点了。

因为我们认为写文章的时候是是按照两步来做的(先确定主题,然后确定词,最后把词写出来),那么在数学上可以变成两个概率的乘积了,所以,这个词为什么会出现在这篇文章呢?是因为

这个词在这篇文章出现的概率=这个主题在这篇文章的概率*这个词在这个主题下出现的概率,也就是
P(W(词)|D(文章))=P(W(词)|T(主题))*P(T(主题)|D(文章)),这个 公式非常重要。

P(W(词)|D(文章)) 这个其实是可以直接统计出来的。

P(W(词)|T(主题)) 这个是模型的一部分,是要求出来的。

P(T(主题)|D(文章)) 这个是最后分类的结果

有新来的文章我们要对这篇文章进行分类的话,先统计出P(W(词)|D(文章)),然后用P(W(词)|D(文章))去除以P(W(词)|T(主题)) ,就得到了这篇文章所属的每个主题的概率了。

人话说完了,我们看看机器怎么来求出这个P(W(词)|T(主题))

  • 首先随机指定每个词属于某个主题,那么假设设定完了以后P(W|T)的矩阵就是

主题你好世界中国搜索
T10.01.00.00.0
T20.330.00.330.33
  • 然后随机的指定每个主题对应的文档概率,P(T|D)的矩阵就是

文档主题1概率主题2概率
文档10.50.5
文档20.50.5
文档30.50.5
  • 然后拿第一篇文章的第一个词出来,统计出来他在这篇文章出现的概率,也就是0.5,再给他随机拍一个主题,比如T1

  • 用上述的计算出来的话0.0*0.5=0.0,而我们统计出来是0.5啊,这不对,我们再试试别的主题,比如T2,这么算出来0.33*0.5=0.165,也不对啊,但比T1好像要靠谱一点,那我们按照这个调整一下上面两个表格的数字吧,比如把那个0.0改成0.2之类的(具体怎么调?呵呵呵,靠说人话我说不出来了,看后面的链接吧)。

  • 这么一个一个词下来,上面两个表格就在不断更新,然后我们一遍一遍的循环迭代,直到上面两个表格能满足所有文档所有词要求了就结束了。放心,不会死循环的。
    这么一轮下来,就得到了两个表格了。这两个表格就是我们的LDA模型了。

  • 新来的文章我们要对这篇文章进行分类的话,先统计出P(W(词)|D(文章)),然后用P(W(词)|D(文章))去除以P(W(词)|T(主题)) ,就得到了这篇文章所属的每个主题的概率了。

  • 然后我们还可以把新文章也放进模型中继续训练,然后又得到一个更新了的表格,这样不断有文章进来,表格就不断变化了。

好了,说完了,在没有一个公式的情况下我只能说到这了,实际的LDA还是有非常大的差距的,如果大家感兴趣,可以看看下面几篇文章:

如果你看了还是没有懂,或者你完全看不懂上面的文章,那么回头去看看高数,概率论吧,我只能帮到这了,但是不懂没关系,你也可以玩LDA,我后面的文章会有工程化的例子,不用懂也行。

主题模型除了LDA还有很多其他的,比如LSI,虽然最后结果也比较靠谱,但是他的可解释性就不强了,我也没法用说人话的方式描述出来了。

主题模型这种东西是基于大规模的语料的情况下才有效果,而且主题的设定个数也是个经验值,据传说300个主题是个比较合适的值,但是具体合适不合适需要根据你自己的情况是测试,然后找到一个你认为合适的主题数。

2. 词向量

最后简单的说一下目前也用得比较多的词向量,就是word2vector了,光有了主题模型很多人还是不满足,他们还想知道词与词之间的关系,人为的表示也就是近义词了,但是词向量能解决的可远远不是近义词同义词了,比如给一堆微博的语料丢给word2vec来训练,他可以找出来范冰冰李晨这两个词有关系,然后我们发挥一下,如果你有个推荐系统,按照文本相关性去推荐内容,发现没有太相关的推荐结果,这时候你就可以用word2vec扩展你的词了,然后用扩展的词去推荐东西了,比如用范冰冰的新闻推荐出李晨的新闻来,当然这只是举个例子啊,我暂时还没看到有地方这么来做推荐。

词向量也就是用一个向量来表示一个词,比如一个词北京,还有三个维度分别是大学,北方,首都,我们把北京用向量表示成[0,9,0.5,1],那么这个词有3个维度,这三个维度的意义是什么呢?可以解释成北京这个词啊是大学的概率是0.6,他在北方的概率是0.5,他是首都的概率是1,是不是和上面的分类很相似啊,如果每个词都可以这么表示的话,那么两个词之间的相似度就是这两个向量之间的距离了,和上一篇的向量空间一样,那么如何来求一个词的向量呢?其实也可以按照上面的LDA的方式分类来求,呵呵,但我们不这么来。

还是按照上面那个例子来看看,你看到范冰冰这个词,为什么你觉得他和李晨会相似呢?我们知道的是他们是夫妻所以才相似,为什么知道他们是夫妻呢?因为新闻的文章中这两个词老是出现在一起,要不就是在一句话中或者在一段话中,所以冥冥之中我们觉得他们是有相似性的,好吧,就是这个冥冥之中怎么让机器知道,有个东西叫神经网络,最会冥冥之中,因为他冥冥之中想出来的东西你解释不了,但又好像也能说得过去。好,那么我们来看看机器怎么弄的。

  • 假设有这么一句话我爱北京天安门,我们先给每个词编个号,我1,爱2,北京3,天安门4。

  • 然后,我们扫描这个句子的每个词,找和他相邻的两个词,分别是他前面一个和后面一个,那么一轮下来,我们得到一个这样的东西:1(2),2(1,3),3(2,4),4(3),这表示什么?表示每个词和他有两个相关的词,如果我们有大量的句子,那么可以得到非常多的上述关系表达式。

  • 然后呢?然后我们设定一个向量的维度,也就是每个词我们可以用几个维度来表示。

  • 再然后呢?再然后就上神经网络了,这东西啊,如果你知道逻辑回归这种机器学习的方法的话,再假设逻辑回归是物理电路中的一条回路的话,那神经网络就是把逻辑回归这种回路串联并联起来的东西。也就是逻辑回归的逻辑回归的逻辑回归【这句话不是我总结的,我忘了在哪看到过了,觉得挺形象】

  • 最后呢?最后每个词就成了一个向量了。

有了向量了,那么就能比较了吧?

总结

好了,今天扯了这么多,我看下来有点耽误人的意思,因为用太通俗的话根本解释不了很多数学模型,而我数学也不好,要说清楚这些个数学模型我也很费劲,关键是一堆公式出来也没人看,自己心里默默的清楚就行了,大家也别想着通过一两篇文章能了解一个数学模型,自然语言处理这个本来就是个拼数学的领域,要深入还得自己去看书看论文,要了解这些数学模型也不是看一篇文章能了解的,真的感兴趣的,想做算法工程师的,自己专研吧。

下一篇我会把这两篇的东西实战一下,看看最终是不是有效果,估计下一篇能有意思一点。


欢迎关注我的公众号,主要聊聊搜索,推荐,广告技术,还有瞎扯。。文章会在这里首先发出来:)扫描或者搜索微信号XJJ267或者搜索西加加语言就行
图片描述

查看原文

Coldstar 收藏了文章 · 2018-07-26

推荐系统技术文本相似性计算(三)实战篇

前两篇可以直接看我的专栏
或者
文本相似性计算(一)
文本相似性计算(二)
前面说了两篇了,分别介绍了TFIDF和向量空间的相关东西,然后介绍了主题模型,这一篇我们就来试试这两个东西。词向量就不在这篇试了,词向量和这两个关系不大,不好对比,不过我最后也给出了代码。

0. 工具准备

工欲善其事,必先利其器,那么我们先来利其器,这里我们使用的是python的gensim工具包,地址是:https://radimrehurek.com/gensim/index.html,这个工具包很强大,我就不一一介绍了,反正我们需要的功能都有,而且我们用得很简单,它还可以分布式部署,感兴趣可以去官网看具体介绍。

为什么不自己写?这个问题....呵呵.....呵呵....我写不出来.....

至于安装,需要先安装python 2.6以上(废话),NumPy 1.3以上,SciPy 0.7以上,后两个是python的科学计算的包。

easy_install很容易搞定,这里就不废话了,windows上安装可能有点困难,但我很久没用过windows了,我电脑上安装很轻松,三四个命令搞定,可以去看gensim的官方文档,上面也有怎么安装,如果你装都装不上,那就google,百度,总有解决办法。

除了gensim,还有个分词的包需要装一下,就是jieba分词,这个也很容易装。

1. 数据准备

数据准备可是个技术活,我的职业操守很高,没有用公司的数据,那只能自己找数据了,如果直接找网上的语料,显得太Low了。于是我自己爬了一些数据。

首先,我瞄准了目前全国最火的全栈技术网站(就是SegmentFault啦),然后瞄准了一个汽车网站,于是开始爬数据,自己写了个爬虫开始爬数据,恩,我的爬虫我觉得还可以,调度器+爬取器组成,爬取器插件化了,可以使用任意语言做编写,甚至可以直接对接chrome爬取纯JS单页面网站爬取,也支持代理池,如果大家感兴趣我也可以说说爬虫相关的东西,分布式的哦,可以随便加机器增加爬取能力。

这里说个小故事,爬取沙发站(SegmentFault.com)的时候,我先撸了一遍所有文章的列表页,本来我就想把标题爬下来就行了,结果发现SF站没有反应,瞬间就爬下来了,于是大意了,在没挂代理池的情况下,直接开始撸文章的详情页,想把文章都爬下来,结果卧槽,爬到6000多篇的时候把我的IP给封了。于是。。我现在只能挂VPN上SF站,挂VPN啊!!!去新加坡打个转来上一个杭州的网站!!如果有管理员或者运营人员看到这篇文章,麻烦给解个封吧。我真没有恶意,不然也不写在这了。

并且,作为一个老程序员,还是个后端人员,跑到一个90后遍地的前端为主的技术社区写后端东西,看的人本来就不多,应该在oschina,cnblog,csdn这种地方写文章的。这是一种什么精神??国际主义精神啊!因为SF的配色我实在是太喜欢了,而且markdown的渲染效果也很好看,并且手机上看效果也非常赞,于是就来了,看的人少就少吧,反正主要给自己看。。。。

好了,八卦完了,爬了两个网站,可以开始干活了,爬两个类型的网站是为了说明后面LDA主题模型,大家就有个认识了。

2. 数据清理

数据爬下来后,要做的就是数据清洗工作了,我之前有一篇搞机器学习的技能说了,数据的清理是一个算法工程师的必备技能,如果没有好的数据,算法怎么好都没用。

拿到数据以后,写个脚本

  • 首先把标题,作者,时间之类的提取出来,通过正则也好,xpath也好,都很容易把这些东西提取出来。

  • 然后把html标签干掉,一堆正则就行了,剩下的基本上就是正文了,另外,SF站的东西还特殊处理了一下,把中的内容干掉了,一堆代码对我来说没什么用。

  • 最后,把标点符号干掉,把特殊符号干掉,调整一下格式,最后的每一篇文章都变成下面的样子

ID(实际上是url)[TAB]TITLE(标题)[TAB]CONTENT(文章详情)

一共有11628篇文章,其中汽车类大约6000,技术类(SF站)大约6000,好了,数据也基本上清洗好了。

4. 训练数据

都觉得这一节才是重点,其实有jieba分词和gensim以后,代码非常简单,不超过50行,我们来一步一步玩。

4.1 分词--建立词典--准备数字语料

分词是基础,首先进行分词

from gensim import corpora,models,similarities,utils
import jieba
import jieba.posseg as pseg
jieba.load_userdict( "user_dic.txt" ) #载入自定义的词典,主要有一些计算机词汇和汽车型号的词汇
#定义原始语料集合
train_set=[]
f=open("./data/all.txt")
lines=f.readlines()
for line in lines:
    content = (line.lower()).split("\t")[2] + (line.lower()).split("\t")[1]
    #切词,etl函数用于去掉无用的符号,cut_all表示非全切分
    word_list = filter(lambda x: len(x)>0,map(etl,jieba.cut(content,cut_all=False)))
    train_set.append(word_list)
f.close()

得到的tain_set就是原始语料了,然后对这些语料导入到词典中,建立一个词典。

#生成字典
dictionary = corpora.Dictionary(train_set)
#去除极低频的杂质词
dictionary.filter_extremes(no_below=1,no_above=1,keep_n=None)
#将词典保存下来,方便后续使用
dictionary.save(output + "all.dic")

将语料导入词典后,每个词实际上就已经被编号成1,2,3....这种编号了,这是向量化的第一步,然后把词典保存下来。
然后生成数字语料

corpus = [dictionary.doc2bow(text) for text in train_set]

这一句表示把每一条原始数据向量化成编号,这样以后,corpus这个变量是个二维数据,每一行表示一个文档的每个词的编号和词频,每一行像这样

[(1,2),(2,4),(5,2)....] 表示编号为1的词出现了2次,编号为2的词出现了4次....

OK,前期准备OK了,原始文章通过切词-->建立词典-->生成语料后已经被我们数字化了,后面就简单了。

4.1 TFIDF模型

有了数字语料以后,我们可以生成一个TFIDF模型

#使用数字语料生成TFIDF模型
tfidfModel = models.TfidfModel(corpus)
#存储tfidfModel
tfidfModel.save(output + "allTFIDF.mdl")

这一句是关键,我们用了原始的数字语料,生成了一个TFIDF模型,这个模型能干什么呢?gensim重载了[]操作符,我们可以用类似[(1,2),(2,4),(5,2)....]的原始向量传进去,变成一个tfidf的向量,像这样[(1,0.98),(2,0.23),(5,0.56)....],这样就说明编号1的词的重要性比后面两个词都要大,这个向量可以作为后面LDA的原始向量输入。

然后我们把所有的语料都TFIDF向量化,并作为一个索引数据存起来方便以后查找的时候使用

#把全部语料向量化成TFIDF模式,这个tfidfModel可以传入二维数组
tfidfVectors = tfidfModel[corpus]
#建立索引并保存
indexTfidf = similarities.MatrixSimilarity(tfidfVectors)
indexTfidf.save(output + "allTFIDF.idx")

好了,TFIDF部分完了,先记下来,我们生成了一个模型数据(allTFIDF.mdl),生成了一份全部语料的TFIDF向量的索引数据(allTFIDF.idx),加上上面的词典数据(all.dic),我们现在有三份数据了,后面再说怎么用,现在先继续LDA部分。

4.2 LDA模型

LDA上一篇讲了那么多,在gensim看来就是下面几行代码,而且使用了传说中的机器学习哦。只能说gensim的代码封装得太简洁了。

#通过TFIDF向量生成LDA模型,id2word表示编号的对应词典,num_topics表示主题数,我们这里设定的50,主题太多时间受不了。
lda = models.LdaModel(tfidfVectors, id2word=dictionary, num_topics=50)
#把模型保存下来
lda.save(output + "allLDA50Topic.mdl")
#把所有TFIDF向量变成LDA的向量
corpus_lda = lda[tfidfVectors]
#建立索引,把LDA数据保存下来
indexLDA = similarities.MatrixSimilarity(corpus_lda)
indexLDA.save(output + "allLDA50Topic.idx")

虽然只有这三步,但是还是挺耗时的,在log打开的情况下可以看到处理过程,我随便截取了几个,像下面一样,很明显,前面几个主题都和汽车相关,后面几个主题都和技术相关,看样子还算比较靠谱的。

#38 (0.020): 0.003*新奇 + 0.003*骏 + 0.002*途安 + 0.002*配备 + 0.002*都市 + 0.001*除 + 0.001*昂科威
#27 (0.020): 0.003*配置 + 0.003*内饰 + 0.003*车型 + 0.002*气囊 + 0.002*瑞风 + 0.002*万元 + 0.002*逸致 +
#0 (0.020): 0.004*奔腾 + 0.003*加速 + 0.003*嘉年华 + 0.002*油门 + 0.002*爱丽舍 + 0.002*秒
#49 (0.020): 0.004*瑞虎 + 0.004*绅宝 + 0.004*欧诺 + 0.002*雷克萨斯 + 0.002*车型 + 0.002*乐途 
#26 (0.020): 0.011*列表 + 0.009*流 + 0.007*快捷键 + 0.006*崩溃 + 0.002*大神 + 0.002*混淆 + 0.002*邮箱
#21 (0.020): 0.035*命令 + 0.018*浏览器 + 0.007*第三方 + 0.007*安装 + 0.006*控制台 
topic #25 (0.020): 0.064*文件 + 0.004*约束 + 0.004*练习 + 0.003*复制到 + 0.003*就行了 + 0.003*反编译

好了,LDA部分也完了,又多了两个文件allLDA50Topic.mdlallLDA50Topic.idx,加上前面的3个,一共5个文件了,OK,休息一下,喝杯可乐,继续下一步。

5. 验证结果

好了,第四部分中不知不觉我们已经使用机器学习这么高端的东西了,那现在要验证一下这么高端的东西到底效果如何了。

前面的TFIDF和LDA我们都保存了模型和向量数据,那么我们就用两篇新的文章,来看看和这篇文章最相似的文章都有哪些来验证这两个模型靠谱不靠谱吧。

我随便打开一个汽车网站,选了一篇汽车的文章(宝马的评测),再找了我之前的一篇技术的文章(讲搜索引擎的),并且只随机截取了文章的一段进行测试。

看开头这应该是一篇为全新宝马X1 Li(下文简称新X1)洗地的文章,我想很多宝马死忠、车神也已经准备移步评论........

一般情况下,搜索引擎默认会认为索引是不会有太大的变化的,所以把索引分为全量索引和增量索引两部分,全量索引一般是以天.......

好,文章选好了,先载入之前保存的数据文件

#载入字典
dictionary = corpora.Dictionary.load(output + "all.dic")
#载入TFIDF模型和索引
tfidfModel = models.TfidfModel.load(output+"allTFIDF.mdl")
indexTfidf = similarities.MatrixSimilarity.load(output + "allTFIDF.idx")
#载入LDA模型和索引
ldaModel = models.LdaModel.load(output + "allLDA50Topic.mdl")
indexLDA = similarities.MatrixSimilarity.load(output + "allLDA50Topic.idx")

然后把测试数据进行切词TFIDF向量化找相似LDA向量化找相似

#query就是测试数据,先切词
query_bow = dictionary.doc2bow(filter(lambda x: len(x)>0,map(etl,jieba.cut(query,cut_all=False))))
#使用TFIDF模型向量化
tfidfvect = tfidfModel[query_bow]
#然后LDA向量化,因为我们训练时的LDA是在TFIDF基础上做的,所以用itidfvect再向量化一次
ldavec = ldaModel[tfidfvect]
#TFIDF相似性
simstfidf = indexTfidf[tfidfvect]
#LDA相似性
simlda = indexLDA[ldavec]

好了,结束,所有代码就这么些了。太简单了。。。。我们来看看效果。

6 输出结果

我们先看TFIDF的结果

  • 汽车的测试文章TFIDF结果(前10结果中随机选3个)

优惠购车推荐宝马x3优惠3.5-7万元
保时捷macan竞争力分析宝马x3
宝马2014年新车展望多达十余款新车

  • 技术的测试文章TFIDF结果(前10结果中随机选3个)

用golang写一个搜索引擎(0x06)
索引那点事
[搜索引擎] sphinx 的介绍和原理探索

很明显,结果基本都比较靠谱,第一个基本是说宝马车的,第二个基本都在说搜索引擎和索引。

我们再看看LDA的结果,LDA的主要功能是文本分类而不是关键词的匹配,就是看测试文章分类分得对不对,我们这里基本上是两类文章,一类技术文章,一类汽车文章,所以我们通过找和测试文章最相似的文章,然后看看找出来最相似的文章是不是正好都是技术类的或者汽车类的,如果是,表示模型比较好。

  • 汽车的测试文章LDA结果(前10结果中随机选3个)

编辑心中最美中级车一汽-大众新cc
25万时尚品质4款豪华紧凑车之奔驰a级
iphone手机html5上传图片方向问题解决

  • 技术的测试文章LDA结果(前10结果中随机选3个)

java 多线程核心技术梳理(附源码)
springsession原理解析
并发中的锁文件模式

从结果看,基本比较靠谱,但汽车那个出现了一个badcaseiphone手机html5上传图片方向问题解决,这是篇技术文章,但是出现在了汽车类上面。

7. 结果分析

我们来分析一下这个结果,对于TFIDF模型,在现有数据集(12000篇文章)的情况下,推荐结果强相关,让人觉得推荐结果很靠谱,这也是TFIDF这种算法简单有效的地方,他把文章中的关键词很好的提取出来了,所以推荐的结果让人觉得强相关,但是他也有自己的问题。

  • 对于比较短的文章(比如微博这类的),由于文本太短了,TFIDF比较难提取出重要的关键词或者提取得不对,导致推荐结果不靠谱。

  • 单纯以词频来说明这个词的重要性感觉不全面,比如这篇文章,人类来看的话应该是文本相似性最重要,但有可能按TFIDF算出来是模型这个词最重要。
    对于纯文本的推荐系统来说,这种文本相关性的推荐可能比较适合垂直类的网站,比如像SegmentFault这种,看某篇文章的人很可能希望看到类似的文章,更深入的了解这个领域,这种算法比较靠谱,不过据我观察,SegmentFault是使用的标签推荐,这种推荐效果更好,但人为因素更多点,要是我写文章的时候随便打标签就比较麻烦了。

再来看看LDA模型,LDA主要用在文本聚类上,而且他的基础是主题,如果把他作为推荐系统的算法来使用,要看具体场景,他的推荐结果在数据样本不太够的情况下,可能看上去不太靠谱(即便样本大可能也看上去不太好),显得粒度很粗,但正因为很粗,所以比较适合做内容发现,比如我对数码新闻感兴趣,这种感兴趣不仅仅是只对iphone感兴趣,只要是数码这个主题的我都感兴趣,所以用LDA可以很好的给我推荐数码这个主题下的东西,这比正在看iphone的文章,下面全是iphone的文章要靠谱多了。

LDA出现上一节的哪种badcase的时候怎么办呢?因为基本不太可能改模型,那么只能从几个方面入手。

  • 如果只是偶尔的一两个,可以选择忍了。

  • 如果多的话,那只能先调一调主题个数,然后LDA里面有些个参数可以调调(算法工程师的价值所在啊)

  • 还有一条路子就是把输入的数据尽可能的清洗干净,把无用的杂质去掉(算法工程师必备技能耐心和细心
    所以,不同的模型对于不同的场景是很重要的,根据你的场景选择合适的模型才能达到合适的效果。

8. 写在后面的话

这篇文章只是一个文本相似性的最最基本的文章,可以最直观的了解一下TFIDF模型和LDA模型,同时,也使用了目前最热的机器学习技术哦。

其实,像LDA,以及word2vec这种模型,已经是被数学抽象得很强的模型了,和实际场景基本上已经脱离了,已经完全数学化了,所以其实不一定要用在文本处理上,在流量分析,用户行为分析上一样有用,这就是算法工程师要想的事情,一个好的算法如何用在现有的场景中。

试想一下,如果我们想给我们的用户分个类,看看哪些用户兴趣比较相似。我们其实可以这么来做:

  • 首先,如果我们有一堆用户的浏览行为数据,每一条数据记录了用户点击某个链接,或者点击了某个按钮。

  • 把这些浏览行为按照用户维度进行合并,那么新数据中每一条数据就是一个用户的操作记录,按顺序就是他几点几分有什么行为。类似于用户A :[浏览了a页面,点击了b按钮,浏览了c页面....]

  • 好,如果我们发挥算法工程师的必备技能之一----想象力,那么我们把每个用户的行为当成一篇文章,每个行为数据当成一个词语,然后使用LDA.....呵呵
    这样算出来的主题,是不是就是用户的类别呢?有相似行为数据的用户会出现在相同的主题下,那么这样就把这些用户分类了,那么是不是可以理解为同样类别的下的用户有着相似的爱好呢?如果你觉得可行,可以拿你公司的用户数据试试,看看效果好不好:)

9. 后面的后面

最后,所有代码在github上,点击这里可以看得到,代码相当简单,整个不超过200行,核心的就是我上面列的那些,代码中也有word2vec的代码和使用,这篇文章就没提了,另外,爬取的文章就不放上来了,太大了。

如果大家想要语料自己玩,可以上wiki百科,他们开放了他们的所有数据给全世界做语料分析,其中有中文的,地址是:https://dumps.wikimedia.org/zhwiki/latest/zhwiki-latest-pages-articles.xml.bz2,但维基上中文语料并不多,中文语料多的是百度百科,但看看百度百科,呵呵,不但不开放,防爬虫跟防贼一样,呵呵,不过我也给大家个地址,100G的百度百科原始页面:http://pan.baidu.com/s/1i3wvfil ,接头密码:neqs,由亚洲第二爬虫天王梁斌penny友情提供。

好了,今天的文章有点长,就到这了,后面会把算法部分放一放,最近工作太忙,等这一段结束了,我会再说说算法部分,因为现在工作中会有一些比较好玩的算法要用,接下来的文章会主要写写系统架构方面的东西了,另外我自己的那个搜索引擎目前太忙没时间整,也要等一小段时间了,不好意思:)但放心,不会有头无尾啦。


欢迎关注我的公众号,主要聊聊搜索,推荐,广告技术,还有瞎扯。。文章会在这里首先发出来:)扫描或者搜索微信号XJJ267或者搜索西加加语言就行

图片描述

查看原文

Coldstar 关注了专栏 · 2018-07-20

kubernetes solutions

专注k8s,serverless,service mesh,devops

关注 2235

Coldstar 收藏了文章 · 2018-07-20

Prometheus实战--存储篇

前言

Prometheus之于kubernetes(监控领域),如kubernetes之于容器编排。
随着heapster不再开发和维护以及influxdb 集群方案不再开源,heapster+influxdb的监控方案,只适合一些规模比较小的k8s集群。而prometheus整个社区非常活跃,除了官方社区提供了一系列高质量的exporter,例如node_exporter等。Telegraf(集中采集metrics) + prometheus的方案,也是一种减少部署和管理各种exporter工作量的很好的方案。
今天主要讲讲我司在使用prometheus过程中,存储方面的一些实战经验。

Prometheus 储存瓶颈

prometheus架构图

通过prometheus的架构图可以看出,prometheus提供了本地存储,即tsdb时序数据库。本地存储的优势就是运维简单,缺点就是无法海量的metrics持久化和数据存在丢失的风险,我们在实际使用过程中,出现过几次wal文件损坏,无法再写入的问题。
当然prometheus2.0以后压缩数据能力得到了很大的提升。为了解决单节点存储的限制,prometheus没有自己实现集群存储,而是提供了远程读写的接口,让用户自己选择合适的时序数据库来实现prometheus的扩展性。

prometheus通过下面两种方式来实现与其他的远端存储系统对接

  • Prometheus 按照标准的格式将metrics写到远端存储
  • prometheus 按照标准格式从远端的url来读取metrics

远程存储

metrics的持久化的意义和价值

其实监控不仅仅是体现在可以实时掌握系统运行情况,及时报警这些。而且监控所采集的数据,在以下几个方面是有价值的

  • 资源的审计和计费。这个需要保存一年甚至多年的数据的。
  • 故障责任的追查
  • 后续的分析和挖掘,甚至是利用AI,可以实现报警规则的设定的智能化,故障的根因分析以及预测某个应用的qps的趋势,提前HPA等,当然这是现在流行的AIOPS范畴了。

Prometheus 数据持久化方案

方案选型

社区中支持prometheus远程读写的方案

  • AppOptics: write
  • Chronix: write
  • Cortex: read and write
  • CrateDB: read and write
  • Elasticsearch: write
  • Gnocchi: write
  • Graphite: write
  • InfluxDB: read and write
  • OpenTSDB: write
  • PostgreSQL/TimescaleDB: read and write
  • SignalFx: write
  • clickhouse: read and write

选型方案需要具备以下几点

  • 满足数据的安全性,需要支持容错,备份
  • 写入性能要好,支持分片
  • 技术方案不复杂
  • 用于后期分析的时候,查询语法友好
  • grafana读取支持,优先考虑
  • 需要同时支持读写

基于以上的几点,clickhouse满足我们使用场景。
Clickhouse是一个高性能的列式数据库,因为侧重于分析,所以支持丰富的分析函数。

下面是Clickhouse官方推荐的几种使用场景:

  • Web and App analytics
  • Advertising networks and RTB
  • Telecommunications
  • E-commerce and finance
  • Information security
  • Monitoring and telemetry
  • Time series
  • Business intelligence
  • Online games
  • Internet of Things

ck适合用于存储Time series
此外社区已经有graphouse项目,把ck作为Graphite的存储。

性能测试

写入测试

本地mac,docker 启动单台ck,承接了3个集群的metrics,均值达到12910条/s。写入毫无压力。其实在网盟等公司,实际使用时,达到30万/s。

查询测试

fbe6a4edc3eb :) select count(*) from metrics.samples;

SELECT count(*)
FROM metrics.samples

┌──count()─┐
│ 22687301 │
└──────────┘

1 rows in set. Elapsed: 0.014 sec. Processed 22.69 million rows, 45.37 MB (1.65 billion rows/s., 3.30 GB/s.)

其中最有可能耗时的查询:
1)查询聚合sum

fbe6a4edc3eb :) select sum(val) from metrics.samples where arrayExists(x -> 1 == match(x, 'cid=9'),tags) = 1 and name = 'machine_cpu_cores' and ts > '2017-07-11 08:00:00'

SELECT sum(val)
FROM metrics.samples
WHERE (arrayExists(x -> (1 = match(x, 'cid=9')), tags) = 1) AND (name = 'machine_cpu_cores') AND (ts > '2017-07-11 08:00:00')

┌─sum(val)─┐
│     6324 │
└──────────┘

1 rows in set. Elapsed: 0.022 sec. Processed 57.34 thousand rows, 34.02 MB (2.66 million rows/s., 1.58 GB/s.)

2)group by 查询

fbe6a4edc3eb :) select sum(val), time  from metrics.samples where arrayExists(x -> 1 == match(x, 'cid=9'),tags) = 1 and name = 'machine_cpu_cores' and ts > '2017-07-11 08:00:00' group by toDate(ts) as time;

SELECT
    sum(val),
    time
FROM metrics.samples
WHERE (arrayExists(x -> (1 = match(x, 'cid=9')), tags) = 1) AND (name = 'machine_cpu_cores') AND (ts > '2017-07-11 08:00:00')
GROUP BY toDate(ts) AS time

┌─sum(val)─┬───────time─┐
│     6460 │ 2018-07-11 │
│      136 │ 2018-07-12 │
└──────────┴────────────┘

2 rows in set. Elapsed: 0.023 sec. Processed 64.11 thousand rows, 36.21 MB (2.73 million rows/s., 1.54 GB/s.)

3) 正则表达式

fbe6a4edc3eb :) select sum(val) from metrics.samples where name = 'container_memory_rss' and arrayExists(x -> 1 == match(x, '^pod_name=ofo-eva-hub'),tags) = 1 ;

SELECT sum(val)
FROM metrics.samples
WHERE (name = 'container_memory_rss') AND (arrayExists(x -> (1 = match(x, '^pod_name=ofo-eva-hub')), tags) = 1)

┌─────sum(val)─┐
│ 870016516096 │
└──────────────┘

1 rows in set. Elapsed: 0.142 sec. Processed 442.37 thousand rows, 311.52 MB (3.11 million rows/s., 2.19 GB/s.)

总结:
利用好所建索引,即使在大数据量下,查询性能非常好。

方案设计

架构设计

关于此架构,有以下几点:

  • 每个k8s集群部署一个Prometheus-clickhouse-adapter 。关于Prometheus-clickhouse-adapter该组件,下面我们会详细解读。
  • clickhouse 集群部署,需要zk集群做一致性表数据复制。

而clickhouse 的集群示意图如下:

ck集群

  • ReplicatedMergeTree + Distributed。ReplicatedMergeTree里,共享同一个ZK路径的表,会相互,注意是,相互同步数据
  • 每个IDC有3个分片,各自占1/3数据
  • 每个节点,依赖ZK,各自有2个副本

这块详细步骤和思路,请参考ClickHouse集群搭建从0到1。感谢新浪的鹏哥指点。

zk集群部署注意事项

  • 安装 ZooKeeper 3.4.9或更高版本的稳定版本
  • 不要使用zk的默认配置,默认配置就是一个定时炸弹。
The ZooKeeper server won't delete files from old snapshots and logs when using the default configuration (see autopurge), and this is the responsibility of the operator.

ck官方给出的配置如下zoo.cfg:

# http://hadoop.apache.org/zookeeper/docs/current/zookeeperAdmin.html

# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=30000
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=10

maxClientCnxns=2000

maxSessionTimeout=60000000
# the directory where the snapshot is stored.
dataDir=/opt/zookeeper/{{ cluster['name'] }}/data
# Place the dataLogDir to a separate physical disc for better performance
dataLogDir=/opt/zookeeper/{{ cluster['name'] }}/logs

autopurge.snapRetainCount=10
autopurge.purgeInterval=1


# To avoid seeks ZooKeeper allocates space in the transaction log file in
# blocks of preAllocSize kilobytes. The default block size is 64M. One reason
# for changing the size of the blocks is to reduce the block size if snapshots
# are taken more often. (Also, see snapCount).
preAllocSize=131072

# Clients can submit requests faster than ZooKeeper can process them,
# especially if there are a lot of clients. To prevent ZooKeeper from running
# out of memory due to queued requests, ZooKeeper will throttle clients so that
# there is no more than globalOutstandingLimit outstanding requests in the
# system. The default limit is 1,000.ZooKeeper logs transactions to a
# transaction log. After snapCount transactions are written to a log file a
# snapshot is started and a new transaction log file is started. The default
# snapCount is 10,000.
snapCount=3000000

# If this option is defined, requests will be will logged to a trace file named
# traceFile.year.month.day.
#traceFile=

# Leader accepts client connections. Default value is "yes". The leader machine
# coordinates updates. For higher update throughput at thes slight expense of
# read throughput the leader can be configured to not accept clients and focus
# on coordination.
leaderServes=yes

standaloneEnabled=false
dynamicConfigFile=/etc/zookeeper-{{ cluster['name'] }}/conf/zoo.cfg.dynamic

每个版本的ck配置文件不太一样,这里贴出一个390版本的

<?xml version="1.0"?>
<yandex>
    <logger>
        <!-- Possible levels: https://github.com/pocoproject/poco/blob/develop/Foundation/include/Poco/Logger.h#L105 -->
        <level>information</level>
        <log>/data/ck/log/clickhouse-server.log</log>
        <errorlog>/data/ck/log/clickhouse-server.err.log</errorlog>
        <size>1000M</size>
        <count>10</count>
        <!-- <console>1</console> --> <!-- Default behavior is autodetection (log to console if not daemon mode and is tty) -->
    </logger>
    <!--display_name>production</display_name--> <!-- It is the name that will be shown in the client -->
    <http_port>8123</http_port>
    <tcp_port>9000</tcp_port>

    <!-- For HTTPS and SSL over native protocol. -->
    <!--
    <https_port>8443</https_port>
    <tcp_port_secure>9440</tcp_port_secure>
    -->

    <!-- Used with https_port and tcp_port_secure. Full ssl options list: https://github.com/ClickHouse-Extras/poco/blob/master/NetSSL_OpenSSL/include/Poco/Net/SSLManager.h#L71 -->
    <openSSL>
        <server> <!-- Used for https server AND secure tcp port -->
            <!-- openssl req -subj "/CN=localhost" -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout /etc/clickhouse-server/server.key -out /etc/clickhouse-server/server.crt -->
            <certificateFile>/etc/clickhouse-server/server.crt</certificateFile>
            <privateKeyFile>/etc/clickhouse-server/server.key</privateKeyFile>
            <!-- openssl dhparam -out /etc/clickhouse-server/dhparam.pem 4096 -->
            <dhParamsFile>/etc/clickhouse-server/dhparam.pem</dhParamsFile>
            <verificationMode>none</verificationMode>
            <loadDefaultCAFile>true</loadDefaultCAFile>
            <cacheSessions>true</cacheSessions>
            <disableProtocols>sslv2,sslv3</disableProtocols>
            <preferServerCiphers>true</preferServerCiphers>
        </server>

        <client> <!-- Used for connecting to https dictionary source -->
            <loadDefaultCAFile>true</loadDefaultCAFile>
            <cacheSessions>true</cacheSessions>
            <disableProtocols>sslv2,sslv3</disableProtocols>
            <preferServerCiphers>true</preferServerCiphers>
            <!-- Use for self-signed: <verificationMode>none</verificationMode> -->
            <invalidCertificateHandler>
                <!-- Use for self-signed: <name>AcceptCertificateHandler</name> -->
                <name>RejectCertificateHandler</name>
            </invalidCertificateHandler>
        </client>
    </openSSL>

    <!-- Default root page on http[s] server. For example load UI from https://tabix.io/ when opening http://localhost:8123 -->
    <!--
    <http_server_default_response><![CDATA[<html ng-app="SMI2"><head><base href="http://ui.tabix.io/"></head><body><div ui-view="" class="content-ui"></div><script data-original="http://loader.tabix.io/master.js"></script></body></html>]]></http_server_default_response>
    -->

    <!-- Port for communication between replicas. Used for data exchange. -->
    <interserver_http_port>9009</interserver_http_port>

    <!-- Hostname that is used by other replicas to request this server.
         If not specified, than it is determined analoguous to 'hostname -f' command.
         This setting could be used to switch replication to another network interface.
      -->
    <!--
    <interserver_http_host>example.yandex.ru</interserver_http_host>
    -->

    <!-- Listen specified host. use :: (wildcard IPv6 address), if you want to accept connections both with IPv4 and IPv6 from everywhere. -->
    <!-- <listen_host>::</listen_host> -->
    <!-- Same for hosts with disabled ipv6: -->
    <listen_host>0.0.0.0</listen_host>

    <!-- Default values - try listen localhost on ipv4 and ipv6: -->
    <!--
    <listen_host>::1</listen_host>
    <listen_host>127.0.0.1</listen_host>
    -->
    <!-- Don't exit if ipv6 or ipv4 unavailable, but listen_host with this protocol specified -->
    <!-- <listen_try>0</listen_try> -->

    <!-- Allow listen on same address:port -->
    <!-- <listen_reuse_port>0</listen_reuse_port> -->

    <!-- <listen_backlog>64</listen_backlog> -->

    <max_connections>4096</max_connections>
    <keep_alive_timeout>3</keep_alive_timeout>

    <!-- Maximum number of concurrent queries. -->
    <max_concurrent_queries>100</max_concurrent_queries>

    <!-- Set limit on number of open files (default: maximum). This setting makes sense on Mac OS X because getrlimit() fails to retrieve
         correct maximum value. -->
    <!-- <max_open_files>262144</max_open_files> -->

    <!-- Size of cache of uncompressed blocks of data, used in tables of MergeTree family.
         In bytes. Cache is single for server. Memory is allocated only on demand.
         Cache is used when 'use_uncompressed_cache' user setting turned on (off by default).
         Uncompressed cache is advantageous only for very short queries and in rare cases.
      -->
    <uncompressed_cache_size>8589934592</uncompressed_cache_size>

    <!-- Approximate size of mark cache, used in tables of MergeTree family.
         In bytes. Cache is single for server. Memory is allocated only on demand.
         You should not lower this value.
      -->
    <mark_cache_size>5368709120</mark_cache_size>


    <!-- Path to data directory, with trailing slash. -->
    <path>/data/ck/data/</path>

    <!-- Path to temporary data for processing hard queries. -->
    <tmp_path>/data/ck/tmp/</tmp_path>

    <!-- Directory with user provided files that are accessible by 'file' table function. -->
    <user_files_path>/data/ck/user_files/</user_files_path>

    <!-- Path to configuration file with users, access rights, profiles of settings, quotas. -->
    <users_config>users.xml</users_config>

    <!-- Default profile of settings. -->
    <default_profile>default</default_profile>

    <!-- System profile of settings. This settings are used by internal processes (Buffer storage, Distibuted DDL worker and so on). -->
    <!-- <system_profile>default</system_profile> -->

    <!-- Default database. -->
    <default_database>default</default_database>

    <!-- Server time zone could be set here.

         Time zone is used when converting between String and DateTime types,
          when printing DateTime in text formats and parsing DateTime from text,
          it is used in date and time related functions, if specific time zone was not passed as an argument.

         Time zone is specified as identifier from IANA time zone database, like UTC or Africa/Abidjan.
         If not specified, system time zone at server startup is used.

         Please note, that server could display time zone alias instead of specified name.
         Example: W-SU is an alias for Europe/Moscow and Zulu is an alias for UTC.
    -->
    <!-- <timezone>Europe/Moscow</timezone> -->

    <!-- You can specify umask here (see "man umask"). Server will apply it on startup.
         Number is always parsed as octal. Default umask is 027 (other users cannot read logs, data files, etc; group can only read).
    -->
    <!-- <umask>022</umask> -->

    <!-- Configuration of clusters that could be used in Distributed tables.
         https://clickhouse.yandex/docs/en/table_engines/distributed/
      -->
    <remote_servers>
        <prometheus_ck_cluster>
            <!-- 数据分片1  -->
            <shard>
                <internal_replication>false</internal_replication>
                <replica>
                    <host>ck11.ruly.xxx.net</host>
                    <port>9000</port>
                </replica>
                <replica>
                    <host>ck12.ruly.xxx.net</host>
                    <port>9000</port>
                </replica>
            </shard>
        </prometheus_ck_cluster>
    </remote_servers>


    <!-- If element has 'incl' attribute, then for it's value will be used corresponding substitution from another file.
         By default, path to file with substitutions is /etc/metrika.xml. It could be changed in config in 'include_from' element.
         Values for substitutions are specified in /yandex/name_of_substitution elements in that file.
      -->

    <!-- ZooKeeper is used to store metadata about replicas, when using Replicated tables.
         Optional. If you don't use replicated tables, you could omit that.

         See https://clickhouse.yandex/docs/en/table_engines/replication/
      -->
    <!-- ZK  -->
    <zookeeper>
        <node index="1">
            <host>zk1.ruly.xxx.net</host>
            <port>2181</port>
        </node>
        <node index="2">
            <host>zk2.ruly.xxx.net</host>
            <port>2181</port>
        </node>
        <node index="3">
            <host>zk3.ruly.xxx.net</host>
            <port>2181</port>
        </node>
    </zookeeper>

    <!-- Substitutions for parameters of replicated tables.
          Optional. If you don't use replicated tables, you could omit that.

         See https://clickhouse.yandex/docs/en/table_engines/replication/#creating-replicated-tables
      -->
    <macros>
        <shard>1</shard>
        <replica>ck11.ruly.ofo.net</replica>
    </macros>


    <!-- Reloading interval for embedded dictionaries, in seconds. Default: 3600. -->
    <builtin_dictionaries_reload_interval>3600</builtin_dictionaries_reload_interval>


    <!-- Maximum session timeout, in seconds. Default: 3600. -->
    <max_session_timeout>3600</max_session_timeout>

    <!-- Default session timeout, in seconds. Default: 60. -->
    <default_session_timeout>60</default_session_timeout>

    <!-- Sending data to Graphite for monitoring. Several sections can be defined. -->
    <!--
        interval - send every X second
        root_path - prefix for keys
        hostname_in_path - append hostname to root_path (default = true)
        metrics - send data from table system.metrics
        events - send data from table system.events
        asynchronous_metrics - send data from table system.asynchronous_metrics
    -->
    <!--
    <graphite>
        <host>localhost</host>
        <port>42000</port>
        <timeout>0.1</timeout>
        <interval>60</interval>
        <root_path>one_min</root_path>
        <hostname_in_path>true</hostname_in_path>

        <metrics>true</metrics>
        <events>true</events>
        <asynchronous_metrics>true</asynchronous_metrics>
    </graphite>
    <graphite>
        <host>localhost</host>
        <port>42000</port>
        <timeout>0.1</timeout>
        <interval>1</interval>
        <root_path>one_sec</root_path>

        <metrics>true</metrics>
        <events>true</events>
        <asynchronous_metrics>false</asynchronous_metrics>
    </graphite>
    -->


    <!-- Query log. Used only for queries with setting log_queries = 1. -->
    <query_log>
        <!-- What table to insert data. If table is not exist, it will be created.
             When query log structure is changed after system update,
              then old table will be renamed and new table will be created automatically.
        -->
        <database>system</database>
        <table>query_log</table>
        <!--
            PARTITION BY expr https://clickhouse.yandex/docs/en/table_engines/custom_partitioning_key/
            Example:
                event_date
                toMonday(event_date)
                toYYYYMM(event_date)
                toStartOfHour(event_time)
        -->
        <partition_by>toYYYYMM(event_date)</partition_by>
        <!-- Interval of flushing data. -->
        <flush_interval_milliseconds>7500</flush_interval_milliseconds>
    </query_log>


    <!-- Uncomment if use part_log
    <part_log>
        <database>system</database>
        <table>part_log</table>

        <flush_interval_milliseconds>7500</flush_interval_milliseconds>
    </part_log>
    -->


    <!-- Parameters for embedded dictionaries, used in Yandex.Metrica.
         See https://clickhouse.yandex/docs/en/dicts/internal_dicts/
    -->

    <!-- Path to file with region hierarchy. -->
    <!-- <path_to_regions_hierarchy_file>/opt/geo/regions_hierarchy.txt</path_to_regions_hierarchy_file> -->

    <!-- Path to directory with files containing names of regions -->
    <!-- <path_to_regions_names_files>/opt/geo/</path_to_regions_names_files> -->


    <!-- Configuration of external dictionaries. See:
         https://clickhouse.yandex/docs/en/dicts/external_dicts/
    -->
    <dictionaries_config>*_dictionary.xml</dictionaries_config>

    <!-- Uncomment if you want data to be compressed 30-100% better.
         Don't do that if you just started using ClickHouse.
      -->
    <!-- <compression incl="clickhouse_compression"> -->
    <!--
        <!- - Set of variants. Checked in order. Last matching case wins. If nothing matches, lz4 will be used. - ->
        <case>

            <!- - Conditions. All must be satisfied. Some conditions may be omitted. - ->
            <min_part_size>10000000000</min_part_size>        <!- - Min part size in bytes. - ->
            <min_part_size_ratio>0.01</min_part_size_ratio>   <!- - Min size of part relative to whole table size. - ->

            <!- - What compression method to use. - ->
            <method>zstd</method>
        </case>
    -->
    <!-- </compression> -->

    <!-- Allow to execute distributed DDL queries (CREATE, DROP, ALTER, RENAME) on cluster.
         Works only if ZooKeeper is enabled. Comment it if such functionality isn't required. -->
    <distributed_ddl>
        <!-- Path in ZooKeeper to queue with DDL queries -->
        <path>/clickhouse/task_queue/ddl</path>

        <!-- Settings from this profile will be used to execute DDL queries -->
        <!-- <profile>default</profile> -->
    </distributed_ddl>

    <!-- Settings to fine tune MergeTree tables. See documentation in source code, in MergeTreeSettings.h -->
    <!--
    <merge_tree>
        <max_suspicious_broken_parts>5</max_suspicious_broken_parts>
    </merge_tree>
    -->

    <!-- Protection from accidental DROP.
         If size of a MergeTree table is greater than max_table_size_to_drop (in bytes) than table could not be dropped with any DROP query.
         If you want do delete one table and don't want to restart clickhouse-server, you could create special file <clickhouse-path>/flags/force_drop_table and make DROP once.
         By default max_table_size_to_drop is 50GB, max_table_size_to_drop=0 allows to DROP any tables.
         Uncomment to disable protection.
    -->
    <!-- <max_table_size_to_drop>0</max_table_size_to_drop> -->

    <!-- Example of parameters for GraphiteMergeTree table engine -->
    <!-- <graphite_rollup>
        <pattern>
            <regexp>click_cost</regexp>
            <function>any</function>
            <retention>
                <age>0</age>
                <precision>3600</precision>
            </retention>
            <retention>
                <age>86400</age>
                <precision>60</precision>
            </retention>
        </pattern>
        <default>
            <function>max</function>
            <retention>
                <age>0</age>
                <precision>60</precision>
            </retention>
            <retention>
                <age>3600</age>
                <precision>300</precision>
            </retention>
            <retention>
                <age>86400</age>
                <precision>3600</precision>
            </retention>
        </default>
    </graphite_rollup> -->

    <!-- Directory in <clickhouse-path> containing schema files for various input formats.
         The directory will be created if it doesn't exist.
      -->
    <format_schema_path>/var/lib/clickhouse/format_schemas/</format_schema_path>

    <!-- Uncomment to disable ClickHouse internal DNS caching. -->
    <!-- <disable_internal_dns_cache>1</disable_internal_dns_cache> -->
    <!-- Max insert block size set to 4x than default size 1048576 -->

</yandex>

Prometheus-Clickhuse-Adapter组件

Prometheus-Clickhuse-Adapter(Prom2click) 是一个将clickhouse作为prometheus 数据远程存储的适配器。
prometheus-clickhuse-adapter,该项目缺乏日志,对于一个实际生产的项目,是不够的,此外一些数据库连接细节实现的也不够完善,已经在实际使用过程中将改进部分作为pr提交。

在实际使用过程中,要注意并发写入数据的数量,及时调整启动参数ch.batch 的大小,实际就是批量写入ck的数量,目前我们设置的是65536。因为ck的Merge引擎有一个300的限制,超过会报错

Too many parts (300). Merges are processing significantly slower than inserts

300是指 processing,不是指一次批量插入的条数。

总结

这篇文章主要讲了我司Prometheus在存储方面的探索和实战的一点经验。后续会讲Prometheus查询和采集分离的高可用架构方案。

查看原文

Coldstar 赞了文章 · 2018-07-20

Prometheus实战--存储篇

前言

Prometheus之于kubernetes(监控领域),如kubernetes之于容器编排。
随着heapster不再开发和维护以及influxdb 集群方案不再开源,heapster+influxdb的监控方案,只适合一些规模比较小的k8s集群。而prometheus整个社区非常活跃,除了官方社区提供了一系列高质量的exporter,例如node_exporter等。Telegraf(集中采集metrics) + prometheus的方案,也是一种减少部署和管理各种exporter工作量的很好的方案。
今天主要讲讲我司在使用prometheus过程中,存储方面的一些实战经验。

Prometheus 储存瓶颈

prometheus架构图

通过prometheus的架构图可以看出,prometheus提供了本地存储,即tsdb时序数据库。本地存储的优势就是运维简单,缺点就是无法海量的metrics持久化和数据存在丢失的风险,我们在实际使用过程中,出现过几次wal文件损坏,无法再写入的问题。
当然prometheus2.0以后压缩数据能力得到了很大的提升。为了解决单节点存储的限制,prometheus没有自己实现集群存储,而是提供了远程读写的接口,让用户自己选择合适的时序数据库来实现prometheus的扩展性。

prometheus通过下面两种方式来实现与其他的远端存储系统对接

  • Prometheus 按照标准的格式将metrics写到远端存储
  • prometheus 按照标准格式从远端的url来读取metrics

远程存储

metrics的持久化的意义和价值

其实监控不仅仅是体现在可以实时掌握系统运行情况,及时报警这些。而且监控所采集的数据,在以下几个方面是有价值的

  • 资源的审计和计费。这个需要保存一年甚至多年的数据的。
  • 故障责任的追查
  • 后续的分析和挖掘,甚至是利用AI,可以实现报警规则的设定的智能化,故障的根因分析以及预测某个应用的qps的趋势,提前HPA等,当然这是现在流行的AIOPS范畴了。

Prometheus 数据持久化方案

方案选型

社区中支持prometheus远程读写的方案

  • AppOptics: write
  • Chronix: write
  • Cortex: read and write
  • CrateDB: read and write
  • Elasticsearch: write
  • Gnocchi: write
  • Graphite: write
  • InfluxDB: read and write
  • OpenTSDB: write
  • PostgreSQL/TimescaleDB: read and write
  • SignalFx: write
  • clickhouse: read and write

选型方案需要具备以下几点

  • 满足数据的安全性,需要支持容错,备份
  • 写入性能要好,支持分片
  • 技术方案不复杂
  • 用于后期分析的时候,查询语法友好
  • grafana读取支持,优先考虑
  • 需要同时支持读写

基于以上的几点,clickhouse满足我们使用场景。
Clickhouse是一个高性能的列式数据库,因为侧重于分析,所以支持丰富的分析函数。

下面是Clickhouse官方推荐的几种使用场景:

  • Web and App analytics
  • Advertising networks and RTB
  • Telecommunications
  • E-commerce and finance
  • Information security
  • Monitoring and telemetry
  • Time series
  • Business intelligence
  • Online games
  • Internet of Things

ck适合用于存储Time series
此外社区已经有graphouse项目,把ck作为Graphite的存储。

性能测试

写入测试

本地mac,docker 启动单台ck,承接了3个集群的metrics,均值达到12910条/s。写入毫无压力。其实在网盟等公司,实际使用时,达到30万/s。

查询测试

fbe6a4edc3eb :) select count(*) from metrics.samples;

SELECT count(*)
FROM metrics.samples

┌──count()─┐
│ 22687301 │
└──────────┘

1 rows in set. Elapsed: 0.014 sec. Processed 22.69 million rows, 45.37 MB (1.65 billion rows/s., 3.30 GB/s.)

其中最有可能耗时的查询:
1)查询聚合sum

fbe6a4edc3eb :) select sum(val) from metrics.samples where arrayExists(x -> 1 == match(x, 'cid=9'),tags) = 1 and name = 'machine_cpu_cores' and ts > '2017-07-11 08:00:00'

SELECT sum(val)
FROM metrics.samples
WHERE (arrayExists(x -> (1 = match(x, 'cid=9')), tags) = 1) AND (name = 'machine_cpu_cores') AND (ts > '2017-07-11 08:00:00')

┌─sum(val)─┐
│     6324 │
└──────────┘

1 rows in set. Elapsed: 0.022 sec. Processed 57.34 thousand rows, 34.02 MB (2.66 million rows/s., 1.58 GB/s.)

2)group by 查询

fbe6a4edc3eb :) select sum(val), time  from metrics.samples where arrayExists(x -> 1 == match(x, 'cid=9'),tags) = 1 and name = 'machine_cpu_cores' and ts > '2017-07-11 08:00:00' group by toDate(ts) as time;

SELECT
    sum(val),
    time
FROM metrics.samples
WHERE (arrayExists(x -> (1 = match(x, 'cid=9')), tags) = 1) AND (name = 'machine_cpu_cores') AND (ts > '2017-07-11 08:00:00')
GROUP BY toDate(ts) AS time

┌─sum(val)─┬───────time─┐
│     6460 │ 2018-07-11 │
│      136 │ 2018-07-12 │
└──────────┴────────────┘

2 rows in set. Elapsed: 0.023 sec. Processed 64.11 thousand rows, 36.21 MB (2.73 million rows/s., 1.54 GB/s.)

3) 正则表达式

fbe6a4edc3eb :) select sum(val) from metrics.samples where name = 'container_memory_rss' and arrayExists(x -> 1 == match(x, '^pod_name=ofo-eva-hub'),tags) = 1 ;

SELECT sum(val)
FROM metrics.samples
WHERE (name = 'container_memory_rss') AND (arrayExists(x -> (1 = match(x, '^pod_name=ofo-eva-hub')), tags) = 1)

┌─────sum(val)─┐
│ 870016516096 │
└──────────────┘

1 rows in set. Elapsed: 0.142 sec. Processed 442.37 thousand rows, 311.52 MB (3.11 million rows/s., 2.19 GB/s.)

总结:
利用好所建索引,即使在大数据量下,查询性能非常好。

方案设计

架构设计

关于此架构,有以下几点:

  • 每个k8s集群部署一个Prometheus-clickhouse-adapter 。关于Prometheus-clickhouse-adapter该组件,下面我们会详细解读。
  • clickhouse 集群部署,需要zk集群做一致性表数据复制。

而clickhouse 的集群示意图如下:

ck集群

  • ReplicatedMergeTree + Distributed。ReplicatedMergeTree里,共享同一个ZK路径的表,会相互,注意是,相互同步数据
  • 每个IDC有3个分片,各自占1/3数据
  • 每个节点,依赖ZK,各自有2个副本

这块详细步骤和思路,请参考ClickHouse集群搭建从0到1。感谢新浪的鹏哥指点。

zk集群部署注意事项

  • 安装 ZooKeeper 3.4.9或更高版本的稳定版本
  • 不要使用zk的默认配置,默认配置就是一个定时炸弹。
The ZooKeeper server won't delete files from old snapshots and logs when using the default configuration (see autopurge), and this is the responsibility of the operator.

ck官方给出的配置如下zoo.cfg:

# http://hadoop.apache.org/zookeeper/docs/current/zookeeperAdmin.html

# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=30000
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=10

maxClientCnxns=2000

maxSessionTimeout=60000000
# the directory where the snapshot is stored.
dataDir=/opt/zookeeper/{{ cluster['name'] }}/data
# Place the dataLogDir to a separate physical disc for better performance
dataLogDir=/opt/zookeeper/{{ cluster['name'] }}/logs

autopurge.snapRetainCount=10
autopurge.purgeInterval=1


# To avoid seeks ZooKeeper allocates space in the transaction log file in
# blocks of preAllocSize kilobytes. The default block size is 64M. One reason
# for changing the size of the blocks is to reduce the block size if snapshots
# are taken more often. (Also, see snapCount).
preAllocSize=131072

# Clients can submit requests faster than ZooKeeper can process them,
# especially if there are a lot of clients. To prevent ZooKeeper from running
# out of memory due to queued requests, ZooKeeper will throttle clients so that
# there is no more than globalOutstandingLimit outstanding requests in the
# system. The default limit is 1,000.ZooKeeper logs transactions to a
# transaction log. After snapCount transactions are written to a log file a
# snapshot is started and a new transaction log file is started. The default
# snapCount is 10,000.
snapCount=3000000

# If this option is defined, requests will be will logged to a trace file named
# traceFile.year.month.day.
#traceFile=

# Leader accepts client connections. Default value is "yes". The leader machine
# coordinates updates. For higher update throughput at thes slight expense of
# read throughput the leader can be configured to not accept clients and focus
# on coordination.
leaderServes=yes

standaloneEnabled=false
dynamicConfigFile=/etc/zookeeper-{{ cluster['name'] }}/conf/zoo.cfg.dynamic

每个版本的ck配置文件不太一样,这里贴出一个390版本的

<?xml version="1.0"?>
<yandex>
    <logger>
        <!-- Possible levels: https://github.com/pocoproject/poco/blob/develop/Foundation/include/Poco/Logger.h#L105 -->
        <level>information</level>
        <log>/data/ck/log/clickhouse-server.log</log>
        <errorlog>/data/ck/log/clickhouse-server.err.log</errorlog>
        <size>1000M</size>
        <count>10</count>
        <!-- <console>1</console> --> <!-- Default behavior is autodetection (log to console if not daemon mode and is tty) -->
    </logger>
    <!--display_name>production</display_name--> <!-- It is the name that will be shown in the client -->
    <http_port>8123</http_port>
    <tcp_port>9000</tcp_port>

    <!-- For HTTPS and SSL over native protocol. -->
    <!--
    <https_port>8443</https_port>
    <tcp_port_secure>9440</tcp_port_secure>
    -->

    <!-- Used with https_port and tcp_port_secure. Full ssl options list: https://github.com/ClickHouse-Extras/poco/blob/master/NetSSL_OpenSSL/include/Poco/Net/SSLManager.h#L71 -->
    <openSSL>
        <server> <!-- Used for https server AND secure tcp port -->
            <!-- openssl req -subj "/CN=localhost" -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout /etc/clickhouse-server/server.key -out /etc/clickhouse-server/server.crt -->
            <certificateFile>/etc/clickhouse-server/server.crt</certificateFile>
            <privateKeyFile>/etc/clickhouse-server/server.key</privateKeyFile>
            <!-- openssl dhparam -out /etc/clickhouse-server/dhparam.pem 4096 -->
            <dhParamsFile>/etc/clickhouse-server/dhparam.pem</dhParamsFile>
            <verificationMode>none</verificationMode>
            <loadDefaultCAFile>true</loadDefaultCAFile>
            <cacheSessions>true</cacheSessions>
            <disableProtocols>sslv2,sslv3</disableProtocols>
            <preferServerCiphers>true</preferServerCiphers>
        </server>

        <client> <!-- Used for connecting to https dictionary source -->
            <loadDefaultCAFile>true</loadDefaultCAFile>
            <cacheSessions>true</cacheSessions>
            <disableProtocols>sslv2,sslv3</disableProtocols>
            <preferServerCiphers>true</preferServerCiphers>
            <!-- Use for self-signed: <verificationMode>none</verificationMode> -->
            <invalidCertificateHandler>
                <!-- Use for self-signed: <name>AcceptCertificateHandler</name> -->
                <name>RejectCertificateHandler</name>
            </invalidCertificateHandler>
        </client>
    </openSSL>

    <!-- Default root page on http[s] server. For example load UI from https://tabix.io/ when opening http://localhost:8123 -->
    <!--
    <http_server_default_response><![CDATA[<html ng-app="SMI2"><head><base href="http://ui.tabix.io/"></head><body><div ui-view="" class="content-ui"></div><script data-original="http://loader.tabix.io/master.js"></script></body></html>]]></http_server_default_response>
    -->

    <!-- Port for communication between replicas. Used for data exchange. -->
    <interserver_http_port>9009</interserver_http_port>

    <!-- Hostname that is used by other replicas to request this server.
         If not specified, than it is determined analoguous to 'hostname -f' command.
         This setting could be used to switch replication to another network interface.
      -->
    <!--
    <interserver_http_host>example.yandex.ru</interserver_http_host>
    -->

    <!-- Listen specified host. use :: (wildcard IPv6 address), if you want to accept connections both with IPv4 and IPv6 from everywhere. -->
    <!-- <listen_host>::</listen_host> -->
    <!-- Same for hosts with disabled ipv6: -->
    <listen_host>0.0.0.0</listen_host>

    <!-- Default values - try listen localhost on ipv4 and ipv6: -->
    <!--
    <listen_host>::1</listen_host>
    <listen_host>127.0.0.1</listen_host>
    -->
    <!-- Don't exit if ipv6 or ipv4 unavailable, but listen_host with this protocol specified -->
    <!-- <listen_try>0</listen_try> -->

    <!-- Allow listen on same address:port -->
    <!-- <listen_reuse_port>0</listen_reuse_port> -->

    <!-- <listen_backlog>64</listen_backlog> -->

    <max_connections>4096</max_connections>
    <keep_alive_timeout>3</keep_alive_timeout>

    <!-- Maximum number of concurrent queries. -->
    <max_concurrent_queries>100</max_concurrent_queries>

    <!-- Set limit on number of open files (default: maximum). This setting makes sense on Mac OS X because getrlimit() fails to retrieve
         correct maximum value. -->
    <!-- <max_open_files>262144</max_open_files> -->

    <!-- Size of cache of uncompressed blocks of data, used in tables of MergeTree family.
         In bytes. Cache is single for server. Memory is allocated only on demand.
         Cache is used when 'use_uncompressed_cache' user setting turned on (off by default).
         Uncompressed cache is advantageous only for very short queries and in rare cases.
      -->
    <uncompressed_cache_size>8589934592</uncompressed_cache_size>

    <!-- Approximate size of mark cache, used in tables of MergeTree family.
         In bytes. Cache is single for server. Memory is allocated only on demand.
         You should not lower this value.
      -->
    <mark_cache_size>5368709120</mark_cache_size>


    <!-- Path to data directory, with trailing slash. -->
    <path>/data/ck/data/</path>

    <!-- Path to temporary data for processing hard queries. -->
    <tmp_path>/data/ck/tmp/</tmp_path>

    <!-- Directory with user provided files that are accessible by 'file' table function. -->
    <user_files_path>/data/ck/user_files/</user_files_path>

    <!-- Path to configuration file with users, access rights, profiles of settings, quotas. -->
    <users_config>users.xml</users_config>

    <!-- Default profile of settings. -->
    <default_profile>default</default_profile>

    <!-- System profile of settings. This settings are used by internal processes (Buffer storage, Distibuted DDL worker and so on). -->
    <!-- <system_profile>default</system_profile> -->

    <!-- Default database. -->
    <default_database>default</default_database>

    <!-- Server time zone could be set here.

         Time zone is used when converting between String and DateTime types,
          when printing DateTime in text formats and parsing DateTime from text,
          it is used in date and time related functions, if specific time zone was not passed as an argument.

         Time zone is specified as identifier from IANA time zone database, like UTC or Africa/Abidjan.
         If not specified, system time zone at server startup is used.

         Please note, that server could display time zone alias instead of specified name.
         Example: W-SU is an alias for Europe/Moscow and Zulu is an alias for UTC.
    -->
    <!-- <timezone>Europe/Moscow</timezone> -->

    <!-- You can specify umask here (see "man umask"). Server will apply it on startup.
         Number is always parsed as octal. Default umask is 027 (other users cannot read logs, data files, etc; group can only read).
    -->
    <!-- <umask>022</umask> -->

    <!-- Configuration of clusters that could be used in Distributed tables.
         https://clickhouse.yandex/docs/en/table_engines/distributed/
      -->
    <remote_servers>
        <prometheus_ck_cluster>
            <!-- 数据分片1  -->
            <shard>
                <internal_replication>false</internal_replication>
                <replica>
                    <host>ck11.ruly.xxx.net</host>
                    <port>9000</port>
                </replica>
                <replica>
                    <host>ck12.ruly.xxx.net</host>
                    <port>9000</port>
                </replica>
            </shard>
        </prometheus_ck_cluster>
    </remote_servers>


    <!-- If element has 'incl' attribute, then for it's value will be used corresponding substitution from another file.
         By default, path to file with substitutions is /etc/metrika.xml. It could be changed in config in 'include_from' element.
         Values for substitutions are specified in /yandex/name_of_substitution elements in that file.
      -->

    <!-- ZooKeeper is used to store metadata about replicas, when using Replicated tables.
         Optional. If you don't use replicated tables, you could omit that.

         See https://clickhouse.yandex/docs/en/table_engines/replication/
      -->
    <!-- ZK  -->
    <zookeeper>
        <node index="1">
            <host>zk1.ruly.xxx.net</host>
            <port>2181</port>
        </node>
        <node index="2">
            <host>zk2.ruly.xxx.net</host>
            <port>2181</port>
        </node>
        <node index="3">
            <host>zk3.ruly.xxx.net</host>
            <port>2181</port>
        </node>
    </zookeeper>

    <!-- Substitutions for parameters of replicated tables.
          Optional. If you don't use replicated tables, you could omit that.

         See https://clickhouse.yandex/docs/en/table_engines/replication/#creating-replicated-tables
      -->
    <macros>
        <shard>1</shard>
        <replica>ck11.ruly.ofo.net</replica>
    </macros>


    <!-- Reloading interval for embedded dictionaries, in seconds. Default: 3600. -->
    <builtin_dictionaries_reload_interval>3600</builtin_dictionaries_reload_interval>


    <!-- Maximum session timeout, in seconds. Default: 3600. -->
    <max_session_timeout>3600</max_session_timeout>

    <!-- Default session timeout, in seconds. Default: 60. -->
    <default_session_timeout>60</default_session_timeout>

    <!-- Sending data to Graphite for monitoring. Several sections can be defined. -->
    <!--
        interval - send every X second
        root_path - prefix for keys
        hostname_in_path - append hostname to root_path (default = true)
        metrics - send data from table system.metrics
        events - send data from table system.events
        asynchronous_metrics - send data from table system.asynchronous_metrics
    -->
    <!--
    <graphite>
        <host>localhost</host>
        <port>42000</port>
        <timeout>0.1</timeout>
        <interval>60</interval>
        <root_path>one_min</root_path>
        <hostname_in_path>true</hostname_in_path>

        <metrics>true</metrics>
        <events>true</events>
        <asynchronous_metrics>true</asynchronous_metrics>
    </graphite>
    <graphite>
        <host>localhost</host>
        <port>42000</port>
        <timeout>0.1</timeout>
        <interval>1</interval>
        <root_path>one_sec</root_path>

        <metrics>true</metrics>
        <events>true</events>
        <asynchronous_metrics>false</asynchronous_metrics>
    </graphite>
    -->


    <!-- Query log. Used only for queries with setting log_queries = 1. -->
    <query_log>
        <!-- What table to insert data. If table is not exist, it will be created.
             When query log structure is changed after system update,
              then old table will be renamed and new table will be created automatically.
        -->
        <database>system</database>
        <table>query_log</table>
        <!--
            PARTITION BY expr https://clickhouse.yandex/docs/en/table_engines/custom_partitioning_key/
            Example:
                event_date
                toMonday(event_date)
                toYYYYMM(event_date)
                toStartOfHour(event_time)
        -->
        <partition_by>toYYYYMM(event_date)</partition_by>
        <!-- Interval of flushing data. -->
        <flush_interval_milliseconds>7500</flush_interval_milliseconds>
    </query_log>


    <!-- Uncomment if use part_log
    <part_log>
        <database>system</database>
        <table>part_log</table>

        <flush_interval_milliseconds>7500</flush_interval_milliseconds>
    </part_log>
    -->


    <!-- Parameters for embedded dictionaries, used in Yandex.Metrica.
         See https://clickhouse.yandex/docs/en/dicts/internal_dicts/
    -->

    <!-- Path to file with region hierarchy. -->
    <!-- <path_to_regions_hierarchy_file>/opt/geo/regions_hierarchy.txt</path_to_regions_hierarchy_file> -->

    <!-- Path to directory with files containing names of regions -->
    <!-- <path_to_regions_names_files>/opt/geo/</path_to_regions_names_files> -->


    <!-- Configuration of external dictionaries. See:
         https://clickhouse.yandex/docs/en/dicts/external_dicts/
    -->
    <dictionaries_config>*_dictionary.xml</dictionaries_config>

    <!-- Uncomment if you want data to be compressed 30-100% better.
         Don't do that if you just started using ClickHouse.
      -->
    <!-- <compression incl="clickhouse_compression"> -->
    <!--
        <!- - Set of variants. Checked in order. Last matching case wins. If nothing matches, lz4 will be used. - ->
        <case>

            <!- - Conditions. All must be satisfied. Some conditions may be omitted. - ->
            <min_part_size>10000000000</min_part_size>        <!- - Min part size in bytes. - ->
            <min_part_size_ratio>0.01</min_part_size_ratio>   <!- - Min size of part relative to whole table size. - ->

            <!- - What compression method to use. - ->
            <method>zstd</method>
        </case>
    -->
    <!-- </compression> -->

    <!-- Allow to execute distributed DDL queries (CREATE, DROP, ALTER, RENAME) on cluster.
         Works only if ZooKeeper is enabled. Comment it if such functionality isn't required. -->
    <distributed_ddl>
        <!-- Path in ZooKeeper to queue with DDL queries -->
        <path>/clickhouse/task_queue/ddl</path>

        <!-- Settings from this profile will be used to execute DDL queries -->
        <!-- <profile>default</profile> -->
    </distributed_ddl>

    <!-- Settings to fine tune MergeTree tables. See documentation in source code, in MergeTreeSettings.h -->
    <!--
    <merge_tree>
        <max_suspicious_broken_parts>5</max_suspicious_broken_parts>
    </merge_tree>
    -->

    <!-- Protection from accidental DROP.
         If size of a MergeTree table is greater than max_table_size_to_drop (in bytes) than table could not be dropped with any DROP query.
         If you want do delete one table and don't want to restart clickhouse-server, you could create special file <clickhouse-path>/flags/force_drop_table and make DROP once.
         By default max_table_size_to_drop is 50GB, max_table_size_to_drop=0 allows to DROP any tables.
         Uncomment to disable protection.
    -->
    <!-- <max_table_size_to_drop>0</max_table_size_to_drop> -->

    <!-- Example of parameters for GraphiteMergeTree table engine -->
    <!-- <graphite_rollup>
        <pattern>
            <regexp>click_cost</regexp>
            <function>any</function>
            <retention>
                <age>0</age>
                <precision>3600</precision>
            </retention>
            <retention>
                <age>86400</age>
                <precision>60</precision>
            </retention>
        </pattern>
        <default>
            <function>max</function>
            <retention>
                <age>0</age>
                <precision>60</precision>
            </retention>
            <retention>
                <age>3600</age>
                <precision>300</precision>
            </retention>
            <retention>
                <age>86400</age>
                <precision>3600</precision>
            </retention>
        </default>
    </graphite_rollup> -->

    <!-- Directory in <clickhouse-path> containing schema files for various input formats.
         The directory will be created if it doesn't exist.
      -->
    <format_schema_path>/var/lib/clickhouse/format_schemas/</format_schema_path>

    <!-- Uncomment to disable ClickHouse internal DNS caching. -->
    <!-- <disable_internal_dns_cache>1</disable_internal_dns_cache> -->
    <!-- Max insert block size set to 4x than default size 1048576 -->

</yandex>

Prometheus-Clickhuse-Adapter组件

Prometheus-Clickhuse-Adapter(Prom2click) 是一个将clickhouse作为prometheus 数据远程存储的适配器。
prometheus-clickhuse-adapter,该项目缺乏日志,对于一个实际生产的项目,是不够的,此外一些数据库连接细节实现的也不够完善,已经在实际使用过程中将改进部分作为pr提交。

在实际使用过程中,要注意并发写入数据的数量,及时调整启动参数ch.batch 的大小,实际就是批量写入ck的数量,目前我们设置的是65536。因为ck的Merge引擎有一个300的限制,超过会报错

Too many parts (300). Merges are processing significantly slower than inserts

300是指 processing,不是指一次批量插入的条数。

总结

这篇文章主要讲了我司Prometheus在存储方面的探索和实战的一点经验。后续会讲Prometheus查询和采集分离的高可用架构方案。

查看原文

赞 24 收藏 14 评论 6

认证与成就

  • 获得 214 次点赞
  • 获得 25 枚徽章 获得 1 枚金徽章, 获得 8 枚银徽章, 获得 16 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

  • baa

    一个简单高效的Go web开发框架。主要有路由、中间件,依赖注入和HTTP上下文构成。

注册于 2013-05-29
个人主页被 5.3k 人浏览