应用市场安装app异常排查

1,修改表结构,导致死锁

由上图死锁导致整个流程卡住,安装app的流程无法继续,导致应用市场安装app异常。

下面的是详细的业务分析
可以发现数据库存在很多死锁

进一步可以查看相关代码

上图显示如果锁表以后,程序一直卡在 preepare write final meta to redis 以及 end write final meta to redis
结合代码发现恰好卡在修改表结构这里。

      // 加载配置
      AppLifecycleFacade.initProcess(meta, MetaConstant.BUSSINESS, allInstallAppList);
      AppLifecycleFacade.initShard();
      // 创建表结构.  修改表结构,可能会导致锁表。
      AppLifecycleFacade.prepareAutoInitModels(appGroupContainer, allInstallAppList);
      // 根据全量模型生成业务种子数据,前端目录要与下载一致
      SeedUtils.loadSeedData(meta, allInstallAppList, appGroupContainer,MetaConstant.BASE_FRONTEND);
      // 加载文件种子数据
      AppLifecycleFacade.loadFileSeed(meta, allInstallAppList);

除了锁表或者死锁等原因以外,可能还存在获取数据conn阻塞,导致安装流程卡死。执行 jstack -l 1 结果如下:

"http-nio-8060-exec-3" #131 daemon prio=5 os_prio=0 tid=0x00007f15d4009800 nid=0x8a waiting on condition [0x00007f161dbe2000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000005d1424370> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
        at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:146)
        at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:33)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1114)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1176)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
--
        at com.alibaba.druid.filter.stat.StatFilter.dataSource_getConnection(StatFilter.java:726)
        at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:5055)
        at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1407)
        at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1399)
        at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:100)
        at com.sie.snest.engine.db.relationdb.RelationDBAccessor.<init>(RelationDBAccessor.java:183)
        at com.sie.snest.engine.a.a.getNewAccessorFromDataSource(AbstractDataSourceStrategy.java:125)
        at com.sie.snest.engine.a.a.getDbAccessor(AbstractDataSourceStrategy.java:108)
        at com.sie.snest.engine.a.f.getRelationDBAccessor(DbDataSourceStrategy.java:18)
        at com.sie.snest.engine.data.access.BussModelDataAccess.getRelationDBAccessor(BussModelDataAccess.java:125)
        at com.sie.snest.engine.container.Meta.getRelationDBAccessor(Meta.java:135)
        at com.sie.snest.engine.model.loaders.SeedLoader.a(SeedLoader.java:2036)
        at com.sie.snest.engine.model.loaders.SeedLoader$$Lambda$1535/1824031629.accept(Unknown Source)
        at java.util.HashMap.forEach(HashMap.java:1289)
        at com.sie.snest.engine.model.loaders.SeedLoader.update(SeedLoader.java:308)
        at com.sie.snest.engine.model.loaders.AppDataInfo.updateSeedData(AppDataInfo.java:921)
        at com.sie.iidp.appstore.utils.SeedUtils.loadSeedData(SeedUtils.java:68)
        at com.sie.iidp.appstore.handler.service.FinalModelHandler.doComputeMeta(FinalModelHandler.java:141)
        at com.sie.iidp.appstore.handler.service.FinalModelHandler.handle(FinalModelHandler.java:60)
        at com.sie.iidp.appstore.handler.InstallStateMachine.execute(InstallStateMachine.java:26)
        at com.sie.iidp.appstore.appstore.DistributedAppLoaderV2.updateApps(DistributedAppLoaderV2.java:405)
        at com.sie.iidp.appstore.meta.MetaApp.updateApp(MetaApp.java:749)
        at com.sie.iidp.appstore.meta.MetaApp$$EnhancerByCGLIB$$9b7d6ddd.CGLIB$updateApp$6(<generated>)
        at com.sie.iidp.appstore.meta.MetaApp$$EnhancerByCGLIB$$9b7d6ddd$$FastClassByCGLIB$$f7fc717f.invoke(<generated>)
        at net.sf.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
        at com.sie.snest.engine.model.ModelInterceptor.intercept(ModelInterceptor.java:84)
        at com.sie.iidp.appstore.meta.MetaApp$$EnhancerByCGLIB$$9b7d6ddd.updateApp(<generated>)
--
可以发现整个线程卡死在 com.alibaba.druid.filter.stat.StatFilter.dataSource_getConnection(StatFilter.java:726) ,一直等待连接。
2,调用distribute安装接口异常

调用distribute接口异常,导致应用市场安装app失败,但是应用市场已经对数据库表、redis元模型数据更新,并没有回滚,导致数据不一致。

如上图可知,当更新或者删除deploymentservice的时候,会产生Conflict,意味着存在并发竞争的情况,这也是很常见的情况,由于etcdf采用mvcc机制来控制并发,常用的解决方式就是重试,详情可以参考官方文档:etcd官网

3,应用市场安装app异常

与场景2类似,应用市场本身的安装app接口异常,也没有回滚数据,导致数据不一致。

实际测试场景中出现的概率可能不是很高,但是肯定也存在这种异常情况,因为任何外部网络io请求、第三方中间件依赖等,都会出现异常的情况,业务必须考虑这种异常情况的存在性,并做必要的处理。

解决方案

1,业务制定回滚流程

应用市场安装app异常后,业务制定回滚流程,回滚数据库表、redis元模型数据等。
数据库在死锁后可以使死锁事务全部回滚,比如mysql可以配置innodb_rollback_on_timeout参数。 一个成熟的数据库产品肯定是满足事务的ACID的.

2,手动卸载并重新安装app

如果应用市场安装app异常,可以手动卸载已经安装的app,并重新安装。 distribute暴露的安装和卸载接口是幂等的,可以多次调用。

对于死锁(数据库锁表、分布式死锁等)情况,需要业务制定死锁处理流程,比如手动kill死锁的sql语句,或者重启应用市场服务等。

3,异步处理,最终一致性

大概就是参考k8s如何启动一个pod的流程,具体还没想好怎么做 ૮(˶ᵔ ᵕ ᵔ˶)ა

4,解耦种子数据和业务数据

详情参考米久业给出的最新解决方案

任何一个系统都无法保证做一件事情一定成功,这是不可能的,但是任何一个系统必须保证做一件事情结果的一致性,要么成功,要么失败,这是确定性的。 我们常说的tcp协议是面向连接的可靠传输协议,但是tcp从来不保证一定传输成功,tcp只保证数据传输了就一定成功了,同时传输失败了就一定失败了,没有中间状态。 类似地,对于distributed的安装、卸载和更新接口,它底层是etcd,etcd也不保证一定成功,但是保证数据的地表最强的一致性,要么成功,要么失败。