Netty应用篇
高级应用
自定义 Tomcat
这里要手写的是一个 Web 容器,一个类似于 Tomcat 的容器,用于处理 HTTP 请求。该 Web 容器没有实现 JavaEE 的 Servlet 规范,不是一个 Servlet 容器。但其是类比着 Tomcat 来写的,这里定义了自己的请求、响应及 Servlet,分别命名为了 NettyRequest,NettyResponse 与 Servnet。 我们这里要定义一个 Tomcat,这个 Web 容器提供给用户后,用户只需要按照使用步骤就可以将其自定义的 Servnet 发布到该 Tomcat 中。我们现在给出用户对于该 Tomcat 的使用步骤:
用户只需将自定义的 Servnet 放入到指定的包中,例如 com.yw.netty.example.webapp 包中。 用户在访问时,需要将自定义的 Servnet 的简单类名全小写后的字符串作为该 Servnet 的 Name 进行访问。 若没有指定的 Servnet,则访问默认的 Servnet。
定义Servnet规范
定义请求接口 NettyRequest:Servnet规范之请求规范
public interface NettyRequest {
String getUri ( ) ;
String getPath ( ) ;
String getMethod ( ) ;
Map < String , List < String > > getParameters ( ) ;
List < String > getParameters ( String name) ;
String getParameter ( String name) ;
}
定义响应接口 NettyResponse:ServNet规范之响应规范
public interface NettyResponse {
void write ( String content) throws Exception ;
}
public abstract class Servnet {
public abstract void doGet ( NettyRequest request, NettyResponse response) throws Exception ;
public abstract void doPost ( NettyRequest request, NettyResponse response) throws Exception ;
}
定义Tomcat服务器
定义 DefaultNettyRequest 类:Tomcat中对Servnet规范的默认实现
public class DefaultNettyRequest implements NettyRequest {
private HttpRequest request;
public DefaultNettyRequest ( HttpRequest request) {
this . request = request;
}
@Override
public String getUri ( ) {
return request. uri ( ) ;
}
@Override
public String getPath ( ) {
QueryStringDecoder decoder = new QueryStringDecoder ( request. uri ( ) ) ;
return decoder. path ( ) ;
}
@Override
public String getMethod ( ) {
return request. method ( ) . name ( ) ;
}
@Override
public Map < String , List < String > > getParameters ( ) {
QueryStringDecoder decoder = new QueryStringDecoder ( request. uri ( ) ) ;
return decoder. parameters ( ) ;
}
@Override
public List < String > getParameters ( String name) {
return getParameters ( ) . get ( name) ;
}
@Override
public String getParameter ( String name) {
List < String > parameters = getParameters ( name) ;
if ( parameters == null || parameters. size ( ) == 0 ) {
return null ;
}
return parameters. get ( 0 ) ;
}
}
定义 DefaultNettyResponse 类:Tomcat中对Servnet规范的默认实现
public class DefaultNettyResponse implements NettyResponse {
private HttpRequest request;
private ChannelHandlerContext context;
public DefaultNettyResponse ( HttpRequest request, ChannelHandlerContext context) {
this . request = request;
this . context = context;
}
@Override
public void write ( String content) throws Exception {
if ( StringUtil . isNullOrEmpty ( content) ) {
return ;
}
DefaultFullHttpResponse response = new DefaultFullHttpResponse ( HttpVersion . HTTP_1_1, HttpResponseStatus . OK,
Unpooled . wrappedBuffer ( content. getBytes ( CharsetUtil . UTF_8) ) ) ;
HttpHeaders headers = response. headers ( ) ;
headers. set ( HttpHeaderNames . CONTENT_TYPE, "text/json" ) ;
headers. set ( HttpHeaderNames . CONTENT_LENGTH, response. content ( ) . readableBytes ( ) ) ;
headers. set ( HttpHeaderNames . EXPIRES, 0 ) ;
if ( HttpUtil . isKeepAlive ( request) ) {
headers. set ( HttpHeaderNames . CONNECTION, HttpHeaderValues . KEEP_ALIVE) ;
}
context. writeAndFlush ( response) ;
}
}
定义 DefaultServnet 类:Tomcat中对Servnet规范的默认实现
public class DefaultServnet extends Servnet {
@Override
public void doGet ( NettyRequest request, NettyResponse response) throws Exception {
String sernetName = request. getUri ( ) . split ( "/" ) [ 1 ] ;
response. write ( "404 - no this servnet : " + sernetName) ;
}
@Override
public void doPost ( NettyRequest request, NettyResponse response) throws Exception {
doGet ( request, response) ;
}
}
public class TomcatStarter {
public static void main ( String [ ] args) throws Exception {
TomcatServer server = new TomcatServer ( "com.yw.netty.example.webapp" ) ;
server. start ( ) ;
}
}
定义业务Servnet
public class OneServnet extends Servnet {
@Override
public void doGet ( NettyRequest request, NettyResponse response) throws Exception {
String uri = request. getUri ( ) ;
String path = request. getPath ( ) ;
String method = request. getMethod ( ) ;
String name = request. getParameter ( "name" ) ;
String content = "uri = " + uri + "\n" +
"path = " + path + "\n" +
"method = " + method + "\n" +
"param = " + name;
response. write ( content) ;
}
@Override
public void doPost ( NettyRequest request, NettyResponse response) throws Exception {
doGet ( request, response) ;
}
}
启动 TomcatStarter,接口测试:http://localhost:8888/oneservnet/xxx/ooo?name=zs
手写RPC框架
RPC 简介
RPC(Remote Procedure Call,远程过程调用)是一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。在 OSI 网络通信模型中, RPC 跨越了传输层和应用层。RPC 使得开发分布式系统应用变得更加容易。 RPC 采 用 C /S 模式。请求程序就是 Client ,而服务提供程序就是 Server。首先,Client 发送一个带有请求参数的调用请求到 Server,然后等待响应 。在 Server 端,进程一直处于睡眠状态直到接收到 Client 的调用请求。当一个调用请求到达,Server 会根据请求参数进行计算,并将计算结果发送给Client,然后等待下一个调用请求。Client接收到响应信息,即获取到调用结果,然后根据情况继续发出下一次调用。
RPC 框架具体需求
定义一个 RPC 框架,这个框架提供给用户后,用户只需要按照使用步骤就可以完成 RPC 远程调用。使用步骤:
用户需要将业务通知到 Server 与 Client,因为业务接口是是服务名称。 用户只需将业务接口的实现类写入到 Server 端的指定包下,那么这个包下的实现类就会被 Server 发布。 Client 端只需根据业务接口名就可获取到 Server 端发布的服务提供者,然后就可以调用到远程 Server 端的实现类方法的执行。
定义工程
工程主要包括:05-rpc-custom-api、05-rpc-custom-server、05-rpc-custom-client,其中 05-rpc-custom-server、05-rpc-custom-client 都依赖 05-rpc-custom-api。
rpc接口
public interface SomeService {
String hello ( String name) ;
}
@Data
@Accessors ( chain = true )
public class Invocation implements Serializable {
private String className;
private String methodName;
private Class < ? > [ ] paramTypes;
private Object [ ] paramValues;
}
rpc服务端
public class RpcServer {
private RpcServer ( ) { }
private static final RpcServer INSTANCE = new RpcServer ( ) ;
public static RpcServer getInstance ( ) {
return INSTANCE;
}
private Map < String , Object > registerMap = new HashMap < > ( 32 ) ;
private List < String > classCache = new ArrayList < > ( ) ;
public RpcServer publish ( String basePackage) throws Exception {
cacheClassName ( basePackage) ;
doRegister ( ) ;
return this ;
}
private void cacheClassName ( String basePackage) {
URL resource = this . getClass ( ) . getClassLoader ( )
. getResource ( basePackage. replaceAll ( "\\." , "/" ) ) ;
if ( resource == null ) {
return ;
}
File dir = new File ( resource. getFile ( ) ) ;
for ( File file : dir. listFiles ( ) ) {
if ( file. isDirectory ( ) ) {
cacheClassName ( basePackage + "." + file. getName ( ) ) ;
} else if ( file. getName ( ) . endsWith ( ".class" ) ) {
String fileName = file. getName ( ) . replace ( ".class" , "" ) . trim ( ) ;
classCache. add ( basePackage + "." + fileName) ;
}
}
System . out. println ( classCache) ;
}
private void doRegister ( ) throws Exception {
if ( classCache. size ( ) == 0 ) {
return ;
}
for ( String className : classCache) {
Class < ? > clazz = Class . forName ( className) ;
registerMap. put ( clazz. getInterfaces ( ) [ 0 ] . getName ( ) , clazz. newInstance ( ) ) ;
}
System . out. println ( registerMap) ;
}
public void start ( ) throws InterruptedException {
NioEventLoopGroup parentGroup = new NioEventLoopGroup ( 1 ) ;
NioEventLoopGroup childGroup = new NioEventLoopGroup ( ) ;
try {
ServerBootstrap bootstrap = new ServerBootstrap ( ) ;
bootstrap. group ( parentGroup, childGroup)
. channel ( NioServerSocketChannel . class )
. option ( ChannelOption . SO_BACKLOG, 1024 )
. childOption ( ChannelOption . SO_KEEPALIVE, true )
. childHandler ( new ChannelInitializer < SocketChannel > ( ) {
@Override
protected void initChannel ( SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch. pipeline ( ) ;
pipeline. addLast ( new ObjectEncoder ( ) ) ;
pipeline. addLast ( new ObjectDecoder ( Integer . MAX_VALUE,
ClassResolvers . cacheDisabled ( null ) ) ) ;
pipeline. addLast ( new RpcServerHandler ( registerMap) ) ;
}
} ) ;
ChannelFuture future = bootstrap. bind ( 8888 ) . sync ( ) ;
System . out. println ( "==>> 服务端已启动,监听的端口为:8888 <<==" ) ;
future. channel ( ) . closeFuture ( ) . sync ( ) ;
} finally {
parentGroup. shutdownGracefully ( ) ;
childGroup. shutdownGracefully ( ) ;
}
}
}
public class SomeServiceImpl implements SomeService {
@Override
public String hello ( String name) {
return name + "欢迎你" ;
}
}
public class RpcServerHandler extends SimpleChannelInboundHandler < Invocation > {
private Map < String , Object > registerMap;
public RpcServerHandler ( Map < String , Object > registerMap) {
this . registerMap = registerMap;
}
@Override
protected void channelRead0 ( ChannelHandlerContext ctx, Invocation msg) throws Exception {
Object result = "没有该提供者,或没有该方法" ;
if ( registerMap. containsKey ( msg. getClassName ( ) ) ) {
Object provider = registerMap. get ( msg. getClassName ( ) ) ;
result = provider. getClass ( ) . getMethod ( msg. getMethodName ( ) , msg. getParamTypes ( ) )
. invoke ( provider, msg. getParamValues ( ) ) ;
}
ctx. writeAndFlush ( result) ;
ctx. close ( ) ;
}
@Override
public void exceptionCaught ( ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause. printStackTrace ( ) ;
ctx. close ( ) ;
}
}
public class RpcStarter {
public static void main ( String [ ] args) throws Exception {
RpcServer . getInstance ( ) . publish ( "com.yw.netty.example.service" )
. start ( ) ;
}
}
rpc客户端
public class RpcProxy {
private RpcProxy ( ) {
}
public static < T > T create ( Class < ? > clazz) {
return ( T ) Proxy . newProxyInstance ( clazz. getClassLoader ( ) , new Class [ ] { clazz} , new InvocationHandler ( ) {
@Override
public Object invoke ( Object proxy, Method method, Object [ ] args) throws Throwable {
if ( Object . class . equals ( method. getDeclaringClass ( ) ) ) {
return method. invoke ( this , args) ;
}
return rpcInvoke ( clazz, method, args) ;
}
} ) ;
}
private static Object rpcInvoke ( Class < ? > clazz, Method method, Object [ ] args) throws InterruptedException {
NioEventLoopGroup group = new NioEventLoopGroup ( ) ;
RpcClientHandler rpcHandler = new RpcClientHandler ( ) ;
try {
Bootstrap bootstrap = new Bootstrap ( ) ;
bootstrap. group ( group) . channel ( NioSocketChannel . class )
. option ( ChannelOption . TCP_NODELAY, true )
. handler ( new ChannelInitializer < SocketChannel > ( ) {
@Override
protected void initChannel ( SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch. pipeline ( ) ;
pipeline. addLast ( new ObjectEncoder ( ) ) ;
pipeline. addLast ( new ObjectDecoder ( Integer . MAX_VALUE, ClassResolvers . cacheDisabled ( null ) ) ) ;
pipeline. addLast ( rpcHandler) ;
}
} ) ;
ChannelFuture future = bootstrap. connect ( "localhost" , 8888 ) . sync ( ) ;
val invocation = new Invocation ( )
. setClassName ( clazz. getName ( ) )
. setMethodName ( method. getName ( ) )
. setParamTypes ( method. getParameterTypes ( ) )
. setParamValues ( args) ;
future. channel ( ) . writeAndFlush ( invocation) . sync ( ) ;
future. channel ( ) . closeFuture ( ) . sync ( ) ;
} finally {
group. shutdownGracefully ( ) ;
}
return rpcHandler. getResult ( ) ;
}
}
public class RpcClientHandler extends SimpleChannelInboundHandler < Object > {
private Object result;
public Object getResult ( ) {
return result;
}
@Override
protected void channelRead0 ( ChannelHandlerContext ctx, Object msg) throws Exception {
this . result = msg;
}
@Override
public void exceptionCaught ( ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause. printStackTrace ( ) ;
ctx. close ( ) ;
}
}
public class RpcConsumer {
public static void main ( String [ ] args) {
SomeService service = RpcProxy . create ( SomeService . class ) ;
System . out. println ( service. hello ( "Netty RPC" ) ) ;
System . out. println ( service. hashCode ( ) ) ;
}
}
自定义Dubbo框架V1
原理
Dubbo框架本身就是一个 RPC 框架,消费者要连接的服务端IP和端口号不是硬编码在客户端,而是从 zk 中读取到的。那么 zk 中的这些信息是从哪里来的呢? 一个 Dubbo 应用中会存在很对服务提供者与消费者。个提供者都是一个Netty Server,其会对外暴露自己所在主机的 IP 与 Port。每个消费者都是一个Netty Client,其会通过连接相应主机的 IP 和 Port 来获取相应的服务。 服务提供者的IP和Port是如何对外暴露的呢?其会未自己所提供的服务起一个服务名称,一般为业务接口。然后将该服务名称与对应提供者主机的 IP 和 Port 相绑定,注册到zk中。 具体注册步骤:
① 在 ZK 中创建一个 dubbo 的持久根节点,例如 /mydubbo。 ② 以服务名称作为节点名称在 /mydubbo 节点下创建一个持久节点,例如 /mydubbo/com.abc.service.SomeService。 ③ 在服务名称下再创建临时节点,节点名称为当前提供者的主机 ip 与 port,完成注册。由于一个服务的提供者可能有多个,所以这样就可以监控到各个提供者主机的在线情况。例如 /mydubbo/com.abc.service.SomeService/192.168.254.1:8888。 服务消费者会从服务注册中心zk中查找自己所需要的服务名称,一般为业务接口名。然后获取到该服务名称对应的所有提供者主机信息,并通过负载均衡方式选取一个主机进行连接,获取相应服务。 具体消费过程:
① 从 ZK 的 /mydubbo 节点下找到指定服务名称的子节点。 ② 为该服务节点添加 watcher 监听,以监听其子节点列表的变更情况。 ③ 获取该服务名称节点的所有子节点,即该服务的所有提供者主机 IP 和 Port。 ④ 通过负载均衡选择一个提供者主机。 ⑤ 消费者将其调用信息发送给提供者主机。 ⑥ 提供者主机将其执行结果返回给消费者。
新增需求
当客户端通过负载均衡策略选择了某一提供者主机后,我们这里新增了一个需求:提供者主机中提供同一服务名称(接口名)的实现类有多个。这样,消费者可以指定其要调用的实现类。若消费者没有指定要调用的实现类,其会调用到第一个注册的实现类。 为了实现提供者端业务接口可以有多个实现类供客户端选择,这里要求实现类名必须是一个前辍 prefix 后是业务接口名 。这样,消费者在进行消费时,可以通过前辍来指定要调用的是哪个实现类。
定义工程
复制 05-rpc-custom 重命名为 06-dubbo-custom,并在此基础上修改。
dubbo api 接口
public interface PayService {
String pay ( String info) ;
}
public class ZkConstant {
public static final String ZK_CLUSTER = "192.168.254.120:2181" ;
public static final String ZK_DUBBO_ROOT_PATH = "/mydubbo" ;
}
dubbo server 服务端
public interface RegistryCenter {
void register ( String serviceName, String serviceAddress) throws Exception ;
}
public class ZkRegistryCenter implements RegistryCenter {
private volatile CuratorFramework client;
private ZkRegistryCenter ( ) {
if ( null == client) {
synchronized ( this ) {
if ( null == client) {
client = CuratorFrameworkFactory . builder ( )
. connectString ( ZkConstant . ZK_CLUSTER)
. connectionTimeoutMs ( 10000 )
. sessionTimeoutMs ( 4000 )
. retryPolicy ( new ExponentialBackoffRetry ( 1000 , 10 ) )
. build ( ) ;
client. start ( ) ;
}
}
}
}
public static ZkRegistryCenter getInstance ( ) {
return new ZkRegistryCenter ( ) ;
}
@Override
public void register ( String serviceName, String serviceAddress) throws Exception {
String servicePath = ZkConstant . ZK_DUBBO_ROOT_PATH + "/" + serviceName;
if ( client. checkExists ( ) . forPath ( servicePath) == null ) {
client. create ( )
. creatingParentsIfNeeded ( )
. withMode ( CreateMode . PERSISTENT)
. forPath ( servicePath) ;
}
String hostPath = servicePath + "/" + serviceAddress;
if ( client. checkExists ( ) . forPath ( hostPath) == null ) {
client. create ( )
. withMode ( CreateMode . EPHEMERAL)
. forPath ( hostPath) ;
}
}
}
public class AlipayPayService implements PayService {
@Override
public String pay ( String info) {
return "使用支付宝支付: " + info;
}
}
public class WechatPayService implements PayService {
@Override
public String pay ( String info) {
return "使用微信支付: " + info;
}
}
修改服务器类:将当前Server以提供者身份注册到zk;启动 Netty Server 时,将 serviceAddress 解析为 IP 和 Port。
dubbo client 客户端
public interface ServiceDiscovery {
String discovery ( String serviceName) throws Exception ;
}
public class ZkServiceDiscovery implements ServiceDiscovery {
private volatile CuratorFramework client;
private List < String > providers;
private ZkServiceDiscovery ( ) {
if ( null == client) {
synchronized ( this ) {
if ( null == client) {
client = CuratorFrameworkFactory . builder ( )
. connectString ( ZkConstant . ZK_CLUSTER)
. connectionTimeoutMs ( 10000 )
. sessionTimeoutMs ( 4000 )
. retryPolicy ( new ExponentialBackoffRetry ( 1000 , 10 ) )
. build ( ) ;
client. start ( ) ;
}
}
}
}
public static ZkServiceDiscovery getInstance ( ) {
return new ZkServiceDiscovery ( ) ;
}
@Override
public String discovery ( String serviceName) throws Exception {
String servicePath = ZkConstant . ZK_DUBBO_ROOT_PATH + "/" + serviceName;
providers = client. getChildren ( )
. usingWatcher ( ( CuratorWatcher ) evt -> providers = client. getChildren ( ) . forPath ( servicePath) )
. forPath ( servicePath) ;
if ( providers. size ( ) == 0 ) {
return null ;
}
return RandomLoadBalance . getInstance ( ) . choose ( providers) ;
}
}
public interface LoadBalance {
String choose ( List < String > providers) ;
}
public class RandomLoadBalance implements LoadBalance {
private RandomLoadBalance ( ) { }
private static final RandomLoadBalance INSTANCE = new RandomLoadBalance ( ) ;
public static RandomLoadBalance getInstance ( ) {
return INSTANCE;
}
@Override
public String choose ( List < String > providers) {
return providers. get ( new Random ( ) . nextInt ( providers. size ( ) ) ) ;
}
}
测试
自定义Dubbo框架V2
在自定义Dubbo框架V1版本基础上进行修改,引入 spring-boot 父工程依赖和 spring-boot-starter
< parent>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-parent</ artifactId>
< version> 2.1.7.RELEASE</ version>
< relativePath/>
</ parent>
< dependencies>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter</ artifactId>
</ dependency>
</ dependencies>
修改服务提供者端
修改服务提供者端:将注册中心实现类、RpcServer、接口实现类、启动类等交给Spring容器管理
@Component
public class ZkRegistryCenter implements RegistryCenter {
private volatile CuratorFramework client;
@PostConstruct
public void init ( ) {
}
@Override
public void register ( String serviceName, String serviceAddress) throws Exception {
}
}
@Component
public class RpcServer {
@Autowired
private ZkRegistryCenter registerCenter;
private void doRegister ( String serviceAddress) throws Exception {
registerCenter. register ( interfaces. getName ( ) , serviceAddress) ;
}
}
@SpringBootApplication
public class RpcProvider implements CommandLineRunner {
@Autowired
private RpcServer rpcServer;
public static void main ( String [ ] args) {
SpringApplication . run ( RpcProvider . class , args) ;
}
@Override
public void run ( String . . . args) throws Exception {
String basePackage = "com.yw.netty.example.service" ;
rpcServer. publish ( basePackage, "192.168.254.1:9999" ) ;
}
}
修改消费者端
将服务发现类、RpcProxy接口实现类、LoadBalance实现类、启动类等交给Spring容器管理
@Component
public class ZkServiceDiscovery implements ServiceDiscovery {
@Autowired
private LoadBalance loadBalance;
private volatile CuratorFramework client;
private List < String > providers;
@PostConstruct
public void init ( ) {
}
@Override
public String discovery ( String serviceName) throws Exception {
}
}
@Component
public class RpcProxy {
@Autowired
private ServiceDiscovery serviceDiscovery;
}
@SpringBootApplication
public class RpcConsumer implements CommandLineRunner {
@Autowired
private RpcProxy rpcProxy;
public static void main ( String [ ] args) {
SpringApplication . run ( RpcConsumer . class , args) ;
}
@Override
public void run ( String . . . args) {
PayService service = rpcProxy. create ( PayService . class , "WeChat" ) ;
for ( int i = 0 ; i < 10 ; i++ ) {
System . out. println ( service. pay ( "9.9元" ) ) ;
}
}
}