互联网级监控平台之内存存储的设计和优化

上两篇文章我们介绍了时序数据库Influxdb在互联网级监控系统下的应用:

互联网级监控系统必备-时序数据库之Influxdb技术

互联网级监控系统必备-时序数据库之Influxdb集群及踩过的坑

在我们监控平台V1.0和V2.0版本的演进过程中,设计上,我们在监控引擎端引入了内存存储的理念,即监控数据内存槽。

为什么需要一个内存存储来做监控数据的内存槽,它的应用场景是什么?

一. 从实际应用场景出发

  首先,我们看一个实际的监控图表:配置中心服务的TPM

横轴是时间,纵轴是数值。每分钟一个点,当然也可以每10s一个点,一段时间区间内所有的点,连接成一条曲线,如图所示:一段时间内配置中心服务的TPM实时监控图表。

      在我们的服务容器中,每次服务调用我们都会上报一次监控数据,即服务的耗时和相关的容器数据、纬度数据等。

由点及线,我们看每个点背后的数据:上图中,21:05:59:996这个时间点配置中心服务的TPM是10K,即这一分钟内发生了1W次调用,上报了1W个监控数据

我们统计了每 1 Minute内服务的调用次数,类似的我们还可以统计每30s, 10s, 1s的调用次数。无论1 Minute,30s,20s,1s,这都代表了一个Time Window:时间窗口。

有了这些上报的监控数据,我们就可以在一个Time Window范围内进行监控数据的采样、分析。监控数据采样主要是在各个纬度下取这个Time Window下所有监控数据的最大值、最小值、个数、合计值、平均

值、最新值、第一个值等,示例中的TPM则是取的一分钟内的监控数据的个数,同样的,如果取平均值,即代表了服务的平均响应时间。采样后的数据写入Influxdb就可以展现了。

  因此,在监控引擎内部需要一个内存结构来存储每个Time Window的监控数据,用于后续的监控数据采样和分析。

  目前,我们的监控平台有2000+的监控项,这么大的监控体量,每天上报2TB的监控数据,折合每分钟1.42GB监控数据。这个内存存储结构应该如何设计?如何保证监控的准确性、实时性、有序性和并发性?

二.  言归正传,用演进的角度看监控内存存储结构的设计

1. 监控数据结构优化

  海量的监控数据实时地从各个服务器上报到监控引擎中。首先我们要保证监控数据尽可能的小:这样才能传输的更快、更多。

  • 不需要的数据不要上报
  • 采用ProtoBuf序列化,尽可能的降低数据序列化带来的性能消耗,保证序列化后的数据的体量要小

2. 连续的内存还是分布的内存 

  监控引擎接收到上报的监控数据后,首先要将数据缓存到内存中,这时有两种选择:一直连续写?分布并行写? 孰优孰劣?

  一直连续写:内存相对来说是连续的,但这是暂时的,当数据到达一定数量后,很多集合要Double扩容,内存瞬间的压力会很大!同时,数据写入是并发的,要保证线程安全,防止写入失败!

分布并行写:以空间换时间,同时降低了并行写入产生的锁争用问题,同时可以避免内存Double、频繁扩容的问题,唯一的劣势就是,内存占用多一点,需要实现内存的分布式管理

监控数据必须快速、及时的得到处理,实时性要求很高,同时,内存的成本基本可控,因此,我们选择了分布并行写的策略!

3. 内存存储的Sharding设计

  在内存存储的设计上,我们借鉴了数据库Sharding的思路。为什么要做Sharding?假如将所有的监控数据都存储在一个内存集合中,每一类监控项在采样时,都要访问这个内存集合,线程争用很大,

必须加锁控制,此时会产生类似数据库阻塞的“内存线程阻塞”。因此:

按监控项做垂直拆分:每个监控项拥有自己单独的内存存储空间。内存的Sharding Key就是监控项。这样的设计,提升了内存操作的并行度,同时减少了锁争用。采样的各个线程操作各自的内存,只有线

程内存的计算处理,采样线程间没有交叉争用。一个监控项一个内存存储后,数据写入和采样数据读取依然有线程争用问题!数据在实时不断的写入,同时采样线程在实时指定Time Window的数据进行采样。

继续优化改进。

  按时间做水平拆分:每一个监控项一个内存存储,同时再按分钟进行水平拆分,每分钟又是一个单独的子存储。即一个内存槽下60个槽点。每分钟一个槽点

  此时,监控数据接收时,首先根据监控数据对应的监控项,定位到对应的内存槽,第二步根据监控数据上的时间,定位到具体分钟槽点。数据写入的速度又提升了。同时,采样线程读取指定Time Window

下的数据时,可以快速根据时间纬度,找到指定的槽点。读取的速度也提升了!

4. 内存中读写分离设计

  将内存中监控数据的读写分开,降低数据的读写争用。

  数据写线程:实时并行写入各个内存槽和槽点中(监控项和时间纬度)

采样读线程:读取指定监控项对应的Time Window下的数据。

  监控数据采样有个前提:例如要对上一分钟的数据进行采样,首先要保证上一分钟的数据尽可能的全部到位,这样数据才会准确。因此有个延迟时间的问题,例如5s延迟,21:57:05时,我们认为21:56的数

据已经全部到位。这样的话,我们就可以采用一个较小的延迟时间,来保证数据的准确性的同时,降低数据读写的争用。

  通过设置了一个合理的延迟时间(5s),21:57:05时:

  采样读线程:读取21:56对应槽点下的数据进行采样分析

数据写线程:将监控数据写入21:57对应的内存槽点中。

读写分开,内存争用非常少,性能和并行度再一次提升!!

5. 多线程优化

  基于线程池,线程内的执行逻辑尽可能的快,使用完后立即放回线程池中。防止线程占用带来的线程暴涨问题!

三. 代码分享

1. 监控数据本地存储

 1  public class MonitorLocalStore
 2     {
 3         private Dictionary<int, Queue<MonitorData>> cache;
 4
 5         private static object syncObj = new object();
 6
 7         //监控元数据ID
 8         private string metaDataID;
 9
10         public DateTime CreateTime;
11         /// <summary>
12         /// 构造函数
13         /// </summary>
14         /// <param name="metaDataID">监控元数据ID</param>
15         public MonitorLocalStore(string metaDataID)
16         {
17             this.metaDataID = metaDataID;
18             cache = new Dictionary<int, Queue<MonitorData>>();
19             for (int i = 0; i < 60; i++)
20             {
21                 cache.Add(i, new Queue<MonitorData>());
22             }
23             CreateTime = DateTime.Now;
24         }
25
26         public void Add(MonitorData value)
27         {
28             if (!cache.ContainsKey(value.Time.Minute))
29             {
30                 throw new Exception("Cannot find Time slot: " + value.Time.ToString() + ", Current slots: " + string.Join(",", cache.Keys));
31             }
32             cache[value.Time.Minute].Enqueue(value);
33         }
34
35         public void Add(IEnumerable<MonitorData> valueSet)
36         {
37             Parallel.ForEach(valueSet, (i) =>
38                     {
39                         cache[i.Time.Minute].Enqueue(i);
40                     }
41                 );
42         }
43
44         public List<MonitorData> Get(params int[] scope)
45         {
46             var valueSet = new List<MonitorData>();
47             foreach (var item in scope)
48             {
49                 while (cache[item].Count > 0)
50                 {
51                     MonitorData data = cache[item].Dequeue();
52                     if (data != null)
53                     {
54                         valueSet.Add(data);
55                     }
56                 }
57                 //valueSet.AddRange(cache[item]);
58                 cache.Remove(item);
59                 cache.Add(item, new Queue<MonitorData>());
60             }
61
62             return valueSet;
63         }
64
65         /// <summary>
66         /// 获取本地缓存的总容量
67         /// </summary>
68         /// <returns>本地缓存的总容量</returns>
69         public long GetCapcity()
70         {
71             var length = 0;
72             foreach (var item in cache)
73             {
74                 length += item.Value.Count;
75             }
76
77             return length;
78         }
79
80         public void Ecvit(params int[] scope)
81         {
82             lock (syncObj)
83             {
84                 foreach (var item in scope)
85                 {
86                     cache[item] = new Queue<MonitorData>();
87                 }
88             }
89         }
90
91         public Dictionary<int, Queue<MonitorData>> GetCache()
92         {
93             return cache;
94         }
95     }

2. 监控存储分布式管理

 1   class MonitorLocalStoreManager
 2     {
 3         private static object syncObj = new object();
 4         private ConcurrentDictionary<string, MonitorLocalStore> storeDic;
 5         private List<string> metaDataList;
 6
 7         /// <summary>
 8         /// 输出方法委托
 9         /// </summary>
10         public Action<string> Output { get; set; }
11         /// <summary>
12         /// 构造函数
13         /// </summary>
14         private MonitorLocalStoreManager()
15         {
16             storeDic = new ConcurrentDictionary<string, MonitorLocalStore>();
17         }
18
19         //监控数据本地存储管理器实例
20         private static MonitorLocalStoreManager instance;
21
22         /// <summary>
23         /// 获取监控数据本地存储管理器实例
24         /// </summary>
25         /// <returns>监控数据本地存储管理器实例</returns>
26         public static MonitorLocalStoreManager GetInstance()
27         {
28             if (instance == null)
29             {
30                 lock (syncObj)
31                 {
32                     if (instance == null)
33                     {
34                         instance = new MonitorLocalStoreManager();
35                     }
36                 }
37             }
38
39             return instance;
40         }
41
42         /// <summary>
43         /// 加载监控元数据存储
44         /// </summary>
45         /// <param name="metaDataList">监控元数据列表</param>
46         internal void LoadMetaDataStore(List<string> metaDataList)
47         {
48             this.metaDataList = metaDataList;
49             foreach (var metaDataID in metaDataList)
50             {
51                 GetOrCreateStore(metaDataID);
52             }
53         }
54
55         /// <summary>
56         /// 根据监控元数据ID获取本机缓存Store
57         /// </summary>
58         /// <param name="metaDataID">监控元数据ID</param>
59         /// <returns>本机缓存Store</returns>
60         public MonitorLocalStore GetOrCreateStore(string metaDataID)
61         {
62             MonitorLocalStore store = null;
63             if (!storeDic.ContainsKey(metaDataID))
64             {
65                 lock (syncObj)
66                 {
67                     if (!storeDic.ContainsKey(metaDataID))
68                     {
69                         if (!metaDataList.Contains(metaDataID))
70                         {
71                             LocalErrorLogService.Write("Find New MetaData:" + metaDataID);
72                         }
73                         store = new MonitorLocalStore(metaDataID);
74                         storeDic.TryAdd(metaDataID, store);
75                     }
76                 }
77             }
78             else
79             {
80                 storeDic.TryGetValue(metaDataID, out store);
81             }
82
83             return store;
84         }
85
86         internal ConcurrentDictionary<string, MonitorLocalStore> GetStore()
87         {
88             return storeDic;
89         }
90 }

总结:

监控引擎使用了内存存储来接收缓存上报上来的监控数据。

使用场景:监控数据实时写入,大批量写入,定时采样归集。

技术挑战:快速写入、多线程读写安全、内存快速分配和释放,防止内存暴涨带来的Full GC和高CPU。

设计上的一些好的经验:

  • 内存存储采用Sharding分区,每个监控项一个内存槽,每个内存槽分为60个槽点:以空间换时间,同时避免大内存分配。
  • 多线程写入时,以监控项作为分区键,路由定位到指定的内存槽,以监控数据上的时间(分钟)作为二次分区键,存储到指定内存槽的槽点。实现多线程并行写入,最大程度上避免了多线程写入时的锁争用。
  • 读写线程分离,读线程设置一个最佳的延迟时间(5~10s),读取内存槽点的已就绪的数据,此时数据没有写入,毫秒级读取,近乎实时的监控数据采样。

周国庆

2017/8/24

时间: 2024-11-05 12:08:57

互联网级监控平台之内存存储的设计和优化的相关文章

互联网级监控系统必备-时序数据库之Influxdb集群及踩过的坑

上篇博文中,我们介绍了做互联网级监控系统的必备-Influxdb的关键特性.数据读写.应用场景: 互联网级监控系统必备-时序数据库之Influxdb 本文中,我们介绍Influxdb数据库集群的搭建,同时分享一下我们使用集群遇到的坑! 一.环境准备 同一网段内,3个CentOS 节点,相互可以ping通 3个节点CentOS配置Hosts文件,相互可以解析主机名 Azure 虚拟机启用root用户 influxdb-0.10.3-1.x86_64.rpm 设置端口8083 8086 8088 8

互联网级监控系统必备-时序数据库之Influxdb技术

时间序列数据库,简称时序数据库,Time Series Database,一个全新的领域,最大的特点就是每个条数据都带有Time列. 时序数据库到底能用到什么业务场景,答案是:监控系统. Baidu一下,互联网监控系统,大家会发现小米.饿了吗等互联网巨头都在用时序数据库实现企业级的互联网监控系统. 很多人会说,用Zabbix不就搞定了,其实不是这样的,简单的主机资源监控.网络监控.小规模的部署环境,Zabbix能搞定. 如果在IDC 上千台服务器环境下,分布式应用架构.各种中间件,这种情况下我们

linux监控平台搭建-内存

上一篇文章说的硬盘.就写一下.更加重要的东西.在手机上面是RAM.机器是memory.内存是按照字节编址.每个地址的存储单元可以存放8bit的数据.cpu 通过内存地址获取一条指令和数据.内存溢出out-of-memory killer 负责终止使用内存过多的进程.详细的细节请查看/var/log/messages文件.建立索引常会发生这种情况.管理员可以限制服务不被OOM.数据的预热.压力测定时.自动化测试.灰度发布.监控采集. 每一个内存都是进程产生的.到底什么是内存.其实进程是有自己的虚拟

阿里云新功能:EIP高精度实时互联网流量秒级监控

大家好,很高兴向大家介绍一下高精度秒级监控 很高兴的告诉大家,阿里云弹性公网IP即日起支持高精度秒级监控了.而令人激动的是,这可能是史上最好用的实时业务流量监控功能,没有之一. 众所周知,弹性公网IP(EIP)承载了海量的互联网BGP流量,这些流量实时性要求很高,对公网带宽的质量要求也很高.如果公网带宽跑满未及时扩容,很容易出现业务流量限速丢包,进而引发客户端访问质量恶化的和用户体验的直线下降. 对于极度关注和珍视用户体验的互联网内容提供方,是十分在意互联网流量的实时监控的.如果业务流量超过预设

【互联网的安全感】(一) 0成本添加访问级监控

互联网的安全感这个概念源于阿里.顾名思义,让互联网的用户对于web产品能够产生足够的信任和依赖.特别是涉及到用户资金交易的站点,一次严重的用户资料泄露就可以彻底毁掉你的品牌. 然后当前阶段除了bat大部分互联网行业的企业对于网络安全给的重视都不够分量.所以网上充斥了各种脱库,泄密,钓鱼.某个知名大型技术论坛被脱后甚至发现他们的密码是用明文保存的,这里就不点名了. 对于这个话题 我准备分2块来说 第一是信息安全 第二是风险评估 信息安全基本是技术趋向 如何做防范 监控 如果大家感兴趣 后面我会陆续

(转)微服务架构 互联网保险O2O平台微服务架构设计

http://www.cnblogs.com/Leo_wl/p/5049722.html 微服务架构 互联网保险O2O平台微服务架构设计 关于架构,笔者认为并不是越复杂越好,而是相反,简单就是硬道理也提现在这里.这也是微服务能够流行的原因,看看市场上曾经出现的服务架构:EJB.SCA.Dubbo等等,都比微服务先进,都比微服务功能完善,但它们都没有微服务这么深入民心,就是因为他们过于复杂.简单就是高科技,苹果手机据说专门有个团队研究如何能让用户更加简单的操作.大公司都是由小公司发展起来的,如果小

亿级日志平台实践

本篇主要讲工作中的真实经历,我们怎么打造亿级日志平台,同时手把手教大家建立起这样一套亿级 ELK 系统.日志平台具体发展历程可以参考上篇 「从 ELK 到 EFK 演进」 废话不多说,老司机们座好了,我们准备发车了~~~ 整体架构 整体架构主要分为 4 个模块,分别提供不同的功能 Filebeat:轻量级数据收集引擎.基于原先 Logstash-fowarder 的源码改造出来.换句话说:Filebeat就是新版的 Logstash-fowarder,也会是 ELK Stack 在 Agent

江西畅行高速IT运维监控平台--PIGOSS BSM

案例所属行业:高速公路行业 项目实施时间:2014年 1.1    项目背景     江西畅行高速工程(以下简称"畅行高速")与高速公路周边系统的建设基于用户的消费账户支付系统和结算系统.既包括高速公路的收费,也包括高速公路周边的连锁超市的消费,互联网业务为江西畅行高速周边服务. 目前,江西畅行高速进行网络建设和核心生产平台应用系统的建设.随着江西畅行高速信息化应用的不断推广,核心生产平台的稳定运行对项目的影响越来越大.随 着更多江西畅行高速业务系统上线运行和日常办公对业务系统的日益依

【项目动态】PIGOSS BSM IT运维监控平台 北京万兴建筑集团有限公司

案例所属行业:企业集团 项目实施时间:2016年 1 项目背景 北京万兴建筑集团有限公司成立于1985年,是一个以房建.市政.装饰.地产开发为四大支柱产业的大型综合性建筑企业集团.万兴集团注册资本金3.06亿元,现有总资产10多亿元,企业拥有一大批高素质专业技术管理人才,其中中高级职称专业技术人员300余人,国家一级.二级 注册建造师200人.年开复工面积500万平方米左右,建安产值约50亿元左右.万兴集团还积极参与社会公益事业,累计捐款1000多万元. 为保障各项业务的稳定运行,需要对IT基础