分布式rpc设计

分布式RPC设计

一、生产者消费者通信方式设计

在这里插入图片描述

【1】服务端

TcpServer

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.util.Map;

/**
 * server 服务端
 * @author : 18073771
 * @see [相关类/方法](可选)
 * @since [产品/模块版本] (可选)
 */
public class TcpServer {
    
    

    // boss线程
    private static EventLoopGroup bossGroup;

    // IO worker线程
    private static EventLoopGroup ioWorkerGroup;

    public static void main(String[] args) {
    
    
        run(null);
    }

    public static void run(Map<String, Object> serviceImplMap) {
    
    
        int serverPort = 8888;
        try {
    
    
            bossGroup = new NioEventLoopGroup(1);
            ioWorkerGroup = new NioEventLoopGroup();
            final ServerBootstrap serverBootstrap = new ServerBootstrap();

            serverBootstrap.group(bossGroup, ioWorkerGroup).channel(NioServerSocketChannel.class)
                    .childHandler(new TcpServerInitializer(serviceImplMap));

            System.out.println(String.format("Netty server started at [%s]", serverPort));
            // 绑定端口号
            ChannelFuture future = serverBootstrap.bind(serverPort).sync();
//            future.channel().closeFuture().await();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
            bossGroup.shutdownGracefully();
            ioWorkerGroup.shutdownGracefully();
        }
    }
}

TcpServerInitializer

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;

import java.util.Map;

/**
 * @author : 18073771
 * @see [相关类/方法](可选)
 * @since [产品/模块版本] (可选)
 */
public class TcpServerInitializer extends ChannelInitializer<SocketChannel> {
    
    

    // 服务实现的map,key服务契约,value对应的服务实现
    private Map<String, Object> serviceImplMap;

    public TcpServerInitializer(Map<String, Object> serviceImplMap) {
    
    
        this.serviceImplMap = serviceImplMap;
    }

    @Override
    protected void initChannel(SocketChannel ch) {
    
    
        System.out.println("channel initialized..........");
        ChannelPipeline pipeline = ch.pipeline();
        // 对象编码解码器
        pipeline.addLast("decoder", new ObjectDecoder(1024 * 1024,
                ClassResolvers.weakCachingConcurrentResolver(this.getClass().getClassLoader())));
        pipeline.addLast("encoder", new ObjectEncoder());
        pipeline.addLast(new RpcMessageHandler(serviceImplMap));
    }
}

RpcMessageHandler

import java.lang.reflect.InvocationTargetException;
import java.util.Map;

import com.handersome.myrpc.util.ClassUtils;
import com.handersome.myrpc.util.MethodUtils;
import com.handersome.myrpc.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.handersome.myrpc.dto.RequestDto;
import com.handersome.myrpc.dto.ResultDto;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;

/**
 * 消息处理器
 * @author : 18073771
 * @see [相关类/方法](可选)
 * @since [产品/模块版本] (可选)
 */
public class RpcMessageHandler extends SimpleChannelInboundHandler<RequestDto> {
    
    

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

    // key 服务名称+实现码,value:服务实现的bean
    private Map<String, Object> serviceImplMap;

    // 服务map
    public RpcMessageHandler(Map<String, Object> serviceImplMap) {
    
    
        this.serviceImplMap = serviceImplMap;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
    
    
        System.out.println("Connection with client has established,channel is " + ctx.channel());
    }

    /**
     *
     * 功能描述: 读取数据并处理
     *
     * @param ctx
     * @param message
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    @Override
    public void channelRead0(ChannelHandlerContext ctx, RequestDto message) {
    
    
        System.out.println("read message : " + message.toString());
        ResultDto result = invoke(message);
        System.out.println("return message : " + result);
        // 将结果返回给客户端
        ctx.channel().writeAndFlush(result);
    }

    /**
     *
     * 功能描述: 远程方法调用执行
     *
     * @param invocation
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public ResultDto invoke(RequestDto requestDto) {
    
    
        ResultDto result;
        try {
    
    
            String contract = requestDto.getContract();
            String implCode = requestDto.getImplCode();
            // 拿到执行改服务的实现对象
            Object serviceImpl = serviceImplMap.get(getKey(contract, implCode));
            if (serviceImpl == null) {
    
    
                throw new RuntimeException(
                        "Can not find serviceImpl, contract is " + contract + ",implCode is " + implCode);
            }
            // 不允许业务方法抛出RSFException可重试异常,否则过于复杂,业务如果希望重试,应封装自己的业务重试异常。
            Object value = invoke(requestDto, serviceImpl);
            result = new ResultDto();
            result.setResponseValue(value);
            return result;
        } catch (Exception ex) {
    
    
            logger.error("Invocation occur ServiceException:", ex);
            result = new ResultDto(ex);
            return result;
        } catch (Throwable throwable) {
    
    
            logger.error("Invocation occur ServiceException:", throwable.getMessage());
            result = new ResultDto(throwable.getMessage());
            return result;
        }
    }

    /**
     *
     * 功能描述: 通过反射执行方法
     *
     * @param invocation
     * @param targetObject
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    private Object invoke(RequestDto requestDto, Object targetObject) throws Throwable {
    
    
        if (logger.isDebugEnabled()) {
    
    
            logger.debug("Executing " + requestDto);
        }
        try {
    
    
            return MethodUtils.invokeMethod(targetObject, requestDto.getMethodName(), requestDto.getArguments(),
                    makeParameterTypes(requestDto.getParameterTypes()));
        } catch (NoSuchMethodException ex) {
    
    
            logger.warn("Could not find target method for " + requestDto, ex);
            throw ex;
        } catch (IllegalAccessException ex) {
    
    
            logger.warn("Could not access target method for " + requestDto, ex);
            throw ex;
        } catch (InvocationTargetException ex) {
    
    
            logger.warn("Target method failed for " + requestDto, ex.getTargetException());
            throw new RuntimeException(ex.getMessage(), ex.getTargetException());
        }
    }

    /**
     *
     * 功能描述: 参数类型转换
     *
     * @param parameterTypes
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    private Class<?>[] makeParameterTypes(String[] parameterTypes) {
    
    
        Class<?>[] parameterTypeClasses = new Class[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; i++) {
    
    
            try {
    
    
                parameterTypeClasses[i] = ClassUtils.forName(parameterTypes[i]);
            } catch (Throwable e) {
    
    
                throw new RuntimeException("Class is not found for parameter type: " + parameterTypeClasses[i], e);
            }
        }
        return parameterTypeClasses;
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
    
    
        logger.error("Unexpected exception from downstream,channel" + ctx.channel() + " will be closed.", cause);
        // 当有异常时,关闭channel
        cause.printStackTrace();
        ctx.close();
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    
    
        logger.info("Channel " + ctx.channel() + " inactive.");
        super.channelInactive(ctx);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
    
    
        // 监听idle事件
        if (IdleStateEvent.class.isAssignableFrom(evt.getClass())) {
    
    
            IdleStateEvent event = (IdleStateEvent) evt;
            // 根据read idle判断channel是否失效
            if (event.state() == IdleState.READER_IDLE) {
    
    
                logger.info("Channel " + ctx.channel() + " has timeout because idle,will be closed.");
                // 如果检测到闲,则关闭channel
                ctx.close();
            }
        }
    }

    private String getKey(String contract, String implCode) {
    
    
        return contract + ((StringUtils.isEmpty(implCode)) ? "default" : "/" + implCode);
    }
}

【2】客户端

TcpClientInvoker

/**
 * @author : 18073771
 * @see [相关类/方法](可选)
 * @since [产品/模块版本] (可选)
 */
public class TcpClientInvoker {
    
    

    private static EventLoopGroup eventLoopGroup = new NioEventLoopGroup(8);

    public static Object invoke(final RequestDto requestDto) {
    
    
        Bootstrap bootstrap = new Bootstrap();
        final ResultDto resultDto = new ResultDto();
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        lock.lock();
        try {
    
    
            bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
                    .handler(new TcpClientInitializer(resultDto, condition, lock));

            ChannelFuture cf = bootstrap.connect("127.0.0.1", 8888).sync();

            System.out.println("invoke " + requestDto.toString());

            cf.channel().writeAndFlush(requestDto);

            // 如果没有拿到数据等在这里
            while (resultDto.getResponseValue() == null) {
    
    
                condition.await();
            }
        } catch (Exception e) {
    
    
            eventLoopGroup.shutdownGracefully();
        } finally {
    
    
            lock.unlock();
        }

        System.out.println("get returned result :" + resultDto.toString());
        return resultDto.getResponseValue();
    }
}

TcpClientInitializer

import com.handersome.myrpc.dto.ResultDto;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @author : 18073771
 * @see [相关类/方法](可选)
 * @since [产品/模块版本] (可选)
 */
public class TcpClientInitializer extends ChannelInitializer<SocketChannel> {
    
    

    // rpc 调用返回的结果
    private ResultDto resultDto;

    // 条件锁,用于控制请求返回等待
    private Condition condition;

    private Lock lock;

    public TcpClientInitializer(ResultDto resultDto, Condition condition, Lock lock) {
    
    
        this.resultDto = resultDto;
        this.condition = condition;
        this.lock = lock;
    }

    @Override
    protected void initChannel(SocketChannel ch) {
    
    
        System.out.println("channel init.......");
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast("decoder", new ObjectDecoder(1024 * 1024,
                ClassResolvers.weakCachingConcurrentResolver(this.getClass().getClassLoader())));
        pipeline.addLast("encoder", new ObjectEncoder());
        pipeline.addLast(new RpcMessageClientHandler(resultDto, condition, lock));
    }
}

RpcMessageClientHandler

import com.handersome.myrpc.dto.ResultDto;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @author : 18073771
 * @see [相关类/方法](可选)
 * @since [产品/模块版本] (可选)
 */
public class RpcMessageClientHandler extends SimpleChannelInboundHandler<ResultDto> {
    
    

    private ResultDto resultDto;

    private Condition condition;

    private Lock lock;

    public RpcMessageClientHandler(ResultDto resultDto, Condition condition, Lock lock) {
    
    
        this.resultDto = resultDto;
        this.condition = condition;
        this.lock = lock;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    
        System.out.println(" channel " + ctx.channel() + " has connected.");
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ResultDto message) {
    
    
        System.out.println("receive data" + message.toString());
        System.out.println(message.getResponseValue().toString());
        lock.lock();
        try {
    
    
            condition.signal();
        } finally {
    
    
            lock.unlock();
        }
        this.resultDto.setResponseValue(message.getResponseValue());
    }

}

【3】netty 编解码器优化

【4】服务端端口号被占用优化

/**
 * server 服务端
 * 
 * @author : 18073771
 * @see [相关类/方法](可选)
 * @since [产品/模块版本] (可选)
 */
public class TcpServer {
    
    

    public static void run(Map<String, Object> serviceImplMap) {
    
    
        // 默认端口号 8888 
        int serverPort = 8888;
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup ioWorkerGroup = new NioEventLoopGroup();
        final ServerBootstrap serverBootstrap = new ServerBootstrap();

        serverBootstrap.group(bossGroup, ioWorkerGroup).channel(NioServerSocketChannel.class)
                .childHandler(new TcpServerInitializer(serviceImplMap));

        System.out.println(String.format("Netty server started at [%s]", serverPort));
        // 绑定端口号
        while (true) {
    
    
            try {
    
    
                serverBootstrap.bind(serverPort).sync();
                break;
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            } catch (Exception ex) {
    
    
                if (ex instanceof BindException) {
    
    
                    // 端口被占用,用于一台服务器启动一个应用的多个JVM的场景
                    System.out.println("NettyServer init fail,will try other port.");
                    serverPort++;
                }
                bossGroup.shutdownGracefully();
                ioWorkerGroup.shutdownGracefully();
            }
        }
    }
}

【5】异步获取返回

方式一:通过wait和notify

方式二:通过condition条件锁

二、服务注册和发现

多个生产者都可以提供服务,消费者本地缓存一份服务的生产者列表,从其中选取一个生产者进行调用。

生产者启动时将其ip+服务注册到zk中,系统名称/服务名称/ip/联通性

扫描二维码关注公众号,回复: 11709836 查看本文章

如果生产者宕机,将生成者从服务的ip列表中移除,如果节点下面发现超过半数的消费者联不通则暂时下线;

消费者本地保存生产者和服务的一份副本Map<服务名称,LIst<生产者列表>>

【1】整合spring

将特定的rpc配置文件中的springbean加载到服务契约实现的容器中

(1)使spring读取指定的文件,并解析

在resources目录下创建META-INF文件加,新建spring.handlers;spring.shcemas;myrpc.xsd文件;

spring会自动扫描META-INF 文件夹下的spring.handlers;spring.shcemas文件来加载自定义的配置文件;

1、spring.handlers: 指定域名空间处理器

http\://www.handersome.com/schema/myrpc=com.handersome.myrpc.spring.MyRpcNameSpaceHandler

2、spring.schemas:指定自定义元素配置文件的路径

http\://www.handersome.com/schema/myrpc/myrpc.xsd=META-INF/myrpc.xsd

3、myrpc.xsd:自定义配置文件的内容

<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns="http://www.handersome.com/schema/myrpc"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://www.handersome.com/schema/myrpc"
            elementFormDefault="qualified" attributeFormDefault="unqualified">

    <xsd:import namespace="http://www.springframework.org/schema/tool"/>
    <xsd:element name="servicesPublish"/>
    <xsd:element name="servicesReference"/>

</xsd:schema>

(2)xml配置文件

spring在解析此配置文件的时候会已指定的方式来解析,然后可以自定义bean的加载方式

spring-myrpc.xml;

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:a="http://www.handersome.com/schema/myrpc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
          http://www.handersome.com/schema/myrpc
          http://www.handersome.com/schema/myrpc/myrpc.xsd">

    <a:servicesPublish/>

    <bean id="testReqServiceImpl"
          class="com.handersome.contract.impl.TestReqServiceImpl" />

    <bean id="productServiceImpl"
          class="com.handersome.contract.impl.ProductServiceImpl" />

</beans>

(3)命名空间解析处理器

MyRpcNameSpaceHandler

/**
 * 解析自定的xml:myrpc.xsd 1\通过spring.handlers和spring.schemas两个配置文件找到对应的域名类
 * 
 * @author 18073773
 * @see [相关类/方法](可选)
 * @since [产品/模块版本] (可选)
 */
public class MyRpcNameSpaceHandler extends NamespaceHandlerSupport {
    
    

    @Override
    public void init() {
    
    
        registerBeanDefinitionParser("servicesPublish", new ServicesPublishBeanDefinitionParser());
    }
}

(4)bean定义解析器

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;

/**
 * @author : 18073771
 * @see [相关类/方法](可选)
 * @since [产品/模块版本] (可选)
 */
public class ServicesPublishBeanDefinitionParser implements BeanDefinitionParser {
    
    

    private static final String BEAN_NAME = "rsf.servicesPublish";

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
    
    
        if (!parserContext.getRegistry().containsBeanDefinition(BEAN_NAME)) {
    
    
            RootBeanDefinition def = new RootBeanDefinition();
            def.setBeanClassName(BeanContainerServicesPublisher.class.getName());
            parserContext.registerBeanComponent(new BeanComponentDefinition(def, BEAN_NAME));
        }
        return null;
    }
}

(5)服务发布

针对添加了指定Implement注解的springbean进行服务注册和暴露

BeanContainerServicesPublisher

import java.util.HashMap;
import java.util.Map;
import com.handersome.myrpc.provider.TcpServer;
import com.handersome.myrpc.provider.ServicePublisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.support.AbstractApplicationContext;

/**
 * 服务注册发布
 * @author 18073773
 * @see [相关类/方法](可选)
 * @since [产品/模块版本] (可选)
 */
public class BeanContainerServicesPublisher implements ApplicationListener<ContextRefreshedEvent>, DisposableBean {
    
    

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

    @Override
    public void onApplicationEvent(final ContextRefreshedEvent event) {
    
    
        // 获取当前上下文下所有的spring bean
        Map<String, Object> allBeans = getAllBeans(event.getApplicationContext());
        Map<String, Object> allServiceImplMap = new HashMap<>();
        for (Map.Entry<String, Object> entry : allBeans.entrySet()) {
    
    
            String beanName = entry.getKey();
            Object bean = entry.getValue();
            try {
    
    
                Class targetClass = AopUtils.getTargetClass(bean);
                if (targetClass == null) {
    
    
                    logger.warn("Can not get targetClass of bean " + beanName);
                    continue;
                }

                // 服务发布,通过获取类的注解,判断该注解是否为自定义的implement注解来判断是否为服务实现注解
                Map<String, Object> serviceImplMap = ServicePublisher.publish(bean, targetClass);
                if (null != serviceImplMap) {
    
    
                    allServiceImplMap.putAll(serviceImplMap);
                }
            } catch (Exception e) {
    
    
                logger.error("can not publish service " + beanName);
                throw new RuntimeException(e);
            }
        }

        // 服务端启动
        if (allServiceImplMap.size() > 0) {
    
    
            TcpServer.run(allServiceImplMap);
        }
    }

    private static Map<String, Object> getAllBeans(ApplicationContext ctx) {
    
    
        Map<String, Object> beans = new HashMap<>();
        String[] all = ctx.getBeanDefinitionNames();
        BeanFactory factory = ((AbstractApplicationContext) ctx).getBeanFactory();
        for (String name : all) {
    
    
            try {
    
    
                Object s = factory.getBean(name);
                if (s != null)
                    beans.put(name, s);
            } catch (BeansException e) {
    
    
                // ignore
            }
        }
        return beans;
    }

    @Override
    public void destroy() {
    
    

    }
}

【2】 服务发布

将服务契约,和服务实现关系的map缓存再jvm内存中

ServicePublisher

import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;

import com.handersome.myrpc.util.StringUtils;

/**
 * 服务注册发布
 * 
 * @author : 18073771
 * @see [相关类/方法](可选)
 * @since [产品/模块版本] (可选)
 */
public class ServicePublisher {
    
    

    /**
     *
     * 功能描述:
     *
     * @param serviceImpl
     * @param serviceImplClass
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public static Map<String, Object> publish(Object serviceImpl, Class serviceImplClass) throws Exception {
    
    
        Annotation[] annotations = serviceImplClass.getDeclaredAnnotations();
        if (annotations == null || annotations.length == 0) {
    
    
            return null;
        }

        Map<String, Object> serviceMap = new HashMap<>();
        for (Annotation annotation : annotations) {
    
    
            if (annotation instanceof Implement) {
    
    
                publishService(serviceImpl, (Implement) annotation, serviceMap);
            }
        }

        return serviceMap;
    }

    private static void publishService(final Object serviceImpl, Implement implement, Map<String, Object> serviceMap) {
    
    
        Class<?> contractClass = implement.contract();
        String contract = contractClass.getName();
        String implCode = implement.implCode();
        // 服务发布
        serviceMap.put(getKey(contract, implCode), serviceImpl);
    }

    private static String getKey(String contract, String implCode) {
    
    
        return contract + ((StringUtils.isEmpty(implCode)) ? "default" : "/" + implCode);
    }
}

【3】服务注册

一个服务可能会有多个服务的生产者,可能有多台应用能够提供这个服务,因此需要一个注册中心来管理这些服务的生产者关系。注册完后消费者可以通过契约找到服务提供方,根据一定的负载均衡策略选择对应的生产者。

在这里插入图片描述

import com.alibaba.fastjson.JSON;
import com.handersome.myrpc.zookeeper.ZookeeperClient;
import java.util.ArrayList;
import java.util.List;

/**
 * 服务注册
 * @author : 18073771
 * @see [相关类/方法](可选)
 * @since [产品/模块版本] (可选)
 */
public class ServiceRegister {
    
    

    /**
     *
     * 功能描述: 注册
     *
     * @param contracts
     * @param hostInfo
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public static void register(List<String> contracts, HostInfo hostInfo) {
    
    
        try {
    
    
            for (String path : contracts) {
    
    
                if (ZookeeperClient.isPathExists("/" + path)) {
    
    
                    byte[] znodeData = ZookeeperClient.getZnodeData(path);
                    String str = new String(znodeData);
                    List<HostInfo> hostInfoList = JSON.parseArray(str, HostInfo.class);
                    List<HostInfo> newHostInfoLIst = updateHostInfo(hostInfoList, hostInfo);
                    ZookeeperClient.setValue("/" + path, JSON.toJSONString(newHostInfoLIst));
                } else {
    
    
                    List<HostInfo> hostInfoList = new ArrayList<>();
                    hostInfoList.add(hostInfo);
                    ZookeeperClient.createZnode("/" + path, JSON.toJSONString(hostInfoList));
                }
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    /**
     *
     * 功能描述: 跟新服务提供方主机信息
     *
     * @param hostInfoList
     * @param hostInfo
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    private static List<HostInfo> updateHostInfo(List<HostInfo> hostInfoList, HostInfo hostInfo) {
    
    
        List<HostInfo> newHostInfoList = new ArrayList<>();
        boolean contains = false;
        for (HostInfo oldHostInfo : hostInfoList) {
    
    
            if (oldHostInfo.getHostIp().equals(hostInfo.getHostIp())) {
    
    
                newHostInfoList.add(hostInfo);
                contains = true;
            } else {
    
    
                newHostInfoList.add(oldHostInfo);
            }
        }
        if (!contains) {
    
    
            newHostInfoList.add(hostInfo);
        }
        return newHostInfoList;
    }

    public static HostInfo buildHostInfo(String ip, String port) {
    
    
        HostInfo hostInfo = new HostInfo();
        hostInfo.setHostIp(ip);
        hostInfo.setHostPort(port);

        return hostInfo;
    }
}

【4】服务发现

消费端从zk中心拿到服务的生产者信息,通过一定的负载均衡策略选择某个服务的生产者进行突兀调用

/**
 * 服务发现
 * @author : 18073771
 * @see [相关类/方法](可选)
 * @since [产品/模块版本] (可选)
 */
public class ServiceFinder {
    
    

    private static Map<String, List<HostInfo>> serviceProducerMap = new ConcurrentHashMap<>();

    static {
    
    
        init();
    }

    private static void init() {
    
    
        String path = "/";
        try {
    
    
            // 所有的服务契约
            List<String> childPaths = ZookeeperClient.getChildPath(path);
            if (null == childPaths) {
    
    
                return;
            }

            for (String childPath : childPaths) {
    
    
                // 获取服务契约下的所有服务实现
                List<String> contractChildPaths = ZookeeperClient.getChildPath("/" + childPath);
                for (String ctPath : contractChildPaths) {
    
    
                    byte[] znodeData = ZookeeperClient.getZnodeData("/" + childPath + "/" + ctPath);
                    String str = new String(znodeData);
                    List<HostInfo> hostInfoList = JSON.parseArray(str, HostInfo.class);
                    serviceProducerMap.put(childPath + "/" + ctPath, hostInfoList);
                }

            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }


    /**
     *
     * 功能描述: 随机获取一个生产这
     *
     * @param contractKey
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public static HostInfo getContractHost(String contractKey, String implCode) {
    
    
        String key = getKey(contractKey, implCode);
        List<HostInfo> hostInfoList = serviceProducerMap.get(key);
        if (null == hostInfoList) {
    
    
            return null;
        }
        int random = new Random().nextInt(hostInfoList.size());
        return hostInfoList.get(random);
    }

    private static String getKey(String contract, String implCode) {
    
    
        return contract + ((StringUtils.isEmpty(implCode)) ? "default" : "/" + implCode);
    }
}

可优化的方向:消费端只加载获取其关注的服务,不关注的服务不需要加载; 同时还需要监听服务节点的变化,如果服务有新的生产者提供服务那么更新消费者的服务列表。

三、序列化方式

整个Rpc框架存在几个地方需要序列化:一是服务注册的时候,将服务信息注册到zk,注册的信息需要序列化,另一个是通信过程中的消息传递是需要将请求的对象进行序列化。通过过程中请求对象的序列化通过netty的编解码器实现。对于zk注册信息的序列化采用了最简单的json序列化的方式进行

【1】hession

Hessian序列化是一种支持动态类型、跨语言、基于对象传输的网络协议,Java对象序列化的二进制流可以被其他语言(如,c++,python)。特性如下:

自描述序列化类型。不依赖外部描述文件或者接口定义,用一个字节表示常用的基础类型,极大缩短二进制流。
语言无关,支持脚本语言
协议简单,比Java原生序列化高效
相比hessian1,hessian2中增加了压缩编码,其序列化二进制流大小事Java序列化的50%,序列化耗时是Java序列化的30%,反序列化耗时是Java序列化的20%。
Hessian会把复杂的对象所有属性存储在一个Map中进行序列化。所以在父类、子类中存在同名成员变量的情况下,hessian序列化时,先序列化子类,然后序列化父类。因此,反序列化结果会导致子类同名成员变量被父类的值覆盖。

换个思路,既然你继承了一个父类,当然希望复用的越多越好,所以,使用hessian序列化的时候,避免开这一点就行了。

【2】json

FastJson是啊里巴巴的的开源库,用于对JSON格式的数据进行解析和打包。

FastJson特点如下:

(1)能够支持将java bean序列化成JSON字符串,也能够将JSON字符串反序列化成Java bean。

(2)顾名思义,FastJson操作JSON的速度是非常快的。

(3)无其他包的依赖。

(4)使用比较方便。

【3】kyro

Kryo序列化机制比默认的Java序列化机制速度要快,序列化后的数据要更小,大概是Java序列化机制的1/10。所以Kryo序列化优化以后,可以让网络传输的数据变少,在集群中耗费的内存资源大大减少。

Kryo 是一个快速高效的Java对象图形序列化框架,主要特点是性能、高效和易用。该项目用来序列化对象到文件、数据库或者网 络。但是,它也有一个致命的弱点:生成的byte数据中部包含field数据,对类升级的兼容性很差!所以,若用kryo序列化对象用于C/S架构的话,两边的Class结构要保持一致。

四、负载均衡

【1】轮询(默认)

每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。

【2】指定权重

指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。

【3】IP绑定 ip_hash

每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。

【4】fair(第三方)

按后端服务器的响应时间来分配请求,响应时间短的优先分配。

【5】url_hash(第三方)

按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。

五、权限控制

在服务端收到服务的调用请求时,需要先对调用方进行鉴权,同样通过zk实现。如果调用方在服务配置的消费方列表中,那么鉴权通过,否则不让其通过。

六、熔断

服务熔断的作用类似于我们家用的保险丝,当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。

七、流控

当资源成为瓶颈时,服务框架需要对消费者进行限流,启动流控保护机制。流量控制有多种策略,比较常用的有:针对访问速率的静态流控、针对资源占用的动态流控、针对消费者并发连接数的连接控制和针对并行访问数的并发控制。

八、存在的问题

1、由于是简单的rpc实现,因此没有考虑将客户端与服务端之间的通信方式维护成长连接的方式,可以通过心跳检测的方式来完善

2、服务端宕机将此服务端从其提供的服务中移除也存在问题,通过将持久化的节点改成注册为临时节点可以解决这个问题,zk能够支持如果注册此临时节点的客户端失联后将该临时节点移除的功能。

3、针对可能存在多活的场景,同样rpc框架应该需要支持各种路由策略。

上述实例的代码留言后私发

猜你喜欢

转载自blog.csdn.net/khuangliang/article/details/107549732
今日推荐