JavaEE 企业级分布式高级架构师(十九)异步事件驱动框架Netty(2)

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 {
    /**
     * 获取URI,包含请求参数,即?后的内容
     */
    String getUri();
    /**
     * 获取请求路径,其不包含请求参数
     */
    String getPath();
    /**
     * 获取请求分发(GET、POST等)
     */
    String getMethod();
    /**
     * 获取所有请求参数
     */
    Map<String, List<String>> getParameters();
    /**
     * 获取指定名称的请求参数
     */
    List<String> getParameters(String name);
    /**
     * 获取指定名称的请求参数的第一个值
     */
    String getParameter(String name);
}
  • 定义响应接口 NettyResponse:ServNet规范之响应规范
public interface NettyResponse {
    /**
     * 将响应写入到Channel
     */
    void write(String content) throws Exception;
}
  • 定义 Servnet 规范:
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 {
        // 处理content为空的情况
        if (StringUtil.isNullOrEmpty(content)) {
            return;
        }
        // 创建响应对象
        DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
                // 根据响应体内容大小为response对象分配存储空间
                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);
        // 若HTTP请求是长连接,则响应也使用长连接
        if (HttpUtil.isKeepAlive(request)) {
            headers.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        }
        // 将响应写入到Channel
        context.writeAndFlush(response);
    }
}
  • 定义 DefaultServnet 类:Tomcat中对Servnet规范的默认实现
public class DefaultServnet extends Servnet {
    @Override
    public void doGet(NettyRequest request, NettyResponse response) throws Exception {
        // http://localhost:8888/someservnet/xxx/ooo?name=zs
        // uri:/someservnet/xxx/ooo?name=zs
        // path:/someservnet/xxx/ooo
        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);
    }
}
  • 定义服务器类:

在这里插入图片描述

在这里插入图片描述

  • 定义服务器端处理器:

在这里插入图片描述

  • 定义启动类 TomcatStarter:
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服务端
  • rpc服务端server:
public class RpcServer {
    private RpcServer() {}
    private static final RpcServer INSTANCE = new RpcServer();

    public static RpcServer getInstance() {
        return INSTANCE;
    }

    /**
     * 服务提供者注册表,key:微服务名称,即业务接口;value:业务接口实例
     */
    private Map<String, Object> registerMap = new HashMap<>(32);
    /**
     * 用于缓存服务提供者的类名
     */
    private List<String> classCache = new ArrayList<>();

    /**
     * 发布服务
     */
    public RpcServer publish(String basePackage) throws Exception {
        // 将指定包下的业务接口实现类名写入到classCache中
        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()) {
                // 若当前file为目录,则递归
                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)
                    // 用于指定当Server的连接请求处理线程全被占用时,临时存放已经完成了三次握手的请求的队列的长度。
                    // 默认是50
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    // 指定使用心跳机制来保证TCP长连接的存活性
                    .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,
                                    // null: 使用默认的类加载器
                                    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();
        }
    }
}
  • rpc服务端服务实现类:
public class SomeServiceImpl implements SomeService {
    @Override
    public String hello(String name) {
        return name + "欢迎你";
    }
}
  • rpc服务端处理器:
public class RpcServerHandler extends SimpleChannelInboundHandler<Invocation> {
    private Map<String, Object> registerMap;

    public RpcServerHandler(Map<String, Object> registerMap) {
        this.registerMap = registerMap;
    }

    /**
     * 解析Client发送来的msg,然后从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();
    }
}
  • rpc服务端启动类:
public class RpcStarter {
    public static void main(String[] args) throws Exception {
        RpcServer.getInstance().publish("com.yw.netty.example.service")
                .start();
    }
}
rpc客户端
  • 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 {
                // 若调用的是Object的方法,则直接进行本地调用
                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)
                    // Nagle算法开关
                    .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);
            // 将参数实例发送给Server
            future.channel().writeAndFlush(invocation).sync();

            future.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
        return rpcHandler.getResult();
    }
}
  • rpc客户端Netty处理器:
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 {
    /**
     * zk集群地址
     */
    public static final String ZK_CLUSTER = "192.168.254.120:2181";
    /**
     * dubbo在zk中的根节点路径
     */
    public static final String ZK_DUBBO_ROOT_PATH = "/mydubbo";
}
  • 修改数据对象Invocation:

在这里插入图片描述

dubbo server 服务端
  • 定义注册中心接口:
public interface RegistryCenter {
    /**
     * 注册到注册中心
     *
     * @param serviceName    服务名,一般为接口名称
     * @param serviceAddress 服务提供者的ip:port
     */
    void register(String serviceName, String serviceAddress) throws Exception;
}
  • 注册中心实现类:
/**
 * zk 注册中心
 */
public class ZkRegistryCenter implements RegistryCenter {
    private volatile CuratorFramework client;

    private ZkRegistryCenter() {
        // 使用双重检查锁初始化zk client
        if (null == client) {
            synchronized (this) {
                if (null == client) {
                    // 创建并初始化zk客户端
                    client = CuratorFrameworkFactory.builder()
                            // 指定要连接的zk集群地址
                            .connectString(ZkConstant.ZK_CLUSTER)
                            // 指定连接超时
                            .connectionTimeoutMs(10000)
                            // 指定会话超时
                            .sessionTimeoutMs(4000)
                            // 指定重试策略,每重试一次,sleep 1 秒,最多重试10次
                            .retryPolicy(new ExponentialBackoffRetry(1000, 10))
                            .build();
                    // 启动zk客户端
                    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 {
    /**
     * @param serviceName 服务名称,即接口名
     * @return 返回经过负载均衡后的server
     */
    String discovery(String serviceName) throws Exception;
}
/**
 * zookeeeper做服务发现
 */
public class ZkServiceDiscovery implements ServiceDiscovery {
    private volatile CuratorFramework client;
    private List<String> providers;

    private ZkServiceDiscovery() {
        if (null == client) {
            synchronized (this) {
                if (null == client) {
                    // 创建并初始化zk客户端
                    client = CuratorFrameworkFactory.builder()
                            // 指定要连接的zk集群地址
                            .connectString(ZkConstant.ZK_CLUSTER)
                            // 指定连接超时
                            .connectionTimeoutMs(10000)
                            // 指定会话超时
                            .sessionTimeoutMs(4000)
                            // 指定重试策略:每重试一次,sleep 1秒,最多重试10次
                            .retryPolicy(new ExponentialBackoffRetry(1000, 10))
                            .build();
                    // 启动zk客户端
                    client.start();
                }
            }
        }
    }

    public static ZkServiceDiscovery getInstance() {
        return new ZkServiceDiscovery();
    }

    @Override
    public String discovery(String serviceName) throws Exception {
        // 要获取的服务在zk中的路径
        String servicePath = ZkConstant.ZK_DUBBO_ROOT_PATH + "/" + serviceName;
        // 获取到指定节点的所有子节点列表,并为该节点设置子节点列表更改的watcher监听
        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 {
    /**
     * 从providers中选择一个
     */
    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()));
    }
}
  • 修改RpcProxy:

在这里插入图片描述

测试
  • 启动多个提供者:

在这里插入图片描述

  • 启动消费者完成对提供者的调用:

在这里插入图片描述

自定义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/> <!-- lookup parent from repository -->
</parent>

<dependencies>
  <!--starter 依赖-->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
  </dependency>
</dependencies>
修改服务提供者端
  • 修改服务提供者端:将注册中心实现类、RpcServer、接口实现类、启动类等交给Spring容器管理
/**
 * zk 注册中心
 */
@Component
public class ZkRegistryCenter implements RegistryCenter {
    private volatile CuratorFramework client;

    @PostConstruct
    public void init() {
        // ...略:创建并初始化zk客户端,启动zk客户端
    }

    @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 {
        // ...略
        // 将当前Server以提供者身份注册到zk
        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() {
        // ...略:创建并初始化zk客户端、启动zk客户端
    }

    @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元"));
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值