【Ovirt 笔记】补偿机制分析

文前说明

作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。

本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。

分析整理的版本为 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 里面的方法都是空的实现。

三种不同的补偿类型,有着不同的实现方式:

  1. CHANGED_ENTITY,需在表中保存整个实体的数据。 而业务(command)在挂靠执行过程中,通过 Context 找到补偿上下文调用方法 snapshotEntity ,比如说 Entity 是 vds,则需要将整个 vds 对象序列化持久化到磁盘中。
private void updateVdsData() {
    TransactionSupport.executeInNewTransaction(new TransactionMethod() {
        @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);
}
  1. 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);
}
  1. CHANGED_STATUS_ONLY,则只需保存状态。对应的是 Context 中的 snapshotEntityStatus 方法。
@Override
public > void  snapshotEntityStatus(BusinessEntityWithStatus 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>) daoForEntity).updateStatus(
            entityStatusSnapshot.getId(), entityStatusSnapshot.getStatus());
    break;
case CHANGED_ENTITY:
    BusinessEntity entitySnapshot = (BusinessEntity) 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);
    }
}

你可能感兴趣的:(【Ovirt 笔记】补偿机制分析)