微服务课程

2021-08-29  本文已影响0人  华木公子

师者——传道、授业、解惑也
学者——知其然而知其所以然

一 微服务的前世今生(什么是微服务?什么是服务?)

\

首先,我们需要理解,什么是单体架构. 之前学习java web开发的商城后台如下

单体.png

之前我们开发的这个就是一个单体架构,为什么这么说呢?
1)从应用部署上看是一个个体
2)从代码类的结构上看是一个整体
3)从方法调用上看是一个整体

首先,大家之前开发的这个商城后台,以及具备了该有的java开发逻辑,这个商城在一些小公司也是能够满足要求的

在现实工作中,往往上述单体是不满足老板要求的,为什么呢?老板会经常有如下要求:
1)双十一来了,我们的商城能不能支持十几万人同时浏览商品?——高并发
2)能不能让订单管理不要挂,不然老是被人投诉?——超稳定

结合上图单体结构,我们会发现都很难满足老板的要求,怎么办?删库跑路?或者把代码呼到老板脸上说你行你来?又或者迎难而上?来,和我一起,我们去解决它,怎么解决呢? 哪里不爽就动那里!

针对第一个问题:
现在的商品管理类支撑不了十几万的并发,那就多部署几个商品管理类,让每个商品管理类单独自己运行,不就可以了?不管来多少商品浏览请求都不怕,反正机器有的是,不够再加!


拆分商品管理.png

针对第一个问题:
能不能让订单管理不要挂,不然老是被人投诉?要让订单管理功能稳定,如果把订单管理也部署在两个机器上,每个机器跑一个订单管理,即便其中一个挂了,还有另外一个还能处理用户订单请求,对用户来说根本不知道后台有个挂了

拆解订单.png

我们从最直观的感受上去解决老板的问题,把单体架构稍微改造成了上面的架构,但也迎来了如下问题:

1)商品管理和订单管理分布在不同的机器上跑,商品管理如何调用订单管理呢?它都不知道订单管理在哪个机器运行。
2)即便商品管理知道订单管理在哪台服务器上,但如果有一台订单管理服务器挂了,商品管理还是会一直往这个挂了的订单管理服务器上发请求,还是不满足要求。

万物皆对象,代码来自于生活,上面两个问题,就很像我们国家的人口管理。 我们商品管理比喻为 张三, 订单管理比喻为李四;他们两互不认识,我们日常生活中是如何解决的呢?

1)李四想要让别人找到他,他在出生时必须把自己的地址注册到派出所—— 注册
2)张三要找李四怎么办? 问派出所!因为派出所有李四的地址;—— 发现
3)李四要经常和派出所保持联系,告诉自己还活着以及住址;—— 续约
4)李四如果正常去世了,会主动告诉派出所自己亡故;—— 下线
5)李四如果非正常死亡,派出所过一定时间还没有李四消息,就把他定义为死亡;—— 剔除

通过设置派出所,增加了注册、发现、续约、下线、剔除这些业务,就是实现了两个互不相识的人,找到对方。

同样,我们也可以在上述架构中,增加一个类似派出所的模块,叫服务注册中心,让它支持 服务注册、服务发现、服务续约、服务下线、服务剔除,那么就可以完美解决之前遇到的问题。


最终架构.png

二、微服务之关键功能

服务注册中心——管理各微服务的地址列表(ip+port)
服务注册——各微服务实例在启动时,都向服务注册中心注册自己可访问的地址(ip+port)
服务发现——微服务实例通过服务注册中心,可以找到所需其他微服务实例的地址
服务续约——微服务实例需要定期向服务注册中心发送心跳
服务下线——微服务实例主动停止时向服务注册中心发送下线消息
服务剔除——服务注册中心把不可用的微服务实例从地址列表中清除

综述:服务注册中心主要是维护各个应用服务的ip+port列表,并保持与各应用服务的通讯,在一定时间间隔内进行心跳检测,如果心跳不能到达则对服务IP列表进行剔除,并同时通知给其它应用服务进行更新。同样要是有新增的服务进来,应用服务会向注册中心进行注册,服务注册中心将通知给其它应用进行更新。每个应用都有需要调用对应应用服务的地址列表;


image.png image.png

三、服务注册中心代码演示

下面,我们将基于zookeeper来实现服务注册与发现功能。

第一步:新建springboot项目,添加一下依赖:

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.101tec</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.10</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

第二步:新增服务注册接口与服务注册实现类

2.1 新增服务注册和发现接口类

package com.qhs.learn.zookeeper.service.registry;

import java.util.List;

public interface Register {
    /**
     * 注册服务
     * @param serviceName
     * @param serviceAddress
     */
    void registry(String serviceName, String serviceAddress);
    /**
     * 服务发现
     * @param name
     * @return
     */
    List<String> discover(String name);
}

2.2 新增服务注册和发现实现类

package com.qhs.learn.zookeeper.service.registry.impl;

import com.qhs.learn.zookeeper.service.registry.Register;
import org.I0Itec.zkclient.ZkClient;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ThreadLocalRandom;

public class ZkServiceRegistry implements Register {
    private String zkAddress = "localhost:2181";
    private final List<String> addressCache = new CopyOnWriteArrayList<>();
    private ZkClient zkClient;

    public void init() {

        zkClient = new ZkClient(zkAddress,
                Constant.ZK_SESSION_TIMEOUT,
                Constant.ZK_CONNECTION_TIMEOUT);
        System.out.println(">>> connect to zookeeper");

    }

    @Override
    public void registry(String serviceName, String url) {

        //创建registry节点(持久)
        String registryPath = Constant.ZK_REGISTRY;
        if (!zkClient.exists(registryPath)) {
            zkClient.createPersistent(registryPath);
            System.out.println(">>> create registry node:" + registryPath);
        }

        //创建service节点(持久)
        String servicePath = registryPath + "/" + serviceName;
        if (!zkClient.exists(servicePath)) {
            zkClient.createPersistent(servicePath);
            System.out.println(">>>create service node:" + servicePath);
        }

        //创建address节点(临时)
        String addressPath = servicePath + "/address-";
        String addressNode = zkClient.createEphemeralSequential(addressPath,url);
        System.out.println(">>> create address node:" + addressNode);

    }

    @Override
    public List<String> discover(String name) {
        List<String> ipPortList =  new ArrayList<>();
        try {
            String servicePath = Constant.ZK_REGISTRY + "/" + name;

            //获取服务节点
            if (!zkClient.exists(servicePath)) {
                throw new RuntimeException(String.format(">>>can't find any service node on path {}",servicePath));
            }

            //从本地缓存获取某个服务地址
            String address;
            int addressCacheSize = addressCache.size();
            if (addressCacheSize > 0) {
                if (addressCacheSize == 1) {
                    address = addressCache.get(0);
                } else {
                    address = addressCache.get(ThreadLocalRandom.current().nextInt(addressCacheSize));
                }
                System.out.println(">>>get only address node:" + address);

                //从zk服务注册中心获取某个服务地址
            } else {
                List<String> addressList = zkClient.getChildren(servicePath);
                addressCache.addAll(addressList);

                //监听servicePath下的子文件是否发生变化
                zkClient.subscribeChildChanges(servicePath,(parentPath,currentChilds)->{
                    System.out.println(">>>servicePath is changed:" + parentPath);
                    addressCache.clear();
                    addressCache.addAll(currentChilds);

                });

                if (CollectionUtils.isEmpty(addressList)) {
                    throw new RuntimeException(String.format(">>>can't find any address node on path {}", servicePath));
                }
                String addressResult = null;
                for(int i=0;i<addressList.size();i++){
                    //获取IP和端口号
                    addressResult = zkClient.readData(servicePath + "/" + addressList.get(i));
                    ipPortList.add(addressResult);
                    System.out.println(">>>get address node:" + addressResult);
                }
            }


        } catch (Exception e) {
            System.out.println(">>> service discovery exception: " + e.getMessage());
            zkClient.close();
        }
        return ipPortList;
    }
}


第四步:模拟客户端微服务(注册/发现)

package com.qhs.learn.zookeeper.service.registry;

import com.qhs.learn.zookeeper.service.registry.impl.ZkServiceRegistry;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ClientServiceApplication {

    private static final String SERVICE_NAME = "qhs.com";

    private static final String SERVER_ADDRESS = "localhost:10081";

    public static void main(String[] args) {

        SpringApplication.run(ClientServiceApplication.class, args);

        ZkServiceRegistry registry = new ZkServiceRegistry();
        registry.init();
        registry.registry(SERVICE_NAME,SERVER_ADDRESS);

        ZkServiceRegistry discovery = new ZkServiceRegistry ();
        discovery.init();
        discovery.discover(SERVICE_NAME);

        while (true){}

    }

}


先启动zookeeper服务,再执行测试用例。我们分别启动三个测试用例,以模拟多个客户端同时进行服务注册场景,程序执行后,观察控制台的输出信息。

四、微服务之问

1、学习微服务能够给我们自身带来什么好处?

 2、学习微服务帮助我们提高面试机会么?

 3、在工作中会用到微服务么?
上一篇下一篇

猜你喜欢

热点阅读