从0到1编写注册中心-提纲目录

1、注册中心简要实现原理

 

2、如何实现注册与发现?

 

3、如何实现LoadBlance?

 

4、如何实现集群Cluster?分布式一致性算法?

 

5、市面上注册中心对比:Nacos、Eureka、Zookeeper

 

一、注册中心简要实现原理

 

1、 主体应用框架演进

注册中心 consule_注册中心 consule

 

1.1 All In One单体应用时代

        在最原始的系统设计中,我们通常将系统分为三层,数据访问层、业务逻辑层、视图层也就是我们常说的MVC模型。此架构没有对业务场景进行划分,所有业务模块的数据访问层、业务逻辑层、视图层都放在一个工程中,最终经过编译、打包,部署在一台服务器上。

 

        

注册中心 consule_服务提供者_02

 

        优点:分层设计明确了不同团队的分工,职责清晰、分工明确,诞生了前端团队、后端团队和DBA团队;J2EE开发简单,所有类都直接本地引用使用,事务处理只需要依赖数据库即可。容易部署。

        缺点:业务之间的耦合程度逐步变得严重,随着应用扩大,JVM内存消耗大,性能也会逐步下降;随着业务逻辑复杂度增加,人员的流动,导致整个应用会越来越困难。当系统到达一定瓶颈后,水平扩展困难;

        

 

1.2 垂直拆分时代

        当流程逐渐增加,可以将应用根据业务拆分成多个应用;拆分完成的应用形成独立对外提供服务的系统,应用之间采用特定的通讯协议(大部分是Http协议)进行调用;垂直拆分从一定程度上缓解了单体应用的臃肿。

        

注册中心 consule_List_03

        但随着时间迁移,逐步发现每个垂直系统在进行调用的过程中协议的不统一(特别是在与不同的供应商提供的系统进行相互调用是),导致开发成本逐步变高,维护难度高。

 

1.3 分布式时代-早期的SOA

        当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,以及提供标准的服务协议,使前端应用能更快速的响应多变的市场需求。

        面向服务架构(SOA)将单一进程的应用进行拆分,形成独立对外提供服务的组件,每个组件通过网络协议对外提供服务。

        

        SOA特点:

        A、明确的协议

        B、明确的接口

        

        典型的SOA实现:ESB(企业服务总线);服务之间的通讯和调用均通过ESB进行完成。

         

注册中心 consule_List_04

        

        ESB负责服务之间消息的解析、转换、路由,统一编排信息处理流程等。

        优点:各个服务系统之间形成统一的交互协议;

 

        但也有2个比较明显的缺点:

        A、ESB逐步成为性能瓶颈,因为所有的交互都经过ESB进行转发,当ESB出现故障时,整个服务系统之间的调用将瘫痪,很容易形成单点问题;

 

        B、服务之间的调用将产生2次调用,性能比较浪费。

                第一次调用:服务调用者访问ESB

                第二次调用:ESB调用访问提供者,然后进行转换消息内容转换。

 

1.4 分布式时代-微服务

        

        为了解决ESB 比较明显缺点,服务化架构得到进一步演进,逐步形成颗粒度更细的微服务化。微服务化通过注册中心来解决ESB对应的单点问题;同时让服务消费者直接与服务提供者进行通讯,减少服务之间的交互次数(由原来的2次变成1次)。

        典型的微服务化架构:Dubbo、SpringCloud。

        

注册中心 consule_服务调用_05

 

2、用生活中的例子形容注册中心职责

 

     用一个生活中经常使用到的打车App做一个比方。

    在没有滴滴等打车平台出现之前,因为市民不知道什么时候会有空的车辆,所以市民需要打车使用车需要站在路边扬手拦车。就好比原来一个应用需要调用其他应用接口时,需要提前知晓并配置好应用接口地址,当应用变更地址时,需要手动更改接口地址,给运维带来了不便。

    为了解决市民打车难的问题,有了滴滴平台。

    A、上班打卡

    出租车司机上班的时候,告知滴滴平台可以开始接单了,等待市民的下单。

    好比:注册中心的注册服务。

    

    B、发布打车需求

    市民需要打车时,发布需求到滴滴平台,滴滴平台根据需要找到对应的出租车,并告知市民和司机对应的联系方式,后续均由市民和司机自行联系。

    好比:服务调用者通过注册中心获取对应的服务提供者信息。

 

    C、市民司机相互联系确认地址

    市民司机均知道对方联系方式后,电话联系确认地址

    好比:服务调用者知晓服务提供者后,通过IP服务地址进行调用服务

    

    D、司机因故不能执行

    当司机因故不能执行任务时,滴滴平台自动更换新的司机信息告知市民

    好比:应用地址发生变更时,可自行获取到最新的地址信息

 

    E、市民取消行程

    市民因故取消行程,告知滴滴平台行程取消,订单取消。

    好比:服务调用者发生故障,注册中心告知服务提供者:调用者发生变化了

 

3、根据注册中心职责设计注册中心功能

    A、服务注册

        服务提供者通过注册接口将服务ID、IP-URL地址告知注册中心

 

    B、服务获取

        服务调用者获取对应需要调用服务的地址信息

 

    C、服务调用

        服务调用者根据服务地址进行调动提供者接口

 

    D、服务提供者掉线异常

        当服务提供者发生异常掉线后,注册中心需要知晓,并及时通知服务调用者地址发生变化

 

    E、服务调用者掉线异常

        当服务调用者发生异常掉线后,注册中心需要知晓。

        

 

注册中心 consule_服务调用_06

 

2、如何实现注册与发现?

Q:注册中心中的服务提供者需要实例化存储么?

A:一般都采用本地化文件实例化存储,很少使用Db进行实例化存储。

 

Q:使用什么通讯协议实现服务提供者/调用者与注册中心通讯?

A:一般采用Http协议或者TCP/IP协议。TCP/IP协议在监听和通知服务提供者/调用者方面有更好的优势,但对服务器的要求比较高。

本文采用Http协议进行通讯。

 

Q:服务提供者/调用者如何实现异常通知?

A:服务提供者/调用者发生掉线时,一般采用心跳模式告知存活或者通过TCP/IP长连接模式。需要考虑:服务提供者/调用者无响应、网络异常导致闪断;

 

2.1 注册中心提供注册服务

package com.test.controller;
 
 
import com.test.storage.CacheStorage;
import com.test.storage.ServiceInfo;
import org.apache.catalina.util.ServerInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
 
import java.util.ArrayList;
import java.util.List;
 
 
@RestController
public class RegistryController {
 
 
    private CacheStorage storage;
 
 
    @Autowired
    public RegistryController(CacheStorage storage) {
        this.storage = storage;
    }
 
 
    @RequestMapping("/registerService")
    public String registerService(String serviceId, String address) {
        this.storage.addServiceInfo(serviceId, address);
        return "ok";
    }
 
 
    @RequestMapping("/getServiceInfo")
    public List<String> getServiceInfo(String serviceId) {
        List<ServiceInfo> serviceInfoList = this.storage.getServiceInfo(serviceId);
        List<String> result = new ArrayList<>();
        for (ServiceInfo serviceInfo : serviceInfoList) {
            result.add(serviceInfo.toString());
        }
        return result;
    }
 
 
    @RequestMapping("/heartbeat")
    public String heartbeat(String serviceId, String address) {
        this.storage.updateService(serviceId, address);
 
 
        return "ok";
    }
}

 

 

 

-- 注册服务存储服务

package com.test.storage;
 
 
import org.springframework.stereotype.Component;
 
 
import java.util.*;
 
 
@Component
public class CacheStorage {
 
 
    private Map<String, List<ServiceInfo>> serviceMap;
 
 
    public CacheStorage() {
        this.serviceMap = new HashMap<>();
    }
 
 
    public void addServiceInfo(String serviceId, String address) {
        ServiceInfo serviceInfo = new ServiceInfo();
        serviceInfo.setServiceId(serviceId);
        serviceInfo.setAddress(address);
        serviceInfo.setLastUpdateTime(new Date());
        List<ServiceInfo> existAddressList = this.serviceMap.getOrDefault(serviceId, null);
        if (existAddressList == null) {
            existAddressList = new ArrayList<>();
            this.serviceMap.put(serviceId, existAddressList);
        }
        existAddressList.add(serviceInfo);
    }
 
 
    public void removeServiceInfo(String serviceId, String address) {
        List<ServiceInfo> existAddressList = this.serviceMap.getOrDefault(serviceId, null);
        if (existAddressList != null) {
            ServiceInfo serviceInfo = new ServiceInfo();
            serviceInfo.setServiceId(serviceId);
            serviceInfo.setAddress(address);
            existAddressList.remove(address);
        }
    }
 
 
    public List<ServiceInfo> getServiceInfo(String serviceId) {
        return this.serviceMap.getOrDefault(serviceId, new ArrayList<>());
    }
 
 
    public void updateService(String serviceId, String address) {
        ServiceInfo serviceInfo = new ServiceInfo();
        serviceInfo.setServiceId(serviceId);
        serviceInfo.setAddress(address);
        serviceInfo.setLastUpdateTime(new Date());
        List<ServiceInfo> existAddressList = this.serviceMap.getOrDefault(serviceId, null);
        if (existAddressList != null) {
            int index = existAddressList.indexOf(serviceInfo);
            if (index >= 0) {
                existAddressList.get(index).setLastUpdateTime(new Date());
            }
        }
    }
}

 

 

2.2 客户端注册服务

package com.test.registry;
 
 
import java.util.List;
 
 
public interface IServiceRegistry {
 
 
    String registerService(String serviceId, String address);
 
 
    void heartBeat(String serviceId, String address);
 
 
    List<String> getServiceInfoList(String serviceId);
}
 
package com.test.registry;
 
 
 
 
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
 
 
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
 
public class DefaultServiceRegistry implements IServiceRegistry {
 
 
    private String registerCenterAddress = "";
 
 
    public DefaultServiceRegistry(String registerCenterAddress) {
        this.registerCenterAddress = registerCenterAddress;
    }
 
 
    @Override
    public String registerService(String serviceId, String address) {
        RestTemplate restTemplate = new RestTemplate();
        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("serviceId", serviceId);
        params.add("address", address);
 
 
        return restTemplate.postForObject(this.registerCenterAddress + "/registerService", params, String.class);
    }
 
    // 心跳更新服务信息
    @Override
    public void heartBeat(String serviceId, String address) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("Start HeartBeat");
                    try {
                        Thread.sleep(10000);
                        RestTemplate restTemplate = new RestTemplate();
                        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
                        params.add("serviceId", serviceId);
                        params.add("address", address);
                        List<String> serviceInfoList = new ArrayList<>();
                        restTemplate.postForObject(registerCenterAddress + "/heartbeat", params, String.class);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
    }
 
 
    @Override
    public List<String> getServiceInfoList(String serviceId) {
        RestTemplate restTemplate = new RestTemplate();
        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("serviceId", serviceId);
        List<String> serviceInfoList = new ArrayList<>();
        serviceInfoList = restTemplate.postForObject(this.registerCenterAddress + "/getServiceInfo", params, serviceInfoList.getClass());
        return serviceInfoList;
    }
}