ffmpeg-2.5.2 在centos 6.4 x86_64 上编译安装全过程

原始编译参数

./configure –extra-libs=-ldl –prefix=/opt/ffmpeg –enable-avresample –disable-debug –enable-nonfree –enable-gpl –enable-version3 –enable-libpulse –enable-libopencore-amrnb –enable-libopencore-amrwb –disable-decoder=amrnb –disable-decoder=amrwb –enable-libx264 –enable-libx265 –enable-libfdk-aac –enable-libvorbis –enable-libmp3lame –enable-libopus –enable-libvpx –enable-libspeex –enable-libass –enable-avisynth –enable-libsoxr –enable-libxvid –enable-libvo-aacenc

最终优化后编译参数

./configure –extra-libs=-ldl –prefix=/opt/ffmpeg –enable-avresample –disable-debug –enable-nonfree –enable-gpl –enable-version3 –enable-libpulse –enable-libopencore-amrnb –enable-libopencore-amrwb –disable-decoder=amrnb –disable-decoder=amrwb –enable-libx264 –enable-libfdk-aac –enable-libvorbis –enable-libmp3lame –enable-libopus –enable-libvpx –enable-libspeex –enable-libass –enable-avisynth –enable-libsoxr –enable-libxvid –enable-libvo-aacenc

为了不用编译每个包,添加如下yum源: 创建一个这样的文件: /etc/yum.repos.d/linuxtech.repo

[linuxtech]
name=LinuxTECH
baseurl=http://pkgrepo.linuxtech.net/el6/release/
enabled=1
gpgcheck=1
gpgkey=http://pkgrepo.linuxtech.net/el6/release/RPM-GPG-KEY-LinuxTECH.NET

  1. yasm/nasm not found or too old. Use –disable-yasm for a crippled build.
    sudo yum install yasm.x86_64 yasm-devel.x86_64
  2. ERROR: libass not found using pkg-config
    sudo yum install libass.x86_64 libass-devel.x86_64
  3. ERROR: libfdk_aac not found
    wget http://iweb.dl.sourceforge.net/project/opencore-amr/fdk-aac/fdk-aac-0.1.3.tar.gz
    tar xzf fdk-aac-0.1.3.tar.gz
    cd fdk-aac-0.1.3
    ./configure
    make
    sudo make install
  4. ERROR: libmp3lame >= 3.98.3 not found
    sudo yum install libmp3lame.x86_64 libmp3lame-devel.x86_64
  5. ERROR: libopencore_amrnb not found
    sudo yum install libopencore-amr-devel.x86_64 libopencore-amr.x86_64
  6. ERROR: libopencore_amrwb not found
    sudo yum install libopencore-amr-devel.x86_64 libopencore-amr.x86_64
  7. ERROR: opus not found using pkg-config sudo yum install opus.x86_64 opus-devel.x86_64
  8. ERROR: libpulse not found using pkg-config
    sudo yum install pulseaudio-libs.x86_64 pulseaudio-libs-devel.x86_64
  9. ERROR: libsoxr not found
    sudo yum install libsoxr-devel.x86_64 libsoxr.x86_64
  10. ERROR: speex not found using pkg-config
    sudo yum install speex.x86_64 speex-devel.x86_64
  11. ERROR: libvo_aacenc not found
    wget http://hivelocity.dl.sourceforge.net/project/opencore-amr/vo-aacenc/vo-aacenc-0.1.3.tar.gz
    tar xzf vo-aacenc-0.1.3.tar.gz
    cd vo-aacenc-0.1.3
    ./configure
    make
    sudo make install
  12. ERROR: libvorbis not found
    sudo yum install libvorbis-devel.x86_64 libvorbis.x86_64
  13. ERROR: libvpx decoder version must be >=0.9.1
    sudo yum install libvpx.x86_64 libvpx-devel.x86_64
  14. ERROR: libx264 not found
    git clone git://git.videolan.org/x264.git
    cd x264
    ./configure –enable-static
    make
    sudo  make install
    sudo ldconfig
  15. ERROR: x265 not found using pkg-config
    去掉编译参数: –enable-libx265
    ./configure –extra-libs=-ldl –prefix=/opt/ffmpeg –enable-avresample –disable-debug –enable-nonfree –enable-gpl –enable-version3 –enable-libpulse –enable-libopencore-amrnb –enable-libopencore-amrwb –disable-decoder=amrnb –disable-decoder=amrwb –enable-libx264 –enable-libfdk-aac –enable-libvorbis –enable-libmp3lame –enable-libopus –enable-libvpx –enable-libspeex –enable-libass –enable-avisynth –enable-libsoxr –enable-libxvid –enable-libvo-aacenc
  16. ERROR: libxvid not found
    sudo yum install libxvidcore-devel.x86_64 libxvidcore.x86_64
  17. ./ffmpeg: error while loading shared libraries: libvo-aacenc.so.0: cannot open shared object file: No such file or directory

    sudo vim /etc/ld.so.conf.d/local_lib.conf

    /usr/local/lib

    sudo ldconfig

[转]ffmpeg+ffserver搭建流媒体服务器

使用FFMpeg和FFServer来搭建流媒体服务器(在windows下实现)
一、基础知识
需要用到以下四个东西:
1. ffmpeg.exe
   负责媒体文件的transcode工作,把你服务器上的源媒体文件转换成要发送出去的流媒体文件。
2. ffserver.exe
   负责响应客户端的流媒体请求,把流媒体数据发送给客户端。
3. ffserver.conf
   ffserver启动时的配置文件,在这个文件中主要是对网络协议,缓存文件feed1.ffm(见下述)和要发送的流媒体文件的格式参数做具体的设定。ffserver.conf参数说明可以访问FFMPEG的官方文档查看。http://ffmpeg.org/ffserver.html
4. feed1.ffm
   可以看成是一个流媒体数据的缓存文件,ffmpeg把转码好的数据发送给ffserver,如果没有客户端连接请求,ffserver把数据缓存到该文件中。
可以在ffserver.conf文件中设置feed1.ffm文件的大小,当缓存数据超过这个大小,它会自动覆盖原始数据。
二、如何实现
1.配置ffserver.conf文件
 下面是一个示例:
HTTPPort 8090                       #绑定端口号
BindAddress 0.0.0.0             #绑定ip
MaxHTTPConnections 2000         #最大HTTP连接数
MaxClients 1000                 #最大客户端连接数
MaxBandwidth 1000               #最大带宽
CustomLog –                     #日志文件,“-”为直接打印
NoDaemon                        #默认全局参数
<Feed feed1.ffm>                #Feed:每一个输入建立一个Feed
    File tests/feed1.ffm             #Feed缓存文件
    FileMaxSize 10M               #缓存文件最大值
    ACL allow 127.0.0.1             #允许写入Feed的ip
</Feed>
#传递实时流
<Stream stream1.flv>               #Stream:每一个广播(转换后的视频流)的转码设置项目
    Format flv                      #视频流格式
    Feed feed1.ffm                  #视频流的种子来源
    VideoFrameRate 35              #视频帧频
    VideoBitRate 128                #视频比特率
    VideoSize 160×80                #视频帧大小
    AVOptionVideo flags +global_header
    AudioBitRate 24                 #音频比特率
    AudioChannels 1                 #音频声道
    AudioSampleRate 44100
    AVOptionAudio flags +global_header
</Stream>
#传递本地文件
<Stream stream2>
File “files/test.avi”
Format avi
</Stream>
<Stream stat.html>  #检查服务器状态,ffserver启动后,在web浏览器中输入地址:http://localhost:8090/stat.html,若能看到 ffserver Status 说明成功启动,通过内容可以看到在ffserver.conf配置的流信息等内容
Format status
</Stream>
2.启动流媒体服务器
a. 可以在ffmpeg.exe、ffserver.exe所在的路径下创建一个.bat文件,在里面写入:ffserver -f ffserver.conf
b. 也可以打开cmd.exe  cd到你的ffmpeg.exe、ffserver.exe所在的路径,输入:ffserver -f ffserver.conf
       执行完这一步,你就可以在web浏览器中输入地址:http://localhost:8090/stat.html,若能看到 ffserver Status 说明成功启动,通过内容可以看到在ffserver.conf配置的流信息等内容。
注意:如果传输的是本地文件,那么到这一步就可以打开客户端(vlc)请求视频流地址:打开vlc–>File–>OpenNetwork–>URL中填写“http://ip:8090/stream2”–>Open (ip为FFserver所在主机电脑的ip,如果是本机测试可以使用localhost;stream2为FFserver配置文件中Stream项的名字,本例中即为stream2)。
3.FFMpeg产生实时流(如果传输的是本地文件,则不需要执行该步骤)
a. 可以在ffmpeg.exe、ffserver.exe所在的路径下创建一个.bat文件,在里面写入:ffmpeg -i rtsp://192.168.1.111:8000 http://localhost:8090/feed1.ffm
b. 也可以打开cmd.exe  cd到你的ffmpeg.exe、ffserver.exe所在的路径,输入:ffmpeg -i rtsp://192.168.1.111:8000 http://localhost:8090/feed1.ffm
其中:
#-i:为输入参数选项
#rtsp://192.168……:为输入的RTSP视频流来源
#http://localho……:为FFserver配置文件中设置的Feed的监听地址和端口
4.实现播放
打开客户端(vlc)请求视频流地址:打开vlc–>File–>OpenNetwork–>URL中填写“http://ip:8090/stream1”–>Open (ip为FFserver所在主机电脑的ip,如果是本机测试可以使用localhost;stream2为FFserver配置文件中Stream项的名字,本例中即为stream1)。
三、总结
ffserver先于ffmpeg启动,它在启动的时候需要加参数-f指定其配置文件。ffserver启动后,feed1.ffm就会被创建,这时如果你打开feed1.ffm看看,会发现feed1.ffm开始的部分已经写入了内容,你可以找到关键字ffm以及向客户端传送流的配置信息,在feed1.ffm做缓冲用的时候,这些信息是不会被覆盖掉的,可以将它们理解为feed1.ffm文件头。
ffserver启动后,ffmpeg启动,它启动时加的一个关键参数就是“http://ip:8090/feed1.ffm”,其中ip是运行ffserver主机的ip,如果ffmpeg和ffserver都在同一系统中运行的话,用localhost也行。ffmpeg启动后会与ffserver建立一个连接(短暂的连接),通过这第一次的连接,ffmpeg从ffserver那里获取了向客户端输出流的配置,并把这些配置作为自己编码输出的配置,然后ffmpeg断开了这次连接,再次与ffserver建立连接(长久的连接),利用这个连接ffmpeg会把编码后的数据发送给ffserver。
如果你观察ffserver端的输出就会发现这段时间会出现两次HTTP的200,这就是两次连接的过程。
ffmpeg获取数据后,按照输出流的编码方式编码,然后发送给ffserver,ffserver收到ffmpeg的数据后,如果网络上没有播放的请求,就把数据写入feed1.ffm中缓存,写入时把数据加上些头信息然后分块,每块4096B(每块也有结构),当feed1.ffm的大小到了ffserver.conf中规定的大小后,就会从文件开始(跳过头)写入,覆盖旧的数据。直到网络上有播放的请求,ffserver从feed1.ffm中读取数据,发送给客户端。
                                 2.http://blog.csdn.net/vblittleboy/article/details/8549835
                         3.http://ffmpeg.org/ffserver.html
友情链接:FFMPEG和FFSERVER搭建流媒体服务器实例,可以根据自己的需求修改里面的ffserver.conf文件。

[转]阿里巴巴开源项目TAC——新的服务端开发模式尝试

尝试

TAC是一个基于java的微服务容器,提供从业务代码编写、编译、发布、jar动态加载、运行等一系列常用开发流程的支持,是天猫App在服务端开发模式下的新尝试。TAC和客户端框架Tangram结合,极大提高开发效率;TAC目前在天猫App、手机淘宝特价版广泛使用;

疼痛之初——产生背景

天猫App(以下简称猫客)首页从2015年的坑位运营走向2016年的全面个性化,当时猫客首页的个性化业务多大50多处。以首页为例,这个过程中除了接入导购链路的二方服务之外,接入了大量的三方服务;同时我们发现:

  • 首页应用越来越复杂、庞大,应用代码修改、编译部署耗费时间太长,大部分时间都是苦力劳动;
  • 线上定位问题周期太长,从问题发现到定位再到处理,需要经过客户端、服务端、下游各种服务提供方,链路太长影响业务快速支撑;
  • 大量服务的接入导致发布线上部署频繁,一个简单服务的接入,几行代码的修改导致首页应用重新开发编译部署,影响整个首页稳定性;

当时微服务的概念已经很火,同时阿里内部也开始大力推广docker(现在大部分应用都跑在docker上)。我们想过将庞大的首页应用拆分成微服务,但是和基础服务不同,前台业务变化快,修改频繁,拆分之后开发同学依然要面临各种服务接入等体力劳动,同时需要维护拆分之后的多个应用,反而增加了劳动成本,因此拆分成微服务的方式治标不治本;


解决之道——TAC

在此背景下TAC孕育而生,它提供低成本开发与发布流程、低成本搭建与维护开发环境、高稳定性保障;TAC通过热部署的方式使得研发同学够从苦力劳动中解放出来,回归到业务开发中去,同时一个基础服务接入之后,能够提供给多个业务使用;在此模式下,业务能够进行更细粒度的拆分,且故障隔离业务A的改动不会影响业务B;

在TAC的帮助下,频繁修改的新业务可快速上线,不会出现因为修改一个字段、几行代码就需要重新发布整个应用的情况;同时与tangram结合,实现页面卡片、坑位的快速调整;


经过近三年的沉淀,我们今日放出了开源版本,将集团版本的TAC剥离与阿里相关的中间件、网络、协议、部署环境等,保留其核心功能;开源版本提供了编译、热加载、运行的基础能力;

TAC开源版本

系统结构


  • 开源版本分两部分,tac容器和tac控制台,存储和通信都依赖redis。
  • 为了简便,容器与外部服务直接的交互只通过http进行;
  • 同时为了提升开发体验,提供了与gitlab集成的能力,用户可直接gitlab提交代码并在控制台操作发布;快速验证;

核心类加载器


上图是tac的类加载器结构,每个线上的微服务实例都通过一个新的classloader加载,同时为了方便用户扩展新的数据源,在AppClassLoader上扩展了一个classloader以加载第三方数据源(当然也可以直接在代码中扩展,见tac-infrastructure);

Quick Start —— 如何使用

  • 安装 redis
  • 运行 container
java -jar tac-container.jar
  • 运行 console 控制台
java -jar tac-console.jar --admin
  • 成功后可打开控制台
http://localhost:7001/#/tacMs/list
  • 代码开发
    • 添加 SDK 依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>tac-sdk</artifactId>
    <version>${project.version}</version>
</dependency>
  • 编写代码
public class HelloWorldTac implements TacHandler<Object> {

    /**
     * 引入日志服务
     */
    private TacLogger tacLogger = TacServiceFactory.getLogger();

    /**
     * 编写一个实现TacHandler接口的类
     *
     * @param context
     * @return
     * @throws Exception
     */

    @Override
    public TacResult<Object> execute(Context context) throws Exception {

        // 执行逻辑
        tacLogger.info("Hello World");

        Map<String, Object> data = new HashMap<>();
        data.put("name", "hellotac");
        data.put("platform", "iPhone");
        data.put("clientVersion", "7.0.2");
        data.put("userName", "tac-userName");
        return TacResult.newResult(data);
    }
}
  • 本地编译、打包
  • 发布及测试

  • 正式发布
  • 线上验证
curl  http://localhost:8001/api/tac/execute/helloworld -s|json
{
  "success": true,
  "msgCode": null,
  "msgInfo": null,
  "data": {
    "helloworld": {
      "data": {
        "name": "hellotac",
        "clientVersion": "7.0.2",
        "userName": "tac-userName",
        "platform": "iPhone"
      },
      "success": true,
      "msCode": "helloworld"
    }
  },
  "hasMore": null,
  "ip": "127.0.0.1"
}

Advanced Usage: Admin Server

link:https://docs.hhvm.com/hhvm/advanced-usage/admin-server

The admin server allows the administrator of the HHVM server to query and control the HHVM server process. It is different and separate than the primary HHVM server that you specified with -m server or -m daemon.

To turn on the admin server, you specify the following options at the command line via -d or within your server.ini (or equivalent).

hhvm.admin_server.port=9001
hhvm.admin_server.password=SomePassword

The port can be any open port. And you should always specify a password to secure the admin port since you don’t want just anybody being able to control your server. In fact, you will probably want to put the admin server behind a firewall. You will specify the password with every request to the admin port.

The admin server uses the same protocol as the main server – so, if you’re using FastCGI mode, the admin server will also be FastCGI, and you will need to configure a front-end webserver (like nginx). If you are using Proxygen mode, the admin server will be an HTTP server.

Querying the Admin Server

Once you have set up your admin server, you can query it via curl.

curl http://localhost:9001/

will bring up a list of commands you can use to control and query your admin server.

The port associated with the curl command is the hhvm.admin_server port set above if you are using ProxygenIf you are using FastCGI, then the port will be the webserver port that is the front end to FastCGI.

Sending a Command

Use one of the commands listed with the curl sequence above, along with your password, to send a command to the admin server.

curl http://localhost:9001/compiler-id?auth=SomePassword

Further Reference

There is a good blog post discussing the admin server even further.

[转]Linux系统安装w3af

  w3af这个工具是扫描网站漏洞,比如 SQL注入、盲注、本地\远程文件包含、跨站脚本攻击、跨站伪造请求等。

找了一些正规的工具定义介绍:w3af是一个Web应用安全的攻击、审计(分析)平台,通过增加插件来对功能进行扩展,这是一款用python写的工具,支持GUI,也支持命令行模式。

w3af目前已经集成了非常多的安全审计及攻击插件,并进行了分类,用户在使用的时候,可

以直接选择已经分类好的插件,只需要填写上URL地址即可对目标站点进行安全审计,并且集成了一些好用的小工具,如自定义request功能、Fuzzy

request功能、代理功能、加解密功能,支持非常多的加解密算法,用户完全可以使用w3af完成对一个网址的安全审计(分析)工作。

《w3af用户手册》英文版本于2012年8月8日发布,由Andres Riancho编写,Javier Andalia、Mike Harbison、Andy Bach、Chris Teodorski审阅。中文版本于2013年4月3日发布,由IDF实验室研究员lenchio翻译,研究员做个好人校对、修改、制作,实习生Leo亦参与了文档校对工作。

可以通过连接下载.有windows、Linux版本.

https://sourceforge.net/projects/w3af/files/w3af/

还有另外一种方法……

sudo apt-get install w3af         即可

本次教程讲的是Ubuntu下git从GitHub安装….

https://github.com/andresriancho/w3af

命令如下:还是觉得图形界面直观….

git clone –depth 1 https://github.com/andresriancho/w3af.git

cd w3af

./w3af_gui

到这一步后,会提示你安装某些工具,安装好后,就可以启动了。祝你好运!!!

作者:不着调的小男生
链接:https://www.jianshu.com/p/a4584e7d0d25
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

wordpress挂站–Error establishing a database connection

今天莫名其妙我的博客出现Error establishing a database connection,一看应该是数据连接不上了。首先看了下wp-config.php,发现无异常。重启nginx,更换php5均没有效果。网上查了下,说是http://blog.csdn.net/mwb310/article/details/53009920,众说纷纭,有文件格式错误,mysql版本错误等等,试了均无效。

想着不如先把sql dump一份备份,所以

mysqldump -uxxx -pxxx --dataname >wordpress.log

发现:

 

warning : 250 clients are using or haven’t closed the table properly
status : OK
wangchunwei.wp_statistics_search
warning : 156 clients are using or haven’t closed the table properly
status : OK
wangchunwei.wp_statistics_useronline
warning : 252 clients are using or haven’t closed the table properly
status : OK
wangchunwei.wp_statistics_visit
warning : 252 clients are using or haven’t closed the table properly
status : OK
wangchunwei.wp_statistics_visitor
warning : 252 clients are using or haven’t closed the table properly
status : OK
wangchunwei.wp_term_relationships
warning : 32 clients are using or haven’t closed the table properly
status : OK
wangchunwei.wp_term_taxonomy
warning : 31 clients are using or haven’t closed the table properly
status : OK

 

 

mysqldump: Got error: 145: Table ‘./xxx/wp_options’ is marked as crashed and should be repaired when using LOCK TABLES

网上参考了:

修复 MySQL 数据库数据表问题可以由 mysqlcheck 来解决,先用 mysqlcheck 查看一下:

# mysqlcheck -u root -p wordpress
Enter password:

然后添加 –auto-repair 参数自动修复,最好修复前备份一下数据库:

# mysqldump -u root -p wordpress > wordpress.sql
Enter password:

# mysqlcheck -u root -p wordpress --auto-repair
Enter password:
wordpress.wp_commentmeta
error    : Table upgrade required. Please do "REPAIR TABLE `wp_commentmeta`" or dump/reload to fix it!
wordpress.wp_comments
error    : Table upgrade required. Please do "REPAIR TABLE `wp_comments`" or dump/reload to fix it!
wordpress.wp_links
error    : Table upgrade required. Please do "REPAIR TABLE `wp_links`" or dump/reload to fix it!
wordpress.wp_options
error    : Table upgrade required. Please do "REPAIR TABLE `wp_options`" or dump/reload to fix it!
wordpress.wp_postmeta
error    : Table upgrade required. Please do "REPAIR TABLE `wp_postmeta`" or dump/reload to fix it!
wordpress.wp_posts
error    : Table upgrade required. Please do "REPAIR TABLE `wp_posts`" or dump/reload to fix it!
wordpress.wp_term_relationships                OK
wordpress.wp_term_taxonomy
error    : Table upgrade required. Please do "REPAIR TABLE `wp_term_taxonomy`" or dump/reload to fix it!
wordpress.wp_terms
error    : Table upgrade required. Please do "REPAIR TABLE `wp_terms`" or dump/reload to fix it!
wordpress.wp_usermeta
error    : Table upgrade required. Please do "REPAIR TABLE `wp_usermeta`" or dump/reload to fix it!
wordpress.wp_users
error    : Table upgrade required. Please do "REPAIR TABLE `wp_users`" or dump/reload to fix it!

Repairing tables
wordpress.wp_commentmeta                       OK
wordpress.wp_comments                          OK
wordpress.wp_links                             OK
wordpress.wp_options                           OK
wordpress.wp_postmeta                          OK
wordpress.wp_posts                             OK
wordpress.wp_term_taxonomy                     OK
wordpress.wp_terms                             OK
wordpress.wp_usermeta                          OK
wordpress.wp_users                             OK

网站恢复了!应该是链接没有释放

[转]Lumen和Laravel错误处理机制修改

在使用Laravel或者Lumen时会碰到这种情况,如果php的代码中产生了Notice或者Warning,会导致Lumen跳到错误页,日志中会打印一个很长很长的stack trace,如下图:

$app->get('/test', function () use ($app) {
	$arr = [];
	print($arr['name']);
});
#请求/test 产生的日志
[2017-07-12 11:55:54] lumen.ERROR: ErrorException: Undefined index: name in /home/vagrant/Code/featurestream/routes/web.php:16
Stack trace:
#0 /home/vagrant/Code/featurestream/routes/web.php(16): Laravel\Lumen\Application->Laravel\Lumen\Concerns\{closure}(8, 'Undefined index...', '/home/vagrant/C...', 16, Array)
#1 [internal function]: Closure->{closure}()
#2 /home/vagrant/Code/featurestream/vendor/illuminate/container/BoundMethod.php(29): call_user_func_array(Object(Closure), Array)
....

上面的日志只截取了一小部分,实际运行时最简单的api请求Lumen的stack trace会有30层左右,Laravel的会有60层..
另外一个重要的问题是,这种处理机制会让一些小错误把整个请求搞挂,代码中到处加if(isset($arr['xx'])) 或者设置默认值。
对于这个问题,Laravel和Lumen设计的初衷是好的:所有的PHP错误都应该被处理,包括Notice和Warning。就是太严格了,有时候我们并不需要代码有这么严格的检查,出现Notice或者Warning时,只需要打印个模块日志或者有PHP日志就行。
先看下Lumen中的错误是如何处理的(Laravel中也差不多,不再单独讲)。

错误处理流程

错误Handler

先看Lumen的入口:bootstrap/app.php,有一段错误处理相关的代码:

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

singleton是往app(Lumen的服务容器)里注入实例的方法,这里实例化了一个ExceptionHandler类,实例为Handler。后面的代码如果有从$app里取ExceptionHandler的实例的话,会返回Handler这个类的实例。
Handler类在App\Exceptions目录下,代码如下。比较简单,只包含两个方法,再去看父类(Laravel\Lumen\Exceptions\Handler)的方法逻辑会发现,report方法负责打印日志(也就是上文那个长长的trace),render方法会根据错误类型的不同构建错误页面

public function report(Exception $e)
{
    parent::report($e);
}

public function render($request, Exception $e)
{
    return parent::render($request, $e);
}

错误的处理逻辑找到了,如何触发进入这个逻辑的呢?

错误触发

正常情况下,PHP产生Notice或者Warning是不会抛出Exception的,会产生Exception肯定是框架内部做了更改。
bootstrap/app.php中没找到设置错误处理的地方,接着往下看Lumen框架的容器类Application(Lumen的核心类和入口),目录:vendor/laravel/lumen-framework/src/Application.php
可以看到在其构造函数中调用了一个registerErrorHandling方法,方法代码:

/**
 * Set the error handling for the application.
 *
 * @return void
 */
protected function registerErrorHandling()
{
    error_reporting(-1);

    set_error_handler(function ($level, $message, $file = '', $line = 0) {
        if (error_reporting() & $level) {
            throw new ErrorException($message, 0, $level, $file, $line);
        }
    });

    set_exception_handler(function ($e) {
        $this->handleUncaughtException($e);
    });

    register_shutdown_function(function () {
        $this->handleShutdown();
    });
}

set_error_handler是PHP设置错误处理的方法。registerErrorHandling下用error_reporting(-1)把PHP的报错开关都打开,这样所有级别的错误都会触发error_handler。在error_handler中抛出了一个ErrorException异常。
至此,我们知道ErrorException这个异常是怎么产生的了,知道异常会交由谁来处理了(上一节中的Handler类)。不过还有个疑问,ErrorException和Handler是怎么关联起来的?

ErrorException和Handler

set_error_handler设置的方法中会抛出异常,那肯定存在针对异常的try catch块。
从请求路口public/index.php往下看,
–>bootstrap/app.php –>Application.php -> run –>Application.php -> dispatch
在dispatch方法中发现了try catch的逻辑,在catch到Exception后调用了Handler的report和render方法。

public function dispatch($request = null)
{
   list($method, $pathInfo) = $this->parseIncomingRequest($request);
   try {
			...
   } catch (Exception $e) {
       return $this->prepareResponse($this->sendExceptionToHandler($e));
   } catch (Throwable $e) {
       return $this->prepareResponse($this->sendExceptionToHandler($e));
   }
}
protected function sendExceptionToHandler($e)
{
   $handler = $this->resolveExceptionHandler();

   if ($e instanceof Error) {
       $e = new FatalThrowableError($e);
   }

   $handler->report($e);

   return $handler->render($this->make('request'), $e);
}

解决

知道了错误从触发到结束的整个流程,再来看怎么解决.

方法1.屏蔽错误

简单粗暴的方法,既然框架用来error_reporting(-1)打开了所有错误开关,那我们再用error_reporting把我们不关心的错误给屏蔽了。把下面代码加在bootstrap/app.php中,注意得加在Applition实例化之后

error_reporting(E_ALL ^ E_NOTICE ^ E_WARNING);

这种方法的弊端显而易见,我们只是不想让E_NOTICE搞挂请求,但是这类错误还是得关注和修复的。。

方法2.修改Handler

既然是Handler负责错误处理,那我们修改Handler(App\Exceptions\Handler)的逻辑就行。

public function report(Exception $e)
{
    //parent::report($e);
    Log::warning($e->getMessage(), ['file' => $e->getFile(), 'line' => $e->getLine()]);
}

public function render($request, Exception $e)
{
    //return parent::render($request, $e);
    header('Content-type: application/json');
    echo json_encode(['errmsg' => $e->getMessage()]);
    return ;
}

如上代码,将错误处理逻辑进行更改,只打印简单的日志,并且也不跳到错误页了。
不过,这种方法还是有问题:

  1. 代码产生的NOTICE和WARNING还是会中断执行流程(因为会抛出异常)。比如一个请求中执行A->B->C三个方法,如果B中产生了一个NOTICE,整个请求还是会被中断,C不会被执行。
  2. 如果一个请求原本不是返回json,是返回一个view,则上面的render方法就不适用了。

方法3.在业务逻辑顶层中catch异常

原理类似方法2,只不过把异常的处理从Handler中移到了业务逻辑里(比如Controller中)。
这个方法的问题和方法2一样,NOTICE还是会中断请求…

方法4.修改error_handler,不抛出异常

罪魁祸首就在于那个set_error_handler注册的处理方法遇到PHP错误就会抛出异常,那我们修改他就行。
set_error_handler所在的registerErrorHandling方法在框架的源代码中,最好不要直接修改,我们可以在它注册完之后再重新注册一个覆盖它。可以加在bootstrap/app.php中,Application实例化之后。代码如下:

set_error_handler(function ($level, $message, $file = '', $line = 0) {
    if ($level == E_NOTICE || $level == E_WARNING) {
        Log::warning("PHP NOTICE or WARNING; MSG:[$message]", ['file' => $file, 'line' => $line]);
        return;
    }
    if (error_reporting() & $level) {
        throw new ErrorException($message, 0, $level, $file, $line);
    }
});

注意,代码中因为使用了Log这个Facades,因此必须放在$app->withFacades()之后
至此,NOTICE和WARNING不会产生烦人的日志,也不会搞挂请求,并且也保留了有效的提示信息。:)

Mysql replace 与 insert on duplicate效率分析

导读

我们在向数据库里批量插入数据的时候,会遇到要将原有主键或者unique索引所在记录更新的情况,而如果没有主键或者unique索引冲突的时候,直接执行插入操作。

这种情况下,有三种方式执行:

直接

直接每条select, 判断, 然后insert,毫无疑问,这是最笨的方法了,不断的查询判断,有主键或索引冲突,执行update,否则执行insert. 数据量稍微大一点这种方式就不行了。

稍微高级一些的方式。

replace

这是mysql自身的一个语法,使用 replace 的时候。其语法为:

replace into tablename (f1, f2, f3) values(vf1, vf2, vf3),(vvf1, vvf2, vvf3)

这中语法会自动查询主键或索引冲突,如有冲突,他会先删除原有的数据记录,然后执行插入新的数据。

insert on duplicate key.

这也是一种方式,mysql的insert操作中也给了一种方式,语法如下:

INSERT INTO table (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE c=c+1;

在insert时判断是否已有主键或索引重复,如果有,一句update后面的表达式执行更新,否则,执行插入。

第一种方式不说了,replace和insert on duplicate key这两种方式,哪中效率更高一些呢,毕竟,我们的执行sql,追求的就是高效。

分析

在最终实践结果中,得到接过如下:
在数据库数据量很少的时候, 这两种方式都很快,无论是直接的插入还是有冲突时的更新,都不错,但在数据库表的内容数量比较大(如百万级)的时候,两种方式就不太一样了,

首先是直接的插入操作,两种的插入效率都略低, 比如直接向表里插入1000条数据(百万级的表(innodb引擎)),二者都差不多需要5,6甚至十几秒。究其原因,我的主机性能是一方面,但在向大数据表批量插入数据的时候,每次的插入都要维护索引的, 索引固然可以提高查询的效率,但在更新表尤其是大表的时候,索引就成了一个不得不考虑的问题了。

其次是更新表,这里的更新的时候是带主键值的(因为我是从另一个表获取数据再插入,要求主键不能变) 同样直接更新1000条数据, replace的操作要比insert on duplicate的操作低太多太多, 当insert瞬间完成(感觉)的时候,replace要7,8s, replace慢的原因我是知道的,在更新数据的时候,要先删除旧的,然后插入新的,在这个过程中,还要重新维护索引,所以速度慢,但为何insert on duplicate的更新却那么快呢。 在向老大请教后,终于知道,insert on duplicate 的更新操作虽然也会更新数据,但其对主键的索引却不会有改变,也就是说,insert on duplicate 更新对主键索引没有影响.因此对索引的维护成本就低了一些(如果更新的字段不包括主键,那就要另说了)。

题外话:

在向数据量大的表里批量插入更新数据的时候,随着插入的数量越来越多,会导致越来越慢,这种情况下,因为我们用的innodb表,可以开启事务, 每次批量执行一批数据更新后提交, 再重新开事务处理下批数据,这样会有效增加效率

还有说明一下: 当我们执行数据库的插入和更新操作很慢的时候,不仅仅是语句,主机性能也很重要, 比如内存和cpu, 如果是虚拟机要相应适当调整, 如果在各种优化了之后效率还是很低, 但cpu和内存的占用却不高,那么就很可能是磁盘的IO性能了,这也会导致数据的更新速度慢。

[转]老生常谈,HashMap的死循环

由于HashMap并非是线程安全的,所以在高并发的情况下必然会出现问题,这是一个普遍的问题,虽然网上分析的文章很多,还是觉得有必须写一篇文章,让关注我公众号的同学能够意识到这个问题,并了解这个死循环是如何产生的。

如果是在单线程下使用HashMap,自然是没有问题的,如果后期由于代码优化,这段逻辑引入了多线程并发执行,在一个未知的时间点,会发现CPU占用100%,居高不下,通过查看堆栈,你会惊讶的发现,线程都Hang在hashMap的get()方法上,服务重启之后,问题消失,过段时间可能又复现了。

这是为什么?

原因分析

在了解来龙去脉之前,我们先看看HashMap的数据结构。

在内部,HashMap使用一个Entry数组保存key、value数据,当一对key、value被加入时,会通过一个hash算法得到数组的下标index,算法很简单,根据key的hash值,对数组的大小取模 hash & (length-1),并把结果插入数组该位置,如果该位置上已经有元素了,就说明存在hash冲突,这样会在index位置生成链表。

如果存在hash冲突,最惨的情况,就是所有元素都定位到同一个位置,形成一个长长的链表,这样get一个值时,最坏情况需要遍历所有节点,性能变成了O(n),所以元素的hash值算法和HashMap的初始化大小很重要。

当插入一个新的节点时,如果不存在相同的key,则会判断当前内部元素是否已经达到阈值(默认是数组大小的0.75),如果已经达到阈值,会对数组进行扩容,也会对链表中的元素进行rehash。

实现

HashMap的put方法实现:

1、判断key是否已经存在

public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);
    int i = indexFor(hash, table.length);
    // 如果key已经存在,则替换value,并返回旧值
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    // key不存在,则插入新的元素
    addEntry(hash, key, value, i);
    return null;
}

2、检查容量是否达到阈值threshold

void addEntry(int hash, K key, V value, int bucketIndex) {
    if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }

    createEntry(hash, key, value, bucketIndex);
}

如果元素个数已经达到阈值,则扩容,并把原来的元素移动过去。

3、扩容实现

void resize(int newCapacity) {
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    ...

    Entry[] newTable = new Entry[newCapacity];
    ...
    transfer(newTable, rehash);
    table = newTable;
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

这里会新建一个更大的数组,并通过transfer方法,移动元素。

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

移动的逻辑也很清晰,遍历原来table中每个位置的链表,并对每个元素进行重新hash,在新的newTable找到归宿,并插入。

案例分析

假设HashMap初始化大小为4,插入个3节点,不巧的是,这3个节点都hash到同一个位置,如果按照默认的负载因子的话,插入第3个节点就会扩容,为了验证效果,假设负载因子是1.

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

以上是节点移动的相关逻辑。


插入第4个节点时,发生rehash,假设现在有两个线程同时进行,线程1和线程2,两个线程都会新建新的数组。


假设 线程2 在执行到Entry<K,V> next = e.next;之后,cpu时间片用完了,这时变量e指向节点a,变量next指向节点b。

线程1继续执行,很不巧,a、b、c节点rehash之后又是在同一个位置7,开始移动节点

第一步,移动节点a


第二步,移动节点b


注意,这里的顺序是反过来的,继续移动节点c


这个时候 线程1 的时间片用完,内部的table还没有设置成新的newTable, 线程2 开始执行,这时内部的引用关系如下:


这时,在 线程2 中,变量e指向节点a,变量next指向节点b,开始执行循环体的剩余逻辑。

Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;

执行之后的引用关系如下图


执行后,变量e指向节点b,因为e不是null,则继续执行循环体,执行后的引用关系


变量e又重新指回节点a,只能继续执行循环体,这里仔细分析下: 1、执行完Entry<K,V> next = e.next;,目前节点a没有next,所以变量next指向null; 2、e.next = newTable[i]; 其中 newTable[i] 指向节点b,那就是把a的next指向了节点b,这样a和b就相互引用了,形成了一个环; 3、newTable[i] = e 把节点a放到了数组i位置; 4、e = next; 把变量e赋值为null,因为第一步中变量next就是指向null;

所以最终的引用关系是这样的:


节点a和b互相引用,形成了一个环,当在数组该位置get寻找对应的key时,就发生了死循环。

另外,如果线程2把newTable设置成到内部的table,节点c的数据就丢了,看来还有数据遗失的问题。

总结

所以在并发的情况,发生扩容时,可能会产生循环链表,在执行get的时候,会触发死循环,引起CPU的100%问题,所以一定要避免在并发环境下使用HashMap。

曾经有人把这个问题报给了Sun,不过Sun不认为这是一个bug,因为在HashMap本来就不支持多线程使用,要并发就用ConcurrentHashmap。

作者:占小狼
链接:https://juejin.im/post/5a66a08d5188253dc3321da0
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

[转]MySQL前缀索引和索引选择性

有时候需要索引很长的字符列,这会让索引变得大且慢。通常可以索引开始的部分字符,这样可以大大节约索引空间,从而提高索引效率。但这样也会降低索引的选择性。索引的选择性是指不重复的索引值(也称为基数,cardinality)和数据表的记录总数的比值,范围从1/#T到1之间。索引的选择性越高则查询效率越高,因为选择性高的索引可以让MySQL在查找时过滤掉更多的行。唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。

一般情况下某个前缀的选择性也是足够高的,足以满足查询性能。对于BLOB,TEXT,或者很长的VARCHAR类型的列,必须使用前缀索引,因为MySQL不允许索引这些列的完整长度。

诀窍在于要选择足够长的前缀以保证较高的选择性,同时又不能太长(以便节约空间)。前缀应该足够长,以使得前缀索引的选择性接近于索引的整个列。换句话说,前缀的”基数“应该接近于完整的列的”基数“。

为了决定前缀的合适长度,需要找到最常见的值的列表,然后和最常见的前缀列表进行比较。下面的示例是mysql官方提供的示例数据库

下载地址如下:

http://downloads.mysql.com/docs/sakila-db.zip

在示例数据库sakila中并没有合适的例子,所以从表city中生成一个示例表,这样就有足够数据进行演示:

复制代码
mysql> select database();                                                           
+------------+
| database() |
+------------+
| sakila     |
+------------+
1 row in set (0.00 sec)

mysql> create table city_demo (city varchar(50) not null);                          
Query OK, 0 rows affected (0.02 sec)

mysql> insert into city_demo (city) select city from city;                          
Query OK, 600 rows affected (0.08 sec)
Records: 600  Duplicates: 0  Warnings: 0

mysql> insert into city_demo (city) select city from city_demo;
Query OK, 600 rows affected (0.07 sec)
Records: 600  Duplicates: 0  Warnings: 0

mysql> update city_demo set city = ( select city from city order by rand() limit 1);
Query OK, 1199 rows affected (0.95 sec)
Rows matched: 1200  Changed: 1199  Warnings: 0

mysql>
复制代码

因为这里使用了rand()函数,所以你的数据会与我的不同,当然那不影响聪明的你。

首先找到最常见的城市列表:

复制代码
mysql> select count(*) as cnt, city from city_demo group by city order by cnt desc limit 10;               
+-----+--------------+
| cnt | city         |
+-----+--------------+
|   8 | Garden Grove |
|   7 | Escobar      |
|   7 | Emeishan     |
|   6 | Amroha       |
|   6 | Tegal        |
|   6 | Lancaster    |
|   6 | Jelets       |
|   6 | Ambattur     |
|   6 | Yingkou      |
|   6 | Monclova     |
+-----+--------------+
10 rows in set (0.01 sec)

mysql>
复制代码

注意到查询结果,上面每个值都出现了6-8次。现在查找到频繁出现的城市前缀。先从3个前缀字母开始,然后4个,5个,6个:

复制代码
mysql> select count(*) as cnt,left(city,3) as pref from city_demo group by pref order by cnt desc limit 10;
+-----+------+
| cnt | pref |
+-----+------+
|  25 | San  |
|  15 | Cha  |
|  12 | Bat  |
|  12 | Tan  |
|  11 | al-  |
|  11 | Gar  |
|  11 | Yin  |
|  10 | Kan  |
|  10 | Sou  |
|  10 | Bra  |
+-----+------+
10 rows in set (0.00 sec)

mysql> select count(*) as cnt,left(city,4) as pref from city_demo group by pref order by cnt desc limit 10; 
+-----+------+
| cnt | pref |
+-----+------+
|  12 | San  |
|  10 | Sout |
|   8 | Chan |
|   8 | Sant |
|   8 | Gard |
|   7 | Emei |
|   7 | Esco |
|   6 | Ying |
|   6 | Amro |
|   6 | Lanc |
+-----+------+
10 rows in set (0.01 sec)

mysql> select count(*) as cnt,left(city,5) as pref from city_demo group by pref order by cnt desc limit 10; 
+-----+-------+
| cnt | pref  |
+-----+-------+
|  10 | South |
|   8 | Garde |
|   7 | Emeis |
|   7 | Escob |
|   6 | Amroh |
|   6 | Yingk |
|   6 | Moncl |
|   6 | Lanca |
|   6 | Jelet |
|   6 | Tegal |
+-----+-------+
10 rows in set (0.01 sec)
复制代码
复制代码
mysql> select count(*) as cnt,left(city,6) as pref from city_demo group by pref order by cnt desc limit 10; 
+-----+--------+
| cnt | pref   |
+-----+--------+
|   8 | Garden |
|   7 | Emeish |
|   7 | Escoba |
|   6 | Amroha |
|   6 | Yingko |
|   6 | Lancas |
|   6 | Jelets |
|   6 | Tegal  |
|   6 | Monclo |
|   6 | Ambatt |
+-----+--------+
10 rows in set (0.00 sec)

mysql>
复制代码

通过上面改变不同前缀长度发现,当前缀长度为6时,这个前缀的选择性就接近完整咧的选择性了。甚至是一样的。

当然还有另外更方便的方法,那就是计算完整列的选择性,并使其前缀的选择性接近于完整列的选择性。下面显示如何计算完整列的选择性:

复制代码
mysql> select count(distinct city) / count(*) from city_demo;
+---------------------------------+
| count(distinct city) / count(*) |
+---------------------------------+
|                          0.4283 |
+---------------------------------+
1 row in set (0.05 sec)

mysql>
复制代码

可以在一个查询中针对不同前缀长度的选择性进行计算,这对于大表非常有用,下面给出如何在同一个查询中计算不同前缀长度的选择性:

复制代码
mysql> select count(distinct left(city,3))/count(*) as sel3,
    -> count(distinct left(city,4))/count(*) as sel4,
    -> count(distinct left(city,5))/count(*) as sel5, 
    -> count(distinct left(city,6))/count(*) as sel6 
    -> from city_demo;
+--------+--------+--------+--------+
| sel3   | sel4   | sel5   | sel6   |
+--------+--------+--------+--------+
| 0.3367 | 0.4075 | 0.4208 | 0.4267 |
+--------+--------+--------+--------+
1 row in set (0.01 sec)

mysql>
复制代码

可以看见当索引前缀为6时的基数是0.4267,已经接近完整列选择性0.4283。

在上面的示例中,已经找到了合适的前缀长度,下面创建前缀索引:

mysql> alter table city_demo add key (city(6));
Query OK, 0 rows affected (0.19 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql>
复制代码
mysql> explain select * from city_demo where city like 'Jinch%';
+----+-------------+-----------+-------+---------------+------+---------+------+------+-------------+
| id | select_type | table     | type  | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-----------+-------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | city_demo | range | city          | city | 20      | NULL |    2 | Using where |
+----+-------------+-----------+-------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)

mysql>
复制代码

可以看见正确使用刚创建的索引。

前缀索引是一种能使索引更小,更快的有效办法,但另一方面也有其缺点:

mysql无法使用其前缀索引做ORDER BY和GROUP BY,也无法使用前缀索引做覆盖扫描。