文章目录

  • 从零开始写高性能的人脸识别服务器(二)
    • 1 高性能服务器架构
      • 1.1 BIO
      • 1.2 NIO
      • 1.3 架构发展
      • 1.4 网络IO模型对比
    • 2 protobuf序列化协议
      • 2.1 什么是序列化与反序列化
      • 2.2 传统的序列化方法的优缺点
      • 2.3 何为protobuf
      • 2.4 protobuf效率对比
        • 2.4.1 Json序列化
        • 2.4.2 protobuf序列化
        • 2.4.3 总结
      • 2.5 protobuf原理
        • 2.5.1 protobuf格式
        • 2.5.2 protobuf序列化规则
        • 2.5.3 Varint编码规则
        • 2.5.4 大数字的编码
        • 2.5.5 ZigZag编码
        • 2.5.6 Length-delimited
        • 2.5.7 repeated
        • 2.5.8 packed
    • 3 AI模型微服务
    • 4 实际运行效果

从零开始写高性能的人脸识别服务器(二)

​ 我们在专栏一中详细介绍了本项目。在这一章,我将带领大家从以下三个方面了解一下项目是如何承载高并发与高负载的,分别是高性能的网络IO模型、protobuf序列化协议以及AI模型多进程微服务。Netty据说可以承受百万级的并发,使用protobuf序列化传输数据,提升数据的传输效率。

1 高性能服务器架构

​ Java服务器架构的发展从一开始的BIO,发展到NIO,再发展到如今基于NIO的Netty。相信大家如果不理解Netty的话,应该也写过socket通信(没写过socket通信的,麻烦去学习一下谢谢),如果一次性有上万个请求过来,你启动了上万个线程去处理请求,此时你的机器就炸了,linux最大支持两千个线程,想支持更多,就得去改配置文件。而且一台机器能承受的线程数是有限的,当线程数量超过一定数量时,系统就会崩溃。

1.1 BIO

​ 同步并阻塞式(传统阻塞式);服务器实现为一个连接一个线程,即客户端有连接请求时,服务器就启动一个线程进行处理,如果连接不做任何事情,就会造成不必要的开销。

1.2 NIO

同步非阻塞式(传统阻塞式);服务器实现为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到**多路复用器**上,多路复用器轮询到连接有I/O请求就进行处理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.3 架构发展

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

主从Reactor多线程模型,将使用两个selector,一个主selector专门维护accept事件,当接收到accept事件,将该连接交给从selector,从selector维护该连接的read和write事件。主从selector的方式,将连接和数据处理完全分开维护,将大大提高并发量Netty就是使用的这种网络模型。

1.4 网络IO模型对比

​ 因为连接请求的处理速度很快,所以前台一般留一个线程负责处理accept请求。剩下的前台负责接受read/write请求,然后根据请求的内容去交给指定的服务员处理。当然主从模型的数量都是可以自己指定的。

2 protobuf序列化协议

​ 上面看完了高性能的网络模型架构。我们接下来看如何在消息传输上提高效率。本小节的主人公便是protobuf。下面我们一步步的解开她的面纱。首先我们来看什么是序列化与反序列化。

2.1 什么是序列化与反序列化

​ 从广义上来说,序列化就是将结构化数据(对象)转换为一种中间格式,利用这种中间格式进行数据的存储与传输,反序列化则是将这种中间格式转换为结构化数据(对象),常见的跨语言的序列化协议有XML, JSON 等,以及一些语言内部私有的序列化协议, 如IAVA的Serialiable序列化。

2.2 传统的序列化方法的优缺点

​ XML. JSON 等传统序列化方式能够流行起来自然有它们优秀的一面,比如说跨语言,易于阅读(人类直接可读),学习成本低等等。但是它们的缺点也是显而易见的,那就是体积过于庞大,有很多冗余字段,以JSON为例子,序列化后的大括号,双引号等都简要进行传输,存在极大的空间浪费。尤其是在对性能与带宽要求比较苛刻的场景下是不能够被接受的,比如游戏,即时通讯等领域。再比如某些语言内部私有序列化协议,序列化后也存在体积过大井且不能夺语言通信等问题,比如Java语言的序列化。

<person><name>马善涛</name><age>24</age><tellphone>110120114</tellphone>
</person>
{"name":"马善涛","age":"24","tellphone":"110120114"
}

2.3 何为protobuf

​ 是谷歌开源的,官方在此,要翻墙,一种与语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序。

​ 说白了,protobuf就是消息序列化工具,与语言无关、可以跨语言,可以由Java或者C++语言序列化,交由C++或者Python进行反序列化。比XML、JSON等常见的序列化工具效率都高,当然XML和JSON的效率不高是人尽皆知的事情了!!protobuf现在也是大部分RPC框架的序列化格式,因为效率高嘛!但是,在IPC摄像头上面,Onvif的私有协议的远程RPC传输格式竟然还是XML!所以说控制摄像头的云台移动,摄像头会很卡。protobuf的工作原理如下图所示

2.4 protobuf效率对比

下面通过几个例子比较一下各个序列化方式。

2.4.1 Json序列化

(1)定义Student类

public class Student {String name;int age;String school;String tellphone;String email;int weight;String homeAddress;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getSchool() {return school;}public void setSchool(String school) {this.school = school;}public String getTellphone() {return tellphone;}public void setTellphone(String tellphone) {this.tellphone = tellphone;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}public int getWeight() {return weight;}public void setWeight(int weight) {this.weight = weight;}public String getHomeAddress() {return homeAddress;}public void setHomeAddress(String homeAddress) {this.homeAddress = homeAddress;}
}

(2)序列化

public class Test {public static void main(String[] args) {Student student = new Student();student.setAge(24);student.setEmail("1205006751@qq.com");student.setHomeAddress("山东省济南市");student.setName("马善涛");student.setSchool("哈尔滨理工大学");student.setWeight(135);student.setTellphone("110120114");String json  = JSON.toJSONString(student);System.out.println(json);// 数据在传输过程中是以二进制的方式传输byte[] bytes = json.getBytes();System.out.println(bytes.length);}
}
{"age":24,"email":"1205006751@qq.com","homeAddress":"山东省济南市","name":"马善涛","school":"哈尔滨理工大学","tellphone":"110120114","weight":135}
162

可以看到Json序列化之后的数据长度为162个字节。

2.4.2 protobuf序列化

(1)定义protobuf消息

syntax = "proto3"; // proto3 必须加此注解
option java_package = "xin.marico.facerecogition.test";
option java_outer_classname = "StudentProto";message  Student {string name = 1;int32 age = 2; string tellphone = 3;string email = 4;int32  weight = 5;string homeAddress = 6;string school = 7;
}

(2)编译成proto类

protoc.exe --java_out=./  StudentProto.proto

(3)编译生成的protobuf对象

// Generated by the protocol buffer compiler.  DO NOT EDIT!
// source: StudentProto.protopackage xin.marico.facerecogition.test;public final class StudentProto {private StudentProto() {}public static void registerAllExtensions(com.google.protobuf.ExtensionRegistryLite registry) {}
........................................................

(4)序列化

public class Test2 {public static void main(String[] args) {StudentProto.Student student = StudentProto.Student.newBuilder().setAge(24).setEmail("1205006751@qq.com").setHomeAddress("山东省济南市").setName("马善涛").setSchool("哈尔滨理工大学").setWeight(135).setTellphone("120114110").build();System.out.println(student);// 数据在传输过程中是以二进制的方式传输byte[] bytes = student.toByteArray();System.out.println(bytes.length);}
}
name: "\351\251\254\345\226\204\346\266\233"
age: 24
tellphone: "120114110"
email: "1205006751@qq.com"
weight: 135
homeAddress: "\345\261\261\344\270\234\347\234\201\346\265\216\345\215\227\345\270\202"
school: "\345\223\210\345\260\224\346\273\250\347\220\206\345\267\245\345\244\247\345\255\246"89

2.4.3 总结

​ Json的序列化得到的长度为162个字节,protobuf序列化之后的长度是89个字节,由此可见protobuf的序列化是Json空间效率的一倍。

2.5 protobuf原理

​ 详细解释一下原理,自己也能好好学习一下。原理十分枯燥,如果之前没有protobuf经验,看起来会非常吃力。

2.5.1 protobuf格式

​ Protobuf消息由字段(field)构成,每个字段有其规则(rule)数据类型(type)字段名(name)tag,以及选项(option)。比如下面这段代码描述了由10个字段构成的Test消息:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zpYRMrYW-1606798973906)(.\resources\20161121140436495.png)]

2.5.2 protobuf序列化规则

序列化时,消息字段会按照tag顺序,以key+val的格式,编码成二进制数据。

​ Protobuf消息序列化之后,会产生二进制数据。这些数据(精确到bit)按照含义不同,可以划分为6个部分:MSB flagtag编码后数据类型(wire type)长度(length)字段值(value)、以及填充(padding)。后文会图解这些部分的具体含义,这里先约定好图中消息各部分使用的颜色:

​ 前面说过,消息的每一个字段,都会以key+val的形式,序列化为二进制数据。val比较好猜测,那么key具体是什么呢?答案是这样:key = tag << 3 | wire_type。也就是说,key是tag左移3位得到的。如果tag=2,那么key就等于8。

​ 后面3个比特是wire type,其他的比特是tag值。Protobuf支持丰富的数据类型,但是编码之后,只剩下Varint(0)64-bit(1)、**Length-delimited(2)32-bit(5)**这4种(还有两种已经废弃了,本文不讨论)类型,用3个比特来表示,足够了。下面举个例子:

byte[] data = Test.newBuilder().setA(3).setB(2).setC(1).build().toByteArray();

以上的数据序列化之后是:

2.5.3 Varint编码规则

​ Varint编码规则使用7个比特位存储数据高位1个用来标记字节的连续性。0000 0001 高位为0,表示当前字节是独立的存储一个值,取出后7个字节就是该值。1010 1100 ,0000 0010, 高位为1,则继续读取后续字节,直到出现一个高位为0的结束,这些直接加一起来存储一个值首先进行小端序列转换为大端序列 1010 1100 ,0000 0010 转换序列 -> 0000 0010 , 1010 1100。0000 0010 , 1010 1100把高位去掉, 000 0010, 010 1100 合并为 100101100 得到300.

​ wire type一共四种,所以用3个bit来表示wire type足够了。

​ 但是tag是用剩下的5个bit来表示吗?tag难道不能超过32(2^5)吗?由上图已经知道,答案是否!为了用尽可能少的字节编码消息,Protobuf在多处都使用了Varint这种格式。比如数据类型里的int32、int64,以及tag值和后面将要解释的length值,都使用Varint类型存储。那么Varint到底有什么神奇之处呢?也没有,其实就是用每个字节的前7个bit来表示数据,而最高位的bit(MSB,Most Significant Bit)则用作记号(flag)。文字不太好描述,看一个例子:

byte[] data2 = Test.newBuilder().setJ(1) // tag=16.build().toByteArray();

​ 由于tag是按Varint编码的,所以要扣掉一个bit(MSB)。再减去wire type占用的3个比特,那么第一个字节里,留给tag值的,实际只剩下4个比特,只能表示0到15。由于Test消息j字段的tag值是16,所以需要两个字节才能表示j字段的key。data2如下图所示。

2.5.4 大数字的编码

​ 前面说了,为了节省字节数,taglength,以及int32int64等数据类型都是用Varint编码的。那么这种编码方式有什么坏处吗?主要有2处。第一,不利于表示大数。对于比较小的数来说,以0到127为例,用Varint很划算。以浪费1bit和少量额外的计算为代价,只要1个字节就可以表示。但是对于比较大的数,就不划算了。以int32为例,大于2^(4*7) - 1的数,需要用5个字节来表示。看一个例子:

​ 也就是说,如果某个消息的某个int字段大部分时候都会取比较大的数,那么这个字段使用Varint这种变长类型来编码就没什么好处。对于这种情况,Protobuf定义了64-bit和32-bit两种定长编码类型。使用64-bit编码的数据类型包括fixed64sfixed64double;使用32-bit编码的数据类型包括fixed32、sfixed32和float。以Test消息e字段(fixed32)为例

byte[] data4 = Test.newBuilder().setE(268435456) // 2^28.build().toByteArray();

序列化之后的数据如下图所示:

2.5.5 ZigZag编码

​ Varint编码格式的第二缺点是不适合表示负数,以int32和-1为例:

byte[] data5 = Test.newBuilder().setA(-1).build().toByteArray();

​ Protobuf想让int32和int64在编码格式上兼容,所以-1需要占用10个字节,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pf4RUxgS-1606798973930)(.\resources\20161121112030282.png)]

负数需要占用更多的字节,为了克服这个缺陷,Protobuf提供了sint32sint64两种数据类型。如果某个消息的某个字段出现负数值的可能性比较大,那么应该使用sint32或sint64。这两种数据类型在编码时,会先使用ZigZig编码将负数映射成正数,然后再使用Varint编码。ZigZag编码规则如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tb7PL3KS-1606798973932)(.\resources\20161121114129107.png)]

​ 序列化之后的数据如下图所示:

2.5.6 Length-delimited

​ 如前所述,64-bit和32-bit是定长编码格式,长度固定。Varint是变长编码格式,长度由字节的MSB决定。Length-delimited编码格式则会将数据的length也编码进最终数据,使用Length-delimited编码格式的数据类型包括stringbytes自定义消息。以string为例:

byte[] data7 = Test.newBuilder().setF("hello") // string.build().toByteArray();

序列化之后的数据如下图所示:

2.5.7 repeated

​ 前面讨论的字段都是optional类型,最多只有一个val,但是repeated字段却可以有多个val。那么repeated字段是如何序列化的呢?以Test消息的g字段为例:

byte[] data9 = Test.newBuilder().addG(1).addG(2).addG(3).build().toByteArray();

序列化之后的数据如下图所示:

可见,repeated字段就是简单的把每个字段值依次序列化而已,注意key也是每次都重复了。

2.5.8 packed

​ 如果repeated字段包含的val比较多,那么每个val都带上key是不是比较浪费呢?是的,所以Protobuf提供了packed选项,以Test消息的h字段为例:

byte[] data10 = Test.newBuilder().addH(1).addH(2).addH(3) // packed.build().toByteArray();

序列化之后的数据如下图所示:

可见,如果repeated字段设置了packed选项,则会使用Length-delimited格式来编码字段值.每个val的值分割用的varint编码的。

3 AI模型微服务

​ 上面看完了高性能网络模型和高性能的序列化机制,我们再来看一下AI模型的最优化处理。因为在实际使用中,机器学习/深度学习模型在初始化时,需要加载模型参数,十分耗时,如果每次执行AI服务都要先加载参数,那不就完犊子了。所以将AI模型做成微服务,微服务不断从消息队列里面取出数据进行处理,然后将处理结果存到redis里面等待其他进程去获取。

​ 我们使用的face_recoginize是基于Python开发的世界上最简洁的人脸识别库,你可以使用Python和命令行工具提取、识别、操作人脸。它人脸识别是基于业内领先的C++开源库 dlib中的深度学习模型,用Labeled Faces in the Wild人脸数据集进行测试,有高达99.38%的准确率。但对小孩和亚洲人脸的识别准确率尚待提升。具体的学习资料,下面给大家罗列一些,大家可以学习一下。

(1)知乎学习地址

(2)windows下python3.6使用face_recognition进行人脸识别

4 实际运行效果

​ 我AI人脸识别微服务启动了50个人脸识别进程和10个人脸上传进程,在华为云企业服务器上把CPU跑到了90+%。

华为云服务器从收到图片到识别成功,耗时一般在0.5s左右,因为华为云性能还是比较低的,没有GPU。算上网络传输+线程协调,C客户端从点击上传到返回识别结果的时间在1.5s左右,WEB端识别一张图片在2s左右

从零开始写高性能的人脸识别服务器(二)相关推荐

  1. 从零开始写高性能的人脸识别服务器(一)

    文章目录 从零开始写高性能的人脸识别服务器(一) 1 技术选型 2 环境准备 2.1 安装**face_recoginize** 2.2 安装Redis 2.3 Qt5.9.9的安装 3 效果演示 3 ...

  2. 从零开始写高性能的人脸识别服务器(三)

    这里写自定义目录标题 从零开始写高性能的人脸识别服务器(三) 1 消息格式 1.1 定义Proto 1.2 编译 2 人脸识别微服务 3 Netty服务器 从零开始写高性能的人脸识别服务器(三) ​ ...

  3. 人脸识别(二)----如何生成CSV文件

    人脸识别(二)----如何生成CSV文件 当我们写人脸模型训练的时候,我们需要读取人脸的路径path和人脸对应的标签label.人脸的路径就是人脸图片所在你的电脑的位置,标签就是一个人对应一个标签(注 ...

  4. 人脸识别智能服务器,智能化人脸识别服务器

    智能化人脸识别服务器,同时,在新基建的运维管理方面有了进一步的创新,通过自身的研发实现了对各基建网点的自动化全实时监测,及时发现故障并发出警示.自动处理故障网点.通知运维人员通过远程及时处理故障网点. ...

  5. 自己写一个控制台人脸识别程序

    自己写一个控制台人脸识别程序 其中用到的技术:IO文件流,api,json,集合 package com.aa; /****************************************** ...

  6. 二自由度云台扫描算法_基于HuskyLens人脸识别的二自由度自动跟踪云台

    "看什么看?" "就盯着你看!" --基于HuskyLens人脸识别的二自由度自动跟踪云台 试用群里的老师们先后放出了各色利用二哈人脸识别功能的案例,实验对象从 ...

  7. 松下推出人脸识别服务器软件 使用深度学习技术

    文章来源:ATYUN AI平台 松下公司宣布,采用深度学习技术的人脸识别服务器软件将于2018年7月在海外先行推出,而8月才在日本本土推出. [视频]使用深度学习技术的人脸识别服务器软件 视频链接:松 ...

  8. IOS人脸识别和二维码识别

    人脸识别应用于许多领域.二维码的识别更是疯狂.下面,我们一起去看看简单的人脸识别和二维码识别. 1.测试数据的展示(人脸). 原图: 1.人脸的大小 // 人脸大小 CGRect FaceRect  ...

  9. 人脸识别智能服务器,【功能实测】大华股份DH-IVS-F7300 动态人脸识别服务器

    大华人脸识别服务器 DH-IVS-F7300系列是大华研发的新一代智能视频分析服务器,产品具有人脸实时抓拍.实时建库.布控报警.抓拍库检索历史轨迹和注册库检索.人像库相互比对等功能,可应用于重点通道出 ...

最新文章

  1. Sqlite3的安装Windows
  2. IntentService使用
  3. [leetcode] Single Number 查找数组中的单数
  4. Tomcat实现Web Socket
  5. activiti6教程四
  6. 2021年11月视频行业用户洞察
  7. python爬虫模式_python爬虫的入门试炼
  8. 全球及中国贴片电容行业市场竞争态势及投资风险预测报告2022-2028年
  9. 《C程序设计语言》(《The C Programming Language》)第二版第六章练习题
  10. # 学号12 2016-2017-2 《程序设计与数据结构》第9周学习总结
  11. [摄影写真工作室网站模板]织梦模板+响应式模特艺术展示类网站+自适应手机版
  12. Android大厂面试题系统分类从基础到困难(BATJ,蚂蚁金服,字节跳动,网易云,QQ音乐...)
  13. java 编程思想 并发_java编程思想-java中的并发(一)
  14. 如何用英语表达“忽悠”?
  15. qpsk通信系统在matlab下的仿真实现毕业设计(论文)开题报告,基于MATLAB的QPSK通信系统仿真设计毕业设计论文.doc...
  16. 关于zebra中thread的解析
  17. 计算机学习之旅——C语言(序言)
  18. linux基于xfs文件系统实现数据备份和恢复
  19. android自定义抽奖转盘
  20. 微信小程序跳转到客服会话,将内容带到服务窗口并发送给客服(只是曲线救国)

热门文章

  1. 【vbs/bat】强制关闭程序
  2. Python学习20230111
  3. AR技术简谈:相关原理,技术应用以及设备推荐,带你感受虚拟信息与真实世界巧妙融合。
  4. 哈希函数(hash函数)
  5. 蝴蝶效应,青蛙现象,鳄鱼法则,鲇鱼效应,羊群效应,刺猬法则,手表定律,破窗理论,二八定律,木桶理论,马太效应,这些你都明白吗?...
  6. 笔记一:认识微信小程序
  7. 微信小程序实现:输入手机号点击按钮查询手机号归属地
  8. 无缝轮播图无缝轮播图
  9. java获取下周一_java 获取下周一日期
  10. java线上CPU、内存打满处理