【Ovirt 笔记】补偿机制分析
2017-04-24 本文已影响22人
58bc06151329
文前说明
作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。
本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。
分析整理的版本为 Ovirt 3.4.5 版本。
Ovirt Engine 自带了一种补偿机制,很多的异步任务,在 VDSM 执行时需要一定的时间。任务失败后,对 engine 的状态进行一个回滚的操作,engine 通过 compensate 机制进行了实现。
- 实现原理:通过在数据库中增加一张表来保存任务执行前某实体的数据。
- 数据库表名:business_entity_snapshot。
字段 | 说明 |
---|---|
表1 | |
id | ID |
command_id | 操作业务 ID(CommandID) |
command_type | 操作类型(Command名称) |
entity_id | 实体 ID |
entity_type | 实体类型(实体类ClassName) |
entity_snapshot | 对应不同的快照类型,序列化不同的数据(修改的实体、新实体ID或者状态) |
snapshot_class | 快照类(快照ClassName) |
snapshot_type | 快照类型(表2) |
insertion_order | 此次操作在缓存中的顺序 |
started_at | 操作时间 |
关键字 | 说明 |
---|---|
表2 | |
快照类型 | 描述 |
CHANGED_ENTITY | 修改实体属性 |
NEW_ENTITY_ID | 创建新的实体,只保存实体ID |
CHANGED_STATUS_ONLY | 修改状态 |
需要补偿机制的 Command 需在实体类上加上注解
@NonTransactiveCommandAttribute(forceCompensation = true)
Command 执行前在 handleTransactivity 方法里会有一个 createCompensationContext 的补偿上下文的方法,需要补偿的 command 实例化 DefaultCompensationContext,实现了 CompensationContext 接口 ,所有的补偿业务逻辑是通过该 context 来完成。其被封装到 commandContext 中。设置在 command 执行时所需的上下文中。
DefaultCompensationContext defaultContext = new DefaultCompensationContext();
defaultContext.setCommandId(commandId);
defaultContext.setCommandType(getClass().getName());
defaultContext.setBusinessEntitySnapshotDAO(getBusinessEntitySnapshotDAO());
defaultContext.setSnapshotSerializer(
SerializationFactory.getSerializer());
不需要补偿,会实例化 NoOpCompensationContext 里面的方法都是空的实现。
三种不同的补偿类型,有着不同的实现方式:
- CHANGED_ENTITY,需在表中保存整个实体的数据。 而业务(command)在挂靠执行过程中,通过 Context 找到补偿上下文调用方法 snapshotEntity ,比如说 Entity 是 vds,则需要将整个 vds 对象序列化持久化到磁盘中。
private void updateVdsData() {
TransactionSupport.executeInNewTransaction(new TransactionMethod<Void>() {
@Override
public Void runInTransaction() {
getCompensationContext().snapshotEntity(getVds().getStaticData());
DbFacade.getInstance().getVdsStaticDao().update(getParameters().getVdsStaticData());
getCompensationContext().stateChanged();
return null;
}
});
}
@Override
public void snapshotEntity(BusinessEntity<?> entity) {
snapshotEntityInMemory(entity, entity, SnapshotType.CHANGED_ENTITY);
}
- NEW_ENTITY_ID 则相对简单,保存 id,任务失败时,根据 id 删除对应的记录即可。调用 Context 中的 snapshotNewEntity 方法,参数只为实体的 id。
getCompensationContext().snapshotNewEntity(vmStatic);
@Override
public void snapshotNewEntity(BusinessEntity<?> entity) {
snapshotEntityInMemory(entity, entity.getId(), SnapshotType.NEW_ENTITY_ID);
}
- CHANGED_STATUS_ONLY,则只需保存状态。对应的是 Context 中的 snapshotEntityStatus 方法。
@Override
public <T extends Enum<?>> void snapshotEntityStatus(BusinessEntityWithStatus<?, T> entity, T status) {
EntityStatusSnapshot snapshot = new EntityStatusSnapshot();
snapshot.setId(entity.getId());
snapshot.setStatus(status);
snapshotEntityInMemory(entity, snapshot, SnapshotType.CHANGED_STATUS_ONLY);
}
- 一次事务中可能存在多个实体发生变化,每次涉及数据库的操作时,会通过上文初始化的对应方法先缓存,而后最后 stateChanged 来保存到数据库中,同时清空缓存。
- 在 Command 执行即将结束时,在某些条件下,如发生异常,异步执行失败等,则会通过执行 compensate 方法,进行补偿。
- 重启 engine,之前如果发生过异常或者底层执行失败,将执行补偿,否则执行 cleanUpCompensationData 清空数据。
- Compensate 根据快照类型,通过数据库中的详细信息,还原实体任务前的状态数据。数据库根据 commandId 找到对应的 snapshot 而后根据 SnapshotType 来进行不同的处理。
switch (snapshot.getSnapshotType()) {
case CHANGED_STATUS_ONLY:
EntityStatusSnapshot entityStatusSnapshot = (EntityStatusSnapshot) snapshotData;
((StatusAwareDao<Serializable, Enum<?>>) daoForEntity).updateStatus(
entityStatusSnapshot.getId(), entityStatusSnapshot.getStatus());
break;
case CHANGED_ENTITY:
BusinessEntity<Serializable> entitySnapshot = (BusinessEntity<Serializable>) snapshotData;
if (daoForEntity.get(entitySnapshot.getId()) == null) {
daoForEntity.save(entitySnapshot);
} else {
daoForEntity.update(entitySnapshot);
}
break;
case NEW_ENTITY_ID:
daoForEntity.remove(snapshotData);
break;
}
private void cleanUpCompensationData() {
if (!(getCompensationContext() instanceof NoOpCompensationContext)) {
getBusinessEntitySnapshotDAO().removeAllForCommandId(commandId);
}
}