精华内容
下载资源
问答
  • promet.github.com-源码

    2021-05-25 01:00:33
    git clone git@github.com:promet/promet.github.com.git blog cd blog bundle install 用法 永远不要推高手 以与更新代码相同的方式更新博客 中的帖子是./source/_posts 分支master或在本地工作以获取更长的文章 ...
  • Promet ERP是一个ERP系统,除了ERP功能还提供消息管理、任务/项目管理、文档管理。 任何功能也可以单独使用。 所有数据都存储在一个中央数据库中。 MSSQL、PostgereSQL、Firebird、SQLite 是受支持的数据库系统。 ...
  • Promet ERP是一个ERP系统,除了ERP功能外,还提供消息管理,任务/项目管理,文档管理。 任何功能也可以单独使用。 所有数据都存储在中央数据库中。 MSSQL,PostgereSQL,Firebird,SQLite是受支持的数据库系统。 ...
  • 这篇blog名字起的搞得和我写论文一样,xxxx的设计与实现。其实这个东西原理很简单,kubernetes的hpa使用的是heapster,heapster是k8s那帮家伙在搞的,所以...如果按照写论文的方式,我这边应该分别介绍一下k8s和promet

    这篇blog名字起的搞得和我写论文一样,xxxx的设计与实现。其实这个东西原理很简单,kubernetes的hpa使用的是heapster,heapster是k8s那帮家伙在搞的,所以k8s还是喜欢自己搞的东西,所以k8s的hpa默认使用的heapster,但在业内,还有一个比heapster更好的监控方案,那就是prometheus。如果按照写论文的方式,我这边应该分别介绍一下k8s和prometheus,但真的没有那个闲功夫,在此略过,我之前blog也做过它们的源码分析。
    这里写图片描述
    上面的图片展示了整个体系结构
    下面就分析一下adapter的具体实现,这里需要结合上一篇blog关于api聚合的功能,这个adapter就是通过api聚合的方式注册到apiserver上面。
    先看一个hpa的例子

    kind: HorizontalPodAutoscaler
    apiVersion: autoscaling/v2alpha1
    metadata:
      name: wordpress
      namespace: default
    spec:
      scaleTargetRef:
        apiVersion: apps/v1beta1
        kind: Deployment
        name: wordpress
      minReplicas: 1
      maxReplicas: 3
      metrics:
      - type: Pods
        pods:
          metricName: memory_usage_bytes
          targetAverageValue: 100000

    上面的代码添加一个内存使用量的hpa例子,通过targetAverageValue去设定阈值还有最大最小副本数,以及管理的deployment。在此额外说一下,hpa支持三种指标,分别是Object:描述k8s对象的某种指标,譬如ingress的hits-per-second。Pods:pod的平均指标,譬如transactions-processed-per-second,描述每个pod的事务每秒事务数,Resource是描述pod资源用量,譬如CPU或者内存。下面举一个object的例子,pod的http请求数。

      - type: Object
        object:
          target:
            kind: Service
            name: sample-metrics-app
          metricName: http_requests
          targetValue: 100

    先从hpa代码开始pkg/controller/podautoscaler/horizontal.go,里面的computeReplicasForMetrics方法,它是负责获取监控指标并且计算副本数的方法。针对不同资源调用不同的方法:

    case autoscalingv2.ObjectMetricSourceType:
    GetObjectMetricReplicas
    ...
    
    case autoscalingv2.PodsMetricSourceType:
    GetMetricReplicas
    
    case autoscalingv2.ResourceMetricSourceType:
    GetRawResourceReplicas

    先看看GetObjectMetricReplicas这个方法
    pkg/controller/podautoscaler/replica_calculator.go

    func (c *ReplicaCalculator) GetObjectMetricReplicas(currentReplicas int32, targetUtilization int64, metricName string, namespace string, objectRef *autoscaling.CrossVersionObjectReference) (replicaCount int32, utilization int64, timestamp time.Time, err error) {
        utilization, timestamp, err = c.metricsClient.GetObjectMetric(metricName, namespace, objectRef)
        if err != nil {
            return 0, 0, time.Time{}, fmt.Errorf("unable to get metric %s: %v on %s %s/%s", metricName, objectRef.Kind, namespace, objectRef.Name, err)
        }
    
        usageRatio := float64(utilization) / float64(targetUtilization)
        if math.Abs(1.0-usageRatio) <= c.tolerance {
            // return the current replicas if the change would be too small
            return currentReplicas, utilization, timestamp, nil
        }
    
        return int32(math.Ceil(usageRatio * float64(currentReplicas))), utilization, timestamp, nil
    }

    GetObjectMetric是一个接口,有两个方法,就是上面图所示的heapster和自定义custom接口。heapster这个就是调用heapster接口去获取性能指标,本blog着重介绍自定义性能指标,在启动controller-manager时候指定–horizontal-pod-autoscaler-use-rest-clients就可以使用自定义的性能指标了
    pkg/controller/podautoscaler/metrics/rest_metrics_client.go

    func (c *customMetricsClient) GetObjectMetric(metricName string, namespace string, objectRef *autoscaling.CrossVersionObjectReference) (int64, time.Time, error) {
        gvk := schema.FromAPIVersionAndKind(objectRef.APIVersion, objectRef.Kind)
        var metricValue *customapi.MetricValue
        var err error
        if gvk.Kind == "Namespace" && gvk.Group == "" {
            // handle namespace separately
            // NB: we ignore namespace name here, since CrossVersionObjectReference isn't
            // supposed to allow you to escape your namespace
            metricValue, err = c.client.RootScopedMetrics().GetForObject(gvk.GroupKind(), namespace, metricName)
        } else {
            metricValue, err = c.client.NamespacedMetrics(namespace).GetForObject(gvk.GroupKind(), objectRef.Name, metricName)
        }
    
        if err != nil {
            return 0, time.Time{}, fmt.Errorf("unable to fetch metrics from API: %v", err)
        }
    
        return metricValue.Value.MilliValue(), metricValue.Timestamp.Time, nil
    }

    上面的objectRef针对本blog只为
    {Kind:Service,Name:wordpress,APIVersion:,},就是我们在yaml文件里面metrics里面定义。

    上面通过vendor/k8s.io/metrics/pkg/client/custom_metrics/client.go

    func (m *rootScopedMetrics) GetForObject(groupKind schema.GroupKind, name string, metricName string) (*v1alpha1.MetricValue, error) {
        // handle namespace separately
        if groupKind.Kind == "Namespace" && groupKind.Group == "" {
            return m.getForNamespace(name, metricName)
        }
    
        resourceName, err := m.client.qualResourceForKind(groupKind)
        if err != nil {
            return nil, err
        }
    
        res := &v1alpha1.MetricValueList{}
        err = m.client.client.Get().
            Resource(resourceName).
            Name(name).
            SubResource(metricName).
            Do().
            Into(res)
    
        if err != nil {
            return nil, err
        }
    
        if len(res.Items) != 1 {
            return nil, fmt.Errorf("the custom metrics API server returned %v results when we asked for exactly one", len(res.Items))
        }
    
        return &res.Items[0], nil
    }

    通过client发送https请求获取metrics。具体发送如下所示object:

    https://localhost:6443/apis/custom-metrics.metrics.k8s.io/v1alpha1/namespaces/default/services/wordpress/requests-per-second

    如果是pod则发送的请求是

    https://localhost:6443/apis/custom-metrics.metrics.k8s.io/v1alpha1/namespaces/default/pods/%2A/memory_usage_bytes?labelSelector=app%3Dwordpress%2Ctier%3Dfrontend

    至于group为啥是custom-metrics.metrics.k8s.io这个不是别的,是代码里面写死的,vendor/k8s.io/metrics/pkg/apis/custom_metrics/v1alpha1/register.go

    // GroupName is the group name use in this package
    const GroupName = "custom-metrics.metrics.k8s.io"
    
    // SchemeGroupVersion is group version used to register these objects
    var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}

    这样k8s的部分已经讲解完毕。下面就是adapter的部分了。

    展开全文
  • prometheus简介

    2021-06-16 00:56:33
    prometheus简介:https://www.cnblogs.com/yangxiaoyi/p/7398156.html作者:Go_小易1.1 什么是prometheus?Promet...

    prometheus简介:https://www.cnblogs.com/yangxiaoyi/p/7398156.html

    作者:Go_小易

    1.1 什么是prometheus?

    Prometheus是一个开源监控系统,它前身是SoundCloud的警告工具包。从2012年开始,许多公司和组织开始使用Prometheus。该项目的开发人员和用户社区非常活跃,越来越多的开发人员和用户参与到该项目中。目前它是一个独立的开源项目,且不依赖与任何公司。为了强调这点和明确该项目治理结构,Prometheus在2016年继Kurberntes之后,加入了Cloud Native Computing Foundation。主要具有如下功能:

    1. 多维 数据模型(时序由 metric 名字和 k/v 的 labels 构成)。

    2. 灵活的查询语句(PromQL)。

    3. 无依赖存储,支持 local 和 remote 不同模型。

    4. 采用 http 协议,使用 pull 模式,拉取数据,简单易懂。

    5. 监控目标,可以采用服务发现或静态配置的方式。

    6. 支持多种统计数据模型,图形化友好。

    1.2 核心架构

            我们将通过prometheus的基础结构来详细了解,他的功能以及如何实现监控、告警的。如下如所示:

    从这个架构图,也可以看出 Prometheus 的主要模块包含, prometheus server, exporters, pushgateway, PromQL, Alertmanager, WebUI 等。下面我就简单介绍各个组件实现的功能:

    1. prometheus server: 定期从静态配置的 targets 或者服务发现(主要是DNS、consul、k8s、mesos等)的 targets 拉取数据。

    2. exporters:负责向prometheus server做数据汇报的程序统。而不同的数据汇报由不同的exporters实现,比如监控主机有node-exporters,mysql有MySQL server exporter,更多请参考链接。

    3. pushgateway:主要使用场景为:Prometheus 采用 pull 模式,可能由于不在一个子网或者防火墙原因,导致 Prometheus 无法直接拉取各个 target 数据。在监控业务数据的时候,需要将不同数据汇总, 由 Prometheus 统一收集。总结:实现类似于zabbix-proxy功能;

    4. Alertmanager:实现prometheus的告警功能。

    5. webui:主要通过grafana来实现webui展示。

    1.3 适用场景

    Prometheus在记录纯数字时间序列方面表现非常好。它既适用于面向服务器等硬件指标的监控,也适用于高动态的面向服务架构的监控。对于现在流行的微服务,Prometheus的多维度数据收集和数据筛选查询语言也是非常的强大。Prometheus是为服务的可靠性而设计的,当服务出现故障时,它可以使你快速定位和诊断问题。它的搭建过程对硬件和服务没有很强的依赖关系。

    Prometheus,它的价值在于可靠性,甚至在很恶劣的环境下,你都可以随时访问它和查看系统服务各种指标的统计信息。如果你对统计数据需要100%的精确,它并不适用,例如:它不适用于实时计费系统。

    二、基础概念

    2.1 数据模型

    Prometheus 存储的是时序数据, 即按照相同时序(相同的名字和标签),以时间维度存储连续的数据的集合。时序(time series) 是由名字(Metric),以及一组 key/value 标签定义的,具有相同的名字以及标签属于相同时序。时序的名字由 ASCII 字符,数字,下划线,以及冒号组成,它必须满足正则表达式 [a-zA-Z_:][a-zA-Z0-9_:]*, 其名字应该具有语义化,一般表示一个可以度量的指标,例如 http_requests_total, 可以表示 http 请求的总数。

    时序的标签可以使 Prometheus 的数据更加丰富,能够区分具体不同的实例,例如 http_requests_total{method="POST"} 可以表示所有 http 中的 POST 请求。标签名称由 ASCII 字符,数字,以及下划线组成, 其中 __ 开头属于 Prometheus 保留,标签的值可以是任何 Unicode 字符,支持中文。

    2.2 时序4种类型

            Prometheus 时序数据分为 Counter, Gauge, Histogram, Summary 四种类型。

    1. Counter:表示收集的数据是按照某个趋势(增加/减少)一直变化的,我们往往用它记录服务请求总量,错误总数等。例如 Prometheus server 中 http_requests_total, 表示 Prometheus 处理的 http 请求总数,我们可以使用data, 很容易得到任意区间数据的增量。

    2. Gauge:表示搜集的数据是一个瞬时的,与时间没有关系,可以任意变高变低,往往可以用来记录内存使用率、磁盘使用率等。

    3. Histogram:Histogram 由 <basename>_bucket{le="<upper inclusive bound>"},<basename>_bucket{le="+Inf"}, <basename>_sum,<basename>_count 组成,主要用于表示一段时间范围内对数据进行采样,(通常是请求持续时间或响应大小),并能够对其指定区间以及总数进行统计,通常我们用它计算分位数的直方图。

    4. Summary:Summary 和 Histogram 类似,由 <basename>{quantile="<φ>"},<basename>_sum,<basename>_count组成,主要用于表示一段时间内数据采样结果,(通常是请求持续时间或响应大小),它直接存储了 quantile 数据,而不是根据统计区间计算出来的。区别在于:

          a. 都包含 <basename>_sum,<basename>_count。

                   b. Histogram 需要通过 <basename>_bucket 计算 quantile, 而 Summary 直接存储了 quantile 的值。

    2.3 总结

    prometheus是属于下一代监控,现在企业中大部分通过使用zabbix来实现主机、服务、设备的监控。与zabbix相比,zabbix还是存在一定的优势,比如丰富的插件、webui能完成大部分工作,而prometheus更多的配置是通过配置文件还实现,并且prometheus相当消耗资源。建议在使用的过程中,认真对比慎重选择,如果使用prometheus,就要配置更好的服务器资源,因为它的监控粒度更细,需要计算相关数值,最好使用SSD硬盘来提高性能。

    展开全文
  • prometheus-example-queries, 简单的地方让人们提供他们发现的查询示例 命令行目普罗米修斯非常棒,但是人类的大脑在PromQL中无法工作。 这个存储库的意图是为人们提供他们有用的...进一步阅读普罗米修斯主站点Promet
  • DhcpWatcher 观看DHCP文件并发布带有Prometheus度量标准的租约分配。 然后可以将其可视化为grafana上的grafana 。 指令 # run tests mix test ...ansible-playbook -i playbooks/hosts playbooks/promet
  • 聚焦源代码安全,网罗国内外最新资讯!编译:奇安信代码卫士团队思科Talos团队和Bitdefender公司分别发布报告指出,APT组织StrongPity(又称Promet...

     聚焦源代码安全,网罗国内外最新资讯!

    编译:奇安信代码卫士团队

    思科 Talos 团队和 Bitdefender公司分别发布报告指出,APT 组织 StrongPity(又称 Promethium)又对叙利亚库尔德族地区和土耳其发动水坑式攻击,实现监控和情报收集目的。

    Bitdefender 公司在报告中指出,StrongPity APT 组织利用新技术控制受陷机器,“该 APT 组织通过水坑式攻击选择性地感染受害者并部署三层 C&C 基础设施,阻挠取证调查,并使用木马化的流行工具如文档、文件恢复应用程序、远程连接应用程序、工具甚至是安全软件广泛涵盖目标受害者可能的去处。”

    Bitdefender 公司表示,攻击活动中使用的恶意软件样本的时间戳和土耳其于去年10月在叙利亚东北地区发动“和平之春行动”中使用的恶意软件一致。

    利用污染的安装程序释放恶意软件

    StrongPity APT 组织首次公开现身于2016年10月,但实际上早在2012年就已经活跃。2016年10月,该组织发动水坑式攻击向比利时和意大利用户传播恶意版本的 WinRAR 和 TrueCrypt 文件加密软件。

    之后,StrongPity APT 组织被指在2018年滥用土耳其电信公司网络将位于土耳其和叙利亚的数百名用户重定向至恶意软件版本。这样,当目标用户试图从官方网站下载合法应用时,攻击者就会通过水坑式攻击或 HTTP 重定向攻陷系统。

    去年7月份,AT&T Alien Labs 发现一款新型间谍软件活动利用木马化的 WinBox 路由器管理软件和 WinRAR 文档安装 StrongPity 并和攻击者的基础设施通信。

    Bitdefender 公司发现的这种新型攻击方法仍然不变:通过利用本地化软件集合体和共享器上托管的遭篡改的安装程序(包括 McAfee Security Scan Plus、Recuva、TeamViewer、WhatsApp和Piriform CCleaner)使用预定义 IP 列表发动攻击。

    研究人员表示,“耐人寻味的是,所有与受污染应用程序有关的文件都是在周一至周五的正常上班时间(UTC 时间早九晚六)编译的。这更加印证了 StrongPity 组织是受资助且有组织的开发团队,他们有偿开发某些项目。”

    下载并执行恶意软件释放器后,就会安装后门,从而和命令和控制服务器建立通信,以便提取文档并检索将被执行的命令。

    攻击者还会在受害者机器上部署“文件搜索器”组件,循环遍历每个驱动器并从中查找具有特定扩展名的文件(例如 Microsoft Office 文档)并以 ZIP 文档形式提取。该 ZIP 文件之后被拆分为多个隐藏的 “.sft” 加密文件,并被发送至 C&C 服务器,并最终从磁盘删除以隐藏提取痕迹。

    受攻击的不止叙利亚和土耳其

    攻击者虽然再次攻击叙利亚和土耳其,但这两个国家并非唯一目标,他们利用恶意版本的 Firefox、VPNpro、DriverPack 和 5kPlayer 还攻击位于哥伦比亚、印度、加拿大和越南的受害者。

    思科 Talos 团队的研究人员将该 APT 组织使用的恶意软件命名为 “StrongPity3”,它使用名为 “winprint32.exe” 模块搜索文档并传输所收集的文件。另外,虚假的火狐安装程序还会在释放恶意软件之前检查受害者是否安装 ESET 或 BitDefender 杀毒软件。

    研究人员表示,“这些特征可解读为该威胁行动者实际上是受雇者提供的企业服务的一个组成部分。我们认为每款恶意软件都极其相似但却通过微小改动后即可用于不同目标,因此它是一款专业的打包解决方案。”

     


    推荐阅读

    希腊和土耳其黑客互相攻击对方媒体机构

    天黑请关灯,特朗普也不例外:间谍通过观察灯光振动实施监听

    强大的间谍软件 FinSpy 被指攻击缅甸 iOS 和安卓用户

    原文链接

    https://thehackernews.com/2020/06/strongpity-syria-turkey-hackers.html

    题图:Pixabay License

    本文由奇安信代码卫士编译,不代表奇安信观点。转载请注明“转自奇安信代码卫士 www.codesafe.cn”。

    奇安信代码卫士 (codesafe)

    国内首个专注于软件开发安全的

    产品线。

        点个 “在看” ,加油鸭~

    展开全文
  • -l = traz删除了criação,permissões,prometárioe grupo和Tamanho的数据。 -a ou -all =包含arquivos ocultos na listagem 垂直或正常使用情况。 pwd Mudando dediretório cd [diretório] cd .. - Volta ...
  • 关注“Java后端技术栈”回复“面试”获取最新资料回复“加群”邀您进技术交流群JVM应用度量框架Micrometer实战前提spring-actuator做度量统计收集,使用Promet...

    关注Java后端技术栈

    回复“面试”获取最新资料

    回复“加群”邀您进技术交流群

    JVM应用度量框架Micrometer实战

    前提

    spring-actuator做度量统计收集,使用Prometheus(普罗米修斯)进行数据收集,Grafana(增强ui)进行数据展示,用于监控生成环境机器的性能指标和业务数据指标。一般,我们叫这样的操作为”埋点”。SpringBoot中的依赖spring-actuator中集成的度量统计API使用的框架是Micrometer,官网是Micrometer.io。

    在实践中发现了业务开发者滥用了Micrometer的度量类型Counter,导致无论什么情况下都只使用计数统计的功能。这篇文章就是基于Micrometer分析其他的度量类型API的作用和适用场景。

    Micrometer提供的度量类库

    Meter是指一组用于收集应用中的度量数据的接口,Meter单词可以翻译为”米”或者”千分尺”,但是显然听起来都不是很合理,因此下文直接叫Meter,理解它为度量接口即可。Meter是由MeterRegistry创建和保存的,可以理解MeterRegistry是Meter的工厂和缓存中心,一般而言每个JVM应用在使用Micrometer的时候必须创建一个MeterRegistry的具体实现。

    Micrometer中,Meter的具体类型包括:Timer,Counter,Gauge,DistributionSummary,LongTaskTimer,FunctionCounter,FunctionTimer和TimeGauge。

    下面分节详细介绍这些类型的使用方法和实战使用场景。而一个Meter具体类型需要通过名字和Tag(这里指的是Micrometer提供的Tag接口)作为它的唯一标识,这样做的好处是可以使用名字进行标记,通过不同的Tag去区分多种维度进行数据统计。

    MeterRegistry

    MeterRegistry在Micrometer是一个抽象类,主要实现包括:

    • SimpleMeterRegistry:每个Meter的最新数据可以收集到SimpleMeterRegistry实例中,但是这些数据不会发布到其他系统,也就是数据是位于应用的内存中的。

    • CompositeMeterRegistry:多个MeterRegistry聚合,内部维护了一个MeterRegistry的列表。

    • 全局的MeterRegistry:工厂类io.micrometer.core.instrument.Metrics中持有一个静态final的CompositeMeterRegistry实例globalRegistry。

    当然,使用者也可以自行继承MeterRegistry去实现自定义的MeterRegistry。SimpleMeterRegistry适合做调试的时候使用,它的简单使用方式如下:

    MeterRegistry registry = new SimpleMeterRegistry(); 
    Counter counter = registry.counter("counter");
    counter.increment();
    

    CompositeMeterRegistry实例初始化的时候,内部持有的MeterRegistry列表是空的,如果此时用它新增一个Meter实例,Meter实例的操作是无效的

    CompositeMeterRegistry composite = new CompositeMeterRegistry();
    
    Counter compositeCounter = composite.counter("counter");
    compositeCounter.increment(); // <- 实际上这一步操作是无效的,但是不会报错
    
    SimpleMeterRegistry simple = new SimpleMeterRegistry();
    composite.add(simple);  // <- 向CompositeMeterRegistry实例中添加SimpleMeterRegistry实例
    
    compositeCounter.increment();  // <-计数成功
    

    全局的MeterRegistry的使用方式更加简单便捷,因为一切只需要操作工厂类Metrics的静态方法:

    Metrics.addRegistry(new SimpleMeterRegistry());
    Counter counter = Metrics.counter("counter", "tag-1", "tag-2");
    counter.increment();
    

    Tag与Meter的命名

    Micrometer中,Meter的命名约定使用英文逗号(dot,也就是”.”)分隔单词。但是对于不同的监控系统,对命名的规约可能并不相同,如果命名规约不一致,在做监控系统迁移或者切换的时候,可能会对新的系统造成破坏。

    Micrometer中使用英文逗号分隔单词的命名规则,再通过底层的命名转换接口NamingConvention进行转换,最终可以适配不同的监控系统,同时可以消除监控系统不允许的特殊字符的名称和标记等。开发者也可以覆盖NamingConvention实现自定义的命名转换规则:registry.config().namingConvention(myCustomNamingConvention);

    在Micrometer中,对一些主流的监控系统或者存储系统的命名规则提供了默认的转换方式,例如当我们使用下面的命名时候:

    MeterRegistry registry = ...
    registry.timer("http.server.requests");
    

    对于不同的监控系统或者存储系统,命名会自动转换如下:

    • Prometheus - http_server_requests_duration_seconds。

    • Atlas - httpServerRequests。

    • Graphite - http.server.requests。

    • InfluxDB - http_server_requests。

    其实NamingConvention已经提供了5种默认的转换规则:dot、snakeCase、camelCase、upperCamelCase和slashes。

    另外,Tag(标签)是Micrometer的一个重要的功能,严格来说,一个度量框架只有实现了标签的功能,才能真正地多维度进行度量数据收集。Tag的命名一般需要是有意义的,所谓有意义就是可以根据Tag的命名可以推断出它指向的数据到底代表什么维度或者什么类型的度量指标。

    假设我们需要监控数据库的调用和Http请求调用统计,一般推荐的做法是:

    MeterRegistry registry = ...
    registry.counter("database.calls", "db", "users")
    registry.counter("http.requests", "uri", "/api/users")
    

    这样,当我们选择命名为”database.calls”的计数器,我们可以进一步选择分组”db”或者”users”分别统计不同分组对总调用数的贡献或者组成。一个反例如下:

    MeterRegistry registry = ...
    registry.counter("calls",
        "class", "database",
        "db", "users");
    
    registry.counter("calls",
        "class", "http",
        "uri", "/api/users");
    

    通过命名”calls”得到的计数器,由于标签混乱,数据是基本无法分组统计分析,这个时候可以认为得到的时间序列的统计数据是没有意义的。可以定义全局的Tag,也就是全局的Tag定义之后,会附加到所有的使用到的Meter上(只要是使用同一MeterRegistry),全局的Tag可以这样定义:

    MeterRegistry registry = ...
    registry.counter("calls",
        "class", "database",
        "db", "users");
    
    registry.counter("calls",
        "class", "http",
        "uri", "/api/users");
    
    
    MeterRegistry registry = ...
    registry.config().commonTags("stack", "prod", "region", "us-east-1");
    // 和上面的意义是一样的
    registry.config().commonTags(Arrays.asList(Tag.of("stack", "prod"), Tag.of("region", "us-east-1")));
    

    像上面这样子使用,就能通过主机,实例,区域,堆栈等操作环境进行多维度深入分析。更多springboot实战文章:SpringBoot内容聚合

    还有两点点需要注意:

    1、Tag的值必须不为null。

    2、Micrometer中,Tag必须成对出现,也就是Tag必须设置为偶数个,实际上它们以Key=Value的形式存在,具体可以看io.micrometer.core.instrument.Tag接口:

    public interface Tag extends Comparable<Tag> {
        String getKey();
    
        String getValue();
    
        static Tag of(String key, String value) {
            return new ImmutableTag(key, value);
        }
    
        default int compareTo(Tag o) {
            return this.getKey().compareTo(o.getKey());
        }
    }
    

    当然,有些时候,我们需要过滤一些必要的标签或者名称进行统计,或者为Meter的名称添加白名单,这个时候可以使用MeterFilter。MeterFilter本身提供一些列的静态方法,多个MeterFilter可以叠加或者组成链实现用户最终的过滤策略。例如:

    MeterRegistry registry = ...
    registry.config()
        .meterFilter(MeterFilter.ignoreTags("http"))
        .meterFilter(MeterFilter.denyNameStartsWith("jvm"));
    

    表示忽略”http”标签,拒绝名称以”jvm”字符串开头的Meter。更多用法可以参详一下MeterFilter这个类。

    Meter的命名和Meter的Tag相互结合,以命名为轴心,以Tag为多维度要素,可以使度量数据的维度更加丰富,便于统计和分析。

    Meters

    前面提到Meter主要包括:Timer,Counter,Gauge,DistributionSummary,LongTaskTimer,FunctionCounter,FunctionTimer和TimeGauge。下面逐一分析它们的作用和个人理解的实际使用场景(应该说是生产环境)。

    Counter

    Counter是一种比较简单的Meter,它是一种单值的度量类型,或者说是一个单值计数器。Counter接口允许使用者使用一个固定值(必须为正数)进行计数。准确来说:Counter就是一个增量为正数的单值计数器。这个举个很简单的使用例子:

    使用场景:

    Counter的作用是记录XXX的总量或者计数值,适用于一些增长类型的统计,例如下单、支付次数、Http请求总量记录等等,通过Tag可以区分不同的场景,对于下单,可以使用不同的Tag标记不同的业务来源或者是按日期划分,对于Http请求总量记录,可以使用Tag区分不同的URL。用下单业务举个例子:

    //实体
    @Data
    public class Order {
    
        private String orderId;
        private Integer amount;
        private String channel;
        private LocalDateTime createTime;
    }
    
    
    public class CounterMain {
    
        private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    
        static {
                Metrics.addRegistry(new SimpleMeterRegistry());
            }
    
            public static void main(String[] args) throws Exception {
                Order order1 = new Order();
                order1.setOrderId("ORDER_ID_1");
                order1.setAmount(100);
                order1.setChannel("CHANNEL_A");
                order1.setCreateTime(LocalDateTime.now());
                createOrder(order1);
                Order order2 = new Order();
                order2.setOrderId("ORDER_ID_2");
                order2.setAmount(200);
                order2.setChannel("CHANNEL_B");
                order2.setCreateTime(LocalDateTime.now());
                createOrder(order2);
                Search.in(Metrics.globalRegistry).meters().forEach(each -> {
                    StringBuilder builder = new StringBuilder();
                    builder.append("name:")
                            .append(each.getId().getName())
                            .append(",tags:")
                            .append(each.getId().getTags())
                            .append(",type:").append(each.getId().getType())
                            .append(",value:").append(each.measure());
                    System.out.println(builder.toString());
                });
        }
    
        private static void createOrder(Order order) {
            //忽略订单入库等操作
            Metrics.counter("order.create",
                    "channel", order.getChannel(),
                    "createTime", FORMATTER.format(order.getCreateTime())).increment();
        }
    }
    

    控制台输出

    name:order.create,tags:[tag(channel=CHANNEL_A), tag(createTime=2018-11-10)],type:COUNTER,value:[Measurement{statistic='COUNT', value=1.0}]
    name:order.create,tags:[tag(channel=CHANNEL_B), tag(createTime=2018-11-10)],type:COUNTER,value:[Measurement{statistic='COUNT', value=1.0}]
    

    上面的例子是使用全局静态方法工厂类Metrics去构造Counter实例,实际上,io.micrometer.core.instrument.Counter接口提供了一个内部建造器类Counter.Builder去实例化Counter,Counter.Builder的使用方式如下:

    public class CounterBuilderMain {
    
            public static void main(String[] args) throws Exception{
                Counter counter = Counter.builder("name")  //名称
                        .baseUnit("unit") //基础单位
                        .description("desc") //描述
                        .tag("tagKey", "tagValue")  //标签
                        .register(new SimpleMeterRegistry());//绑定的MeterRegistry
                counter.increment();
            }
    }
    

    FunctionCounter

    FunctionCounter是Counter的特化类型,它把计数器数值增加的动作抽象成接口类型ToDoubleFunction,这个接口JDK1.8中对于Function的特化类型接口。

    FunctionCounter的使用场景和Counter是一致的,这里介绍一下它的用法:

    public class FunctionCounterMain {
    
            public static void main(String[] args) throws Exception {
                MeterRegistry registry = new SimpleMeterRegistry();
                AtomicInteger n = new AtomicInteger(0);
                //这里ToDoubleFunction匿名实现其实可以使用Lambda表达式简化为AtomicInteger::get
                FunctionCounter.builder("functionCounter", n, new ToDoubleFunction<AtomicInteger>() {
                    @Override
                    public double applyAsDouble(AtomicInteger value) {
                        return value.get();
                    }
                }).baseUnit("function")
                        .description("functionCounter")
                        .tag("createOrder", "CHANNEL-A")
                        .register(registry);
                //下面模拟三次计数        
                n.incrementAndGet();
                n.incrementAndGet();
                n.incrementAndGet();
            }
    }
    

    FunctionCounter使用的一个明显的好处是,我们不需要感知FunctionCounter实例的存在,实际上我们只需要操作作为FunctionCounter实例构建元素之一的AtomicInteger实例即可,这种接口的设计方式在很多框架里面都可以看到。更多springboot实战文章:SpringBoot内容聚合

    Timer

    Timer(计时器)适用于记录耗时比较短的事件的执行时间,通过时间分布展示事件的序列和发生频率。所有的Timer的实现至少记录了发生的事件的数量和这些事件的总耗时,从而生成一个时间序列。

    Timer的基本单位基于服务端的指标而定,但是实际上我们不需要过于关注Timer的基本单位,因为Micrometer在存储生成的时间序列的时候会自动选择适当的基本单位。Timer接口提供的常用方法如下:

    public interface Timer extends Meter {
        ...
        void record(long var1, TimeUnit var3);
    
        default void record(Duration duration) {
            this.record(duration.toNanos(), TimeUnit.NANOSECONDS);
        }
    
        <T> T record(Supplier<T> var1);
    
        <T> T recordCallable(Callable<T> var1) throws Exception;
    
        void record(Runnable var1);
    
        default Runnable wrap(Runnable f) {
            return () -> {
                this.record(f);
            };
        }
    
        default <T> Callable<T> wrap(Callable<T> f) {
            return () -> {
                return this.recordCallable(f);
            };
        }
    
        long count();
    
        double totalTime(TimeUnit var1);
    
        default double mean(TimeUnit unit) {
            return this.count() == 0L ? 0.0D : this.totalTime(unit) / (double)this.count();
        }
    
        double max(TimeUnit var1);
        ...
    }
    

    实际上,比较常用和方便的方法是几个函数式接口入参的方法:

    Timer timer = ...
    timer.record(() -> dontCareAboutReturnValue());
    timer.recordCallable(() -> returnValue());
    
    Runnable r = timer.wrap(() -> dontCareAboutReturnValue());
    Callable c = timer.wrap(() -> returnValue());
    

    使用场景:

    根据个人经验和实践,总结如下:

    • 记录指定方法的执行时间用于展示。

    • 记录一些任务的执行时间,从而确定某些数据来源的速率,例如消息队列消息的消费速率等。

    这里举个实际的例子,要对系统做一个功能,记录指定方法的执行时间,还是用下单方法做例子:

    public class TimerMain {
    
            private static final Random R = new Random();
    
            static {
                Metrics.addRegistry(new SimpleMeterRegistry());
            }
    
            public static void main(String[] args) throws Exception {
                Order order1 = new Order();
                order1.setOrderId("ORDER_ID_1");
                order1.setAmount(100);
                order1.setChannel("CHANNEL_A");
                order1.setCreateTime(LocalDateTime.now());
                Timer timer = Metrics.timer("timer", "createOrder", "cost");
                timer.record(() -> createOrder(order1));
            }
    
            private static void createOrder(Order order) {
                try {
                    TimeUnit.SECONDS.sleep(R.nextInt(5)); //模拟方法耗时
                } catch (InterruptedException e) {
                    //no-op
                }
            }
    }
    

    在实际生产环境中,可以通过spring-aop把记录方法耗时的逻辑抽象到一个切面中,这样就能减少不必要的冗余的模板代码。上面的例子是通过Mertics构造Timer实例,实际上也可以使用Builder构造:

    MeterRegistry registry = ...
    Timer timer = Timer
        .builder("my.timer")
        .description("a description of what this timer does") // 可选
        .tags("region", "test") // 可选
        .register(registry);
    

    另外,Timer的使用还可以基于它的内部类Timer.Sample,通过start和stop两个方法记录两者之间的逻辑的执行耗时。例如:

    Timer.Sample sample = Timer.start(registry);
    
    // 这里做业务逻辑
    Response response = ...
    
    sample.stop(registry.timer("my.timer", "response", response.status()));
    

    FunctionTimer

    FunctionTimer是Timer的特化类型,它主要提供两个单调递增的函数(其实并不是单调递增,只是在使用中一般需要随着时间最少保持不变或者说不减少):一个用于计数的函数和一个用于记录总调用耗时的函数,它的建造器的入参如下:

    public interface FunctionTimer extends Meter {
        static <T> Builder<T> builder(String name, T obj, ToLongFunction<T> countFunction,
                                      ToDoubleFunction<T> totalTimeFunction,
                                      TimeUnit totalTimeFunctionUnit) {
            return new Builder<>(name, obj, countFunction, totalTimeFunction, totalTimeFunctionUnit);
        }
        ...
    }
    

    官方文档中的例子如下:

    IMap<?, ?> cache = ...; // 假设使用了Hazelcast缓存
    registry.more().timer("cache.gets.latency", Tags.of("name", cache.getName()), cache,
        c -> c.getLocalMapStats().getGetOperationCount(),  //实际上就是cache的一个方法,记录缓存生命周期初始化的增量(个数)
        c -> c.getLocalMapStats().getTotalGetLatency(),  // Get操作的延迟时间总量,可以理解为耗时
        TimeUnit.NANOSECONDS
    );
    

    按照个人理解,ToDoubleFunction用于统计事件个数,ToDoubleFunction用于记录执行总时间,实际上两个函数都只是Function函数的变体,还有一个比较重要的是总时间的单位totalTimeFunctionUnit。简单的使用方式如下:

    public class FunctionTimerMain {
    
            public static void main(String[] args) throws Exception {
                //这个是为了满足参数,暂时不需要理会
                Object holder = new Object();
                AtomicLong totalTimeNanos = new AtomicLong(0);
                AtomicLong totalCount = new AtomicLong(0);
                FunctionTimer.builder("functionTimer", holder, p -> totalCount.get(), 
                        p -> totalTimeNanos.get(), TimeUnit.NANOSECONDS)
                        .register(new SimpleMeterRegistry());
                totalTimeNanos.addAndGet(10000000);
                totalCount.incrementAndGet();
            }
    }
    

    LongTaskTimer

    LongTaskTimer也是一种Timer的特化类型,主要用于记录长时间执行的任务的持续时间,在任务完成之前,被监测的事件或者任务仍然处于运行状态,任务完成的时候,任务执行的总耗时才会被记录下来。

    LongTaskTimer适合用于长时间持续运行的事件耗时的记录,例如相对耗时的定时任务。在Spring应用中,可以简单地使用@Scheduled和@Timed注解,基于spring-aop完成定时调度任务的总耗时记录:

    @Timed(value = "aws.scrape", longTask = true)
    @Scheduled(fixedDelay = 360000)
    void scrapeResources() {
        //这里做相对耗时的业务逻辑
    }
    

    当然,在非spring体系中也能方便地使用LongTaskTimer:

    public class LongTaskTimerMain {
    
            public static void main(String[] args) throws Exception{
                MeterRegistry meterRegistry = new SimpleMeterRegistry();
                LongTaskTimer longTaskTimer = meterRegistry.more().longTaskTimer("longTaskTimer");
                longTaskTimer.record(() -> {
    
                    //这里编写Task的逻辑
                });
                //或者这样
                Metrics.more().longTaskTimer("longTaskTimer").record(()-> {
                    //这里编写Task的逻辑
                });
            }
    } 
    

    Gauge

    Gauge(仪表)是获取当前度量记录值的句柄,也就是它表示一个可以任意上下浮动的单数值度量Meter。Gauge通常用于变动的测量值,测量值用ToDoubleFunction参数的返回值设置,如当前的内存使用情况,同时也可以测量上下移动的”计数”,比如队列中的消息数量。

    官网文档中提到Gauge的典型使用场景是用于测量集合或映射的大小或运行状态中的线程数。Gauge一般用于监测有自然上界的事件或者任务,而Counter一般使用于无自然上界的事件或者任务的监测,所以像Http请求总量计数应该使用Counter而非Gauge。

    MeterRegistry中提供了一些便于构建用于观察数值、函数、集合和映射的Gauge相关的方法:

    List<String> list = registry.gauge("listGauge", Collections.emptyList(), new ArrayList<>(), List::size); 
    List<String> list2 = registry.gaugeCollectionSize("listSize2", Tags.empty(), new ArrayList<>()); 
    Map<String, Integer> map = registry.gaugeMapSize("mapGauge", Tags.empty(), new HashMap<>());
    

    上面的三个方法通过MeterRegistry构建Gauge并且返回了集合或者映射实例,使用这些集合或者映射实例就能在其size变化过程中记录这个变更值。更重要的优点是,我们不需要感知Gauge接口的存在,只需要像平时一样使用集合或者映射实例就可以了。

    此外,Gauge还支持java.lang.Number的子类,java.util.concurrent.atomic包中的AtomicInteger和AtomicLong,还有Guava提供的AtomicDouble:

    AtomicInteger n = registry.gauge("numberGauge", new AtomicInteger(0));
    n.set(1);
    n.set(2);
    

    除了使用MeterRegistry创建Gauge之外,还可以使用建造器流式创建:

    //一般我们不需要操作Gauge实例
    Gauge gauge = Gauge
        .builder("gauge", myObj, myObj::gaugeValue)
        .description("a description of what this gauge does") // 可选
        .tags("region", "test") // 可选
        .register(registry);
    

    使用场景:

    根据个人经验和实践,总结如下:

    • 有自然(物理)上界的浮动值的监测,例如物理内存、集合、映射、数值等。

    • 有逻辑上界的浮动值的监测,例如积压的消息、(线程池中)积压的任务等,其实本质也是集合或者映射的监测。

    举个相对实际的例子,假设我们需要对登录后的用户发送一条短信或者推送,做法是消息先投放到一个阻塞队列,再由一个线程消费消息进行其他操作:

    public class GaugeMain {
    
        private static final MeterRegistry MR = new SimpleMeterRegistry();
        private static final BlockingQueue<Message> QUEUE = new ArrayBlockingQueue<>(500);
        private static BlockingQueue<Message> REAL_QUEUE;
    
            static {
                REAL_QUEUE = MR.gauge("messageGauge", QUEUE, Collection::size);
            }
    
            public static void main(String[] args) throws Exception {
                consume();
                Message message = new Message();
                message.setUserId(1L);
                message.setContent("content");
                REAL_QUEUE.put(message);
            }
    
            private static void consume() throws Exception {
                new Thread(() -> {
                    while (true) {
                        try {
                            Message message = REAL_QUEUE.take();
                            //handle message
                            System.out.println(message);
                        } catch (InterruptedException e) {
                            //no-op
                        }
                    }
                }).start();
            }
    }
    

    上面的例子代码写得比较糟糕,只为了演示相关使用方式,切勿用于生产环境。更多springboot实战文章:SpringBoot内容聚合

    TimeGauge

    TimeGauge是Gauge的特化类型,相比Gauge,它的构建器中多了一个TimeUnit类型的参数,用于指定ToDoubleFunction入参的基础时间单位。这里简单举个使用例子:

    public class TimeGaugeMain {
    
            private static final SimpleMeterRegistry R = new SimpleMeterRegistry();
    
            public static void main(String[] args) throws Exception{
                AtomicInteger count = new AtomicInteger();
                TimeGauge.Builder<AtomicInteger> timeGauge = TimeGauge.builder("timeGauge", count,
                        TimeUnit.SECONDS, AtomicInteger::get);
                timeGauge.register(R);
                count.addAndGet(10086);
                print();
                count.set(1);
                print();
            }
    
            private static void print()throws Exception{
                Search.in(R).meters().forEach(each -> {
                    StringBuilder builder = new StringBuilder();
                    builder.append("name:")
                            .append(each.getId().getName())
                            .append(",tags:")
                            .append(each.getId().getTags())
                            .append(",type:").append(each.getId().getType())
                            .append(",value:").append(each.measure());
                    System.out.println(builder.toString());
                });
            }
        }
    
    //输出
    name:timeGauge,tags:[],type:GAUGE,value:[Measurement{statistic='VALUE', value=10086.0}]
    name:timeGauge,tags:[],type:GAUGE,value:[Measurement{statistic='VALUE', value=1.0}]
    

    DistributionSummary

    Summary(摘要)主要用于跟踪事件的分布,在Micrometer中,对应的类是DistributionSummary(分发摘要)。它的使用方式和Timer十分相似,但是它的记录值并不依赖于时间单位。

    常见的使用场景:使用DistributionSummary测量命中服务器的请求的有效负载大小。使用MeterRegistry创建DistributionSummary实例如下:

    DistributionSummary summary = registry.summary("response.size");
    

    通过建造器流式创建如下:

    DistributionSummary summary = DistributionSummary
        .builder("response.size")
        .description("a description of what this summary does") // 可选
        .baseUnit("bytes") // 可选
        .tags("region", "test") // 可选
        .scale(100) // 可选
        .register(registry);
    

    DistributionSummary中有很多构建参数跟缩放和直方图的表示相关,见下一节。

    使用场景:

    根据个人经验和实践,总结如下:

    1、不依赖于时间单位的记录值的测量,例如服务器有效负载值,缓存的命中率等。

    举个相对具体的例子:

    public class DistributionSummaryMain {
    
            private static final DistributionSummary DS  = DistributionSummary.builder("cacheHitPercent")
                    .register(new SimpleMeterRegistry());
    
            private static final LoadingCache<String, String> CACHE = CacheBuilder.newBuilder()
                    .maximumSize(1000)
                    .recordStats()
                    .expireAfterWrite(60, TimeUnit.SECONDS)
                    .build(new CacheLoader<String, String>() {
                        @Override
                        public String load(String s) throws Exception {
                            return selectFromDatabase();
                        }
                    });
    
            public static void main(String[] args) throws Exception{
                String key = "doge";
                String value = CACHE.get(key);
                record();
            }
    
            private static void record()throws Exception{
                CacheStats stats = CACHE.stats();
                BigDecimal hitCount = new BigDecimal(stats.hitCount());
                BigDecimal requestCount = new BigDecimal(stats.requestCount());
                DS.record(hitCount.divide(requestCount,2,BigDecimal.ROUND_HALF_DOWN).doubleValue());
            }
    }
    

    直方图和百分数配置

    直方图和百分数配置适用于Summary和Timer,这部分相对复杂,等研究透了再补充。

    基于SpirngBoot、Prometheus、Grafana集成

    集成了Micrometer框架的JVM应用使用到Micrometer的API收集的度量数据位于内存之中,因此,需要额外的存储系统去存储这些度量数据,需要有监控系统负责统一收集和处理这些数据,还需要有一些UI工具去展示数据,一般大佬只喜欢看炫酷的图表或者动画。

    常见的存储系统就是时序数据库,主流的有Influx、Datadog等。比较主流的监控系统(主要是用于数据收集和处理)就是Prometheus(一般叫普罗米修斯,下面就这样叫吧)。而展示的UI目前相对用得比较多的就是Grafana。

    另外,Prometheus已经内置了一个时序数据库的实现,因此,在做一套相对完善的度量数据监控的系统只需要依赖目标JVM应用,Prometheus组件和Grafana组件即可。下面花一点时间从零开始搭建一个这样的系统,使用CentOS7。

    SpirngBoot中使用Micrometer

    SpringBoot中的spring-boot-starter-actuator依赖已经集成了对Micrometer的支持,其中的metrics端点的很多功能就是通过Micrometer实现的,prometheus端点默认也是开启支持的,实际上actuator依赖的spring-boot-actuator-autoconfigure中集成了对很多框架的开箱即用的API。

    其中prometheus包中集成了对Prometheus的支持,使得使用了actuator可以轻易地让项目暴露出prometheus端点,作为Prometheus收集数据的客户端,Prometheus(服务端软件)可以通过此端点收集应用中Micrometer的度量数据。

    我们先引入spring-boot-starter-actuator和spring-boot-starter-web,实现一个Counter和Timer作为示例。依赖:

    <dependencyManagement>
          <dependencies>
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-dependencies</artifactId>
                  <version>2.1.0.RELEASE</version>
                  <type>pom</type>
                  <scope>import</scope>
              </dependency>
          </dependencies>
      </dependencyManagement>
      <dependencies>
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-web</artifactId>
          </dependency>
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-actuator</artifactId>
          </dependency>
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-aop</artifactId>
          </dependency>
          <dependency>
              <groupId>org.projectlombok</groupId>
              <artifactId>lombok</artifactId>
              <version>1.16.22</version>
          </dependency>
    <dependency>
              <groupId>io.micrometer</groupId>
              <artifactId>micrometer-registry-prometheus</artifactId>
              <version>1.1.0</version>
          </dependency>
      </dependencies>
    

    接着编写一个下单接口和一个消息发送模块,模拟用户下单之后向用户发送消息:

    //实体
    @Data
    public class Message {
    
            private String orderId;
            private Long userId;
            private String content;
        }
    
        @Data
        public class Order {
    
            private String orderId;
            private Long userId;
            private Integer amount;
            private LocalDateTime createTime;
        }
    
        //控制器和服务类
        @RestController
        public class OrderController {
    
            @Autowired
            private OrderService orderService;
    
            @PostMapping(value = "/order")
            public ResponseEntity<Boolean> createOrder(@RequestBody Order order){
                return ResponseEntity.ok(orderService.createOrder(order));
            }
        }
    
        @Slf4j
        @Service
        public class OrderService {
    
            private static final Random R = new Random();
    
            @Autowired
            private MessageService messageService;
    
            public Boolean createOrder(Order order) {
                //模拟下单
                try {
                    int ms = R.nextInt(50) + 50;
                    TimeUnit.MILLISECONDS.sleep(ms);
                    log.info("保存订单模拟耗时{}毫秒...", ms);
                } catch (Exception e) {
                    //no-op
                }
                //记录下单总数
                Metrics.counter("order.count", "order.channel", order.getChannel()).increment();
                //发送消息
                Message message = new Message();
                message.setContent("模拟短信...");
                message.setOrderId(order.getOrderId());
                message.setUserId(order.getUserId());
                messageService.sendMessage(message);
                return true;
            }
        }
    
        @Slf4j
        @Service
        public class MessageService implements InitializingBean {
    
            private static final BlockingQueue<Message> QUEUE = new ArrayBlockingQueue<>(500);
            private static BlockingQueue<Message> REAL_QUEUE;
            private static final Executor EXECUTOR = Executors.newSingleThreadExecutor();
            private static final Random R = new Random();
    
            static {
                REAL_QUEUE = Metrics.gauge("message.gauge", Tags.of("message.gauge", "message.queue.size"), QUEUE, Collection::size);
            }
    
            public void sendMessage(Message message) {
                try {
                    REAL_QUEUE.put(message);
                } catch (InterruptedException e) {
                    //no-op
                }
            }
    
            @Override
            public void afterPropertiesSet() throws Exception {
                EXECUTOR.execute(() -> {
                    while (true) {
                        try {
                            Message message = REAL_QUEUE.take();
                            log.info("模拟发送短信,orderId:{},userId:{},内容:{},耗时:{}毫秒", message.getOrderId(), message.getUserId(),
                                    message.getContent(), R.nextInt(50));
                        } catch (Exception e) {
                            throw new IllegalStateException(e);
                        }
                    }
                });
            }
        }
    
        //切面类
        @Component
        @Aspect
        public class TimerAspect {
    
            @Around(value = "execution(* club.throwable.smp.service.*Service.*(..))")
            public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
                Signature signature = joinPoint.getSignature();
                MethodSignature methodSignature = (MethodSignature) signature;
                Method method = methodSignature.getMethod();
                Timer timer = Metrics.timer("method.cost.time", "method.name", method.getName());
                ThrowableHolder holder = new ThrowableHolder();
                Object result = timer.recordCallable(() -> {
                    try {
                        return joinPoint.proceed();
                    } catch (Throwable e) {
                        holder.throwable = e;
                    }
                    return null;
                });
                if (null != holder.throwable) {
                    throw holder.throwable;
                }
                return result;
            }
    
            private class ThrowableHolder {
    
                Throwable throwable;
            }
    }
    

    yaml的配置如下:

    server:
      port: 9091
    management:
      server:
        port: 10091
      endpoints:
        web:
          exposure:
            include: '*'
          base-path: /management
    

    注意多看spring官方文档关于Actuator的详细描述,在SpringBoot-2.x之后,配置Web端点暴露的权限控制和1.x有很大的不同。更多springboot实战文章:SpringBoot内容聚合

    总结一下就是:除了shutdown端点之外,其他端点默认都是开启支持的这里仅仅是开启支持,并不是暴露为Web端点,端点必须暴露为Web端点才能被访问,禁用或者开启端点支持的配置方式如下:

    management.endpoint.${端点ID}.enabled=true/false可以查
    

    可以查看actuator-api文档查看所有支持的端点的特性,这个是2.1.0.RELEASE版本的官方文档,不知道日后链接会不会挂掉。端点只开启支持,但是不暴露为Web端点,是无法通过http://{host}:{management.port}/{management.endpoints.web.base-path}/{endpointId}访问的。

    暴露监控端点为Web端点的配置是:

    management.endpoints.web.exposure.include=info,health
    management.endpoints.web.exposure.exclude=prometheus
    

    management.endpoints.web.exposure.exclude用于指定不暴露为Web端点的监控端点,指定多个的时候用英文逗号分隔management.endpoints.web.exposure.include默认指定的只有info和health两个端点,我们可以直接指定暴露所有的端点:management.endpoints.web.exposure.include=*,如果采用YAML配置,记得要加单引号’‘。暴露所有Web监控端点是一件比较危险的事情,如果需要在生产环境这样做,请务必先确认http://{host}:{management.port}不能通过公网访问(也就是监控端点访问的端口只能通过内网访问,这样可以方便后面说到的Prometheus服务端通过此端口收集数据)。

    Prometheus的安装和配置

    Prometheus目前的最新版本是2.5,鉴于笔者没深入玩过Docker,这里还是直接下载它的压缩包解压安装。

    wget https://github.com/prometheus/prometheus/releases/download/v2.5.0/prometheus-2.5.0.linux-amd64.tar.gz
    tar xvfz prometheus-*.tar.gz
    cd prometheus-*
    

    先编辑解压出来的目录下的prometheus配置文件prometheus.yml,主要修改scrape_configs节点的属性:

    scrape_configs:
      # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
      - job_name: 'prometheus'
    
        # metrics_path defaults to '/metrics'
        # scheme defaults to 'http'.
        # 这里配置需要拉取度量信息的URL路径,这里选择应用程序的prometheus端点
        metrics_path: /management/prometheus
        static_configs:
        # 这里配置host和port
        - targets: ['localhost:10091']
    

    配置拉取度量数据的路径为localhost:10091/management/metrics,此前记得把前一节提到的应用在虚拟机中启动。接着启动Prometheus应用:

    # 参数 --storage.tsdb.path=存储数据的路径,默认路径为./data
    ./prometheus --config.file=prometheus.yml
    

    Prometheus引用的默认启动端口是9090,启动成功后,日志如下:

    此时,访问ttp://${虚拟机host}:9090/targets就能看到当前Prometheus中执行的Job

    访问ttp://${虚拟机host}:9090/graph以查找到我们定义的度量Meter和spring-boot-starter-actuator中已经定义好的一些关于JVM或者Tomcat的度量Meter。

    我们先对应用的/order接口进行调用,然后查看一下监控前面在应用中定义的rder_count_total``ethod_cost_time_seconds_sum

    可以看到,Meter的信息已经被收集和展示,但是显然不够详细和炫酷,这个时候就需要使用Grafana的UI做一下点缀。

    Grafana的安装和使用

    Grafana的安装过程如下:

    wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.3.4-1.x86_64.rpm 
    sudo yum localinstall grafana-5.3.4-1.x86_64.rpm
    

    安装完成后,通过命令service grafana-server start启动即可,默认的启动端口为3000,通过ttp://${host}:3000即可。初始的账号密码都为admin,权限是管理员权限。接着需要在Home面板添加一个数据源,目的是对接Prometheus服务端从而可以拉取它里面的度量数据。数据源添加面板如下:

    其实就是指向Prometheus服务端的端口就可以了。接下来可以天马行空地添加需要的面板,就下单数量统计的指标,可以添加一个Graph的面板

    配置面板的时候,需要在基础(General)中指定Title:

    接着比较重要的是Metrics的配置,需要指定数据源和Prometheus的查询语句:

    最好参考一下Prometheus的官方文档,稍微学习一下它的查询语言PromQL的使用方式,一个面板可以支持多个PromQL查询。

    前面提到的两项是基本配置,其他配置项一般是图表展示的辅助或者预警等辅助功能,这里先不展开,可以取Grafana的官网挖掘一下使用方式。然后我们再调用一下下单接口,过一段时间,图表的数据就会自动更新和展示:

    接着添加一下项目中使用的Timer的Meter,便于监控方法的执行时间,完成之后大致如下:

    作者:云扬四海

    cnblogs.com/rolandlee/p/11343848.html

    推荐阅读

    数据库缓存最终一致性的四种方案

    【建议收藏】MySQL 面试高频 100 问!

    实现扫码登陆的最简单方案与原理

    瞬间几千次的重复提交,用 SpringBoot+Redis 扛住了

    目前10000+ 人已关注加入我们

           

           

    展开全文
  • QTranslate.6.8.0.zip

    2020-09-21 15:40:45
    这款软件综合了5款在线翻译引擎,分别为谷歌翻译、微软翻译、Promet Mobile、SDL和雅虎翻译,输入一段文字后就可通过QTranslate查询五个翻译引擎的翻译结果,让你得到最准确翻译。软件目前支持 Babylon、Definr、...
  • “「K8S 生态周报」内容主要包含我所接触到的 K8S 生态相关的每周值得推荐的一些信息。欢迎订阅知乎专栏「k8s生态」[1]。”Prometheus v2.26.0 正式发布Promet...
  • 点击上方蓝色字体,选择“设为星标”回复”资源“获取更多资源大数据技术与架构点击右侧关注,大数据开发领域最强公众号!暴走大数据点击右侧关注,暴走大数据!前言虽然笔者之前写过基于Promet...
  • 1、MySQL连接驱动问题 django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module: libmysqlclient.so.18: cannot open shared object file: No such file or directory ...(venv) [root@promet...
  • 去年,云原生计算基金会(CNCF)启动了为旗下项目进行第三方安全审计的流程。这些安全审计的目的是提高CNCF生态系统的整体安全性。CoreDNS、Envoy和Promet...
  • 清华源下载 选择合适自己电脑的版本下载。...记住保存的路径,比如F:\谷歌下载,进入Acanonda promet 选择环境进行安装 pip install F:\谷歌下载\pycocotools_windows-2.0-cp37-cp37m-win_amd64.whl --user
  • WAO-Scheduler-源码

    2021-04-05 09:08:55
    MinimizePower插件Kubernetes是一个便携式,可扩展的开源平台,用于促进声明性配置管理和自动化,以及管理容器化工作负载... 您还可以自己克隆存储库,并使用make build进行构建,它将在Web资产中进行编译,以便Promet
  • Prometheus安装

    2020-09-09 23:06:00
    直接安装: root@localhost ~]# wget ...--2020-09-09 22:18:52-- https://github.com/prometheus/promet...
  • 把prometheus-operator部署起来的prometheus metrics远程存储到es。 prometheus官方推荐通过metricbeat写入Elasticsearch 官网地址 ... 但是prometheus并没有实现remotewrite到elasticsearch的功能。...通过promet...
  • 安装Prometheus和Grafana

    2019-09-27 16:23:41
    (一) 安装prometheus install $ wget https://github.com/prometheus/prometheus/releases/download/v2.4.3/prometheus-2.4.3.linux-amd64.tar.gz... $ tar zxvf prometheus-2.4.3.linux-amd64.tar.gz $ cd promet...
  • prometheus监控 我录制了一个视频,该视频如何通过简单地配置服务器功能,使用Prometheus和Grafana向Open Liberty实例添加... 如果我们进一步使用MicroProfile Metrics功能( mpMetrics-1.1 ),则这些度量以Promet...
  • rpi-traefik-hass-pihole-zerotier 该存储库的内容适用于那些希望在一个树莓运行 , , , , 和 。 除了Zerotier和Pi-hole(可使用器)之外,所有其他功能均启用了指标。...下载并运行traefik v2容器,启用Promet
  • 某一台服务器 clickhouse启动时 最大打开文件为1024,导致群里经常报错errno: 24, strerror: Too many open ... 定期抓取ClickHouse(https://clickhouse.yandex/)统计信息,并通过HTTP导出它们,以用于Promet...
  • Prometheus监控学习记录

    2019-01-31 19:13:00
    官方文档 Prometheus基础文档 从零开始:Prometheus 进阶之路:Prometheus —— 技巧篇 进阶之路:Prometheus —— 理解篇 prometheus的数据类型介绍 Prometheus 查询语言 ...Prometheus源码分析(三)Promet...
  • 环境要求: 根据环境下载 对应环境的:prometheus包 根据环境下载 对应prometheus Service 端环境的 alertmanger Grafana图形工具 常用的exporter Linux环境: node_exporter ...下载完 prometheus的包后 点击 promet
  • 一、prometheus和grafana安装 1、promethues安装 2、grafana安装 二、获取监控数据 三、配置grafana展示数据 1、修改配置重启promethues和grafana 2、创建数据源 ...先写好配置文件,保存为promet...
  • Promethes监控华为S5720交换机

    千次阅读 2020-06-04 17:18:53
    Promethes监控华为交换机 一、部署Promethes 1.1 下载安装包 wget -c ... 1.2 解压至 /data/tools/ [root@snmp:/root]# groupadd prometheus [root@snmp:/root]# useradd -g promet
  • prometheus + grafana + m3db

    千次阅读 2019-07-02 15:10:07
    目的 每个物理机部署 node_export 用于收集本机当前系统状态信息 创建 prometheus 用于收集物理机的监控状态 只支持通过 prometheus 对 node_export 进行 pull 方法收集数据 后端使用 M3DB 作为 ...promet...
  • 时序性数据库Prometheus

    千次阅读 2019-06-14 19:54:42
    时序性数据库Prometheus Author: Lijb ...Prometheus 简介 Prometheus 是一套开源的系统监控报警框架。它启发于 Google 的 borgmon 监控系统,由工作在 SoundCloud 的 google 前员工在 2012 年创建,...2016 年,Promet...
  • 目录导航前言动态发现流程相关yaml文件consul.yamlconsul-service.yamlconsul-service-web.yaml部署注册exporter验证 导航 进阶之路:从零到一在k8s上部署高可用prometheus —— 总览 ...1.在promet

空空如也

空空如也

1 2 3 4 5 ... 8
收藏数 154
精华内容 61
关键字:

promet