zoukankan      html  css  js  c++  java
  • 性能优化-数据传输效率优化

    客户端与服务端经常进行着频繁的数据传输,而数据传输又影响着用户体验,本文就传输速率的优化,提出合理的优化建议

    传统的传输方案

    在开始的时候,采用的是xml传输,这就要使用到Serializable/Parcelable序列化以及反序列化,其传输速度之慢,基本已经被遗弃,后来又出现了JSON序列化传输,其常用工具就是GSON和fastjson,但随着时代的进步,json也体现出了局限性

    json的局限性主要体现在其是基于字符串的传输,在转换的时候会生成大量JsonObject,然后转化为字符串,送进流里面,然后传输,在服务端也要从流中取出,然后反序列化,一大堆繁琐的过程,其也渐渐不适合当今传输数据的要求

    那么什么样的方案才满足当今数据传输的要求呢?

    新的数据传输方式

    现在有如下选择可以用

    • Protocal Buffers:强大,灵活,但是对内存的消耗会比较大,并不是移动终端上的最佳选择
    • Nano-Proto-Buffers:基于Protocal,为移动终端做了特殊的优化,代码执行效率更高,内存使用效率更佳
    • FlatBuffers:这个开源库最开始是由Google研发的,专注于提供更优秀的性能

    以下两幅图是这三个工具的性能对比
    性能对比图1
    性能对比图2
    可见,FlatBuffers几乎从空间和时间复杂度上完胜其他技术
    FlatBuffers是一个开源的跨平台数据序列化库,可以应用到几乎任何语言(C++,C#,Go,Java,JavaScript,PHP,Python),最开始是Google为游戏或者其他对性能要求很高的应用开发的。项目地址在GitHub上。官方的文档在这里

    FlatBuffer的优点
    FlatBuffer相对于其他序列化技术,例如XMLJSONProtocol Buffers等,有哪些优势呢?官方文档的说法如下:

    1. 直接读取序列化数据,而不需要解析(Parsing)或者解包(Unpacking):FlatBuffer把数据层级结构保存在一个扁平化的二进制缓存(一维数组)中,同时能够保持直接获取里面的结构化数据,而不需要解析,并且还能保证数据结构变化的前后向兼容
    2. 高效的内存使用和速度:FlatBuffer 使用过程中,不需要额外的内存,几乎接近原始数据在内存中的大小
    3. 灵活:数据能够前后向兼容,并且能够灵活控制你的数据结构
    4. 很少的代码侵入性:使用少量的自动生成的代码即可实现
    5. 强数据类性,易于使用,跨平台,几乎语言无关
      性能对比表
      JSON是Android中很常用的数据序列化技术,但却很消耗内存,而FlatBuffer正好解决了这个问题,性能还更好了

    使用方法

    简单来说:FlatBuffers的使用方法是,首先按照使用特定的IDL定义数据结构schema,然后使用编译工具flatc编译schema生成对应的代码,把生成的代码应用到工程中即可

    • 首先,我们需要得到flatc,这个需要从源码编辑得到。从GitHubClone代码
    git clone https://github.com/google/flatbuffers
    

    首先要使用FlatBuffersIDL定义好数据结构Schema,编写Schema的详细文档在这里。其语法和C语言类似,比较容易上手。我们这里引用一个简单的例子,假设数据结构如下:

    class Person {  
        String name;
        int friendshipStatus;
        Person spouse;
        List<Person>friends;
    }
    

    编写成Schema如下,文件名为Person.fbs

    namespace com.race604.fbs;
    
    enum FriendshipStatus: int {Friend = 1, NotFriend}
    
    table Person {  
      name: string;
      friendshipStatus: FriendshipStatus = Friend;
      spouse: Person;
      friends: [Person];
    }
    
    root_type Person;  
    

    然后,使用flatc可以把Schema编译成多种编程语言,我们仅仅讨论Android平台,所以把Schema编译成Java,找到flatc.exe执行命令如下:

    ./flatc –j -b Person.fbs
    

    在当前目录生成如下文件:

    .
    └── com
        └── race604
            └── fbs
                ├── FriendshipStatus.java
                └── Person.java
    

    Person类有响应的函数直接获取其内部的属性值,使用非常简单:

    Person person = ...;  
    // 获取普通成员
    String name = person.name();  
    int friendshipStatus = person.friendshipStatus();  
    // 获取数组
    int length = person.friendsLength()  
    for (int i = 0; i < length; i++) {  
        Person friends = person.friends(i);
        ...
    }
    

    下面我们来构建一个Person对象,名字是"John",其配偶(spouse)是"Mary",还有两个朋友,分别是"Dave""Tom",实现如下:

    private ByteBuffer createPerson() {  
        FlatBufferBuilder builder = new FlatBufferBuilder(0);
        int spouseName = builder.createString("Mary");
        int spouse = Person.createPerson(builder, spouseName, FriendshipStatus.Friend, 0, 0);
    
        int friendDave = Person.createPerson(builder, builder.createString("Dave"),
                FriendshipStatus.Friend, 0, 0);
        int friendTom = Person.createPerson(builder, builder.createString("Tom"),
                FriendshipStatus.Friend, 0, 0);
    
        int name = builder.createString("John");
        int[] friendsArr = new int[]{ friendDave, friendTom };
        int friends = Person.createFriendsVector(builder, friendsArr);
    
        Person.startPerson(builder);
        Person.addName(builder, name);
        Person.addSpouse(builder, spouse);
        Person.addFriends(builder, friends);
        Person.addFriendshipStatus(builder, FriendshipStatus.NotFriend);
    
        int john = Person.endPerson(builder);
        builder.finish(john);
    
        return builder.dataBuffer();
    }
    

    基本方法就是通过FlatBufferBuilder工具,往里面填写数据,详细的写法可以参考官方文档。可见,其实写法略显繁琐,不太直观

    基本原理

    如官方文档的介绍,FlatBuffers就像它的名字所表示的一样,就是把结构化的对象,用一个扁平化(Flat)的缓冲区保存,简单的来说就是把内存对象数据,保存在一个一维的数组中。借用Facebook文章的一张图如下:
    facebook描述
    可见,FlatBuffers保存在一个byte数组中,有一个支点指针(pivot point)以此为界,存储的内容分为两个部分:元数据和数据内容。其中元数据部分就是数据在前面,其长度等于对象中的字段数量,每个byte保存对应字段内容在数组中的索引(从支点位置开始计算)

    如图,上面的Person对象第一个字段是name,其值的索引位置是1,所以从索引位置1开始的字符串,就是name字段的值"John"。第二个字段是friendshipStatus,其索引值是6,找到值为2, 表示NotFriend。第三个字段是spouse,也一个Person对象,索引值是12,指向的是此对象的支点位置。第四个字段是一个数组,图中表示的数组为空,所以索引值是0

    通过上面的解析,可以看出,FlatBuffers通过自己分配和管理对象的存储,使对象在内存中就是线性结构化的,直接可以把内存内容保存或者发送出去,加载解析数据只需要把byte数组加载到内存中即可,不需要任何解析,也不产生任何中间变量

    它与具体的机器或者运行环境无关,例如在Java中,对象内的内存不依赖Java虚拟机的堆内存分配策略实现,所以也是跨平台的

    使用建议

    通过前面的体验,FlatBuffers几乎秒杀了JSON
    下面说说FlatBuffers的几点缺点:

    1. FlatBuffers需要生成代码,对代码有侵入性
    2. 数据序列化没有可读性,不方便 Debug
    3. 构建FlatBuffers对象比较麻烦,不直观,特别是如果对象比较复杂情况下需要写大段的代码
    4. 数据的所有内容需要使用Schema严格定义,灵活性不如JSON

    所以,在什么情况下选择使用FlatBuffers呢?个人感觉需要满足以下几点:
    1.项目中有大量数据传输和解析,使用JSON成为了性能瓶颈
    2.稳定的数据结构定义

    用一个完整例子说明

    假如存在一个数据结构Items,Items里面有很多属性,其中Items又包含LetterItems,LetterItems又有自己的属性还包含Details,其又有自己的属性,那么这样一个结构应该怎样去写成fbs文件呢?编写文本文件,其后缀名要为.fbs

    namespace com.cj5785.flatbufferstest;
    
    table Items {
    	id:int;
    	title:string;
    	show:bool;
    	time:long;
    	LetterItems:[LetterItems];
    }
    
    table LetterItems {
    	id:int;
    	title:string;
    	author:string;
    	time:long;
    	Details:[Details];
    }
    
    table Details {
    	id:int;
    	name:string;
    	price:double;
    	date:long;
    }
    root_type Items;
    

    fbs还支持enumunionstruct的定义
    windows平台可以直接下载flatc.exe使用:https://github.com/google/flatbuffers/releases
    使用如下命令生成文件

    flatc --java test.fbs
    

    执行上述命令会生成三个java文件:Items.javaLetterItems.javaDetails.java
    将生成的文件和FlatBufferBuilder.java以及Table.java复制到项目目录中
    适当修改包名和一些引用错误,就完美融入到项目中了
    接下来做一个简单测试,序列化然后写入本地,之后再读取出来显示出来
    这里做了一个简单布局。一个TextView用来显示,两个Button,一个解析,一个读取,为了方便,直接使用了onClick属性

    import android.os.Environment;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.widget.TextView;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.nio.channels.FileChannel;
    
    public class MainActivity extends AppCompatActivity {
    
        private static final String TAG = "cj5785";
        private TextView textView;
        private String path;
        private File file;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            textView = (TextView) findViewById(R.id.text_view);
            path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "flattest.bin";
            file = new File(path);
        }
    
        public void serialize(View view) {
            FlatBufferBuilder flatBufferBuilder = new FlatBufferBuilder();
    
            long startTime = System.currentTimeMillis();
            //Details数据
            int orange = flatBufferBuilder.createString("orange");
            int orangeDetail = Details.createDetails(flatBufferBuilder, 1, orange, 5.0, 20180101L);
            int apple = flatBufferBuilder.createString("apple");
            int appleDetail = Details.createDetails(flatBufferBuilder, 2, apple, 8.0, 20180101L);
            int details[] = new int[2];
            details[0] = orangeDetail;
            details[1] = appleDetail;
            int detailsList = LetterItems.createDetailsVector(flatBufferBuilder, details);
    
            //LetterItems数据
            int title = flatBufferBuilder.createString("title");
            int author = flatBufferBuilder.createString("author");
            int letterItems = LetterItems.createLetterItems(flatBufferBuilder, 1, title, author,
                    20180101L, detailsList);
            int letterItemsList = Items.createLetterItemsVector(flatBufferBuilder, new int[]{letterItems});
    
    
            //Items根数据
            //在开始构建根的时候,不允许再创建,否则会报错:object serialization must not be nested
            int titleOffset = flatBufferBuilder.createString("article");
            Items.startItems(flatBufferBuilder);
            Items.addId(flatBufferBuilder, 1);
            Items.addTitle(flatBufferBuilder, titleOffset);
            Items.addShow(flatBufferBuilder, false);
            Items.addTime(flatBufferBuilder, 20180101L);
            Items.addLetterItems(flatBufferBuilder, letterItemsList);
            int rootItems = Items.endItems(flatBufferBuilder);
            Items.finishItemsBuffer(flatBufferBuilder, rootItems);
            long endTime = System.currentTimeMillis();
            textView.setText("序列化用时:" + (endTime - startTime) + "ms
    ");
            textView.append("写入的数据为:
    ");
            textView.append("Item(1,article,false,20180101L,*)
    ");
            textView.append("LetterItems(1,title,author,20180101L,*)
    ");
            textView.append("Details(1,organge,5.0,20180101L)
    ");
            textView.append("Details(2,apple,5.0,20180101L)
    ");
    
            //保存文件到本地
            if (file.exists()) {
                file.delete();
            }
            ByteBuffer data = flatBufferBuilder.dataBuffer();
            FileOutputStream out = null;
            FileChannel channel = null;
            try {
                out = new FileOutputStream(file);
                channel = out.getChannel();
                while (data.hasRemaining()) {
                    channel.write(data);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if (out != null) {
                        out.close();
                    }
                    if (channel != null) {
                        channel.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public void desserialize(View view) {
            FileInputStream fis = null;
            FileChannel readChannel = null;
            try {
                fis = new FileInputStream(file);
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                readChannel = fis.getChannel();
                int readBytes = 0;
                while ((readBytes = readChannel.read(byteBuffer)) != -1) {
                    System.out.println("读取数据个数:" + readBytes);
                }
                //把指针回到最初的状态,准备从byteBuffer当中读取数据
                byteBuffer.flip();
                //解析出二进制为Items对象
                textView.append("读取的数据为:
    ");
    
                Items items = Items.getRootAsItems(byteBuffer);
                textView.append("Items:" + items.id() + "," + items.title() + "," + items.show()
                        + "," + items.time() + "
    ");
    
                LetterItems letterItems = items.LetterItems(0);
                textView.append("LetterItems:" + letterItems.id() + "," + letterItems.title() + ","
                        + letterItems.author() + "," + letterItems.time() + "
    ");
    
                int length = letterItems.DetailsLength();
                for (int i = 0; i < length; i++) {
                    Details details = letterItems.Details(i);
                    textView.append("Details:" + details.id() + "," + details.name() + ","
                            + details.price() + "," + details.date() + "
    ");
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if (readChannel != null) {
                        readChannel.close();
                    }
                    if (fis != null) {
                        fis.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    运行效果如下图:
    运行效果
    由此可见其序列化速度之快

  • 相关阅读:
    RabbitMq windows 安装
    JQuery.Ajax()的data参数传递方式
    [转载]ASP.NET中TextBox控件设立ReadOnly="true"后台取不到值
    vue-cli 3.0脚手架搭建项目
    二、操作符
    一、JavaScript基础
    html苹方字体
    js十大排序算法收藏
    iframe高度自适应的6个方法
    CSS3:不可思议的border属性
  • 原文地址:https://www.cnblogs.com/cj5785/p/10664633.html
Copyright © 2011-2022 走看看