1 微服务框架介绍
1.1 远程调用
浏览器解析客户端发起的ajax跨域请求.请求虽然可以被B服务器正确的调用并处理,但是浏览器可以监控用户的这次发的ajax请求的所有的参数及返回值.在一些特定的条件下该操作不安全.
一般使用跨域的请求都是用来获取其他服务器的数据(查询操作),如果遇到了POST需要提交的参数应该使用更加安全的请求方式实现.
1.2 HttpClient介绍
HTTP 协议可能是现在 Internet 上使用得最多、最重要的协议了,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源。虽然在 JDK 的 java net包中已经提供了访问 HTTP 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活。
HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。HttpClient 已经应用在很多的项目中,比如 Apache Jakarta 上很著名的另外两个开源项目 Cactus 和 HTMLUnit 都使用了 HttpClient。现在HttpClient最新版本为 HttpClient 4.5 .6(2015-09-11)
1.3 HttpClient入门案例
1.3.1 导入jar包
在jt父级项目的pom.xml文件中
<!--添加httpClient jar包 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
1.3.2 入门案例
package com.jt.test;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.junit.jupiter.api.Test;
import java.io.IOException;
public class TestHttpClient {
/**
* 步骤:
* 1.实例化httpClient工具API
* 2.定义请求url地址 任意网络地址....
* 3.定义请求的类型 get/post/put/delete
* 4.发起请求,获取响应的结果
* 5.判断响应的状态码信息. 200 404 500 406 400....
* 6.动态解析返回值执行后续操作.
*/
@Test
public void test01(){
HttpClient httpClient = HttpClients.createDefault();
String url = "https://www.baidu.com/";
HttpGet get = new HttpGet(url);
try {
HttpResponse httpResponse = httpClient.execute(get);
//判断状态码是否正确
int statusCode = httpResponse.getStatusLine().getStatusCode();
if(statusCode == 200){
//表示请求正确
HttpEntity httpEntity = httpResponse.getEntity(); //获取服务器的全部响应信息(json/html/xml/xxxx)
String result = EntityUtils.toString(httpEntity,"UTF-8");
//获取之后可以执行业务处理......
System.out.println(result);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
在微服务框架中,这段代码已经又被封装起来了,后人直接调用即可。
1.4 再通过一个例子去理解HttpClient
1.4.1 需求:
根据userId,去查询用户的信息。
1.用户想查询id为7的这个用户的信息,于是他在浏览器的地址栏输入:
http://www.jt.com/findUserById/7
2.这时,jt-web服务器中的controller接收到了这条用户的请求,但是它自己没连数据库,查不到数据。
所以jt-web服务器就把这个请求转给了jt-sso服务器。
jt-web发的请求的url为:
http://sso.jt.com/findUserById/7
3.jt-sso服务器中的controller接收到请求后,由于它连着数据库呢,它就可以调用service层,再去查数据库,就能得到
id为7的这个用户的信息。
这个涉及到跨域了,怎么实现呢?
1.4.2 编辑jt-web中的controller
在UserController中新增方法
/**
* 为了进一步理解HttpClient的工作过程:
* 通过用户在地址栏输入的userId,查询出这个user的具体信息
* @param userId 7
* @return user对象
*/
@RequestMapping("/findUserById/{userId}") //restful风格
@ResponseBody //确保返回的是json串
public User findUserById(@PathVariable Long userId){
User user = userService.findUserById(userId);
return user;
}
1.4.3 编辑jt-web中的service
在UserService接口中自动生成findUserById()方法
User findUserById(Long userId);
1.4.4 编辑jt-web中的serviceImpl
在UserServiceImpl中新增findUserById()方法:
重点就是这里:
要在jt-web的serviceImpl中将用户的请求转发给jt-sso服务器。
@Service
public class UserServiceImpl implements UserService{
@Override
public User findUserById(Long userId) {
//以下过程是HttpClient工作的底层原理的过程,在微服务框架中,框架已将这些过程进行了封装,程序员直接调用即可
//1.定义要跨域请求的jt-sso服务器的url地址
String url = "http://sso.jt.com/user/findUserById/"+userId;
//2.新建一个HttpClient对象
HttpClient httpClient = HttpClients.createDefault();
//3.新建一个Get请求,参数就是第1步中的地址
HttpGet httpGet = new HttpGet(url);
//4.一系列API的调用
try {
//4.1 通过httpClient去.execute(),参数是httpGet,得到jt-sso返回的HttpResponse对象,这个对象中包含着所有有关这个User的信息
//由于不能保证jt-sso一定能返回数据,(即httpResponse能有值),所以这步会提示 try-catch
HttpResponse httpResponse = httpClient.execute(httpGet);
//4.2 通过httpResponse.getStatusLine()再.getStatusCode(),得到httpResponse的状态码,由此可以判断jt-sso本次给返回的数据是不是正常可用的
int status = httpResponse.getStatusLine().getStatusCode();
//4.3 根据jt-sso返回的数据结果的状态码是不是200 去决定进行哪个步骤
if(status == 200){
//4.4 通过httpResponse对象获取到那个User对象的实体httpEntity
//(这个Entity中包含着User的全部信息,而我需要从Entity身上获取一部分信息,封装成json串)
HttpEntity httpEntity = httpResponse.getEntity();
//4.5 把User对象的实体httpEntity 用过工具API 转化为json串,并设置UTF-8编码
String result = EntityUtils.toString(httpEntity,"UTF-8");
//4.6 将json传通过工具API 转为具体的User对象 并返回
return ObjectMapperUtil.toObject(result,User.class);
}else {
//5 如果jt-sso返回的数据结果有问题,就返回这个信息
throw new RuntimeException("请求失败,请校验url信息");
}
} catch (IOException e) {
//返回报错信息,给程序员看
e.printStackTrace();
//返回报错信息,给用户看(调用全局异常处理类)
throw new RuntimeException(e);
}
}
1.4.5 编辑jt-sso中的controller
在UserController中新增方法
/**
* 通过此例子,进一步理解HttpClient的工作过程
* 接收jt-web的serviceImpl中转发过来的请求信息
* 对方转发来的请求的地址:http://sso.jt.com/user/findUserById/+userId
* 返回值类型:json字符串
*/
@RequestMapping("/findUserById/{userId}")
public User findUserById(@PathVariable Long userId){
User user = userService.findUserById(userId);
return user;
}
1.4.6 编辑jt-sso中的service
在UserService中新增自动生成findUserById()方法
User findUserById(Long userId);
1.4.7 编辑jt-sso中的serviceImpl
接收到自家的Controller发来的请求后,去数据库中查询信息
/**
* 接收到自家controller中发来的请求后,去数据库中根据userId查询出这个User的信息,并返回
* @param userId
* @return json串
*/
@Override
public User findUserById(Long userId) {
User user = userMapper.selectById(userId);
return user;
}
1.4.8 结果验证
在浏览器中输入:
http://www.jt.com/user/findUserById/7
在页面中得到了该User的数据(json串形式)
问题引出:1.4中这个例子这个过程涉及的部分太多了,能否简化一下?
可以的,通过Dubbo框架
2 Dubbo框架
在学习Dubbo框架前,要先知道什么是SOA思想?什么是RPC(远程过程调用)?
2.1 SOA思想
知识回顾,之前我学过的有:
面向对象的思想、
面向接口开发(为了降低代码的耦合性,对共性方法进行抽取)、
面向切面开发(将抽取进行的更彻底,耦合性更低)
而SOA思想就是:面向服务开发
面向服务的架构(SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)进行拆分,并通过这些服务之间定义良好的接口和协议联系起来。
(注意:这个“接口”不能狭隘地理解为Service层的那个interface),它是一个独立于服务消费者和服务提供者之间的第三方。
它的另一个中文名之一叫:注册中心。
zookeeper是一个注册中心,eruka也是一个注册中心。它俩都属于是“接口”。
接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台、操作系统和编程语言。这使得构件在各种各样的系统中的服务可以以一种统一和通用的方式进行交互。
(实现)的意思是SSO的service去实现中间那个独立的第三方接口。
2.2 RPC(远程过程调用)
由浅入深的解释:
如何给老婆解释什么是RPC
https://zhuanlan.zhihu.com/p/36427583
**RPC是远程过程调用(Remote Procedure Call)**的缩写形式。由不同的服务之间进行的通讯就称之为RPC,RPC通讯用户无需了解协议的细节.像调用本地服务一样简单. RPC调用本质就是代理思想的应用.
远程过程调用:
总结: 服务A想要完成某项任务,但是自己手中没有该资源,则通知服务B 帮我去完成该操作. 这样的操作方式称之为RPC
其实,HttpClient也是一种RPC的实现。
2.3 微服务的调用原理
2.3.1 传统的 服务的调用方式
由于nginx做负载均衡时需要依赖配置文件.
但是当服务器新增/减少时.都需要手动的修改nginx的conf文件.很不智能.
所以传统的服务的调用方式达不到微服务的标准.
2.3.2 微服务的调用方式
步骤:
1.当服务的提供者(MANAGE-A,MANAGE-B)启动时,会将自己的服务信息(服务名称/IP/端口号)上报给注册中心,由注册中心进行记录。
2.服务注册中心需要记录服务提供者(MANAGE-A,MANAGE-B)提交的信息,并随时维护服务列表中的信息。
3.当服务消费者(比如:WEB)启动时会链接注册中心.
4.服务消费者(比如:WEB)从注册中心中获取服务列表信息,方便下次服务消费者(比如:WEB)直接去调用服务提供者(MANAGE-A,MANAGE-B)。
5.当服务消费者(比如:WEB)调用服务时,微服务框架会根据负载均衡的机制挑选其中的一个服务提供者(比如MANAGE-A)进行访问.
6.当那个服务提供者(MANAGE-A)宕机时,由于注册中心有心跳检测机制,会修改服务列表.将宕机的服务提供者(MANAGE-A)标识为down。
7.当服务列表维护之后,注册中心会全网广播,通知所有服务器消费者(比如:WEB)更新服务列表信息.
8.这样,服务器消费者(比如:WEB)看到更新后的列表后,下次再去访问服务提供者,微服务框架就会提供那个剩余的服务提供者(MANAGE-B)
2.4 关于集群的几点注意事项
2.4.1 为什么搭建集群时,服务器的数量要求是奇数?
原则(前辈定的规矩):
要想让一个集群正常运行(或者说,这几台服务器能形成一个集群,去工作),
那么这个集群中正在正常工作的服务器节点数量n 要 > 集群中服务器节点的总数N/2
即 n > N/2
有了这个原则,也就能解释了为什么在搭建集群时,最少要3台服务器节点.
通常情况下,服务器一直正常工作是不可能的,难免会因为各种意外而宕机。
所以不考虑这种,一直不宕机的完美的情况,也不能考虑都宕机的这种最糟的情况。
这里的前提是,集群中的服务器只宕机1台,集群还能正常工作。
假设:
只有1台服务器时 而它还宕机了 那么n=1-1 0<1/2 所以 1台服务器成不了集群
当有2台服务器时 其中有1台宕机了 那么n=2-1 1<2/2 所以 2台服务器,如果有1台宕机了,也成不了集群
重点来了:
当有3台服务器时 如果有1台宕机了 那么n=3-1 2>3/2 所以 3台服务器,如果有1台宕机了,还剩2台服务器,还能以集群的方式去工作。
当有4台服务器时 如果有1台宕机了 那么n=4-1 3>4/2 所以 4台服务器,如果有1台宕机了,还剩3台服务器,更能以集群的方式去工作了。
以此类推,以后更多服务器时,当只宕机1台服务器时,集群仍能正常工作。
集群中最多允许宕机的台数为多少???
如果是3台服务器搭建成的集群,那么最多只允许1台服务器宕机。
如果是4台服务器搭建成的集群,那么最多也只允许1台服务器宕机。
如果是5台服务器搭建成的集群,那么最多可以允许2台服务器宕机。
如果是6台服务器搭建成的集群,那么最多可以允许2台服务器宕机。
可知,当最多宕机的数量相同时(比如都为1台),用4台服务器去搭建,和用3台服务器去搭建的效果相同。
为了省钱,肯定都选用3台服务器去搭建。
所以奇数台和偶数台的容灾能力相同.所以选用奇数台.
2.4.2 zookeeper的安装 及 zookeeper集群的搭建
见:
2.4.3 zookeeper集群选举的原理======//TODO
原理说明:
zookeeper集群的选举根据myid文件中的最大值优先的规则,进行选举.
如果集群一旦超过半数以上的票数同意,则当选主机,同时选举结束.
2.5 Dubbo框架介绍(阿里研究出来的,后来过继给了Apache)
2.5.1 官网介绍
Apache Dubbo |ˈdʌbəʊ|
是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
当前微服务框架:
Dubbo框架中用的注册中心就是zookeeper,
SpringCloud框架用的注册中心是eurka。
2.5.2 Dubbo框架的特性
2.5.3 Dubbo工作原理
2.6 Dubbo入门案例
2.6.1 导入老师写好的dubbo-jt工作区
注意:dubbo-jt 是一个全新的工作区,不要跟jt工作区混在一起
把dubbo-jt文件夹整体放在我自己规定的IDEA工作区存放的目录。
然后在IDEA中:FILE------OPEN-------找到dubbo-jt-------点OK。
在稍后弹出的框中,记得选“用Maven模型”导入
2.6.2 dubbo-jt中的4个子项目之间的关系
dubbo-jt 有4个子项目:
dubbo-jt-demo-consumer (服务消费者) 依赖于dubbo-jt-demo-interface
dubbo-jt-demo-interface
dubbo-jt-demo-provider (服务提供者1) 依赖于dubbo-jt-demo-interface
dubbo-jt-demo-provider2 (服务提供者2) 依赖于dubbo-jt-demo-interface
2.6.3 服务提供者的说明(以dubbo-jt-demo-provider 为例)
dubbo-jt-demo-provider和dubbo-jt-demo-provider2之间的改动很小
dubbo-jt-demo-provider的port是9000
dubbo-jt-demo-provider2的port是9003
2.6.3.1 编辑Dubbo实现类
package com.jt.dubbo.service;
import com.alibaba.dubbo.config.annotation.Service;
import com.jt.dubbo.mapper.UserMapper;
import com.jt.dubbo.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
@Service(timeout=3000) //3秒超时 内部实现了rpc
//@org.springframework.stereotype.Service//将对象交给spring容器管理
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public List<User> findAll() {
System.out.println("我是第一个服务的提供者");
return userMapper.selectList(null);
}
@Override
public void saveUser(User user) {
userMapper.insert(user);
}
}
2.6.3.2 编辑dubbo-jt-demo-provider的YML文件
server:
port: 9000 #定义端口
spring:
datasource:
#引入druid数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
username: root
password: root
#关于Dubbo配置
dubbo:
scan:
basePackages: com.jt #指定dubbo的包路径
application: #应用名称
name: provider-user #一个接口对应一个服务名称 dubbo-jt-demo-provider的这个name与dubbo-jt-demo-provider2的这个name是相同的,但dubbo-jt-demo-consumer的这个name跟它俩这个是不同的
registry: #这里要先写从机(即backup前面要写从机),因为这里的配置是消费者查询服务者的信息时,跟zookeeper中的谁去联系,在zookeeper集群中主机和从机中的内容是相同的,但主机主要的任务是监控整个zookeeper是否在正常运行,有宕机的就开展选举。 而像给消费者提供数据的这种低等的活就交给从机干就行了,所以要先把从机祭出去,要是这个从机gg了,消费者再去找后面的备用的主机和从机
address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
protocol: #指定协议
name: dubbo #使用dubbo协议(tcp-ip) web-controller直接调用sso-Service
port: 20880 #每一个服务都有自己特定的端口 不能重复.
mybatis-plus:
type-aliases-package: com.jt.dubbo.pojo #配置别名包路径
mapper-locations: classpath:/mybatis/mappers/*.xml #添加mapper映射文件
configuration:
map-underscore-to-camel-case: true #开启驼峰映射规则
2.6.4 服务消费者说明(dubbo-jt-demo-consumer )
2.6.4.1 编辑Controller层
package com.jt.dubbo.controller;
import com.alibaba.dubbo.config.annotation.Reference;
import com.jt.dubbo.pojo.User;
import com.jt.dubbo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class UserController {
利用dubbo的方式为接口创建代理对象 利用rpc调用
//check=false 先启动提供者/消费者没有关系
@Reference(check = false,timeout = 3000) //(loadbalance="leastactive")
private UserService userService;
/**
* Dubbo框架调用特点:远程RPC调用就像调用自己本地服务一样简单
* @return
*/
@RequestMapping("/findAll")
public List<User> findAll(){
//远程调用时传递的对象数据必须序列化.
return userService.findAll();
}
@RequestMapping("/saveUser/{name}/{age}/{sex}")
public String saveUser(User user) {
userService.saveUser(user);
return "用户入库成功!!!";
}
}
2.6.4.2 编辑dubbo-jt-demo-consumer 的YML配置文件
server:
port: 9001
dubbo:
scan:
basePackages: com.jt
application:
name: consumer-user #定义消费者名称
registry: #注册中心地址
address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
2.6.5 dubbo启动测试
2.6.6 关闭dubbo框架的面试题
问题1: 如果将其中的一个服务的提供者关闭,问 用户访问是否受影响??
答:不受任何影响
问题2:服务消费者拿到《记录服务列表》后,如果将dubbo中的注册中心全部关闭,问用户访问是否受到影响???
答:不受影响,因为服务消费者已经将服务列表数据保存到他自己那一份了.