美文网首页
微服务课程

微服务课程

作者: 华木公子 | 来源:发表于2021-08-29 19:24 被阅读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、在工作中会用到微服务么?
    

    相关文章

      网友评论

          本文标题:微服务课程

          本文链接:https://www.haomeiwen.com/subject/ilfgiltx.html