关于redis:Redis源码5-异步事件

2次阅读

共计 2765 个字符,预计需要花费 7 分钟才能阅读完成。

指标

在 Redis 源码 -3 网络编程, 学习了 redis 封装的网络库。其中用了循环不断去执行anetTcpAccept, 这是机制效率差,为了进步程序的并发数,操作系统引入了 epoll 等相似的机制,防止了死等,能够做到当事件产生时告诉用户。Redis 没有才用现有的异步库,自研了一个适宜 redis 的库。比拟玲珑,适宜学习。本文引入 Redis 中的 ae 模块,革新之前的流程。

可运行代码

源码

筹备工作:从 redis 源码中拷贝代码

 cp /home/vagrant/github/server_installer/servers/redis/redis-6.2/src/ae* .
 ...

批改 server.c

#include "anet.h"
#include "zmalloc.h"
#include <sys/socket.h>
#include <unistd.h>
#include "sys/socket.h"
#define NET_IP_STR_LEN 46 /* INET6_ADDRSTRLEN is 46, but we need to be sure */
#include "ae.h"

void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);

#define MAX_ACCEPTS_PER_CALL 1000
#define UNUSED(V) ((void) V)


void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
    int cport, cfd, max = MAX_ACCEPTS_PER_CALL;
    char cip[NET_IP_STR_LEN];
    UNUSED(el);
    UNUSED(mask);
    UNUSED(privdata);

    while(max--) {
        char* neterr;
        neterr = zmalloc(100);
        cfd = anetTcpAccept(neterr, fd, cip, sizeof(cip), &cport);
        if (cfd == ANET_ERR) {continue;}
        anetCloexec(cfd);

        printf("accept...%d\n",cfd);
        char buf[1024];
        recv(cfd, buf, sizeof(buf), MSG_WAITALL);
        printf("recv from %s:%d  %s\n",cip, cport, buf);
        close(cfd);

    }
}



int main() {
    // 错误信息
    char *neterr = zmalloc(10);

    printf("staring...\n");
    aeEventLoop *el;
    el = aeCreateEventLoop(100);



    // 端口 6380
    int serverSocket = anetTcpServer(neterr, 6380,"*" , 2);
    if (! neterr) {printf("start err %s \n", neterr);
        return 1;
    }
    printf("listening...%d \n",serverSocket);

    if (aeCreateFileEvent(el, serverSocket, AE_READABLE, acceptTcpHandler,NULL) == AE_ERR) {aeDeleteFileEvent(el, serverSocket, AE_READABLE);
        return 1;
    }

    aeMain(el);
    aeDeleteEventLoop(el);
    return 0;
 }

去掉了 while(1)循环,调用 anetTcpServer 创立事件循环,调用 aeCreateFileEvent 注册文件事件。之后进入事件循环aeMain。当有文件事件产生时,会调用acceptTcpHandler, 在这里执行anetTcpAccept, 对文件描述符进行读操作。

创立 Makefile

all: server client
    @echo "anet demo"

server :  anet.o zmalloc.o ae.o server.o monotonic.o
    $(CC)   -o $@   $^

client : anet.o zmalloc.o client.o
    $(CC)   -o $@   $^

%.o: %.c 
    $(CC) -O0 -DREDIS_TEST=1 -MMD -o $@ -c $<


.PHONY: clean
clean:
    rm -rf *.o *.d server client

输入

同 Redis 源码 -3 网络编程

性能简略压测

为了看下应用事件循环和不应用事件循环的区别,进行简略压测。网络上找了写 tcp 压测工具,没找到适合的,就本人写个简略的程序。

测试代码

新建 main.go

连贯 server 端,Hello server

package main

import (
    "fmt"
    "net"
    "os"
)

func main() {Conn("localhost:6380")

}

func Conn(service string) {

    // 绑定
    tcpAddr, err := net.ResolveTCPAddr("tcp", service)
    checkError(err)
    // 连贯
    conn, err := net.DialTCP("tcp", nil, tcpAddr)
    checkError(err)
    //for {
    // 发送
    _, err = conn.Write([]byte("Hello server"))
    checkError(err)
    //}
    conn.Close()}

func checkError(err error) {
    if err != nil {fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
        os.Exit(1)
    }
}

新建 main_test.go

基准测试

package main

import "testing"

func BenchmarkConn(b *testing.B) {
    for n := 0; n < b.N; n++ {Conn("localhost:6380")
    }
}

测试后果

应用异步事件
go test -bench=.

goos: linux
goarch: amd64
pkg: test
cpu: Intel(R) Celeron(R) N4100 CPU @ 1.10GHz
BenchmarkConn-4             6278            356108 ns/op
PASS
ok      test    2.264s

不应用异步事件

goos: linux
goarch: amd64
pkg: test
cpu: Intel(R) Celeron(R) N4100 CPU @ 1.10GHz
BenchmarkConn-4              100          10393680 ns/op
PASS
ok      test    1.048s

可见异步编程能进步程序相应速度的。

参考

  • understanding-the-redis-event-model
  • ae
正文完
 0