1.反应器模式和网状线程模式。最近由于工作需要,研究了一段时间Netty的源代码,做了一个简单的分享。研究不是特别深入,继续努力。因为分享不涉及公司业务,下面是源代码的研究结果。以下是Netty在游戏服务器开发中使用需要了解的知识点和相关优化。
这次分享以下主要设计内容。
Netty线程模型Netty的配置和TCP相关参数的具体含义Netty对Epoll的封装Netty的优雅关闭了客户端连接的限制。
内存资源CPU资源端口号资源
cat/proc/sys/net/IP v4/IP _ local _ port _ range文件描述符资源
系统级别:当前可以打开的最大系统数量。通过cat /proc/sys/fs/file-max检查用户级别:指定可以打开的最大用户数,通过cat /etc/security/limits.conf检查进程级别:可以打开的单个进程的最大数量,通过cat /proc/sys/fs/nr_open检查线程资源BIO/NIO。
1.生物模型的所有操作都是同步块(接受、读取)。客户端连接数与服务器线程数之比为1:1
2.NIO模型的非阻塞IO可以通过选择器实现,通过选择器的事件注册(op _ read/op _ write/op _ connect/op _ accept)实现一个线程管理多个连接,客户端连接数与处理自己感兴趣事件的服务器线程数之比为n:1。
3.反应堆模型
①单个Reacor单线程模型的所有IO都在同一个NIO线程中完成(处理连接、调度请求、编码、解码、逻辑运算、发送)
优势:
编码简单,没有共享资源竞争和并发安全的弊端;
单线程处理大量链接时,性能无法支撑,多核处理无法合理使用。线程过载后,处理速度会变慢,导致消息积压。一旦线程被挂起,整个通信层都不可用。redis使用反应器单流程模型。由于redis在内存级运行,使用这种模型没有问题。反应器单线程模型图:
网状反应器单线程模型图:
Netty对应实现
// Netty对应的实现方法:由boss和worker创建io线程组,使用同一个线程组,线程数为1 EventLoopgroup IO Group = newnioeventLoopgroup(1);b.group(ioGroup,ioGroup)。通道(NioServerSocketChannel.class)。childHandler(初始化器);channel future f = b . bind(portNumner);cf = f . sync();f . get();
②单反应器多线程模型根据单线程模型,将io处理中最耗时的编码、解码、逻辑运算等cpu消耗部分提取出来,用多线程实现,充分利用多核cpu的优势。
优势:
多线程逻辑运算提高多核CPU利用率
缺点:
对于单个反应器,大量的关联IO事件仍然是性能瓶颈。
反应器多线程模型图:
Netreactor多线程模型图:
Netty对应实现
netty的对应实现:创建io线程组,boss和worker,使用同一个线程组,线程数为1,将逻辑运算部分发布到用户自定义线程处理EventLoopgroup IO Group = newnioeventLoopgroup(1);b.group(ioGroup,ioGroup)。通道(NioServerSocketChannel.class)。childHandler(初始化器);channel future f = b . bind(portNumner);cf = f . sync();f . get();
③主从式反应器多线程模型根据多线程模型,其性能瓶颈可以进一步优化,即反应器由单个反应器改为反应器线程池,原反应器分为主反应器和子反应器。
优势:
解决单个反应器的性能瓶颈问题(Netty/Nginx采用这种设计)。反应器主从多线程模型图:
Netty反应器主从多线程模型图:
Netty对应实现
netty的对应实现:创建io线程组boss和worker,boss线程数为1,worker线程数为cpu*2(一般IO密集型可以设置为cpu核数的两倍)。EventLoopgroup boss group = newnioeventLoopgroup(1);EventLoopGroup worker group = new NioEventLoopGroup();b.group(bossGroup,workerGroup)。通道(NioServerSocketChannel.class)。childHandler(初始化器);channel future f = b . bind(portNumner);cf = f . sync();f . get();
④部分源代码分析创建组实例。// 1.构造参数不转移或转移为0,默认为系统参数配置。如果没有参数配置,CPU核心数*2super(nThreads == 0?DEFAULT _ EVENT _ LOOP _ THREADS:n THREADS,executor,args);private static final int DEFAULT _ EVENT _ LOOP _ THREADS;static { DEFAULT _ EVENT _ LOOP _ THREADS = math . max(1,systempropertyutil . getint(" io . netty . eventloopthreads ",nettyruntime . available processors()* 2));}// 2.不同版本的JDK将有不同版本的selectorProvider实现。在Windows下,默认选择器是Windows选择器提供者加NioeventloopGroup (Intnth Reads,Executor Executor) {//,最终实现类似:https://github . com/frohoff/jdk8u -JDK/blob/master/src/ma cosx/classes/sun/nio/ch/defaultselectorprovider . Java//基本流:1 Java . nio . channels . SPI . selectorprovider 2 meta -INF/services 3默认this(nThreads,Executor,SelectorProvider.provider())}// 3.创建第n个EventExecutor,并将它们封装到选择器chooser中。chooser会根据nThread的数量有两种实现(GenericEventExecutorChooser和PowerOfTwoEventExecutorChooser,算法不同但实现逻辑相同,Eventexecutorchooserfactory。Eventexecutorchooserchooserchildren =新事件执行者
通道类图
5.配置Netty为epolleventploop//创建指定的eventloopgroupgroup = new epolleventloopgroup(1,new defaultthreadfactory(" boss _ loop "));worker group = new EpollEventLoopGroup(32,new DefaultThreadFactory(" IO _ LOOP "));B. group (boss group,worker group)//指定类。信道(epollserversocket channel.class)。通道的childhandler(初始值设定项);//channel(clz)方法是创建一个工厂类的公共b通道(class
三。与网络相关的参数
1.so _ keepalivechildoption(通道选项。so _ keepalive,true) TCP链接探测
2.so _ reusadroption(channel option . so _ reusaddr,true)复用处于TIME_WAIT但没有完全关闭的套接字地址,这样端口释放后可以立即复用。默认情况下,需要手动开启。
3.TCP _ nodelayaction(通道选项。TCP _ nodelay,true) IP消息格式
TCP消息格式
然后开启和关闭TCP Negal算法,该算法在大量小数据包的情况下,具有低延迟、低网络利用率的优点。
TCP Negal算法关闭再开启,具有提高网络利用率(数据缓存到发送一定量)和高延迟的优点。
Negal算法
如果数据包长度达到MSS(最大段大小最大段长度),则允许发送;如果包含FIN,则允许发送;如果设置了TCP_NODELAY选项,则允许发送;当TCP_CORK选项(是否拦截不完整消息)未设置时,如果所有发送的小数据包(包长小于MSS)都被确认,则允许发送;如果不满足上述条件,但发生超时(一般200ms),则立即发送。MSS计算规则MSS的值在802.3标准中,在TCP三次握手建立连接的过程中,由双方协商确定。规定以太网帧有效载荷的最大长度为1500字节(MTU)。
以太网环境下MSS = MTU -IP header -TCP header:MTU = 1500字节IP header = 32*5/4 = 160bit = 20字节TCP header = 32*5/4 = 160bit = 20字节,最后得到MSS = 1460字节。
结论:由于游戏服务器的实时性要求,在网络带宽充足的情况下,建议开启TCP_NODELAY,关闭Negal算法,这样带宽可以不浪费,响应一定要及时。
注意:所有客户端和服务器都要关闭Negal算法,否则发送仍然会有延迟,影响传输速度。
4.so _ backlog选项(channeloption.so _ backlog,100)操作系统内核中维护的两个队列
Synqueue:保存syn到达但未完成的三次握手的半连接cat/proc/sys/net/IP v4/TCP _ max _ syn _ backlog接受队列:保存三次握手,backlog的连接cat/proc/sys/net/core/somaxconnecttty的默认值设置在NetUtil类的第253行。
SOMAXCONN = access controller . doprivileged(new privileged action & lt;整数& gt(){@覆盖公共整数run () {//1。设置默认值intsomaxconn = platform dependent . is windows()?200 : 128;File file =新文件("/proc/sys/net/core/somaxconn ");If (file.exists()) {// 2。文件存在,读取= new buffered reader(new file reader(file))中的操作系统配置;somax conn = integer . parse int(in . readline());} else {// 3。文件不存在,从每个参数中读取if(systempropertutil . get boolean(" io . netty . net . somaxconn . tryssctl ",false)){ tmp = sysctlgetint(" kern . IPC . somaxconn ");if(tmp = = null){ tmp = sysctlGetInt(" kern . IPC . soacceptqueue ");如果(tmp!= null){ somaxconn = tmp;} } else { somaxconn = tmp}}}}结论:
在Linux下,/proc/sys/net/core/somaxconn必须存在,所以backlog必须得到它的值。我参考prod机的参数配置65535,即不设置backlog,服务器运行,缓存65535个全连接。
5.分配器和RCVBUF_ALLOCATOR
ByteBuffAllocator的默认赋值如下:ByteBuffAllocator
Static {//以io.netty.allocator.type为准,如果不是,则Android平台采用非池化实现,其他采用池化字符串allocytype = systempropertytil . get(" io . netty . allocator . type ",platformdependent.isandroid()?“未池化”:“池化”);allocType = allocType . tolowercase(区域设置。美国)。trim();ByteBufAllocator allocif ("unpooled "。equals(allocytype)){ alloc = unpoedbytebullocator。违约;logger . debug(" -dio . netty . allocator . type:{ } ",allocType);} else if(“汇集”。equals(allocytype)){ alloc = pooledbytebufolucator。违约;logger . debug(" -dio . netty . allocator . type:{ } ",allocType);} else {//io . netty . allocator . type未设置为“未池化”或“池化”,因此通过池化实现。alloc = PooledByteBufAllocator。违约;logger . debug(" -dio . netty . allocator . type:pooled(unknown:{ })",allocType);} DEFAULT _ ALLOCATOR = alloc}RCVBUF_ALLOCATOR默认适配器
公共类DefaultChannelConfig实现ChannelConfig { //...public DefaultChannelConfig(Channel Channel){ this(Channel,new adapteverecvbytebufolucator());} // ...}
四。Netty使用敏感的默认值正常关闭{ @ link # shut down(long,long,time unit)}的/* * *快捷方法。* * @ return { @ link # termination future()} */future <?& gtshut down gracefully();/** *向该执行器发出信号,表明调用者希望关闭该执行器。一旦这个方法被调用,* {@link #isShuttingDown()}开始返回{@code true},执行程序准备自行关闭。*与{@link #shutdown()}不同,正常关机可确保不会为& lt我& gt安静时期' & lt/I & gt;*(通常几秒钟后)它就会自动关闭。如果任务是在静默期提交的,*它肯定会被接受,静默期将重新开始。* * @ param静默期文档中描述的静默期:在此期间,您仍可以提交任务* @ param timeout等待执行程序完成的最长时间{@ linkplain # shutdown ()} *无论任务是否在静默期提交超时:等待所有任务完成的最长时间* @ param unit { @ code quiet period }和{@code timeout}的单位* * @ return { @ link # termination Future()} */Future & lt;?& gt优雅关闭(长静默期、长超时、时间单位单位);//在抽象类中实现静态final long default _ shut down _ quiet _ period = 2;静态最终长DEFAULT _ SHUTDOWN _ TIMEOUT = 15@ Overridepublic Future & lt?& gtshut down graceful(){ return shut down graceful(DEFAULT _ shut down _ QUIET _ PERIOD,DEFAULT_SHUTDOWN_TIMEOUT,TimeUnit。秒);}将NIO线程的状态位设置为ST_SHUTTING_DOWN状态,不再处理新消息(不再允许外部消息);退出前的预处理操作:完成发送队列中尚未发送或正在发送的消息的发送,完成退出超时前已经过期或过期的调度任务的执行,完成在NIO线程中注册用户的退出钩子任务的执行;资源释放操作:释放所有通道,注销并关闭多路复用器,清空并取消所有队列和调度任务,最后退出NIO线程。以上是Netty在游戏服务器中的应用细节和源代码分析。关于Netty游戏服务器的更多信息,请关注主机频道zhujipindao的其他相关文章。com!
评论前必须登录!
注册