Skip to content

Commit c789f84

Browse files
committed
Merge remote-tracking branch 'upstream/master' into DOD
2 parents d338b3e + 292a839 commit c789f84

14 files changed

+778
-176
lines changed

doc/tablet-availability.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# tablet可用性监控与统计
2+
3+
基于tablet维度,给出系统可用性指标。
4+
5+
tablet有多种状态,只有ready状态的tablet才可以正常提供服务(read、write、scan等),
6+
tablet会在各种状态间迁移(例如负载均衡),迁移时,tablet会有短时间(正常情况是秒级)不可服务,
7+
我们认为不可服务时间到达一定阈值(例如30s)的tablet处于不可用状态,这个阈值与使用场景紧密相关。
8+
9+
## 数据采集
10+
```
11+
t1 t2
12+
tablet-a | <-not-ready-> |
13+
tablet-b | <-not-ready--------|---->
14+
tablet-c | |
15+
tablet.. | |
16+
```
17+
18+
19+
tablet状态的迁移由master控制,
20+
master用`std::map<std::string, int64_t>`结构采集tablet状态数据,
21+
std::map中std::string即tablet的path,是tablet全局唯一的id;int64_t即not-ready的开始时间。
22+
23+
当一个tablet状态迁移为not-ready时,master将`<tablet->GetPath(), current_time>`记录在std::map里,
24+
在这个tablet恢复ready时再从map中删除掉。
25+
26+
master重启时可以恢复这个内存结构,所以不用持久化。
27+
28+
### 持久化的数据
29+
30+
master在定时任务(例如小时一次)里通过上述std::map计算过去一小时中,`tablet不可用总时间`(秒级)和`tablet应服务总时间`,并存入stat表。
31+
32+
`tablet不可用总时间`:例如tablet-a不可用5s,tablet-b不可用10s,那么过去1小时内,tablet总的不可用时间为15s;
33+
34+
在定时任务里,上述std::map里可以找到当前非ready状态的tablet(如图中tablet-b),采集它们的不可用时间;
35+
还有部分tablet在定时任务之前已经恢复ready(如图中tablet-a),我们在将其从std::map中删除时,用counter累计它们在这一小时内不可用时间。
36+
37+
`tablet应服务总时间`:例如执行定时任务时总共有1000个tablet,那么过去1小时应服务总时间近似为 1000 * 60 * 60s.
38+
39+
## 分析计算
40+
41+
- 当前可用tablet比例(%)[master定时任务,数据采集时顺便完成]
42+
- 当前可用tablet比例 = 1 - 不可用tablet数/总tablet数
43+
- 不可用tablet数:master定时检查map中not-ready的tablet,如果not-ready达到一定阈值判定为不可用,计为1个不可用tablet
44+
- 总tablet数:通过tablet_manager获取
45+
46+
- 过去1h/1d/1m/1y的可用性 [teracli功能]
47+
- 每小时可用性数据已由master存入stat表;
48+
- teracli读stat表,获取原始数据,进行计算,给出过去1h/1d/1m/1y的可用性数据。
49+
- 过去1h可用性 = 1 - `tablet不可用总时间`/`tablet应服务总时间`
50+
51+
52+
## FAQ
53+
54+
- master挂掉导致的数据缺失?
55+
- master控制tablet状态的迁移,除非集群接近崩溃否则tablet状态不会大规模变化。
56+
- 如果master有0.1%的时间不可用,那么损失0.1%的原始数据,对可用性计算影响不大。当master长时间不可用时,系统已不工作,可用性统计失去意义。

doc/tera_question_answer_hjh.md

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
tera串讲时被问到的问题,及我整理的答案
2+
======
3+
by 黄俊辉
4+
5+
1. master的内存和meta不一致怎么办
6+
- master对meta的操作都是先更新自己本地内存,然后meta上的数据;
7+
- 如果master更新了自己本地内存的数据,但是写meta表失败,则master会重试;
8+
- 超过重试次数后,则设置失败,并返回给客户端对应的错误码;
9+
- 对于分裂,TS是直接把两个新tablet的信息更新到meta表上,而没有经过master;
10+
- 如果TS返回分裂成功,则master扫描该tablet在meta的信息,确认分裂是否真的成功;
11+
12+
2. merge之后master宕机怎么保证meta状态正确
13+
- merge主要分为3个步骤:(1) unload两个旧的tablet;(2) 向meta表的ts发送删除两个旧tablet、新增1个新tablet的信息请求;(3) load新tablet;
14+
- master在步骤1或步骤2宕机,不会是meta状态不一致;对于步骤2,master向ts发送WriteTablet请求,ts内部是一个原子操作,保证都成功或都失败;
15+
16+
3. merge后leveldb load时路径参数是什么
17+
- 两个父tablet的id,根据这两个id可以生成父tablet的路径,进而知道父tablet下面的所有lg,即leveldb的数据,根据这些信息生成merge后tablet的每个lg的manifest文件;
18+
- 所以,两个tablet合并成一个新tablet,不是移动原db数据到新目录,而是在新db目录上修改manifest文件,指向真实的db数据的位置;
19+
20+
4. master在gc时怎么知道哪些数据可以删除
21+
- 遍历Table类的m\_tablets\_list成员,如果tablet的状态不是ready,则不对这个table作gc,否则把tablet放到存活的tablet\_list;
22+
- 扫描table的存储目录,获取table里所有占用磁盘的tablet;
23+
- 如果某tablet占用磁盘,但不在存活tablet\_list里,则认为该tablet已死状态;
24+
- 遍历已死的tablet,扫描数据目录,除sst外的其它文件都删除,m\_gc\_live\_files保存需要进一步确认是否可以删除的文件;
25+
- m\_gc\_live\_files是表名到GcFileSet的映射,GcFileSet是一个vector,成员的类型是set;
26+
- set的内容是uint64\_t的数字,最高位固定为1,接着31位是tablet的num,低32位是sst的num;
27+
- master通过query请求的响应,获得哪些仍然在用的tablet;
28+
- 根据前面的m\_gc\_live\_files-ts返回的live\_files,剩余的就是需要删除的sst文件;
29+
30+
5. client读写时如何寻找对应ts
31+
- client访问zk拿到加载meta表的ts地址;
32+
- client访问meta ts,得到待读写key所属的ts;
33+
- client访问ts,进行读写操作;
34+
35+
6. tera的读操作在leveldb中的流程
36+
- 设置rowkey=用户指定行key, cf="", qualifier="", type=TKT\_FORSEEK, ts=INT64\_MAX, 把这四元组序列化为leveldb的user\_key,seek到leveldb的行首开始读;其中TKT\_FORSEEK是所有操作类型中值最小的key,值为0;
37+
- 如果seek成功,则根据leveldb的迭代器遍历读出需要的数据,否则结束。
38+
39+
7. leveldb的compact流程
40+
- 见tera\_understanding.md文档
41+
42+
8. leveldb写吞吐瓶颈在哪里(影响写吞吐的因素)
43+
- 写日志文件,immutable memtable写sst文件,compact操作
44+
45+
9. ts挂掉一台后,上面的tablet多久能够恢复服务(ts挂掉的,tera内部的流程)
46+
- ts挂掉后,zk最长10(tera\_zk\_timeout)秒能检测到ts挂掉,然后通知master;
47+
- master等待60(tera\_master\_tabletnode\_timeout)秒,如果ts在60s内没有起来,则上面所有tablet将重新分配;
48+
- 当正在load的tablet大于或等于5(tera\_master\_max\_load\_concurrency)个时,剩下的tablet就要排队了,完成一个load,再开始load一个新的;
49+
50+
10. 热备的ts间同步的是什么数据
51+
- 目前tera没有做ts的热备;
52+
- 如果需要做热备的话,ts之间最好同步所有数据;
53+
54+
11. 表格操作的过程
55+
- master在自己本地内存检查表名是否存在;
56+
- master检查参数是否正确;
57+
- maste检查数据目录下是否存在同样表名的数据,如果存在则把老数据移到trash目录;
58+
- master把新表的表名,表的模式,tablet信息更新到本地内存;
59+
- master向加载meta表的ts发送更新meta的请求;
60+
- master等待响应,成功就返回给客户端,失败就重试,超过次数返回客户建表失败;
61+
- master选择ts加载该表的tablet;
62+
63+
12. 建表时写完meta就返回成功,是否有问题
64+
- tera目前的做法是:把表格的相关信息写到本地内存后,就更新meta表,写完meta表后就返回客户端;
65+
- 对于该表格的第一个tablet是由master异步调用ts加载的,这就有可能导致问题:建表成功后,用户立马写数据,有可能因为tablet还没成功加载导致写数据失败;
66+
- 为什么不是写完meta,并且load表格的第一个tablet后,才返回客户端,而是采用上面这种做法?建表是定义表格的行为,往表格读写数据是表格的可用性问题,表格可用性问题是tera比较常见的问题,如某ts挂掉后,上面的tablet需要重新分配给新ts,在成功加载前,tablet的读写就是不可用的;
67+
68+
13. load流程,特别是与dfs的交互
69+
- 遍历tablet下所有的lg;
70+
- 如果lg目录下不存在CURRENT和MANIFEST文件,则认为是新db,
71+
- 对于旧db,读取lg的CURRENT文件,获取当前的MANIFEST文件名;
72+
- 如果parent\_size为0,则获取当然目录的CURRENT和MANIFEST文件;
73+
- 如果parent\_size为1(合并),则获取父tablet的CURRENT和MANIFESST文件;
74+
- 如果parent\_size为2(分裂),则分别获取双亲tablet的CURRENT和MANIFEST文件;
75+
- 扫描MANIFEST文件(CURRENT指向的那个),检查sst文件的key范围是否属于自己的tablet,如果不是,则跳过;如果sst文件的内容只有部分是属于自己tablet的,则修改smallest和或largest的值,更新FileMetaData的信息;
76+
- 获取所有lg最小的log\_sequence,从log\_sequence+1开始从log文件恢复数据;
77+
- 更新MANIFEST文件;
78+
79+
14. bloomfilter加载到内存是在什么时刻进行的,数据特别多时内存不够用怎么办
80+
- 打开sst文件时加载对应的bloomfilter;
81+
- bloomfilter是table cache是一个成员,table cache是通过LRU管理内存;
82+
83+
15. tera在存储介质上的优化有哪些
84+
- tera基于lg实现类似多级cache的功能,对于数据量比较小,且实时性要求比较高的列,可以放到内存中;
85+
- tera基于lg实现按列存储,提高访问的效率;
86+
- tera是批量写hdfs,然后sync的,提高写的吞吐量;
87+
88+
16. 写入的数据什么时候落盘,每次写都sync有什么问题
89+
- 数据先写到本地消息队列,由一个线程批量写数据到磁盘上;
90+
- 数据写到日志文件后就执行sync;
91+
- 返回客户端写数据成功与否;
92+
- 每次执行sync操作,对磁盘压力比较大,会导致写性能低,为保证数据的可靠性只能这样;
93+
94+
17. sdk如何更新meta,对zk的压力如何解决
95+
- sdk从zk拿到meta的地址后,就在本地缓存,后面直接使用缓存的meta地址;
96+
- 当读写数据返回kKeyNotInRange时,sdk重新从zk获取meta地址;
97+
98+
18. 负载均衡是如何实现的
99+
- 当某ts的负载是数据量最小ts的1.2倍时,就会触发负载均衡策略;
100+
- 通过迁移数据量比较大的tablet到另一个ts实现负载均衡的上的;
101+
- 计算数据量比较大的tablet的方法:分别计算源ts和目的ts上该table的数据量,ideal\_move\_size = (src\_node\_size - dst\_node\_size) / 2,找出tablet集合中小于该值的最大tablet,作为迁移的目标;
102+
103+
19. 一致性如何保证,即如何保证一个区间只由一个ts提供服务(例如load超时)
104+
- master逻辑保证一个tablet只能由一个ts提供服务;
105+
- master选定一个ts后,要求其load某个tablet,如果load超时,master就是会重试让ts load这个tablet,超过重试次数后,就unload该tablet,unload失败,也会重试unload,超过unload次数后,就把这个ts踢出服务;
106+
- ts发现自己被踢出服务后,进程主动退出;
107+
- ts退出后,zk最长10秒就发现ts退出,因master监听了ts节点变化的事件,zk将调用master的RefreshTabletNodeList函数;
108+
- master延时60秒(tera\_master\_tabletnode\_timeout)把已经退出的ts上所有tablet重新分配,如果ts在60秒内重新注册上来,原来的在这台ts加载的tablet,master还会要求其加载;
109+
- 通过上面机制,master保证了只有tablet没有被ts服务后,才会重新把这个tablet分配给新ts;
110+
111+
20. 如何踢掉ts
112+
- ts监听自己在zk节点的变化事件;
113+
- master把ts在zk的节点移动到kick目录;
114+
- ts发现自己被移到kick了,就主动退出服务;
115+
- ts先打印一行日志,然后以FATAL错误的方式退出,所以不存在因某种原因卡住而没有及时退出的问题。
116+
117+
21. locality group的作用
118+
- 首先,不同的lg可以指定不同的存储介质,所以可以根据实时性要求,把一个表的某些cf放到内存,某些cf放到ssd,某些cf放到磁盘;
119+
- 第二,lg实现了tera按列存储,所以当查询只要表格某几列里,不需要扫描表格的所有列,只要访问这几列所在的lg就行,提高读取性能;
120+
- 第三,以lg为单位,设置不同的压缩算法,有利于提高存储效率;
121+
122+
22. ts的内存主要消耗在哪里
123+
- table cache,当sst里包含的记录很多时,占内存比较多;
124+
- block cache,缓存了已经读过的数据;
125+
- memtable和immutable memtable
126+
127+
23. ts一般有多少memtable
128+
- 一个lg对应一个leveldb的对象,一个leveldb就有一个memtable;
129+
130+
24. master启动后会做什么
131+
- 分配还没分配的tablet;
132+
- 启动loadbalance、gc、query的定时任务;
133+
134+
25. bigtable模型到leveldb的映射
135+
- 见tera\_understanding.md文档
136+
137+
26. tera对leveldb的改造(split、merge、LG、切log、GC)
138+
- split,merge是原leveldb不支持的功能,tera新加的;
139+
- LG对应一个leveldb的对象,一个tablet有一个或多个LG;
140+
- 不是一个leveldb对应一个log,而一个tablet里的多个leveldb对应一个log文件;
141+
- compact后,原生leveldb是删除旧数据的,但是tera里不能删除的,因为存在split和merge的操作;
142+
143+
27. 一致性保证(master的mem与meta;ts与tablet)
144+
- master更新完mem后,就要要求保证meta的更新成功;
145+
- 一个tablet只能由一个ts服务,所以写是只有唯一的入口;
146+
147+
28. 性能相关(读写瓶颈、io/cpu/mem等资源细节、zk和hdfs压力、各类cache等等)
148+
- 见前面的8和22问;
149+
- sdk通过缓存meta地址,缓解zk的压力;
150+
- 批量写,然后sync,缓存hdfs的压力;
151+
152+
29. Snapshot、行级事务
153+
- snapshot是tera系统在某一时刻的数据状态,用于数据恢复;
154+
- sdk向master发送创建某个表格的快照的请求,master向所有加载该表格的ts发送创建快照请求;
155+
- 目前tera只支持行内的事务,不支持跨行事务;
156+
- db\_table保存了last sequence,读只能读到该sequence前的数据,所以只有当某行所有列都写成功了,才更新last sequence;
157+
- 这样就避免读数据读到有些列是新数据,有些列是旧数据;
158+

src/master/availability.cc

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright (c) 2015, Baidu.com, Inc. All Rights Reserved
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "master/availability.h"
6+
7+
#include <string>
8+
9+
#include <gflags/gflags.h>
10+
#include <glog/logging.h>
11+
12+
#include "common/base/string_ext.h"
13+
#include "common/timer.h"
14+
15+
DECLARE_int64(tera_master_not_available_threshold);
16+
DECLARE_string(tera_master_meta_table_name);
17+
DECLARE_string(tera_master_meta_table_path);
18+
19+
namespace tera {
20+
namespace master {
21+
22+
void TabletAvailability::AddNotReadyTablet(const std::string& path) {
23+
MutexLock lock(&mutex_);
24+
tablets_.insert(std::pair<std::string, int64_t>(path, get_micros()));
25+
}
26+
27+
void TabletAvailability::EraseNotReadyTablet(const std::string& path) {
28+
MutexLock lock(&mutex_);
29+
tablets_.erase(path);
30+
}
31+
32+
static std::string GetNameFromPath(const std::string& path) {
33+
if (path == FLAGS_tera_master_meta_table_path) {
34+
return FLAGS_tera_master_meta_table_name;
35+
}
36+
std::vector<std::string> t;
37+
SplitString(path, "/", &t); // table_name/tablet00...001
38+
return t[0];
39+
}
40+
41+
double TabletAvailability::GetAvailability() {
42+
MutexLock lock(&mutex_);
43+
int64_t not_avai_count = 0;
44+
int64_t start = ::common::timer::get_micros();
45+
std::map<std::string, int64_t>::iterator it;
46+
for (it = tablets_.begin(); it != tablets_.end(); ++it) {
47+
std::string table_name = GetNameFromPath(it->first);
48+
TablePtr table;
49+
if (!tablet_manager_->FindTable(table_name, &table)) {
50+
LOG(ERROR) << "[availability] unknown table:" << table_name;
51+
continue;
52+
}
53+
if ((table->GetStatus() == kTableEnable)
54+
&& (start - it->second > FLAGS_tera_master_not_available_threshold * 1000 * 1000)) {
55+
VLOG(12) << "[availability] not available:" << it->first;
56+
not_avai_count++;
57+
}
58+
}
59+
int64_t cost = ::common::timer::get_micros() - start;
60+
int64_t all_tablets = tablet_manager_->GetAllTabletsCount();
61+
LOG(INFO) << "[availability][current-status] (not-available/not-ready/all-tablets: "
62+
<< not_avai_count << "/" << tablets_.size() << "/" << all_tablets << ")"
63+
<< " cost time:" << cost/1000 << " ms";
64+
return 1 - not_avai_count/(double)all_tablets;
65+
}
66+
67+
} // master
68+
} // tera

src/master/availability.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) 2015, Baidu.com, Inc. All Rights Reserved
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef TERA_MASTER_TABLET_AVAILABILITY_H_
6+
#define TERA_MASTER_TABLET_AVAILABILITY_H_
7+
8+
#include <string>
9+
10+
#include "master/tablet_manager.h"
11+
12+
#include "common/mutex.h"
13+
14+
namespace tera {
15+
namespace master {
16+
17+
class TabletAvailability {
18+
public:
19+
TabletAvailability(boost::shared_ptr<TabletManager> t) : tablet_manager_(t) {}
20+
double GetAvailability();
21+
void AddNotReadyTablet(const std::string& id);
22+
void EraseNotReadyTablet(const std::string& id);
23+
24+
private:
25+
Mutex mutex_;
26+
boost::shared_ptr<TabletManager> tablet_manager_;
27+
std::map<std::string, int64_t> tablets_;
28+
};
29+
30+
} // master
31+
} // tera
32+
33+
#endif // TERA_MASTER_TABLET_AVAILABILITY_H_

0 commit comments

Comments
 (0)