ZStack原始碼剖析之核心庫鑑賞——Defer

泊浮目發表於2017-10-03
本文首發於泊浮目的專欄:https://segmentfault.com/blog...

在Go語言中,有一個關鍵字叫做defer——其作用是在函式return前執行。在ZStack中也有類似的工具類,讓我們來看看吧。

演示程式碼

    private void handle(APICreateInstanceOfferingMsg msg) {
        APICreateInstanceOfferingEvent evt = new APICreateInstanceOfferingEvent(msg.getId());

        String type = msg.getType() == null ? UserVmInstanceOfferingFactory.type.toString() : msg.getType();
        InstanceOfferingFactory f = getInstanceOfferingFactory(type);

        InstanceOfferingVO vo = new InstanceOfferingVO();
        if (msg.getResourceUuid() != null) {
            vo.setUuid(msg.getResourceUuid());
        } else {
            vo.setUuid(Platform.getUuid());
        }
        HostAllocatorStrategyType allocType = msg.getAllocatorStrategy() == null ? HostAllocatorStrategyType
                .valueOf(HostAllocatorConstant.LEAST_VM_PREFERRED_HOST_ALLOCATOR_STRATEGY_TYPE) : HostAllocatorStrategyType.valueOf(msg.getAllocatorStrategy());
        vo.setAllocatorStrategy(allocType.toString());
        vo.setName(msg.getName());
        vo.setCpuNum(msg.getCpuNum());
        vo.setCpuSpeed(msg.getCpuSpeed());
        vo.setDescription(msg.getDescription());
        vo.setState(InstanceOfferingState.Enabled);
        vo.setMemorySize(msg.getMemorySize());
        vo.setDuration(InstanceOfferingDuration.Permanent);
        vo.setType(type);

        InstanceOfferingInventory inv = new SQLBatchWithReturn<InstanceOfferingInventory>() {
            @Override
            @Deferred
            protected InstanceOfferingInventory scripts() {
                Defer.guard(() -> dbf.remove(vo));
                InstanceOfferingInventory inv = f.createInstanceOffering(vo, msg);
                acntMgr.createAccountResourceRef(msg.getSession().getAccountUuid(), vo.getUuid(), InstanceOfferingVO.class);
                tagMgr.createTagsFromAPICreateMessage(msg, vo.getUuid(), InstanceOfferingVO.class.getSimpleName());
                return inv;
            }
        }.execute();

        evt.setInventory(inv);
        bus.publish(evt);
        logger.debug("Successfully added instance offering: " + printer.print(inv));
    }

這段程式碼是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:

public class TestDefer1 {
    CLogger logger = Utils.getLogger(TestDefer1.class);
    int count = 0;

    @Before
    public void setUp() throws Exception {
    }


    @Deferred
    private void case1() {
        count++;
        Defer.guard(new Runnable() { //當捕捉到異常時,執行其中的匿名Runable語句
            @Override
            public void run() {
                count--;
            }
        });
        //丟擲一個異常
        throw new CloudRuntimeException("Roll back count");
    }

    @Test(expected = CloudRuntimeException.class)
    public void test() {
        case1();
        Assert.assertEquals(0, count);
    }
}

實現

Defer的庫非常的小。其本質通過對Spring提供的AOP和Java提供的ThreadLocal以及一個Stack資料結構進行封裝:對於執行函式的當前執行緒存入一個Stack資料結構,每一個填寫在Defer中的Runable都會被放入,之後根據呼叫的Defer的函式來決定其行為。

這裡Runable的放入來自系統啟動時利用反射所做的一個行為。因此並不會影響使用時的效能。

相關文章