Nacos服务注册与发现

序言

参考文章:http://www.julianbrowne.com/article/brewers-cap-theorem
学习就应该从最基础学起,不要让你的知识有间隔,万丈高楼平地起.

服务注册中心有那几大类?

我目前所知道的服务注册中心总共有: zk,eureka,nacos。 他们都是服务注册中心,zk主要的大家应该非常熟悉,他经常和dubbo一起使用,eureka是springcloud全家桶产品,在2.0的时候 就已经不维护了,Nacos 是阿里巴巴推出来的一个新开源项目。

zk,eureka,nacos 都离不开基础原理:RPC ,RMI ,在实现上框架上应该保障CAP理论知识

什么是RPC

RPC全英名字:Remote Procedure Call 英文缩写,中文名字为远程过程调用,作用是对数据进行处理后显示或打印,最开始的时候我以为RPC属于JavaEE13个规范里的内容,其实不是搞混了,13个规范里指的是RMI,RPC来源于C语言,它是一个相对较为旧的协议,继承C语言的规范。使用RPC

可以像调用本地方法一样调用远程的方法方法

RPC与RMI区别

RMI是JavaEE里的13个规范,RMI是面向对象的范式,也就是用户需要知道他调用的对象和对象中的方法,RPC不是面向对象也不能处理对象,而是调用具体的子程序,并且RMI是基于JavaEE规范的,也就是说他只能用户Java语言,RMI则可以跨语言

RMI调用机制是什么?

如果想知道RMI调用机制什么,那么必须先知道他是什么,学习就是先从是什么入手,看缩写字母一定要去查,当你查出全拼的时候就恍然大悟~ Java Remote method protocol

我们先使用一个简单的小栗子来复现一下RMI
Nacos服务注册与发现_第1张图片

代码

/**
 * @创建人 wxf
 * @创建时间 2020/1/6
 * @描述
 */
public class JudyEntry implements Serializable {
     
    private String name;
    private Integer age;

    public String getName() {
     
        return name;
    }

    public void setName(String name) {
     
        this.name = name;
    }

    public Integer getAge() {
     
        return age;
    }

    public void setAge(Integer age) {
     
        this.age = age;
    }
}
**
 * @创建人 wxf
 * @创建时间 2020/1/6
 * @描述
 */
public interface JudyInterface extends Remote {
     
    public String getJudyName() throws RemoteException;
}

/**
 * @创建人 wxf
 * @创建时间 2020/1/6
 * @描述
 */
public class JudyServerImpl extends UnicastRemoteObject implements JudyInterface {
     
    protected JudyServerImpl() throws RemoteException {
     
        super();
    }

    @Override
    public String getJudyName() throws RemoteException {
     
        JudyEntry judyEntry = new JudyEntry();
        judyEntry.setAge(18);
        judyEntry.setName("judy");
        return judyEntry.getName();
    }
}

/**
 * @创建人 wxf
 * @创建时间 2020/1/6
 * @描述
 */
public class ProgramClient {
     
    public static void main(String[] args) {
     
        try {
     
            JudyInterface judyInterface = (JudyInterface) Naming.lookup("rmi://127.0.0.1:6600/JudyInterface");
            String judyName = judyInterface.getJudyName();
            System.out.println(judyName);
        } catch (Exception e) {
     
            e.printStackTrace();
        }
    }
}

/**
 * @创建人 wxf
 * @创建时间 2020/1/6
 * @描述
 */
public class ProgramServer {
     
    public static void main(String[] args) {
     
        try {
     
            JudyInterface judyInterface = new JudyServerImpl();
            //创建注册表,让服务注册到这上面。
            LocateRegistry.createRegistry(6600);
            Naming.rebind("rmi://127.0.0.1:6600/JudyInterface",judyInterface);
            System.out.println("begin");
            Thread.sleep(600000);
        } catch (Exception e) {
     
            e.printStackTrace();
        }
    }
}

运行结果:
Nacos服务注册与发现_第2张图片
Nacos服务注册与发现_第3张图片

我们换一种思路想,快递再给我们寄出物品的时候,首先他会存储在一个地方,然后写上我们的地方,然后我们去固定的地点取物品。其实跟远程调用一样,有注册存储,有唯一标识。

CAP 理论

C的全英是Consisency 一致性: 在分布式系统中所有节点的数据都应该保持一致,当然当数据节点越多的时候数据同步也是越耗时的

A的全英是Availability 可用性:服务一直可用,这是必须要达到的一点

p的全英是Partition tolerance : 分区容错性 。高可用的提现也就是在某个服务单个节点挂点之后,不会影响整个服务崩溃。

布鲁尔定理到底是什么?

假如目前我们有两个节点N1和N2,A表示程序 V表示数据库存储数据。
Nacos服务注册与发现_第4张图片
在正常情况下N1 发送->V1 消息 -> 给 V0 存储, V0 存储收到V1消息 之后通过M --> 发送给B中V0,这个时候N1和N2 节点的数据是一致的。
Nacos服务注册与发现_第5张图片

在非正常情况下,论文阐述的是在网络异常的情况下,将会发生什么? 从图中我们可以看出A中的V1到B中V0 由于网络原因无法发送消息V0,那么这个时候N1节点应该高可用还是服务一致性,所以这也就是CAP原理中分区容错性和高可用的二选一原则。(根据不同业务场景选择CP或者AP)
Nacos服务注册与发现_第6张图片

解决CAP引发的问题。例如:服务降级,分区容错,最终一致性, 所以无论是nacos ,eureka,zk都有这些特性的相关实现。Eureka是AP,zk是CP,Nacos是AP

Nacos 注册与发现

对于Nacos的注册与发现实现过程我就不再粘贴代码,我把github地址附上,大家有需要可以自己拿取~

https://github.com/JudyWang88/nacos-config-discovery-gateway.git如果你感觉不错,那就来波关注点赞,嘻嘻~ ,这里面Nacos是服务注册,使用Dubbo服务发现

根据源码我们来看一下是如何实现的,我们从图中指的这个类入手
Nacos服务注册与发现_第7张图片
第一步获取属性

        Properties properties = new Properties();
        properties.setProperty("serverAddr","127.0.0.1");
        properties.setProperty("namespace", "judy");

第二步创建注册表

        NamingService naming = NamingFactory.createNamingService(properties);

第三步注册

        naming.registerInstance("nacos.test.3", "11.11.11.11", 8888, "TEST1");

细节:注册表如何创建

   public static NamingService createNamingService(Properties properties) throws NacosException {
     
        try {
     
            //3 利用反射,跟config使用的是一个套路
            Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");
            Constructor constructor = driverImplClass.getConstructor(Properties.class);
            NamingService vendorImpl = (NamingService)constructor.newInstance(properties);
            return vendorImpl;
        } catch (Throwable e) {
     
            throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
        }
    }
 //4 走构造函数
    public NacosNamingService(Properties properties) {
     
    //初始化方法,也就是把该准备的都准备好
        init(properties);
    }

接下来看看EventDispatcher,NamingProxy,BeatReactor,HostReactor

    private void init(Properties properties) {
     
        namespace = InitUtils.initNamespaceForNaming(properties);
        initServerAddr(properties);
        InitUtils.initWebRootContext();
        initCacheDir();
        initLogName(properties);

        eventDispatcher = new EventDispatcher();
        serverProxy = new NamingProxy(namespace, endpoint, serverList);
        serverProxy.setProperties(properties);
        beatReactor = new BeatReactor(serverProxy, initClientBeatThreadCount(properties));
        hostReactor = new HostReactor(eventDispatcher, serverProxy, cacheDir, isLoadCacheAtStart(properties),
            initPollingThreadCount(properties));
    }
        //创建一个线程吧监听的服务获取到所有元数据
        executor.execute(new Notifier());

    public NamingEvent(String serviceName, String groupName, String clusters, List<Instance> instances) {
     
        this.serviceName = serviceName;
        this.groupName = groupName;
        this.clusters = clusters;
        this.instances = instances;
    }

// 注册domain放到list中

 serverProxy = new NamingProxy(namespace, endpoint, serverList);

  public NamingProxy(String namespaceId, String endpoint, String serverList) {
     

        this.namespaceId = namespaceId;
        this.endpoint = endpoint;
        if (StringUtils.isNotEmpty(serverList)) {
     
            this.serverList = Arrays.asList(serverList.split(","));
            if (this.serverList.size() == 1) {
     
                this.nacosDomain = serverList;
            }
        }

        initRefreshSrvIfNeed();
    }

    private void initRefreshSrvIfNeed() {
     
        if (StringUtils.isEmpty(endpoint)) {
     
            return;
        }

        ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
     
            @Override
            public Thread newThread(Runnable r) {
     
                Thread t = new Thread(r);
                t.setName("com.alibaba.nacos.client.naming.serverlist.updater");
                t.setDaemon(true);
                return t;
            }
        });
        // 这块是根据serversFromEndpoint来判断是否需要有值和获取serverlist,如果没有值则定时循环获取
        executorService.scheduleWithFixedDelay(new Runnable() {
     
            @Override
            public void run() {
     
                refreshSrvIfNeed();
            }
        }, 0, vipSrvRefInterMillis, TimeUnit.MILLISECONDS);

        refreshSrvIfNeed();
    }

serversFromEndpoint 是什么? 从nacos远程获取服务list


    public List<String> getServerListFromEndpoint() {
     

        try {
     
            String urlString = "http://" + endpoint + "/nacos/serverlist";
            List<String> headers = builderHeaders();
            //重点
            HttpClient.HttpResult result = HttpClient.httpGet(urlString, headers, null, UtilAndComs.ENCODING);
            if (HttpURLConnection.HTTP_OK != result.code) {
     
                throw new IOException("Error while requesting: " + urlString + "'. Server returned: "
                    + result.code);
            }
  }

BeatReactor

  public BeatReactor(NamingProxy serverProxy, int threadCount) {
     
        this.serverProxy = serverProxy;
        //executorService定时器
        executorService = new ScheduledThreadPoolExecutor(threadCount, new ThreadFactory() {
     
            @Override
            public Thread newThread(Runnable r) {
     
                Thread thread = new Thread(r);
                thread.setDaemon(true);
                thread.setName("com.alibaba.nacos.naming.beat.sender");
                return thread;
            }
        });
    }

注册

naming.registerInstance("nacos.test.3", "11.11.11.11", 8888, "TEST1");
   @Override
    public void registerInstance(String serviceName, String groupName, String ip, int port, String clusterName) throws NacosException {
     

        Instance instance = new Instance();
        instance.setIp(ip);
        instance.setPort(port);
        instance.setWeight(1.0);
        instance.setClusterName(clusterName);

        registerInstance(serviceName, groupName, instance);
    }
    @Override
    public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
     

        if (instance.isEphemeral()) {
     
            BeatInfo beatInfo = new BeatInfo();
            beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));
            beatInfo.setIp(instance.getIp());
            beatInfo.setPort(instance.getPort());
            beatInfo.setCluster(instance.getClusterName());
            beatInfo.setWeight(instance.getWeight());
            beatInfo.setMetadata(instance.getMetadata());
            beatInfo.setScheduled(false);
            beatInfo.setPeriod(instance.getInstanceHeartBeatInterval());

            beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
        }

        serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
    }
public final Map<String, BeatInfo> dom2Beat = new ConcurrentHashMap<String, BeatInfo>();

    public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
     
        NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
        String key = buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());
        BeatInfo existBeat = null;
        //fix #1733
        if ((existBeat = dom2Beat.remove(key)) != null) {
     
            existBeat.setStopped(true);
        }
        dom2Beat.put(key, beatInfo);
        executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
        MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
    }

总结
Nacos总结了两篇文章,目前先总结到这吧~ 下两篇文章分享分布式事务解决方案 ~ 期待下周相约吧~

你可能感兴趣的:(Nacos服务注册与发现)