# Ceph中的scrub机制

Ceph的一大主要特点是强一致性,这里主要指的是端到端的一致性。传统存储路径上,从
上层应用到内核的文件系统、通用块层、SCSI层到最后的HBA的磁盘控制器,每层都有发生
错误的可能。因此,传统的端到端的解决方案是以数据块校验为主来解决。而在Ceph层面,
更是加入了Ceph自己的客户端、存储逻辑、数据迁移,势必会导致更高的错误概率。

简单说来,Ceph的OSD会定时启动Scrub检查,当符合条件时会进行调度。

## Scrub的调度

Scrub的调度解决了一个PG在何时启动Scrub机制,主要有以下几种方式:

* 手动立即启动执行扫描

* 在后台设置一定的时间间隔,按照间隔的时间来启动。(默认为一天执行一次)

* 设置启动的时间段,一般设定一个系统负载比较轻的时间段来执行Scrub操作

## 与Scrub相关的数据结构

```cpp
mutex sched_scrub_lock;  // scrub相关变量的保护锁
int scrubs_pending;		// 资源预约成功,正等待scrub的pg的数量
int scrubs_active;		// 正在进行scrub的pg的数量
set<ScrubJob> sched_scrub_pg; // pg对应的所有ScrubJob列表
```



## Chunky Scrub

Chunky Scrub一个清洗对象的一个块,并阻止对该块的写入。对象存储被分割成以哈希边界结尾的块。而对于每个块,都执行下列逻辑:

* 阻止对该块的写入
* 从其他副本获取map
* 等待推送被应用(在恢复之后)
* 等待写刷入到块
* 等待副本的map
* 比较/修复所有的scrub map
* 等待digest更新应用

## Deep Scrub

Deep Scrub计算每个对象的数据部分的CRC,存储到digest中,并且计算对象header和omap的kv对的CRC,存储到omap_digest。(这里有个疑问,为什么计算omap的CRC需要先获取header对象的CRC呢?)

## 数据结构

ScrubJob

```cpp
struct ScrubJob {
  spg_t pgid;
  utime_t sched_time;
  utime_t deadline;
  ...
};
```

### 字段解释

* pgid: 需要进行scrub的pg
* sched_time: 安排进行scrub的时间。但是在系统负载高或者没有没有在scrub时间段,则scrub会延迟
* deadline: scrub时间的上限,过了该时间必须scrub,不受系统负载和scrub时间段的限制

Scrubber

```cpp
struct Scrubber {
  ...
  set<pg_shard_t> reserved_peers;
  bool reserved, reserved_failed; // 是否预约资源,资源预约是否失败
  epoch_t epoch_start; // 开始scrub的epoch
  bool active; // scrub是否开始
  bool queue_snap_trim; //
  int waitting_on; // 等待的副本数
  set<pg_shart_t> waiting_on_whom; // 等待的副本
  int shallow_errors; // 轻度扫描错误数
  int deep_errors; // 深度扫描错误数
  int fixed; // 已经修复的对象数
  ScrubMap primary_scrubmap; // 主副本的ScrubMap
  map<pg_shard_t, ScrubMap> received_maps; // 接收到的从副本的ScrubMap
  ...
  bool auto_repair; // 是否自动扫描
  map<hobject_t, set<pg_shard_t>, hobject_t::BitwiseComparator> missing; // 扫描出的缺失对象
  map<hobject_t, set<pg_shard_t>, hobject_t::BitwiseComparator> inconsistent; // 扫描出的不一致的对象
  map<hobject_t, list<pair<ScrubMap::object, pg_shard_t> >, hobject_t::BitwiseComparator> authoritative; // 如果所有副本对象中有不一致的对象,authoritative记录了正确对象所在的OSD
  int num_digest_updates_pending; // 等待更新digest的对象数目
  hobject_t start, end; // 需要扫描的对象列表的开始和结尾
  eversion_t subset_last_update; // 扫描对象列表中的最新的版本号
  bool deep; // 是否为深度扫描
  uint32_t seed; // 计算CRC校验的种子

  ...
};
```



## 参考链接

[解析Ceph: 数据的端到端正确性和Scrub机制](https://www.ustack.com/blog/ceph-internal-scrub/)

[SCSI中端到端能解决数据完整性问题吗](http://alanwu.blog.51cto.com/3652632/1093600/)

[Ceph源码解析 -- Scrub故障检测](http://www.cnblogs.com/chenxianpao/p/5878159.html)