本文首发于泊浮目的专栏:https://segmentfault.com/blog/camile

在Go语言中,有一个关键字叫做defer——其作用是在函数return前执行。在ZStack中也有类似的工具类,让我们来看看吧。

演示代码

  1. private void handle(APICreateInstanceOfferingMsg msg) {
  2. APICreateInstanceOfferingEvent evt = new APICreateInstanceOfferingEvent(msg.getId());
  3. String type = msg.getType() == null ? UserVmInstanceOfferingFactory.type.toString() : msg.getType();
  4. InstanceOfferingFactory f = getInstanceOfferingFactory(type);
  5. InstanceOfferingVO vo = new InstanceOfferingVO();
  6. if (msg.getResourceUuid() != null) {
  7. vo.setUuid(msg.getResourceUuid());
  8. } else {
  9. vo.setUuid(Platform.getUuid());
  10. }
  11. HostAllocatorStrategyType allocType = msg.getAllocatorStrategy() == null ? HostAllocatorStrategyType
  12. .valueOf(HostAllocatorConstant.LEAST_VM_PREFERRED_HOST_ALLOCATOR_STRATEGY_TYPE) : HostAllocatorStrategyType.valueOf(msg.getAllocatorStrategy());
  13. vo.setAllocatorStrategy(allocType.toString());
  14. vo.setName(msg.getName());
  15. vo.setCpuNum(msg.getCpuNum());
  16. vo.setCpuSpeed(msg.getCpuSpeed());
  17. vo.setDescription(msg.getDescription());
  18. vo.setState(InstanceOfferingState.Enabled);
  19. vo.setMemorySize(msg.getMemorySize());
  20. vo.setDuration(InstanceOfferingDuration.Permanent);
  21. vo.setType(type);
  22. InstanceOfferingInventory inv = new SQLBatchWithReturn<InstanceOfferingInventory>() {
  23. @Override
  24. @Deferred
  25. protected InstanceOfferingInventory scripts() {
  26. Defer.guard(() -> dbf.remove(vo));
  27. InstanceOfferingInventory inv = f.createInstanceOffering(vo, msg);
  28. acntMgr.createAccountResourceRef(msg.getSession().getAccountUuid(), vo.getUuid(), InstanceOfferingVO.class);
  29. tagMgr.createTagsFromAPICreateMessage(msg, vo.getUuid(), InstanceOfferingVO.class.getSimpleName());
  30. return inv;
  31. }
  32. }.execute();
  33. evt.setInventory(inv);
  34. bus.publish(evt);
  35. logger.debug("Successfully added instance offering: " + printer.print(inv));
  36. }

这段代码是ZStack ManageNode(简称MN)在接收到创建计算规格的请求后的处理逻辑,大致的逻辑即:

  1. 在DB中创建一条记录
  2. 添加当前账户与该计算规格的关联
  3. 创建相应的系统标签
  4. 回复该消息,并打印一行log

在这段代码中,我们可以看到在执行逻辑2、3时,这里做了一个Defer.guard(() -> dbf.remove(vo));,其作用是在执行下面的逻辑发生异常时执行,这样就移除了脏数据。

如何使用

Defer的使用方法也很简单,如果你要在某个函数使用它,在上面添加一个 @Deferred的Annotation,然后就可以在该函数内使用它了。

一般有两种用法:

  • Defer.guard:在该函数抛出异常时执行Runable。
  • Defer.defer:在该函数返回前执行。我们可以使用其释放局部锁。

为了避免不熟悉ZStack读者理解起来生涩,建议参考其Case在这里,我们将展现一个较为简单的case:

  1. public class TestDefer1 {
  2. CLogger logger = Utils.getLogger(TestDefer1.class);
  3. int count = 0;
  4. @Before
  5. public void setUp() throws Exception {
  6. }
  7. @Deferred
  8. private void case1() {
  9. count++;
  10. Defer.guard(new Runnable() { //当捕捉到异常时,执行其中的匿名Runable语句
  11. @Override
  12. public void run() {
  13. count--;
  14. }
  15. });
  16. //抛出一个异常
  17. throw new CloudRuntimeException("Roll back count");
  18. }
  19. @Test(expected = CloudRuntimeException.class)
  20. public void test() {
  21. case1();
  22. Assert.assertEquals(0, count);
  23. }
  24. }

实现

Defer的库非常的小。其本质通过对Spring提供的AOP和Java提供的ThreadLocal以及一个Stack数据结构进行封装:对于执行函数的当前线程存入一个Stack数据结构,每一个填写在Defer中的Runable都会被放入,之后根据调用的Defer的函数来决定其行为。

这里Runable的放入来自系统启动时利用反射所做的一个行为。因此并不会影响使用时的性能。