玩转Prometheus(3)--数据存储

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.引言 在监控系统中,海量的监控数据如何存储,一直是设计人员所必须关心的问题。OpenTSDB选择了Hbase;Open-Falcon选择了RRD(round-robin database)。Prometheus另辟蹊径,借鉴了facebook的paper(参考资料2),设计了自己的TSDB(time series database)。本文试图简单介绍TSDB中使用的2个压缩算法 2. 简单的聊聊TSDB的文件结构 值得一提的是在prometheus中 1)数据是没有做预聚合的,所有的聚合操作都是在查询阶段进行。 据笔者观察查询的时间跨度如果超过7d,速度就会变得比较慢(3 ~ 5秒) 2)Prometheus数据都是单机存储的,数据存在丢失的可能,最近产生的数据存储在内存中,历史数据落在硬盘上,默认数据存储15天。 可以使用storage.tsdb.retention.time来修改数据存储的跨度 --storage.tsdb.retention.time=7d 文件结构 ├── 01D5X2A81S8FMS16S5Q1GWNQDE │ ├── chunks // chunk数据 │ │ └── 000001 │ ├── index // 索引文件 │ ├── meta.json // 人类可读的文件 │ └── tombstones ├── 01D5X2A83TVJD7FFGKPHED5VA1 │ ├── chunks │ │ └── 000001 │ ├── index │ ├── meta.json │ └── tombstones └── wal // wal下的全是预写日志,类似于MySQL中的binlog ├── 00000010 ├── 00000011 ├── 00000012 ├── 00000013 └── checkpoint.000009 └── 00000000 如果读者仔细观察会发现,文件夹的modify时间是递增。 没错,监控数据首先按照时间维度,划分在不同的文件夹中, 然后通过索引文件index去定位不同的series, 实际的数据在chunks文件夹中 。(补充,WAL文件夹存有最近的数据。可简单的把WAL理解为最近的临时数据,chunks中的为归档数据。) ...

March 14, 2019 · 3 min

ES内部分享

ES内部分享 1. ES简介 ElasticSearch是Elastic公司开发的开源分布式搜索引擎 开源 分布式 全文检索 OLAP(结合kibana使用) Resful API NoSQL database 1.1 和Lucene的关系 { "name": "uf_-1wJ", "cluster_name": "UT", "cluster_uuid": "xvGp84DyQVOSxLXP43oDpA", "version": { "number": "6.2.4", "build_hash": "ccec39f", "build_date": "2018-04-12T20:37:28.497551Z", "build_snapshot": false, "lucene_version": "7.2.1", "minimum_wire_compatibility_version": "5.6.0", "minimum_index_compatibility_version": "5.0.0" }, "tagline": "You Know, for Search" } 简单而言,ES是Lucene的分布式版本,当然扩充了接口和在线分析功能 1.2 基本概念 1.2.1 索引层面 index type doc term 1.2.2 倒排索引 正常顺序 doc -> term 倒排索引 term -> doc 1.2.2 集群层面 Cluster Node Shard Segment 多种角色 Master-eligible node 2)Data node Ingest node Tribe node coordinating node ...

December 24, 2018 · 1 min

elasticsearch中自定义doc的路由(routing)规则

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 前几天有人在群里问,es是否可以指定某个字段为路由值。ES自定义的doc的路由,不过es的操作是,在写入一个doc时,指定doc的routing key。 2. 例子 2.1 创建一个新的index PUT /my_index2 { "settings": { "index": { "number_of_shards": 2, "number_of_replicas": 1 } } } index只有2个shard,shard 0和 shard 1 2.2 设置mapping PUT /my_index2/_mapping/student { "_routing": { "required": true }, "properties": { "name": { "type": "keyword" }, "age": { "type": "integer" } } } 2.3 指定doc路由 PUT /my_index2/student/1?routing=key1 { "name":"n1", "age":10 } PUT /my_index2/student/2?routing=key1 { "name":"n2", "age":10 } PUT /my_index2/student/3?routing=key1 { "name":"n3", "age":10 } 上面的3条命令会使得doc1、2、3放置在同一个shard上 shard 0 ...

December 12, 2018 · 1 min

基于redis的分布式限频库

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 总述 这个库的目标是为了能够简单粗暴的实现分布式限频功能, 类似于ID生成器的用法, client每次从Redis拿回-批数据(在这里只是一个数值)进行消费, 只要没有消费完,就没有达到频率限制。 English README 优势 依赖少,只依赖redis,不需要专门的服务 使用的redis自己的时钟,不需要相应的服务器时钟完全一致 线程(协程)安全 系统开销小,对redis的压力很小 安装 go get github.com/vearne/ratelimit 用法 1. 创建 redis.Client 依赖 “github.com/go-redis/redis” client := redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "xxx", // password set DB: 0, // use default DB }) 2. 创建限频器 limiter, _ := ratelimit.NewRedisRateLimiter(client, "push", 1 * time.Second, 200, 10, ratelimit.TokenBucketAlg, ) 表示允许每秒操作200次 limiter, _ := ratelimit.NewRedisRateLimiter(client, "push", 1 * time.Minute, 200, 10, ratelimit.TokenBucketAlg, ) 表示允许每分钟操作200次 ...

August 24, 2018 · 2 min

Lucene索引结构漫谈

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 前言 注意: 本文基于Lucene 3.0.2, 目前Lucene的版本最新已经是7.3.x 虽然Lucene的版本变化较大, 但是索引结构已经构建它的核心思想并没有发生。 Lucene是solr和Elasticsearch的基础,汽车中的引擎,它的每次改版都会引起上层系统的巨大变化。研究它对于提升查询性能,降低存储开销有非常大的帮助。 笔者有三年多的ES使用经验,但是真正踏踏实实探究Lucene和ES也是最近时间的事情。 重要 首先,推荐的是一本书《Lucene实战》这本书的作者有好几个都是Lucene的核心开发人员,因此对Lucene的理解是非常透彻的, 非常推荐。 推荐索引文件的查看工具Luke, 它可以打开Lucene和ES的索引文件,直观的观察它们的内部数据 1. 索引文件列表 Lucene有2种文件格式 1.1 CompoundFile == false -rw-r--r-- 1 zhuwei wheel 1471 6 5 15:44 _2.fdt -rw-r--r-- 1 zhuwei wheel 12 6 5 15:44 _2.fdx -rw-r--r-- 1 zhuwei wheel 66 6 5 15:44 _2.fnm -rw-r--r-- 1 zhuwei wheel 323 6 5 15:44 _2.frq -rw-r--r-- 1 zhuwei wheel 8 6 5 15:44 _2.nrm -rw-r--r-- 1 zhuwei wheel 442 6 5 15:44 _2.prx -rw-r--r-- 1 zhuwei wheel 61 6 5 15:44 _2.tii -rw-r--r-- 1 zhuwei wheel 2611 6 5 15:44 _2.tis -rw-r--r-- 1 zhuwei wheel 9 6 5 15:44 _2.tvd -rw-r--r-- 1 zhuwei wheel 1647 6 5 15:44 _2.tvf -rw-r--r-- 1 zhuwei wheel 20 6 5 15:44 _2.tvx -rw-r--r-- 1 zhuwei wheel 20 6 5 15:44 segments.gen -rw-r--r-- 1 zhuwei wheel 233 6 5 15:44 segments_4 1.2 CompoundFile == true total 33976 -rw-r--r-- 1 zhuwei wheel 2459065 7 31 13:17 _0.cfs -rw-r--r-- 1 zhuwei wheel 13468962 7 31 13:17 _0.cfx -rw-r--r-- 1 zhuwei wheel 1451842 7 31 13:17 _1.cfs -rw-r--r-- 1 zhuwei wheel 20 7 31 13:17 segments.gen -rw-r--r-- 1 zhuwei wheel 442 7 31 13:17 segments_2 组合文件只是将原来放在多个文件中的数据整合到少数的几个文件中,减少了打开的文件描述符的数量,其它并没有大的区别,所以我们重点来看非组合文件。 ...

July 31, 2018 · 3 min

聊聊Elasticsearch的集群状态的管理和维护

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 注意 本文参考的ES 5.0.1的源码 1. 查看集群状态 可以通过API查看集群状态 GET /_cluster/state 大致可以得到如下内容 version 集群状态数字版本号 每次更新version + 1 集群重启version不会置0(只会单调增) state_uuid 是集群状态字符串版本号(UUID) master_node master节点 nodes 该版本中的所有节点信息 routing_table 和 routing_nodes 都是描述不同index上的shard在node上的分布关系 2. 集群状态的维护 ES源码中集群状态的对应类为ClusterState.java The cluster state can be updated only on the master node. All updates are performed by on a single thread and controlled by the {@link ClusterService}. After every update the {@link Discovery#publish} method publishes new version of the cluster state to all other nodes in the cluster. In the Zen Discovery it is handled in the {@link PublishClusterStateAction#publish} method ...

July 12, 2018 · 2 min

elasticsearch 中暂时移除一个节点

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 前言 在维护ES集群的过程中,我们会经常遇到将某个ES实例临时下线,比如机器换硬盘,系统参数调整,调整完毕后,再将ES实例重新上线。ES提供了非常便利的API来支持这一点。 操作过程 比如我们有这样一个ES集群,node-2需要临时下线 step 1 PUT _cluster/settings { "transient" : { "cluster.routing.allocation.exclude._name" : "node-2" } } 注意 这个操作是transient集群重启后,这个设置会失效 step 2 step1 配置完成以后,我们就会看到shard在集群中开始迁移,待迁移完成以后,对node-2进行处理 step 3 PUT _cluster/settings { "transient" : { "cluster.routing.allocation.exclude._name" : "" } } 只要让_name匹配不到对用的node即可 总结 除了_name 之外, 还可以用_ip、_host进行匹配 参考资料 Shard Allocation Filtering 请我喝瓶饮料

June 11, 2018 · 1 min

lucene中的 *.del文件

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 注意:本文基于lucene 3.1 引言 最近一直在看lucene相关的资料,今天介绍一下lucene中的索引文件的一部分 *.del文件 概述 如果索引文件中,有被删除的doc,则lucene会创建*.del文件 *.del 记录了被删除doc的ID 采用2中方法 1)BitVector BitVector等同于C++中的bitset,每个bit对应一个doc 如下图,假定共有24个doc,其中10、12被删除 注意 在一个segment中,doc ID从0开始 2) DGaps 如果segment中的文档数量较多,而被删除的文档很少,那么将有大量的bit为0,少量为1。这种稀疏的BitVector,非常浪费存储空间。因此lucene提供了 DGaps这种存储格式,我们可以把它看做是BitVector的一种压缩格式。 它的思想是这样的,只有少量的bit被置为1,那么就只记录非0的字节, 所以对于每个非0的字节,需要一个二元组去记录 (Dgap, NonzeroByte) Dgap 表示该字节是第几个字节(从0开始,用vint表示, 关于vint可参考我的另一篇文章VINT–针对INT型的压缩格式) NonzeroByte 表示这个字节的值 对于上面的例子,只有byte1 是非0字节,所以最终结果如下 00000001 00010100 参考资料 1.Apache Lucene - Index File Formats 请我喝瓶饮料

June 5, 2018 · 1 min

redigo提示connection pool exhausted

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 引言 线上的某个服务(Golang开发)使用Redis作为消息队列,使用的redis库是garyburd/redigo, 这两天出现如下错误 connection pool exhausted 2. 产生原因 阅读源码pool.go 阅读get()即可 type Pool struct { // Dial()方法返回一个连接,从在需要创建连接到的时候调用 Dial func() (Conn, error) // TestOnBorrow()方法是一个可选项,该方法用来诊断一个连接的健康状态 TestOnBorrow func(c Conn, t time.Time) error // 最大空闲连接数 MaxIdle int // 一个pool所能分配的最大的连接数目 // 当设置成0的时候,该pool连接数没有限制 MaxActive int // 空闲连接超时时间,超过超时时间的空闲连接会被关闭。 // 如果设置成0,空闲连接将不会被关闭 // 应该设置一个比redis服务端超时时间更短的时间 IdleTimeout time.Duration // 如果Wait被设置成true,则Get()方法将会阻塞 Wait bool ... ... } 以上异常的原因是这样的,在garyburd/redigo中,得到连接的步骤如下 尝试从空闲列表中,获得一个可用连接;如果成功直接返回,失败则尝试步骤2 如果当前的Active 连接数 < MaxActive,则尝试创建一个新连接;如果成功直接返回,失败则尝试步骤3 判断Wait为true则等待,否则报出异常 // ErrPoolExhausted is returned from a pool connection method (Do, Send, // Receive, Flush, Err) when the maximum number of database connections in the // pool has been reached. var ErrPoolExhausted = errors.New(&quot;redigo: connection pool exhausted&quot;) 3. 解决方法 设置MaxActive,设MaxActive=0(表示无限大)或者足够大。 设置Wait=true,当程序执行get(),无法获得可用连接时,将会暂时阻塞。 4. 完整的初始化连接池的代码 func NewRedisPool(redisConf context.RedisConf) *redis.Pool { address := fmt.Sprintf(&quot;%v:%v&quot;, redisConf.Host, redisConf.Port) dbOption := redis.DialDatabase(redisConf.Db) pwOption := redis.DialPassword(redisConf.Password) // **重要** 设置读写超时 readTimeout := redis.DialReadTimeout(time.Second * time.Duration(redisConf.ConTimeout)) writeTimeout := redis.DialWriteTimeout(time.Second * time.Duration(redisConf.ConTimeout)) conTimeout := redis.DialConnectTimeout(time.Second * time.Duration(redisConf.ConTimeout)) redisPool := &amp;redis.Pool{ // 从配置文件获取maxidle以及maxactive,取不到则用后面的默认值 MaxIdle: redisConf.MaxIdleConn, MaxActive: redisConf.MaxActiveConn, // **重要** 如果空闲列表中没有可用的连接 // 且当前Active连接数 &lt; MaxActive // 则等待 Wait: true, IdleTimeout: time.Duration(redisConf.IdleTimeout) * time.Second, Dial: func() (redis.Conn, error) { c, err := redis.Dial(&quot;tcp&quot;, address, dbOption, pwOption, readTimeout, writeTimeout, conTimeout) if err != nil { return nil, err } return c, nil }, } return redisPool }

June 4, 2018 · 1 min

基于version的MySQL并发无锁策略

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 引子: 有这么一种场景,对于外部系统提交的任务,我们要把任务扫出来,推送到 消息队列中,然后消费者监听在消息队列上, 取到任务进行消费。要防止任务被重复消费,扫出的任务要修改对应数据库状态值。 问题 假定数据库表结构为 task 字段 类型 说明 备注 id int 主键 task_id int 任务ID status int 状态 0:等待中, 1:运行中, 2:成功, 3:失败 body string 任务body体 version string 为了区分写入成功的对象 我们知道把任务扫出来,至少需要执行3步操作 扫描出等待中的任务 select * from task where status = 0 limit 10; 2)将扫出的任务推送到消息队列中 3) 修改任务状态 假定扫描出的任务task_id 分别为为1、2、3 update task set status = 1 where task_id in (1,2,3) and status = 0; 显然这个过程不是原子的,如果同时有多个scanner进行操作,显然会任务可能被重复推入消息队列中 ...

January 29, 2018 · 2 min