tera串讲时被问到的问题,及我整理的答案
by 黄俊辉
master的内存和meta不一致怎么办
- master对meta的操作都是先更新自己本地内存,然后更新meta上的数据;
- 如果master更新了自己本地内存的数据,但是写meta表失败,则master会重试;
- 超过重试次数后,则设置失败,并返回给客户端对应的错误码;
- 对于分裂,TS是直接把两个新tablet的信息更新到meta表上,而没有经过master;
- 如果TS返回分裂成功,则master扫描该tablet在meta的信息,确认分裂是否真的成功;
merge之后master宕机怎么保证meta状态正确
- merge主要分为3个步骤:(1) unload两个旧的tablet;(2) 向meta表的ts发送删除两个旧tablet、新增1个新tablet的信息请求;(3) load新tablet;
- master在步骤1或步骤3宕机,不会是meta状态不一致;对于步骤2,master向ts发送WriteTablet请求,ts内部是一个原子操作,保证都成功或都失败;
merge后leveldb load时路径参数是什么
- 两个父tablet的id,根据这两个id可以生成父tablet的路径,进而知道父tablet下面的所有lg,即leveldb的数据,根据这些信息生成merge后tablet的每个lg的manifest文件;
- 所以,两个tablet合并成一个新tablet,不是移动原db数据到新目录,而是在新db目录上修改manifest文件,指向真实的db数据的位置;
master在gc时怎么知道哪些数据可以删除
- 遍历Table类的m_tablets_list成员,如果tablet的状态不是ready,则不对这个table作gc,否则把tablet放到存活的tablet_list;
- 扫描table的存储目录,获取table里所有占用磁盘的tablet;
- 如果某tablet占用磁盘,但不在存活tablet_list里,则认为该tablet已死状态;
- 遍历已死的tablet,扫描数据目录,除sst外的其它文件都删除,m_gc_live_files保存需要进一步确认是否可以删除的文件;
- m_gc_live_files是表名到GcFileSet的映射,GcFileSet是一个vector,成员的类型是set;
- set的内容是uint64_t的数字,最高位固定为1,接着31位是tablet的num,低32位是sst的num;
- master通过query请求的响应,获得哪些仍然在用的tablet;
- 根据前面的m_gc_live_files-ts返回的live_files,剩余的就是需要删除的sst文件;
client读写时如何寻找对应ts
- client访问zk拿到加载meta表的ts地址;
- client访问meta ts,得到待读写key所属的ts;
- client访问ts,进行读写操作;
tera的读操作在leveldb中的流程
- 设置rowkey=用户指定行key, cf=””, qualifier=””, type=TKT_FORSEEK, ts=INT64_MAX, 把这四元组序列化为leveldb的user_key,seek到leveldb的行首开始读;其中TKT_FORSEEK是所有操作类型中值最小的key,值为0;
- 如果seek成功,则根据leveldb的迭代器遍历读出需要的数据,否则结束。
leveldb的compact流程
- 见tera_understanding.md文档
leveldb写吞吐瓶颈在哪里(影响写吞吐的因素)
- 写日志文件,immutable memtable写sst文件,compact操作
ts挂掉一台后,上面的tablet多久能够恢复服务(ts挂掉的,tera内部的流程)
- ts挂掉后,zk最长10(tera_zk_timeout)秒能检测到ts挂掉,然后通知master;
- master等待60(tera_master_tabletnode_timeout)秒,如果ts在60s内没有起来,则上面所有tablet将重新分配;
- 当正在load的tablet大于或等于5(tera_master_max_load_concurrency)个时,剩下的tablet就要排队了,完成一个load,再开始load一个新的;
热备的ts间同步的是什么数据
- 目前tera没有做ts的热备;
- 如果需要做热备的话,ts之间最好同步所有数据;
表格操作的过程
- master在自己本地内存检查表名是否存在;
- master检查参数是否正确;
- maste检查数据目录下是否存在同样表名的数据,如果存在则把老数据移到trash目录;
- master把新表的表名,表的模式,tablet信息更新到本地内存;
- master向加载meta表的ts发送更新meta的请求;
- master等待响应,成功就返回给客户端,失败就重试,超过次数返回客户建表失败;
- master选择ts加载该表的tablet;
建表时写完meta就返回成功,是否有问题
- tera目前的做法是:把表格的相关信息写到本地内存后,就更新meta表,写完meta表后就返回客户端;
- 对于该表格的第一个tablet是由master异步调用ts加载的,这就有可能导致问题:建表成功后,用户立马写数据,有可能因为tablet还没成功加载导致写数据失败;
- 为什么不是写完meta,并且load表格的第一个tablet后,才返回客户端,而是采用上面这种做法?建表是定义表格的行为,往表格读写数据是表格的可用性问题,表格可用性问题是tera比较常见的问题,如某ts挂掉后,上面的tablet需要重新分配给新ts,在成功加载前,tablet的读写就是不可用的;
load流程,特别是与dfs的交互
- 遍历tablet下所有的lg;
- 如果lg目录下不存在CURRENT和MANIFEST文件,则认为是新db,
- 对于旧db,读取lg的CURRENT文件,获取当前的MANIFEST文件名;
- 如果parent_size为0,则获取当然目录的CURRENT和MANIFEST文件;
- 如果parent_size为1(合并),则获取父tablet的CURRENT和MANIFESST文件;
- 如果parent_size为2(分裂),则分别获取双亲tablet的CURRENT和MANIFEST文件;
- 扫描MANIFEST文件(CURRENT指向的那个),检查sst文件的key范围是否属于自己的tablet,如果不是,则跳过;如果sst文件的内容只有部分是属于自己tablet的,则修改smallest和或largest的值,更新FileMetaData的信息;
- 获取所有lg最小的log_sequence,从log_sequence+1开始从log文件恢复数据;
- 更新MANIFEST文件;
bloomfilter加载到内存是在什么时刻进行的,数据特别多时内存不够用怎么办
- 打开sst文件时加载对应的bloomfilter;
- bloomfilter是table cache是一个成员,table cache是通过LRU管理内存;
tera在存储介质上的优化有哪些
- tera基于lg实现类似多级cache的功能,对于数据量比较小,且实时性要求比较高的列,可以放到内存中;
- tera基于lg实现按列存储,提高访问的效率;
- tera是批量写hdfs,然后sync的,提高写的吞吐量;
写入的数据什么时候落盘,每次写都sync有什么问题
- 数据先写到本地消息队列,由一个线程批量写数据到磁盘上;
- 数据写到日志文件后就执行sync;
- 返回客户端写数据成功与否;
- 每次执行sync操作,对磁盘压力比较大,会导致写性能低,为保证数据的可靠性只能这样;
sdk如何更新meta,对zk的压力如何解决
- sdk从zk拿到meta的地址后,就在本地缓存,后面直接使用缓存的meta地址;
- 当读写数据返回kKeyNotInRange时,sdk重新从zk获取meta地址;
负载均衡是如何实现的
- 当某ts的负载是数据量最小ts的1.2倍时,就会触发负载均衡策略;
- 通过迁移数据量比较大的tablet到另一个ts实现负载均衡的上的;
- 计算数据量比较大的tablet的方法:分别计算源ts和目的ts上该table的数据量,ideal_move_size = (src_node_size - dst_node_size) / 2,找出tablet集合中小于该值的最大tablet,作为迁移的目标;
一致性如何保证,即如何保证一个区间只由一个ts提供服务(例如load超时)
- master逻辑保证一个tablet只能由一个ts提供服务;
- master选定一个ts后,要求其load某个tablet,如果load超时,master就是会重试让ts load这个tablet,超过重试次数后,就unload该tablet,unload失败,也会重试unload,超过unload次数后,就把这个ts踢出服务;
- ts发现自己被踢出服务后,进程主动退出;
- ts退出后,zk最长10秒就发现ts退出,因master监听了ts节点变化的事件,zk将调用master的RefreshTabletNodeList函数;
- master延时60秒(tera_master_tabletnode_timeout)把已经退出的ts上所有tablet重新分配,如果ts在60秒内重新注册上来,原来的在这台ts加载的tablet,master还会要求其加载;
- 通过上面机制,master保证了只有tablet没有被ts服务后,才会重新把这个tablet分配给新ts;
如何踢掉ts
- ts监听自己在zk节点的变化事件;
- master把ts在zk的节点移动到kick目录;
- ts发现自己被移到kick了,就主动退出服务;
- ts先打印一行日志,然后以FATAL错误的方式退出,所以不存在因某种原因卡住而没有及时退出的问题。
locality group的作用
- 首先,不同的lg可以指定不同的存储介质,所以可以根据实时性要求,把一个表的某些cf放到内存,某些cf放到ssd,某些cf放到磁盘;
- 第二,lg实现了tera按列存储,所以当查询只要表格某几列里,不需要扫描表格的所有列,只要访问这几列所在的lg就行,提高读取性能;
- 第三,以lg为单位,设置不同的压缩算法,有利于提高存储效率;
ts的内存主要消耗在哪里
- table cache,当sst里包含的记录很多时,占内存比较多;
- block cache,缓存了已经读过的数据;
- memtable和immutable memtable
ts一般有多少memtable
- 一个lg对应一个leveldb的对象,一个leveldb就有一个memtable;
master启动后会做什么
- 分配还没分配的tablet;
- 启动loadbalance、gc、query的定时任务;
bigtable模型到leveldb的映射
- 见tera_understanding.md文档
tera对leveldb的改造(split、merge、LG、切log、GC)
- split,merge是原leveldb不支持的功能,tera新加的;
- LG对应一个leveldb的对象,一个tablet有一个或多个LG;
- 不是一个leveldb对应一个log,而一个tablet里的多个leveldb对应一个log文件;
- compact后,原生leveldb是删除旧数据的,但是tera里不能删除的,因为存在split和merge的操作;
一致性保证(master的mem与meta;ts与tablet)
- master更新完mem后,就要要求保证meta的更新成功;
- 一个tablet只能由一个ts服务,所以写是只有唯一的入口;
性能相关(读写瓶颈、io/cpu/mem等资源细节、zk和hdfs压力、各类cache等等)
- 见前面的8和22问;
- sdk通过缓存meta地址,缓解zk的压力;
- 批量写,然后sync,缓存hdfs的压力;
Snapshot、行级事务
- snapshot是tera系统在某一时刻的数据状态,用于数据恢复;
- sdk向master发送创建某个表格的快照的请求,master向所有加载该表格的ts发送创建快照请求;
- 目前tera只支持行内的事务,不支持跨行事务;
- db_table保存了last sequence,读只能读到该sequence前的数据,所以只有当某行所有列都写成功了,才更新last sequence;
- 这样就避免读数据读到有些列是新数据,有些列是旧数据;