epoll 惊群效应实测

惊群效应

惊群简单来说就是多个进程或者线程在等待同一个事件,当事件发生时,所有线程和进程都会被内核唤醒。唤醒后通常只有一个进程获得了该事件并进行处理,其他进程发现获取事件失败后又继续进入了等待状态,在一定程度上降低了系统性能。
常见的惊群问题有两种:
Accept惊群问题,多个accept的进程同时被唤醒,该问题已于 linux2.6 解决,本文不再讨论
Epoll惊群问题,虽然accept惊群问题已被内核解决,但epoll仍旧会触发fd的可读状态,触发读事件

epoll 惊群测试

测试思路

  • 主进程创建socket
  • 从进程通过把该socket注册为epoll的可读事件,需要在fork之后创建epoll,否则多个进程会公用同一个epoll,进程不能识别其他进程产生的fd
  • 注册listen fd的可读状态,并触发accept
  • 观察输出信息
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>


const int MAX_PROC_NUM = 4;
const int MAX_EVENTS = 128;
const int PORT = 8081;
const int BUF_SIZE = 1024;
const int CLIENT_SIZE = 128;
int g_pids[MAX_PROC_NUM] = {0};

void sig_handler(int signo) {
    int i;
    for (i = 0; i < MAX_PROC_NUM; i++) {
        kill(g_pids[i], SIGKILL);
    }
}


int child_procedure(int listenfd) {
    struct epoll_event ev;
    struct epoll_event events[MAX_EVENTS];
    int epfd = 0;
    int pid = 0;
    char buf[BUF_SIZE];
    int cnt = 0;
    int i = 0;

    pid = getpid();

    epfd = epoll_create(CLIENT_SIZE + 1);
    if (0 == epfd) {
        printf("Create epoll failed\n");
        return -1;
    }

    ev.events = EPOLLIN | EPOLLET;
    ev.data.fd = listenfd;

    if (epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev) < 0) {
        printf("Add ev failed\n");
        return -1; 
    }   

    printf("Epoll init finished, process pid: %d\n", pid);

    while (1) {
        cnt = epoll_wait(epfd, events, MAX_EVENTS, 0);
        if (cnt <= 0) {
            continue;
        }

        for (i = 0; i < cnt; i++) {
            if (events[i].data.fd == listenfd) {
                // 新连接请求
                int newfd;
                printf("Process %d receive a connection request\n", pid);
                newfd = accept(listenfd, NULL, 0);
                if(newfd <=0) {
                    printf("Process %d accept failed\n", pid);
                    continue;
                }
                fcntl(newfd, F_SETFL, fcntl(newfd, F_GETFD, 0)|O_NONBLOCK); 
                ev.events = EPOLLIN | EPOLLET;
                ev.data.fd = newfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, newfd, &ev);

            } else {
                int n = read(events[i].data.fd, buf, 1024);
                printf("Process %d receive a msg, length %d\n", pid, n);
                if (n != 0) {
                    write(events[i].data.fd, buf, n);
                }
                close(events[i].data.fd);
                epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
            }

        }

    }
    return 0;
}

int main()
{
    int listenfd = 0;
    int cnt = 0;
    int i = 0;
    struct sockaddr_in servaddr;
    
    // signal(SIGINT, sig_handler);
    // signal(SIGKILL, sig_handler);

    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(PORT);
   
    fcntl(listenfd, F_SETFL, fcntl(listenfd, F_GETFD, 0)|O_NONBLOCK);
    
    if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(struct sockaddr)) == -1) {
        printf("bind error\n");
        return -1;
    }

    if (listen(listenfd, CLIENT_SIZE) == -1) {
        printf("Listen failed\n");
        return -1;
    }

    for (i = 0; i < MAX_PROC_NUM; i++) {
        g_pids[i] = fork();
        if (0 == g_pids[i]) {
            // 子进程
            break;
        }
    }

    if (i == MAX_PROC_NUM) {
        // 注册信号,父进程退出,子进程一起kill掉
        signal(SIGINT, sig_handler);
        signal(SIGKILL, sig_handler);

        // 父进程,阻塞
        while(1) {
            sleep(100);
        }
    } else {
        // 子进程,进入子进程流程
        return child_procedure(listenfd);
    }
    
    return 0;

}



测试结果

Epoll init finished, process pid: 20628
Epoll init finished, process pid: 20629
Epoll init finished, process pid: 20630
Epoll init finished, process pid: 20631
Process 20630 receive a connection request
Process 20631 receive a connection request
Process 20628 receive a connection request
Process 20629 receive a connection request
Process 20630 receive a msg, length 169
Process 20631 accept failed
Process 20628 accept failed
Process 20629 accept failed

根据结果显示,fork了4个进程,4个进程都收到了可读事件导致被唤醒,实际只能有1个进程accept该fd

惊群问题改进

同一个listen fd添加到多个epoll中,其中一个典型应用就是nginx,nginx增加了一把锁,同一时刻只有一个进程在wait状态,这样就保证了同一个可读事件不会触发给多个进程,为了减少加锁的时间,采用先将事件放入队列,处理完accept立即解锁,收发包并不占用这个全局锁,这把锁不单单用于解决惊群问题,还是进程间负载均衡重要的一环
详细可参考:https://blog.csdn.net/initphp/article/details/52266844

使用google-perftools进行C/C++性能分析

安装libunwind


wget https://github.com/libunwind/libunwind/releases/download/v1.3.0/libunwind-1.3.0.tar.gz --no-check-certificate

tar -xvf libunwind-1.3.0.tar.gz

cd libunwind-1.3.0

./configure

make & make install

 

安装perftool


git clone https://github.com/gperftools/gperftools

cd gperftools/

sh autogen.sh

./configure

make & make install

ln -s /usr/local/lib/libprofiler.so.0 /usr/lib64/libprofiler.so.0 (64位机,32位目录在/usr/lib/)

 

安装graphviz

yum -y install graphviz

 

修改编译选项,增加“-L/usr/local/lib -lprofiler”

在性能评测点前运行:ProfilerStart(“MyProfile”)

在性能评测点后运行:ProfilerStop()

以上两个命令,可以直接写在代码里,也可以通过gdb call

 

目录检查,发现:MyProfile文件

pprof –svg ./myapp MyProfile > profile.svg

其他选项可以直接执行pprof查看,可通过多种形式查看报告,我们这里选择svg,svg可使用浏览器打开

golang panic recover不生效的一个原因

前言:笔者遇到不确定性panic,暂未确定原因,所以通过recover()暂时屏蔽,并打印信息定位
代码存在无法运行到的地方,查看了recover的规则:“程序首先运行panic,出现故障,此时跳转到包含recover()的defer函数执行,recover捕获panic,此时panic就不继续传递.但是recover之后,程序并不会返回到panic那个点继续执行以后的动作,而是在recover这个点继续执行以后的动作”

所以,如果是通过go f()调用panic,recover放到goroutine外面的话,不在同一个goroutine,自然也就无法reached了

错误示范:

func test() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("panic")
        }
    go f()
}

应该:

func test() {
    go func() {
        defer func() {
            if err := recover(); err != nil {
                fmt.Println("panic")
            }
        f()
    }()
}

golang pprot访问debug/pprof报404 page not found的解决办法

这个问题要从net/http/pprof的原理说起,可以看到

func init() {
	http.HandleFunc("/debug/pprof/", Index)
	http.HandleFunc("/debug/pprof/cmdline", Cmdline)
	http.HandleFunc("/debug/pprof/profile", Profile)
	http.HandleFunc("/debug/pprof/symbol", Symbol)
	http.HandleFunc("/debug/pprof/trace", Trace)
}

引入 _ “net/http/pprof”,init函数会添加pprof的路由信息,而如果http注册了其他路由,导致http.HandleFunc失效,也就会造成了404的问题,我使用的是httprouter包

知道这个原理,手动添加上面5条路由,再次尝试,访问http://localhost:8080/debug/pprof/goroutine?debug=1等其他内部页面时,仍旧报404,再次分析原因,httprouter添加路由信息,如第一条,仅添加了/debug/pprof/的路由信息,并不会对子路径作路由牵引,导致404

所以解决办法:

用http.Handle(“/”, router) 将httprouter的路由注册给http路由,http.ListenAndServe(addr, nil) 替代http.ListenAndServe(addr, router),这时,router和http/pprof都可以生效了

http.Handle("/", router)
http.ListenAndServe(addr, nil)

当然还可以用第三方router的prefix功能,如Gorilla的

router.NewRoute().PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)

httprouter还未发现这样的功能,有了解的可以留言给我

 

DNS协议的axfr和ixfr解析

一、简介

axfr:DNS Zone Transfer Protocol (AXFR),dns的全量更新协议,dns主从架构更新,从向主获取zone的全量数据,由主返回axfr消息,全量刷新该zone的slave信息

ixfr:Incremental Zone Transfer in DNS,dns的增量更新协议,和axfr对应,axfr是一次性将一个zone的全量数据返回至Slave,而ixfr仅将增量更新数据返回

二、扩展内容

提到axfr和ixfr,不得不提到dns的另外一个概念,SOA记录中的序列号,SOA记录本文不作赘述,该序列号用于标识zone的版本信息,常规情况下,zone每发生一次变化,序列号加1,通过序列号标识版本,获取增量更新信息

如上,忽略tcp信令报文,

1,2位notify更新通告响应,表示zone发生变化,master通告slave

3,4为SOA查询响应,slave发起,请求master最新的序列号

10,12为ixfr更新请求和响应,slave发情,请求更新zone信息,axfr同理

 

三、axfr

如上两张图,图一为axfr query,图二为axfr response,query很好理解,对于response,axfr结果放在answers section内,开头和结尾的SOA记录表示左括号和右括号,当中的内容表示这个zone的所有记录

 

四、ixfr

如上,为一个最基本的ixfr请求与响应,与axfr不同的是,ixfr除query type与axfr不同,还会额外在第三section也就是表示权威服务器(NS服务器)区域携带一条slave当前的SOA记录,由master根据slave的序列号(上图红框),判别增量更新信息,并返回给slave

对于应答,上图包含4个SOA,同样在answer section内,开始和结尾的soa含义仍旧和axfr保持一致,代表括号,第二个SOA为老的序列号,后面跟的是需要删除掉的RRs,第三个SOA为较新的序列号,表示需要增加的RRs,下面截取rfc1995中的部分内容,多个版本更新可以顺次将更新串起来,也可以直接经过运算,得到最终增量结果。


上面说了ixfr正常情况的返回结果,那么还存在以下几种情况

  1. ixfr获取增量失败,增量信息不完整或已丢失,直接返回全量结果,answer section同axfr (SOA、records…..、SOA)
  2. ixfr请求序列号和最新序列号一致,answer区域仅返回一条SOA记录
  3. ixfr请求序列号小于最新序列号,但无更新RR,直接返回两条SOA(SOA、SOA)
  4. ixfr请求序列号大于master最新序列号,异常,返回axfr

 

五、文档

rfc1995 IXFR https://www.rfc-editor.org/rfc/rfc1995.txt

https://tools.ietf.org/id/draft-ah-dnsext-rfc1995bis-ixfr-02.html

rfc5936 AXFR https://www.rfc-editor.org/rfc/rfc5936.txt

 

linux ext2、ext3无损升级ext4的方法

英文参考文章:https://www.ghacks.net/2010/08/11/convert-ext23-to-ext4/

注意备份,注意备份,注意备份

勤快的可以看下英文原文

1. 前提条件:
kernel版本2.6.28-11以上,查看方法:

uname -a

2. 停用磁盘:

umount -l /dev/sda1

如果系统磁盘,需要使用liveCD

3. convert
ext2->ext4

tune2fs -O extents,uninit_bg,dir_index,has_journal /dev/sda1

ext3->ext4

tune2fs -O extents,uninit_bg,dir_index /dev/sda1

如果执行失败,提示busy的话,有进程占用,fsck /dev/sda1,如果不可以,注释掉/etc/fstab中这个分区的mount,重启

4. 磁盘检查
e2fsck -pf /dev/sda1
如果报错,看提示,有可能提示手动执行,那么就执行e2fsck -f /dev/sda1,一路y

5. 修改/etc/fstab修改/dev/sda1的格式

6. refresh grub,引用原文,我改的/home目录使用的分区,不用这一步,自己理解这个
Now you need to refresh grub. Depending upon how your boot partition is will determine how you do this. If your boot partition is SEPARATE, do the following:

sudo bash
mkdir /mnt/boot
mount /dev/sda1 /mnt/boot
grub-install /dev/sda –root-directory=/mnt –recheck

If your boot partition is NOT separate, do the following:

sudo bash
mount /dev/sda1 /mnt
grub-install /dev/sda –root-directory=/mnt –recheck

golang报错lfstackPack redeclared in this block解决办法

/usr/local/go/src/runtime/lfstack_amd64.go:16: lfstackPack redeclared in this block
previous declaration at /usr/local/go/src/runtime/lfstack_64bit.go:37
/usr/local/go/src/runtime/lfstack_amd64.go:20: lfstackUnpack redeclared in this block
previous declaration at /usr/local/go/src/runtime/lfstack_64bit.go:41
/usr/local/go/src/runtime/os_linux_generic.go:13: _SS_DISABLE redeclared in this block
previous declaration at /usr/local/go/src/runtime/os2_linux_generic.go:12
/usr/local/go/src/runtime/os_linux_generic.go:14: _NSIG redeclared in this block
previous declaration at /usr/local/go/src/runtime/os2_linux_generic.go:13
/usr/local/go/src/runtime/os_linux_generic.go:15: _SI_USER redeclared in this block
previous declaration at /usr/local/go/src/runtime/os2_linux_generic.go:14
/usr/local/go/src/runtime/os_linux_generic.go:16: _SIG_BLOCK redeclared in this block
previous declaration at /usr/local/go/src/runtime/os2_linux_generic.go:15
/usr/local/go/src/runtime/os_linux_generic.go:17: _SIG_UNBLOCK redeclared in this block
previous declaration at /usr/local/go/src/runtime/os2_linux_generic.go:16
/usr/local/go/src/runtime/os_linux_generic.go:18: _SIG_SETMASK redeclared in this block
previous declaration at /usr/local/go/src/runtime/os2_linux_generic.go:17
/usr/local/go/src/runtime/os_linux_generic.go:19: _RLIMIT_AS redeclared in this block
previous declaration at /usr/local/go/src/runtime/os2_linux_generic.go:18
/usr/local/go/src/runtime/os_linux_generic.go:25: sigset redeclared in this block
previous declaration at /usr/local/go/src/runtime/os2_linux_generic.go:24
/usr/local/go/src/runtime/os_linux_generic.go:25: too many errors

这个错误目前还没发现根本原因,在segment fault找到的解决办法
rm -rf /usr/local/go
后重新解压压缩包
tar -C /usr/local -xzf go$VERSION.$OS-$ARCH.tar.gz

nginx配置ssl证书失败X509_check_private_key:key values mismatch

[root@instance vhost]# /usr/local/nginx/sbin/nginx -s reload
nginx: [emerg] SSL_CTX_use_PrivateKey_file(“/XXX.key”) failed (SSL: error:X:x509 certificate routines:X509_check_private_key:key values mismatch)

顾名思义,私钥和证书不匹配,导致解密失败
第一步先确认是否证书和私钥指定错误,一般申请证书,会得到两个证书,一个是申请到的证书,另外一个是认证机构的证书,指定错了就会报这个错
另外,还需要排除私钥文件中是否有tab空格换行等不可见字符(未修改私钥文件忽略)

netsh.xyz.zone.jnl: create: permission denied,bind9 ixfr同步jnl生成失败的解决办法

调测bind9同步及notify、ixfr、axfr机制,通过rndc reload zone,在slave抓包,并未发现notify及ixfr包,检查named.run日志,发现日志中有netsh.xyz.zone.jnl: create: permission denied
第一反应:权限或者SELinux有问题,经检查不是这的事,搜索,在centos.org找到了解决方案:
Stop Bind Server
service named stop

Move all zones
/var/named/example.com
to
/var/named/data/example.com

and on named.conf
file "data/example.com"

Start Bind Server
service named start

测试,问题解决,原文连接:https://www.centos.org/forums/viewtopic.php?t=5543