微服务课程

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

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

\

首先,我们需要理解,什么是单体架构. 之前学习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、在工作中会用到微服务么?
最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容