netty中引入spring-boot

netty是Java世界中高并发的框架,号称单台物理机能够支撑100万并发连接,是Java世界中高性能并发的不二之选。不过,跟spring-boot相比,其开发有点偏于底层,写起来没有spring-boot那么爽快,开发的效率不高。
我的一个项目中,有高并发的需求,单靠spring-boot自带的tomcat无法满足性能上的要求。因此,我选择netty,作为底层框架。为了能够提高开发效率,我尝试将spring-boot引入我的开发中。仔细想想,其实整个spring都是建立在IOC和AOP之上的,所以只要我引入spring-boot这两个最基础的组件,那么势必整个spring-boot的组件都能为我所用。

不过spring-web不晓得该咋引入,其它的组件都不成问题。不过从我的角度看,netty本身就是网络框架,基本没必要在引入一个spring-web

我的项目中使用maven做整个工程管理,以下是pom.xml,我只保留了spring-boot和netty的部分:


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>

    <groupId>com.testgroupId>
    <artifactId>netty.spring-bootartifactId>
    <version>1.0.0-SNAPSHOTversion>
    <packaging>pompackaging>

    <name>cdn-routername>
    <url>http://maven.apache.orgurl>

    <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
        <java.version>1.8java.version>
        <spring.version>4.3.10.RELEASEspring.version>
        <build-tool.version>1.0.0build-tool.version>
        <cdn-opentsdb.version>1.0.0-SNAPSHOTcdn-opentsdb.version>
    properties>
    <dependencies>
        <dependency>
            <groupId>io.nettygroupId>
            <artifactId>netty-allartifactId>
            <version>4.1.13.Finalversion>
        dependency>
        
        <dependency>
            <groupId>org.apache.logging.log4jgroupId>
            <artifactId>log4j-apiartifactId>
            
        dependency>
        <dependency>
            <groupId>org.apache.logging.log4jgroupId>
            <artifactId>log4j-coreartifactId>
            
        dependency>

        <dependency>
            <groupId>org.slf4jgroupId>
            <artifactId>slf4j-apiartifactId>
            
        dependency>

        <dependency>
            <groupId>org.apache.logging.log4jgroupId>
            <artifactId>log4j-slf4j-implartifactId>
            
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-starter-loggingartifactId>
                exclusion>
            exclusions>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-aopartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-log4j2artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.logging.log4jgroupId>
                    <artifactId>log4j-apiartifactId>
                exclusion>
                <exclusion>
                    <groupId>org.apache.logging.log4jgroupId>
                    <artifactId>log4j-coreartifactId>
                exclusion>
                <exclusion>
                    <groupId>org.apache.logging.log4jgroupId>
                    <artifactId>log4j-slf4j-implartifactId>
                exclusion>
                <exclusion>
                    <groupId>org.slf4jgroupId>
                    <artifactId>slf4j-apiartifactId>
                exclusion>
            exclusions>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-configuration-processorartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.31version>
        dependency>

    dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-dependenciesartifactId>
                <version>1.5.6.RELEASEversion>
                <type>pomtype>
                <scope>importscope>
            dependency>
        dependencies>
    dependencyManagement>
    <build>
        <finalName>${finalName}finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
                <configuration>
                    <mainClass>com.test.CDNRouterServermainClass>
                configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackagegoal>
                        goals>
                    execution>
                executions>
            plugin>

        plugins>
    build>
project>

接下来,我实现一个http消息的handler,并将其设置为IOC的bean,让spring-boot去管理它。

/**
 * @Author Derek.
 * @Date 2017/7/18 9:24.
 */
@Component
@Scope("prototype")
public class OpsHttpMessageHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
        System.out.println("OK");
        ctx.channel().writeAndFlush(Unpooled.copiedBuffer("Channel Test".getBytes("utf-8")));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

注意:其中的@Scope(“prototype”),因为netty会为每个eventloop重新生成一个handler的处理链,因此默认情况下,线程间不会共享handler。这样做的好处可以避免临界区访问的问题,从而避免了线程冲突和切换,提高并发率。而spring-boot中默认情况下,bean是单例模式的,也就是说这个bean只会有一个实例,而显然不适合与netty对handler的默认假设。因此,我们将bean改成原型模式,即@Scope(“prototype”)。在这个状态下,每次引用这个bean的时候,都会创建一个实例。当然,netty中也支持共享的handler,这时候需要在handler中注上@Sharable,此时就可以使用spring-boot默认的单例的bean了。具体见下面的代码:

/**
 * @Author Derek.
 * @Date 2017/5/18 10:58.
 */
@Component
@Sharable
public class Http2MessageHandler extends ChannelDuplexHandler {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("OK");
        super.channelRead(ctx, msg);
    }
}

接下来,我将使用上面创建的handler创建http server,同样也设置为spring的bean。

@Component(value = "opsHttpServer")
public class OpsHttpServer implements Runnable {
    @Autowired
    final private HttpProperty httpProperty = null;

    @Autowired
    private ServerBootstrapFactory factory;

    @Autowired
    final private ApplicationContext applicationContext = null;

    final  private EventExecutorGroup pool = new DefaultEventExecutorGroup(Runtime.getRuntime().availableProcessors() * 2);

    private static final int MAX_CONTENT_LENGTH = 1024 * 100;


    @Override
    public void run() {
        ServerBootstrap tcpBootStrap = factory.newServerBootstrap(0);
        tcpBootStrap.handler(new LoggingHandler(LogLevel.INFO))
                .childHandler(new ChannelInitializer() { // (4)
                    @Override
                    public void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new HttpServerCodec())
                                .addLast(new HttpContentDecompressor())
                                .addLast(new ChunkedWriteHandler())
                                .addLast(new HttpContentCompressor())
                                .addLast(new HttpObjectAggregator(MAX_CONTENT_LENGTH))
                                .addLast(pool, applicationContext.getBean("opsHttpMessageHandler", OpsHttpMessageHandler.class));
                    }
                });
        try {
            ChannelFuture cf = tcpBootStrap.bind(httpProperty.getBackport()).sync();
            cf.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            logger.error(e.getMessage(), e);
        } finally {
            factory.shutdownGracefully(false);
        }
    }
}
@Component
@Scope("prototype")
public class ServerBootstrapFactory {
    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;

    /**
     * New server bootstrap server bootstrap.
     *
     * @param ioThreadCount the io thread count
     * @return the server bootstrap
     */
    public ServerBootstrap newServerBootstrap(int ioThreadCount) {
        if (Epoll.isAvailable()) {
            return newEpollServerBootstrap(ioThreadCount);
        }

        return newNioServerBootstrap(ioThreadCount);
    }

    /**
     * Shutdown gracefully.
     *
     * @param shouldWait the should wait
     */
    public void shutdownGracefully(boolean shouldWait) {
        Future workerFuture = workerGroup.shutdownGracefully();
        Future bossFuture = bossGroup.shutdownGracefully();

        if (shouldWait) {
            workerFuture.awaitUninterruptibly();
            bossFuture.awaitUninterruptibly();
        }
    }

    private ServerBootstrap newNioServerBootstrap(int ioThreadCount) {
        if (ioThreadCount > 0) {
            bossGroup = new NioEventLoopGroup(ioThreadCount);
            workerGroup = new NioEventLoopGroup(ioThreadCount);
        } else {
            bossGroup = new NioEventLoopGroup();
            workerGroup = new NioEventLoopGroup();
        }

        return new ServerBootstrap().group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class);
    }

    private ServerBootstrap newEpollServerBootstrap(int ioThreadCount) {
        if (ioThreadCount > 0) {
            bossGroup = new EpollEventLoopGroup(ioThreadCount);
            workerGroup = new EpollEventLoopGroup(ioThreadCount);
        } else {
            bossGroup = new EpollEventLoopGroup();
            workerGroup = new EpollEventLoopGroup();
        }

        return new ServerBootstrap().group(bossGroup, workerGroup)
                .channel(EpollServerSocketChannel.class);
    }
}

请注意这一句代码,addLast(pool, applicationContext.getBean("opsHttpMessageHandler", OpsHttpMessageHandler.class))。我使用applicationContext来获取opsHttpMessageHandler这个bean,因为此处位于ChannelInitializer中,而ChannelInitializer本身并不是spring管理的bean,所以只能通过applicationContext来获取对应的bean。

上述代码中,我将server设置为线程,这样是为了能在一个程序中,同时监听多个端口。最后是整个程序的入口,使用ApplicationContext来获取server的bean,并使用ExecutorService 来启动server对应的线程。

@SpringBootApplication
public class CDNRouterServer {

    private static Logger logger = LoggerFactory.getLogger(CDNRouterServer.class);

    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = SpringApplication.run(CDNRouterServer.class, args);
        ExecutorService service = Executors.newCachedThreadPool();
        OpsHttpServer server = ctx.getBean("opsHttpServer", OpsHttpServer.class);
        service.execute(server);
        service.shutdown();
    }
}

引入Spring-boot后,我们就可以很方便的引入spring-data-jpa来做数据库的访问了,而不需要再手动得写JDBC的程序,极大简化了数据库的访问。
ps: 小编是从python路转java的,念念不忘python世界中的sqlalchemy和Django中的ORM,实在是无法忍受jdbc写sql语句。得亏还有个jpa,虽然我个人认为jpa绝对无法跟sqlalchemy相比,但是有胜于无嘛,况且写起来也还是挺方便的。虽然,总觉得它缺胳膊少腿的……

你可能感兴趣的:(java,netty,spring-boot)