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相比,但是有胜于无嘛,况且写起来也还是挺方便的。虽然,总觉得它缺胳膊少腿的……