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
推荐阅读
- Android事件传递源码分析
- Quartz|Quartz 源码解析(四) —— QuartzScheduler和Listener事件监听
- [源码解析]|[源码解析] NVIDIA HugeCTR,GPU版本参数服务器---(3)
- ffmpeg源码分析01(结构体)
- Java程序员阅读源码的小技巧,原来大牛都是这样读的,赶紧看看!
- springboot使用redis缓存
- Vue源码分析—响应式原理(二)
- (1)redis集群原理及搭建与使用(1)
- SwiftUI|SwiftUI iOS 瀑布流组件之仿CollectionView不规则图文混合(教程含源码)
- java|java b2b2c shop 多用户商城系统源码- config 修改配置