目录

分布式锁颗粒度小记--加锁时间过长

业务背景

​ 在某业务单据中,需要对单据进行审核操作,发现分录有1000条数据,审核时间30s,需要进行优化

​ 审核插件主要进行了两个操作:

​ 1、数据校验(组map,对数据进行校验)

​ 2、更新中间表(同步资源余量表)

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class AbstractResourceDetailLockOp extends AbstractOperationServicePlugIn {

    protected List<DLock> resourceLockList = Lists.newArrayList();

    protected static final long DEFAULT_LOCK_TIME = 30000L;

    @Override
    public void beforeExecuteOperationTransaction(BeforeOperationArgs e) {
        DynamicObject[] dataEntities = e.getDataEntities();
        String operationKey = e.getOperationKey();

        if (!opKeySet().contains(operationKey)){
            return;
        }

        Set<String> lockKeySet = generateLockKeySet(dataEntities);
        if (CollectionUtils.isEmpty(lockKeySet)){
            return;
        }
        for (String lockKey : lockKeySet){
            DLock resourceLock = DLock.createReentrant(lockKey, lockKey).fastMode();
            boolean tryLock = resourceLock.tryLock(getLockTime());
            if (!tryLock) {
                throw new KDBizException(String.format(ResManager.loadKDString("获取库存锁失败,请稍后再试。lock_key:%s", "InventoryDLockOp_0", "ec-ecma-opplugin", new Object[0]), lockKey));
            }
            this.resourceLockList.add(resourceLock);
        }
    }

    /**
     * 生成互斥锁Key
     *
     * 根据实际情况生成,如项目ID_单位工程ID_资源ID
     */
    protected Set<String> generateLockKeySet(DynamicObject[] dataEntities){
        return Sets.newHashSet();
    }

    /**
     * 操作代码列表
     */
    protected Set<String> opKeySet(){
        return Sets.newHashSet();
    }

    /**
     * 获取加锁时间
     */
    protected Long getLockTime(){
        return DEFAULT_LOCK_TIME;
    }

    @Override
    public void onReturnOperation(ReturnOperationArgs e) {
        super.onReturnOperation(e);
        if (CollectionUtils.isEmpty(this.resourceLockList)){
            return;
        }
        this.resourceLockList.forEach(DLock::unlock);
        this.resourceLockList.clear();
    }
}

耗时查询

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
代码调用性能消耗报告:
kd.bos.service.ServiceFactory.FormService.batchInvokeAction(), cost 30217 ms.
|----FormService.batchInvokeAction('983ee13baa4049329a0b1f2a71ec3732'), cost 30216 ms.
    |----FormService.ecma_totalrequireplan.itemClick('ecma_totalrequireplan','ecma_totalrequireplan','tbmain'), cost 30211 ms.
        |----operate.BillView.invokeOperation('audit','ecma_totalrequireplan'), cost 30184 ms.
            |----operate.FormView.invokeOperation('audit'), cost 30183 ms.
                |----operate.FormOperate.execute('ecma_totalrequireplan','audit','Audit','kd.bos.entity.operate.Audit'), cost 30182 ms.
                    |----operate.Audit.invokeOperation(), cost 30171 ms.
                        |----ModelCache.getAll(), cost 55 ms.
                            |----DataEntitySerializer.readCollection(), cost 23 ms.
                            |----datareader.loadRefence(), cost 13 ms.
                        |----opservice.OperationService.invokeOperation('ecma_totalrequireplan','audit'), cost 30092 ms.
                            |----opservice.initialize(), cost 222 ms.
                                |----msopfacade.kd.bos.service.botp.facade.OperateBfFacade.afterInitialize(), cost 208 ms.
                                    |----FunctionManage.get(), cost 206 ms.
                            |----opservice.excute(dataEntities)(), cost 29867 ms.
                                |----opservice.doExcete(), cost 27015 ms.
                                    |----opservice.validate(), cost 61 ms.
                                        |----opvalidate.ValidationService.validate(), cost 61 ms.
                                            |----opvalidate.kd.bos.service.operation.validate.MutexValidatorvalidate(), cost 36 ms.
                                                |----MutexValidator.batchRequireMutex(), cost 36 ms.
                                    |----opplugin.cbtgc.ec.ecma.op.ZJJTTotalRequirePlanInitResourceOp.beforeExecuteOperationTransaction(), cost 26599 ms.
                                    |----opservice.beforeCallOperationTransaction(), cost 59 ms.
                                        |----msopfacade.kd.bos.service.botp.facade.OperateBfFacade.afterUpdateBillStatus(), cost 59 ms.
                                    |----opservice.callOperationTransaction(), cost 294 ms.
                                        |----opservice.TX.required(), cost 292 ms.
                                            |----opplugin.kd.ec.material.opplugin.TotalRequirePlanOp.beginOperationTransaction(), cost 82 ms.
                                                |----BusinessDataServiceHelper.loadSingle(), cost 77 ms.
                                                    |----datareader.loadSingle(), cost 50 ms.
                                            |----opplugin.cbtgc.ec.ecma.op.ZJJTTotalRequirePlanInitResourceOp.beginOperationTransaction(), cost 197 ms.
                                                |----BusinessDataServiceHelper.load(), cost 43 ms.
                                                    |----datareader.load(), cost 23 ms.
                                                        |----datareader.loadRefence(), cost 11 ms.
                                |----opplugin.cbtgc.ec.ecma.op.ZJJTTotalRequirePlanInitResourceOp.onReturnOperation(), cost 2843 ms.
        |----datamodel.updateCache(), cost 25 ms.
            |----ModelCache.storeAll(), cost 25 ms.

opvalidate.ValidationService.validate():校验数据,61ms

opplugin.cbtgc.ec.ecma.op.ZJJTTotalRequirePlanInitResourceOp.beforeExecuteOperationTransaction(): 更新中间表之前加分布式事务锁, 26599 ms.

opplugin.kd.ec.material.opplugin.TotalRequirePlanOp.beginOperationTransaction():更新中间表 ,82 ms

opplugin.cbtgc.ec.ecma.op.ZJJTTotalRequirePlanInitResourceOp.onReturnOperation():最后执行,解锁 2843ms

从以上日志分析发现,代码耗时主要发生在加分布式事务锁;分布式事务锁的颗粒度为:项目、单位工程、资源;锁的颗粒度为最细。

那么单据一共有1000个分录,就会加1000个分布式事务锁。

解决方案

原因:

​ 分布式锁的粒度,到了boq级别,粒度很小,导致锁的数量多,获取锁和释放锁耗费大量时间;

优化:

1、总体需用计划生成中间表的时候,不需要加锁,因为还没有下游业务;

2、锁的粒度加大,增加到单位工程+项目的级别,这样锁的次数只要获取一次,(合同、合同变更、招标立项、差旅报销、对公报销、费用报销的更新中间表的地方都要改,继承了AbstractResourceDetailLockOp的类)