写点什么

苏宁 11.11:苏宁物流移动端百万级离线数据处理方案

  • 2018-11-09
  • 本文字数:2950 字

    阅读完需:约 10 分钟

苏宁11.11:苏宁物流移动端百万级离线数据处理方案

本文为“InfoQ x 苏宁 2018 双十一”特别策划系列文章之一。

背景

随着苏宁物流的发展,对网点的整合,很好的强化了苏宁物流最后一公里配送能力,可以在相对短期内整合双方在仓储、干线、末端等方面的快递网络资源,编织一张覆盖最后一公里的密织高效网络,整合完成将极大地强化苏宁物流最后一公里配送能力,提高配送效率,降低运营成本。


其中物流移动端在仓储,干线和末端等各个环节都扮演一个非常重要的角色,然而移动端的用户和我们一般的移动互联网用户有相同的地方也有很大区别,首先物流移动端的用户基本上是我们处在一线的操作人员,界面的交互和服务的响应速度直接影响用户的作业速度。另外移动互联网用户一般是手机,而物流移动端的用户包括手机和手持码枪,这就对我们移动应用提出了更高的要求,不仅仅要具有手机的灵活性还要有手持的实用性。最后随着 3G,4G 的普及移动互联网用户手机联网性普通很好,但是我们的一线作业人员可能处在仓库的角落,信号的盲区,这就要求我们的应用不仅要在有信号的情况下可以作业,在无信号的状态下也可以正常作业。


为了实现各大区网点不能满足实时的网络交互环境的情况下正常作业,这就要求我们操作终端多向前走一步,同时能满足各网点无论在网络环境好坏的情况下都能完成作业。如果满足这些条件就需要本来在服务端处理的数据下载到码枪,在码枪本地本地进行校验处理,服务器的主数据量在都在万级以上,最大的一张表数据在百万级,而正常作业下我们用的码枪都是安卓系统,运行内存在 2G,4G,有的码枪甚至处理内存在 1G。如果通过对码枪的更新换代可以更换性能更好的码枪,但是这种成本是巨大的 ,所以通过对技术上的实现去节省成本和增加用户体验是我们苏宁物流要首当其冲面对的问题。

我们的解决方案

一、数据处理实际逻辑

  1. 通过网络下载 txt 文件

  2. 按行读取每行字符串文本

  3. 采用 String.split 函数按照指定的格式分割字符串为字符数组

  4. 通过字符数组组装 Entity 数据类,讲数据类添加到 List 里缓存

  5. 判断 List 的 Size 大小,达到 3000 条后调用 Greendao 方法启动线程开启事务批量插入,清空 List 继续循环

  6. 未达 3000 条继续循环走 2 流程



图 1、数据处理逻辑流程

二、性能瓶颈分析

使用 TraceView 找到性能的瓶颈,TraceView 是 Android SDK 中内置的一个工具,它可以加载 trace 文件,用图形的形式展示代码的执行时间、次数及调用栈。


生成 trace 文件有三种方法:


  • 使用代码

  • 使用 Android Studio

  • 使用 DDMS



图 2、开始 trace



图 3、写入到 trace 文件



图 4、执行耗时分析



图 5、函数耗时分析


通过 TraceView 分析,另外结合代码分析总结出几个性能瓶颈


  1. String .split 采用正则匹配,大数据量效率低

  2. 读文件比较耗时,如果数据库速度大于文件读取速度,则会等待文件读取

  3. 插入数据库也比较耗时,如果插入速度小于文件读取速度,则文件读取会阻塞等待

  4. 每次使用 GreenDao 开启线程,没有限制最大线程数,开启大量线程,导致数据库连接数超出上限,极易 OOM

  5. 另外,通过代码日志分析得出,Entity 没有重用,每次循环创建新的 Entity,导致内存不足,频繁耗时 GC,界面卡顿。

三、解决方案:

  1. 针对 Spilt 的优化:重写了 String.split 的实现方式,采用效率更高的 index 方式代替原来的正则匹配,依据业务特点固定了分割数目

  2. 针对对象没有重用的优化:采用动态事务,在事务中采用单条连续插入数据库,操作避免创建多余对象,达到指定数量后一起 Commit 到数据库

  3. 单次提交数量优化:经过多次测试,统计出单次提交 12000 条为效率最高的提交量

  4. 针对相互等待问题:采用阻塞队列,对象池方式减少等待


具体流程图如下:



图 6、优化流程图

四、结果对比:

通过测试的对比可以发现,同样的数据,合理匹配文件的读写速度和数据库的读写速度,减少等待间隔。


  • 在单次 8000 量的时候 ,速度可以比原来缩减 3 分 40 秒,

  • 在单次 10000 量的时候可以比原来缩减 3 分 50 秒,

  • 在单量 15000 量的时候可以缩减 3 分 50 秒左右,

  • 在单次 12000 量的时候可以缩减 3 分 57 秒。



图 7、优化对比

五、优化方案+

完成的上步之后,虽然性能上有了很大的提升,但是为了给一线的苏宁物流用户提供更优的用户体验,我们想到了优化方案+。

1. 传统 Rollback Journal

SQLite 实现 原子性提交和回滚操作 的默认方法是 rollback journal。当对 DB 进行写操作的时候,SQLite 首先将准备要修改部分的原始内容(以 Page 为单位)拷贝到“回滚日志”中,用于后续可能的 Rollback 操作以及 Crash、断电等意外造成写操作中断时恢复 DB 的原始状态,回滚日志存放于名为“DB 文件名-journal”的独立文件中。对原始内容做备份后,才能写入修改后的内容到 DB 主文件中,当写入操作完成,用户提交事务后,SQLite 清空 -journal 的内容,至此完成一个完整的写事务。


Rollback 模式中,每次拷贝原始内容或写入新内容后,都需要确保之前写入的数据真正写入到磁盘,而不是缓存在操作系统中,这需要发起一次 fsync 操作,通知并等待操作系统将缓存真正写入磁盘,这个过程十分耗时。


除了耗时的 fsync 操作,写入 -journal 以及 DB 主文件的时候,是需要独占整个 DB 的,否则别的线程/进程可能读取到写到一半的内容。这样的设计使得写操作与读操作是互斥的,并发性很差。

2. WAL 模式的应用

WAL 模式则改变了上述流程,写操作不直接写入 DB 主文件,而是写到“DB 文件名-wal”文件(以下简称“-wal”)的末尾,并且通过 -shm 共享内存文件来实现 -wal 内容的索引。读操作时,将结合 DB 主文件以及 -wal 的内容返回结果。由于读操作只读取 DB 主文件和 -wal 前面没在写的部分,不需要读取写操作正在写到一半的内容,WAL 模式下读与写操作的并发由此实现。


WAL 写操作除了上面的流程,还增加了一步:Checkpoint,即将 -wal 的内容与合并到 DB 主文件。 由于写操作将内容临时写到 -wal 文件,-wal 文件会不断增大且拖慢读操作,因此需要定期进行 Checkpoint 操作将 -wal 文件保持在合理的大小。Checkpoint 操作比较耗时且会阻塞读操作,但由于时效性要求较低,遇到堵塞可以暂时放弃继续 DB 读写操作,不至于太过影响读写性能。SQLite 官方默认的 Checkpoint 阈值是 1000 page,即当 -wal 文件达到 1000 page 大小时,写操作的线程在完成写操作后同步进行 Checkpoint 操作;Android Framework 的 Checkpoint 阈值是 100 page。


基于 WAL 的基本工作方式,做额外的两个优化点:


  1. 写入 -wal 文件时不进行 fsync 操作,因为 -wal 文件损坏只影响新写入的没 Checkpoint 部分数据而非整个数据库损坏,影响相对小;

  2. 将需要进行 fsync 的 Checkpoint 操作放到独立线程执行,让写操作能尽快返回。

结束语

物流行业快速发展的今天移动端在作业中的占比量越来越大,苏宁物流一线的作业人员的手持数量每年也在呈几何倍数上涨,在苏宁物流移动端速度发展的这些年,我们从老的黑白机到现在的智能安卓机,从传统过渡到移动互联网和正在进行的智能化,我们一直坚持用技术为一线作业人员减轻作业负责,提升作业效率,在这种大环境下对我们苏宁物流来说是压力也是机遇,不忘初心,砥砺前行,始终不忘造极,以极客的精神打造极致的用户体验。


作者:李健普,苏宁易购物流研发中心移动端开发工程师,负责物流移动端的开发工作,曾负责开发了快递,分拨,仓库,售后等客户端 ,具有较丰富的移动开发经验,对物流作业相关设备开发经验丰富。


2018-11-09 09:562154
用户头像

发布了 164 篇内容, 共 110.6 次阅读, 收获喜欢 392 次。

关注

评论 11 条评论

发布
用户头像
厉害了,又学到了不少
2018-11-13 16:28
回复
用户头像
牛B
2018-11-13 16:24
回复
用户头像
牛B
2018-11-13 16:24
回复
用户头像
学习了
2018-11-13 16:22
回复
用户头像
牛逼了,受益匪浅,点赞👍
2018-11-13 16:20
回复
用户头像
加个队列也能写一篇。。
2018-11-09 15:52
回复
用户头像
深入浅出,收获很多。感谢楼主分享。
2018-11-09 11:58
回复
用户头像
作者有心了,感谢分享。
2018-11-09 11:05
回复
用户头像
很棒,支持一下。受益匪浅。感谢楼主分享
2018-11-09 10:35
回复
用户头像
很棒,支持一下。受益匪浅。感谢楼主分享
2018-11-09 10:34
回复
用户头像
支持作者,学习到很多,棒棒哒!
2018-11-09 10:32
回复
没有更多了
发现更多内容

谈谈控制感(8):元控制感

史方远

职场 心理 成长

游戏夜读 | 怎么让游戏跑起来?

game1night

圆圈正义 - 读后感

石云升

读书笔记 法律 公平 现实

python实现·十大排序算法之快速排序(Quick Sort)

南风以南

Python 排序算法 快速排序

云直播平台的选型与使用

音视频专家-李超

我是如何拿下PMP认证和系统架构设计师考试的?

Nick

RocketMQ - 什么是RocketMQ

Java收录阁

RocketMQ

Go: g0, 特殊的goroutine

陈思敏捷

原理 Go 语言

Go: Trace包探秘

陈思敏捷

原理 Go 语言

《零基础学 Java》 FAQ 之 12-理解引用

臧萌

Java

Mysql增量更新-ON DUPLICATE KEY UPDATE

BerryMew

MySQL 增量更新 ON DUPLICATE KEY UPDATE

k8s上运行我们的springboot服务之——cloud gateway

柠檬

k8s Spring Cloud

JUC整理笔记一之细说Unsafe

JFound

Java

利与弊-传统框架要不要部署在Serverless架构上

刘宇

Serverless Web

Flask-SQLAlchemy 多表对单模型

Leetao

Python flask 编程语言 flask-sqlalchemy

我的编程之路 -5(停滞)

顿晓

网络编程 操作系统 编程之路 停滞 三年

其实你就是我羡慕的别人

小天同学

个人成长 感悟 日常思考

python实现·十大排序算法之插入排序(Insertion Sort)

南风以南

Python 排序算法 插入排序

看完这篇 Session、Cookie、Token,和面试官扯皮就没问题了

苹果看辽宁体育

https

《零基础学 Java》 FAQ 之 10-Scanner里nextInt的小坑

臧萌

Java

Java环境搭建

编号94530

Java java8 Java环境 环境安装 jdk安装

工作=投资=创业?

二鱼先生

个人成长 工作思路 工作方式 创业心态 创业者

区块链2.0--以太坊概述

皮卡丘的猫

prometheus中honor_labels配置项的源码分析

陈思敏捷

Prometheus Go 语言

Tekton 的工作原理

张晓辉

Kubernetes cicd 云原生

new() 和 make的区别

陈思敏捷

源码 源码分析 Go 语言

《零基础学 Java》 FAQ 之 11-为什么构造方法中调用重载的构造方法必须得是在第一行

臧萌

Go: 应该使用指针还是结构体副本?

陈思敏捷

struct 原理 pointer Go 语言

你是个伪工作者么?

池建强

个人成长 伪工作者

2020 年 5 月 23 日 Java 集合专题

瑞克与莫迪

Java

除了直接看余额,谁更有钱还能怎么比(一)

石君

零知识证明 多方计算 同态加密

苏宁11.11:苏宁物流移动端百万级离线数据处理方案_5G/IoT_徐川_InfoQ精选文章