师者——传道、授业、解惑也
学者——知其然而知其所以然
一 微服务的前世今生(什么是微服务?什么是服务?)
\
首先,我们需要理解,什么是单体架构. 之前学习java web开发的商城后台如下
之前我们开发的这个就是一个单体架构,为什么这么说呢?
1)从应用部署上看是一个个体
2)从代码类的结构上看是一个整体
3)从方法调用上看是一个整体
首先,大家之前开发的这个商城后台,以及具备了该有的java开发逻辑,这个商城在一些小公司也是能够满足要求的
在现实工作中,往往上述单体是不满足老板要求的,为什么呢?老板会经常有如下要求:
1)双十一来了,我们的商城能不能支持十几万人同时浏览商品?——高并发
2)能不能让订单管理不要挂,不然老是被人投诉?——超稳定
结合上图单体结构,我们会发现都很难满足老板的要求,怎么办?删库跑路?或者把代码呼到老板脸上说你行你来?又或者迎难而上?来,和我一起,我们去解决它,怎么解决呢? 哪里不爽就动那里!
针对第一个问题:
现在的商品管理类支撑不了十几万的并发,那就多部署几个商品管理类,让每个商品管理类单独自己运行,不就可以了?不管来多少商品浏览请求都不怕,反正机器有的是,不够再加!
针对第一个问题:
能不能让订单管理不要挂,不然老是被人投诉?要让订单管理功能稳定,如果把订单管理也部署在两个机器上,每个机器跑一个订单管理,即便其中一个挂了,还有另外一个还能处理用户订单请求,对用户来说根本不知道后台有个挂了
我们从最直观的感受上去解决老板的问题,把单体架构稍微改造成了上面的架构,但也迎来了如下问题:
1)商品管理和订单管理分布在不同的机器上跑,商品管理如何调用订单管理呢?它都不知道订单管理在哪个机器运行。
2)即便商品管理知道订单管理在哪台服务器上,但如果有一台订单管理服务器挂了,商品管理还是会一直往这个挂了的订单管理服务器上发请求,还是不满足要求。
万物皆对象,代码来自于生活,上面两个问题,就很像我们国家的人口管理。 我们商品管理比喻为 张三, 订单管理比喻为李四;他们两互不认识,我们日常生活中是如何解决的呢?
1)李四想要让别人找到他,他在出生时必须把自己的地址注册到派出所—— 注册
2)张三要找李四怎么办? 问派出所!因为派出所有李四的地址;—— 发现
3)李四要经常和派出所保持联系,告诉自己还活着以及住址;—— 续约
4)李四如果正常去世了,会主动告诉派出所自己亡故;—— 下线
5)李四如果非正常死亡,派出所过一定时间还没有李四消息,就把他定义为死亡;—— 剔除
通过设置派出所,增加了注册、发现、续约、下线、剔除这些业务,就是实现了两个互不相识的人,找到对方。
同样,我们也可以在上述架构中,增加一个类似派出所的模块,叫服务注册中心,让它支持 服务注册、服务发现、服务续约、服务下线、服务剔除,那么就可以完美解决之前遇到的问题。
二、微服务之关键功能
服务注册中心——管理各微服务的地址列表(ip+port)
服务注册——各微服务实例在启动时,都向服务注册中心注册自己可访问的地址(ip+port)
服务发现——微服务实例通过服务注册中心,可以找到所需其他微服务实例的地址
服务续约——微服务实例需要定期向服务注册中心发送心跳
服务下线——微服务实例主动停止时向服务注册中心发送下线消息
服务剔除——服务注册中心把不可用的微服务实例从地址列表中清除
综述:服务注册中心主要是维护各个应用服务的ip+port列表,并保持与各应用服务的通讯,在一定时间间隔内进行心跳检测,如果心跳不能到达则对服务IP列表进行剔除,并同时通知给其它应用服务进行更新。同样要是有新增的服务进来,应用服务会向注册中心进行注册,服务注册中心将通知给其它应用进行更新。每个应用都有需要调用对应应用服务的地址列表;
三、服务注册中心代码演示
下面,我们将基于zookeeper来实现服务注册与发现功能。
第一步:新建springboot项目,添加一下依赖:
org.springframework.boot
spring-boot-starter
com.101tec
zkclient
0.10
org.springframework.boot
spring-boot-devtools
runtime
org.springframework.boot
spring-boot-starter-test
test
第二步:新增服务注册接口与服务注册实现类
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 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 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 discover(String name) {
List 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 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>>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、在工作中会用到微服务么?