Redis源码-5|Redis源码-5 异步事件

目标 【Redis源码-5|Redis源码-5 异步事件】在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 #include #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 mainimport ( "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 mainimport "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-46278356108 ns/op PASS oktest2.264s

不使用异步事件
goos: linux goarch: amd64 pkg: test cpu: Intel(R) Celeron(R) N4100 CPU @ 1.10GHz BenchmarkConn-410010393680 ns/op PASS oktest1.048s

可见异步编程能提高程序相应速度的。
参考
  • understanding-the-redis-event-model
  • ae

    推荐阅读