图像处理 服务器

2018-02-07 21:58:54 zk3326312 阅读数 1175

      日志是调试一个服务器的最好方法了,在大型的服务器项目中,如果一直日志的输出总是有问题,或者输出的信息含糊不明,那么,你的调试过程将会非常痛苦,如果是普通的内存泄漏或者越界访问,或者TCP通信过程中的一些问题,那么通过errno也许还能帮你一下,但如果是跑了一段时间的服务器突然宕机,那么定位问题将会非常的困难。由此可见,一个高效安全且保证输出所有信息(我这里的保证输出所有信息表示即使服务器立即停止,日志系统也能把当下所有的信息输出到日志文件中)的日志系统,是一个服务器项目中不可或缺的一个环节,下面我将介绍在我的高性能图像处理服务器中的日志模块的一些编写思路和具体细节。

      我的日志系统框架是参照陈硕大大的muduo来进行编写的,总的框架思路为:     

      1.采用了双缓冲技术的buffer,具体思路为准备4个(如果短时间内数据写入量巨大,buffer数量会随情况增加,正常情况下为4个)buffer,其中两个buffer(这里称之A,B)作为前端输入,另外两个buffer(这里称之C,D)作为后端预备输入,当A,B写满或者达到写入时间间隔时,交换AB和CD,然后后端将A,B的信息写入日志文件,前端如有信息写入则写入到C,D中,如此循环往复。这样做的好处是前端把消息做了汇总,然后通知后端处理,避免频繁唤醒线程,降低开销。

      2.开启一个专门的线程作为一个异步日志的后端处理线程,该线程的主要任务为固定时间或者收到通知后交换写Buffer,然后将已写好数据的Buffer中的数据写入到文件中。这么做的好处是异步处理,其他线程阻塞的时候不会影响到日志信息的写入。

      3.编写Logger类,通过重载<<操作符,将信息写入Logger的buffer中,当Logger析构的时候通过调用注册在Logger中的output回调将信息写入异步日志的buffer中。这么做的好处是在应用程序中使用方便,并且可以方便的给日志等级做分级处理。

      基本消息处理框架如下:

然后再稍微消息说明下异步日志的具体工作流程:

1.首先在LogStream声明了几个宏定义,用来做日志消息等级分级,我的日志分4级:

 LOG_INFO(用于记录普通的信息,如有新的消息写入或者client接入等)

       LOG_TRACE(记录用于调试的信息,如EPOLL中的事件发生等)

 LOG_WARNING(记录可能产生错误但不会让程序立即崩溃的代码是否发生,如调用注册的回调事件时出现未定义的事件编号等)

 LOG_ERROR(记录错误信息,如记录errno等)

2.每一条消息写入日志前会给其添加头尾信息,如添加代码行数,发生时间等,这么做的好处是方便定位错位,我的日志消息标准格式如下:


 实际生成的日志如下:


       同理,当log文件过多时,log文件的命名也需要规范,我的log文件命名格式如下:

     main.cpp-time-2018.2.7.20.49.10.955452-pid-30075.log(1)

     有格式的日志消息在日志消息量巨大时可以起到很好的作用,你可以用awk工具轻松的提取你想要的信息。

3.一条日志消息的完整写入过程如下,以LOG_INFO为例


具体的代码见我的GitHub:https://github.com/zk3326312/ZK_ImageServer/tree/master/log,在log库中有一个testAsynLogging文件,可以用来测试日志库的写入性能和是否能成功写入,如果觉得可以的话帮我点个星吧,下一篇博文我将写一个reactors in threads的网络库编写方法。






 







2018-02-08 18:54:39 zk3326312 阅读数 888

传统Unix并发的解决方案(为每一个新的连接fork()一个子进程)比较适合以下的情况:并发量很小并且计算的工作量远大于一个fork()的开销,但这种方案情况比较适合长连接,不太适合短连接,并发量大时这种模式也无法满足,因此不太适合本项目。

对传统Unix并发方案差的一种小改进是thread per connection(即为每一个连接分配一个线程),这种方案比起上一种在一些场景下会好一些,比如thread的开销通常比fork()小的时候(在windows下进程开销很大,在linux下thread和fork的开销差不多),或者进程间需要有数据通讯时(进程间数据通讯很麻烦,而线程由于共享地址空间和数据空间,可以直接互相访问数据,注意写的时候要加锁)。但是,这个解决方案依然存在着一样的问题,适合长连接,不太适合短连接,无法解决高并发量下的情况。

现代服务器编程模式普遍采用reactor模式, reactor设计模式,是一种基于事件驱动的设计模式。Reactor框架是ACE各个框架中最基础的一个框架,其他框架都或多或少地用到了Reactor框架。在事件驱动的应用中,将一个或多个客户的服务请求分离(demultiplex)和调度(dispatch)给应用程序。在事件驱动的应用中,同步地、有序地处理同时接收的多个服务请求。 在reactor模式中,IO复用模型有3种,select,poll,epoll,虽然epoll在大部分情况下性能表现都远优于select和poll,但在低并发的情况下select比epoll会快一些,下面简单介绍下这3种IO复用模型,借此说明下为什么要选择epoll作为本项目的IO复用模型。

1.select

selcect的调用过程如下:
(1)使用copy_from_user从用户空间拷贝fd_set到内核空间
(2)注册回调函数__pollwait
(3)遍历所有fd,调用其对应的poll方法(对于socket,这个poll方法是sock_poll,sock_poll根据情况会调用到tcp_poll,udp_poll或者datagram_poll)
(4)以tcp_poll为例,其核心实现就是__pollwait,也就是上面注册的回调函数。
(5)__pollwait的主要工作就是把current(当前进程)挂到设备的等待队列中,不同的设备有不同的等待队列,对于tcp_poll来说,其等待队列是sk->sk_sleep(注意把进程挂到等待队列中并不代表进程已经睡眠了)。在设备收到一条消息(网络设备)或填写完文件数据(磁盘设备)后,会唤醒设备等待队列上睡眠的进程,这时current便被唤醒了。
(6)poll方法返回时会返回一个描述读写操作是否就绪的mask掩码,根据这个mask掩码给fd_set赋值。
(7)如果遍历完所有的fd,还没有返回一个可读写的mask掩码,则会调用schedule_timeout是调用select的进程(也就是current)进入睡眠。当设备驱动发生自身资源可读写后,会唤醒其等待队列上睡眠的进程。如果超过一定的超时时间(schedule_timeout指定),还是没人唤醒,则调用select的进程会重新被唤醒获得CPU,进而重新遍历fd,判断有没有就绪的fd。
(8)把fd_set从内核空间拷贝到用户空间。
select存在几大缺点:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
(3)select支持的文件描述符数量太小了,默认是1024
2 poll
poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多。总的来说,poll 去掉了select中1024个链接的限制,于是要多少链接呢, 主人你开心就好。poll 从设计上来说,不再修改传入数组,不过这个要看你的平台了。但依然还是存在着高并发时轮询效率低下的问题。
3、epoll
  epoll既然是对select和poll的改进,就应该能避免上述的三个缺点。那epoll都是怎么解决的呢?在此之前,我们先看一下epoll和select和poll的调用接口上的不同,select和poll都只提供了一个函数——select或者poll函数。而epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait,epoll_create是创建一个epoll句柄;epoll_ctl是注册要监听的事件类型;epoll_wait则是等待事件的产生。
  对于第一个缺点,epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。
  对于第二个缺点,epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果,和select实现中的第7步是类似的)。
  对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。

      说到这里,再提一下epoll的ET工作模式(edge trigger边缘模式)和LT工作模式(level trigger电平模式),这两种模式有一定的区别,首先,默认情况下epoll是工作在LT模式下的,LT模式下既支持block模式,也支持non-block模式,ET模式下只支持non-block模式,ET模式是更高效的模式,它高效在哪呢?LT模式下,当epoll_wait函数检测到有事件发生并将通知应用程序,而应用程序不一定必须立即进行处理,这样epoll_wait函数再次检测到此事件的时候还会通知应用程序,直到事件被处理。而ET模式,只要epoll_wait函数检测到事件发生,通知应用程序立即进行处理,后续的epoll_wait函数将不再检测此事件。因此ET模式在很大程度上降低了同一个事件被epoll触发的次数,因此效率比LT模式高。举个例子说明,当服务器端的fd收到“OK”这个字符串时,epoll_wait会通知该fd就绪,在LT模式下,不对这个fd进行处理,在下一个循环中epoll_wait还是会通知该fd就绪,而在ET模式下,该fd只会通知一次,也就是说,第一次不进行处理的话,该fd就不会再通知了,ET模式强制编写代码时每次都对fd进行处理

总结一下这3种模型的优缺点:
(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。
(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。
      因此,本项目采用epoll作为IO复用的模型(也不是说epoll就是绝对的高效,如果并发数量小而且每个连接都比较活跃,那么select可能会更高效一点)。

 仅靠epoll就可以完成一个简单的支持高并发的服务器(在我的GitHub上有一个简单的用epoll实现的支持高并发的echo服务器,网址https://github.com/zk3326312/EpollServer/),但这样的服务器存在以下问题:如果你想修改业务逻辑,那么你就需要去修改网络部分的代码,这样非常不利于代码的扩展,如果想将业务代码抽象出来,就需要依靠reactor模式。因此,我们可以知道这种模式比起thread per connection和for per connection有以下几个优点:

1.支持高并发,能处理大量的用户连接。

2.epoll需配合非阻塞IO使用,因此,这种方案适合IO密集型的应用。

3.结合reactor模式可以将业务逻辑抽象出来,利于代码扩展。

单线程的reactor处理高并发和IO密集的任务时可以有很好的表现,但并没有发挥出现在服务器设备的多核优势,大量的用户连接时服务器的连接处理能力可能会有所下降,因此,本项目采用的是reactors in threads的方案,用一个main reactors来处理连接请求,连接成功后就将其挂载在一个sub reactors中,这和muduo和netty的方案是一样的(如果你的线程间彼此没有很多信息交互,那么采用Nginx的reactors in processes也是很好的)。总的方案框架图如下:


      
2018-02-01 16:48:35 zk3326312 阅读数 2265

高性能图像处理服务器的实现

  最近打算写一个高性能的图像处理服务器,功能大致为单帧图像或多帧图像的超分辨率恢复,并且支持远端图像存储到服务器端,支持高并发情况下的高效处理。
  大致构思了一下思路,决定服务器采用c++编写,具体架构参照陈硕大大提倡的reactors in threads的思想,然后图像传输方式以http的方式传输,本地数据库采用MySQL(可能内存数据库会选择用Redis),图像恢复算法实现用Python或者c++来实现(算法预计采用基于GAN的图像超分辨率恢复技术),具体实现架构大致如下。
  这里写图片描述
  项目托管在GitHub上,网址为https://github.com/zk3326312/ZK_ImageServer
  关于整个服务器的技术实现和技术细节,我会在我的系列博客高性能图像处理服务器里面进行详细的阐述。
  现已测试并通过的功能有:支持高并发量下文件的高效传输(并发量由于机器限制只模拟测试了10000台,均能成功连接,测试方式为客户端开启10000个线程,每个线程连接服务器并等待1s后做echo任务)。client端可以远程登录server端,并向server端上传或者从server端下载指定文件,并可以指定相应的图片进行中值滤波,锐化等基本图像处理操作,图像的超分辨率恢复功能还在编写当中。

2013-08-14 11:22:47 zhang0311 阅读数 11150

1                什么是GPU高性能计算

      近年来,以NVIDIA Tesla为代表的异构芯片(协处理器)逐渐被引入到通用计算领域中。最初的图形处理(Graphic Processing Unit, GPU)就是用来处理大规模的并行计算,并且GPU的并行处理能力在不断的上升。GPU将更多的晶体管用于执行单元,而非像CPU那样用作复杂的数据cache和指令控制。由于GPU具有超强的浮点计算能力,除了在传统领域(图形显示,多用于游戏)的应用以外,GPU越来越多地应用在科学计算领域上,并且逐渐进入高性能计算的主流。

       CUDA(Compute Unified Device Architecture,统一计算设备架构)是一种由NVIDIA推出的通用并行计算架构,该架构使GPU能够解决复杂的高性能计算问题。CUDA编程是在C语言上的基础上对C语言的扩展,不再需要借助于图形学APICUDA开发人员只需要在C语言的基础上从CPU过度到GPU上,CUDA提供了简单、容易的GPU开发工具,因此对于开发者来说,CUDA的开发门槛大大降低了。CUDA编程采用了异构体系结构的模式,CPU作为主机,GPU作为CPU的协处理器(或称设备),CPUGPU协同工作,CPU负责逻辑性强的事务处理和串行计算,GPU负责高度并行化的计算任务,CPUGPU共同完成加速任务。

     CUDA编程语言的推出,使得在GPU平台上的高性能计算应用软件越来越多。目前,CUDA在石油勘测、流体动力学模拟、天文计算、分子动力学仿真、生物计算、图像处理、音频视频编解码、医疗成像、金融、数据库等领域得到广泛的应用,在很多应用中获得2-3个数量级的加速比,极大地提高了性能。

 

     GPU已广泛应用在图像增强处理、图像目标搜索、公安行业、国防安全、平安城市、交通系统、救灾、卫星成像、信号处理、人脸识别、指纹识别、多路高清直播、智能视频监控等领域中。在这些领域,利用GPU可以达到实时性计算,相对CPU平台加速数十倍甚至数百倍,CUDA计算在图像处理领域中的成功案例如下图:

2                GPU高性能计算特点

     目前,高端的Nvidia GPU包含上千个核,双精度峰值性能超过1Tflops,单精度性能超过3Tflops,性价比远超过CPUNVIDIA GPUTeslaGeForce两个系列,Tesla为服务器上专用,内存空间较大,运行稳定,但价格较高,GeForce系列显卡性能达到Tesla的性能,然而成本很低,中端系列的GeForce显卡只需要一千多元配置在PC上即可获得数十倍的性能。

     目前,很多应用运行在PC机器上,然后PC的性能有限,尤其随着处理的数据规模的增大,PC已远不能满足性能的需求,当采用CPU服务器解决时,成本将提高5-10倍,不利于成本的控制。然后,Nvidia提供了PC版本的GeForce系列GPUGeForce系列GPU价格在数百元到上万元之前,我们可以采用PC+ GeForce GPU的配置方案,性能同样可以获得原PC的数十倍,然而,成本仅增加10%-50%PC+GPU的优化方案将成为一些应用软件的重要配置方案。


          Emailzhang03_11@163.com

          QQ331526010

 

 

2018-10-26 10:11:21 sinat_27741463 阅读数 1522

Java服务器部署基于OpenCV的C++图像处理项目(一)

由于最近项目需要在后台处理图片并返回结果给移动端,所以折腾了一周如何将c++代码和opencv打包并部署到java服务器中供后台调用,这里记录下详细过程。

基础环境

服务器环境
阿里云服务器 Red Hat 4.4.7-16 x86_64
java version “1.8.0_171”

开发环境
(将需要的库编译好之后传到服务器上,不要在服务器上编译,环境太老就换服务器环境吧)

VM虚拟机 + CentOS-7-x86_64-Everything-1804 Red Hat 4.8.5-28 (centos下载:http://isoredirect.centos.org/centos/7/isos/x86_64/CentOS-7-x86_64-Everything-1804.iso
java version “1.8.0_191”
IDE:ideaIU-2018.2.5 (IntelliJ IDEA 激活注册 破解版安装及配置:https://blog.csdn.net/sinat_27741463/article/details/89096000)
cmake version 2.8.12.2
gcc 版本 4.8.5 20150623

环境配置

在centos中需要配置java开发环境及c++编译环境。全程使用root用户

1.jdk安装 (服务器和开发环境都需要)

centos安装后可能会自带OpenJDK,先删除再安装想要的版本。
删除可参考:https://www.cnblogs.com/winner-0715/p/5154326.html
安装可参考:http://www.cnblogs.com/xqzt/p/4934451.html

2.IDEA安装 (开发需要)

参考:https://blog.csdn.net/shengshengshiwo/article/details/79599761 按照步骤来,注意其中最后一步在hots中添加key验证地址。由于不同版本IDEA的key验证窗口可能弹出时间不同,影响不大,弹出后添加key即可。
破解地址:http://idea.lanyus.com

开发springboot项目时,springboot自带tomcat容器,不需要另外安装tomcat
而其他web项目开发还需要tomcat环境,下载tomcat解压到自定义目录即可:
tomcat8下载地址:https://tomcat.apache.org/download-80.cgi
解压,无需其他操作:

tar -zxvf apache-tomcat-8.5.29.tar.gz -C /usr/tomcat

IDEA创建非springboot项目并配置tomcat:
参考:https://www.cnblogs.com/soundcode/p/6485686.html
在这里插入图片描述

3.cmake安装 (开发需要)

直接执行命令

yum install cmake

同时可查看系统gcc版本,如未安装可直接yum安装

yum install gcc
gcc -v

3.1 一些编译依赖

ant yum install ant这个也要装,不然jar包生成不了
dot yum install graphvizgraphviz中包含dot
zlib 需要自行下载安装,之后工程运行会依赖这个库。下载安装方法:https://yq.aliyun.com/ziliao/454311 ,这个题主使用的是 get http://zlib.net/zlib-1.2.8.tar.gz ,这个版本我没找到。我在centos下使用 wget http://zlib.net/zlib-1.2.11.tar.gz,后安装步骤就和这个题主一样了。
以上依赖安装参考https://blog.csdn.net/S_gy_Zetrov/article/details/80993533 。链接里是mac安装步骤,但是centos安装同理。
ffmpeg (可选) 支持opencv的一些音视频处理 (在mac下安装较方便,但是centos下版本过老,需自己下载安装,参考: https://www.cnblogs.com/subo_peng/p/6732826.html

4.opencv安装 (开发需要)

下载地址:https://opencv.org/releases.html ,我这里下载的版本是 3.4.1。
参考:https://blog.csdn.net/u013685902/article/details/78695094 。其中安装之前的编译命令教程中使用:cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/home/software/opencv .. ,其中CMAKE_INSTALL_PREFIX表示安装目录,可以安装在自定义目录,我这里在根目录下创建 /app/opencv 目录用于安装,安装命令:cmake -D CMAKE_INSTALL_PREFIX=/app/opencv .. ,也可直接安装在系统默认目录,命令简化为:cmake .. 然后接下来按照参考网址操作就完事了。
安装完成后可以在/app/opencv 下看到4个目录,否则可能安装的有问题,这里面的一些文件在后面编译会用到:
opencv安装目录

编译opencv使用make时可能出现问题,需升级gcc:
cc1plus: 警告:命令行选项“-Wstrict-prototypes”对 Ada/C/ObjC 是有效的,但对 C++ 无效

升级gcc

虚拟机直接安装CentOS7就不需要升级gcc,不然问题太多,麻烦的一批。如果有升级gcc需要,下面步骤仍可参考。

原因是cmake或者gcc版本过低,可查看cmake版本是否合适。而gcc在centos下版本过老,需自己下载安装,我这里下载gcc-4.8.5:

 wget http://ftp.tsukuba.wide.ad.jp/software/gcc/releases/gcc-4.8.5/gcc-4.8.5.tar.gz

同时升级gdb,gdb安装和gcc升级可参考:https://www.cnblogs.com/highway-9/p/5628852.html
gcc,make,cmake的关系可参考:https://blog.csdn.net/libaineu2004/article/details/77119908
安装好之后再make可能会出现:cc1plus: 警告:无法识别的命令行选项“-Wno-tautological-undefined-compare” 之类的错误,原因是新版老版同时存在,系统仍然使用的老版gcc。解决方法:https://blog.csdn.net/lhh1113/article/details/64441802?locationNum=8&fps=1 。复制以下命令直接运行即可,其作用是备份老版gcc并激活新版:

mv /usr/bin/gcc /usr/bin/gcc4.4.7
ln -s /usr/local/bin/gcc /usr/bin/gcc
mv /usr/bin/g++ /usr/bin/g++4.4.7
ln -s /usr/local/bin/g++ /usr/bin/g++
mv /usr/bin/cc /usr/bin/cc4.4.7
ln -s /usr/local/bin/cc /usr/bin/cc
mv /usr/bin/c++ /usr/bin/c++4.4.7
ln -s /usr/local/bin/c++ /usr/bin/c++

之后重启系统,我在重启系统之后遇到了系统保护问题,由于更改核心库,硬盘被挂载为只读。操作文件会提示 ***只读的文件系统 。使用命令 mount -o remount,rw / 重新挂载硬盘。

删除opencv的build目录,重新创建build,重新安装cmake并重复上方编译步骤,否则直接继续make,可能出现很多错误:
/usr/lib64/libstdc++.so.6: version GLIBCXX_3.4.21' not found,解决方法参考:https://blog.csdn.net/shine_journey/article/details/62039381
make[1]: *** 正在等待未完成的任务....SyntaxError: invalid syntax make[2]: *** [CMakeFiles/dephelper/gen_opencv_java_source] 错误 1
CMake Error: your C compiler: "/usr/bin/cc" was not found. Please set CMAKE_C_COMPILER to a valid compiler path or name. 等莫名其妙的错误。

我在阿里云服务器上经过上述步骤升级完gcc之后,编译opencv还是遇到了很多问题,然后我直接在我的开发环境中编译opencv并安装,然后将安装目录传到了服务器上,这样程序部署之后也是可以运行的。详细方法如下。
opencv配置到服务器

在开发环境中编译安装好opencv之后(我的安装目录是 /app/opencv),直接将安装目录压缩上传。操作方法如下:
在这里插入图片描述
在这里插入图片描述

tar zcvf opencv.tar.gz opencv
scp /app/opencv.tar.gz root@api.**你的域名**.com:/app 

然后在服务器/app目录直接解压即可 tar -zxvf opencv.tar.gz

至此已完成基础环境配置,编译和打包在下一篇中详细讲解。