小编目前大一,刚开始着手学习微服务的相关知识,小编会把它们整理成知识点发布出来。我认为同为初学者,我把我对知识点的理解以这种代码加观点的方式分享出来不仅加深了我的理解,或许在某个时候对你也有所帮助,同时也欢迎大家在评论区分享你们的观点。
知不足而奋进,望远山而前行。
目录
远程调用
前言
我们知道一旦微服务进行了拆分,那数据产生了隔离,服务之间也产生了隔离,没法像单体服务那样做本地调用了。我们要去做数据查询,查别的服务的数据,我们就必须通过网络调用。
概述
我们知道服务进行拆分后,无论是部署到服务器上还是部署到容器上,容器之间以及服务器之间都是可以通过网络连接的,所以我们可以通过网络去请求这个数据。
用Java代码通过网络发送请求,相信大家并不是很熟悉,但是我们了解前端是如何通过网络发送请求的,所以我们只需要按照前端那样掌握好通过网络发送请求的几个要素,那我们也可以实现用Java代码通过网络发送请求。
前端向浏览器发送请求其实是采用HTTP协议的这种请求方式。同样我们Java代码也可以去模拟这种方式。我们会发现发送请求需要请求的方式以及请求的URL地址。
那怎么去发呢?其实这里Spring已经给我们提供好了API----RestTemplate,它可以方便的实现Http请求的发送。使用的步骤其实并不复杂。第一步就是注入RestTemplate到Spring容器。第二步通过它的exchange方法发起远程调用,这个方法一共有五个参数,分别是请求路径,请求方式,请求实体,返回值类型,请求参数。
下面就让我们尝试去使用一下。
例如下面这个方法是购物车(cart)模块的一个转VO的方法,但是我们需要用到商品(item)的查询商品的方法,显然对于微服务来说,已经不存在这样的内部调用了,这样写肯定是会报错的。所以这时就需要远程调用了。
既然要使用远程调用,我们来看第一步,首先创建RestTemplate对象,并把它交给Spring容器管理,这一块就在配置文件里配一下就好了,相信这一块对大家来说并不难。
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
接着我们在我们需要使用远程调用的地方自动注入一下RestTemplate,接着我们就可以去尝试使用它了。其实RestTemplate有许多以get,post,delete,put开头的方法分别表示发送哪种请求,但是这种简化方法的功能都比较有限,另外其实我们会用exchange方法的话,这些简化方法自然也就不在话下了。前面我们知道exchange方法共有五个参数,分别是请求路径,请求方式,请求实体,返回值类型,请求参数,前面三个都容易就知道的,但是第四个返回值类型,我们这时是一个泛型为ItemDTO的List集合,所以字节码表示也就不可能了,这里我们可以传参数化类型的引用ParameterizedTypeReference<List<ItemDTO>>,传字节码泛型会被擦除,但是当我们传的是对象时,泛型就还会在,所以我们只需要在第四个参数上new一个参数化类型的引用就好了,这样他就会根据反射拿到我们的泛型引用。最后一个参数就是把集合拼接成字符串就好了。
// 2.查询商品
// 2.1 利用RestTemplate发起http请求,得到http的响应
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
"http://localhost:8080/items?ids={ids}",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<ItemDTO>>() {
},
Map.of("ids", CollUtil.join(itemIds, ","))
);
// 2.2 解析响应
if (!response.getStatusCode().is2xxSuccessful()) {
// 查询失败,直接结束
return;
}
List<ItemDTO> items = response.getBody();
// 获取响应头
HttpHeaders headers = response.getHeaders();
// 获取相应码
int statusCodeValue = response.getStatusCodeValue();
以上就是我们远程调用的基本使用,其实主要还是发送http请求时的参数问题,参数确定好了,解析响应也就不是事。可能很多人会觉得上面的代码也太麻烦了,但是这样书写才能达到充分解耦,具体简化书写,我后面也会介绍。
下面我们来尝试测试一下,这样的远程调用是否行得通。控制台打印SQL语句和swagger接口测试工具也是没有问题,所以说咱们现在已经实现了从购物车服务到商品服务的远程查询了。
其实RestTemplate这种方式并不需要去学会,我们只需要去理解它是怎么发送远程调用的,因为接下来我们会学习别的方式来取代RestTemplate这种远程调用的方式。
服务治理Nacos
其实我们上面的RestTemplate是存在一点问题的,我们在请求路径上把ip地址写死了,实际的开发中为了抗住一个更高的并发请求压力,往往会把这一个服务部署多份,形成一个负载均衡的集群,也就是多实例部署,所以这时如果把ip地址写死了,那其它实例就相当于一直闲着,那显然不合理。另外倘若有些服务挂掉了,我们的调用方也没法知道哪些服务挂掉了,如果我们下次远程调用到了挂掉的服务,那岂不是我们整个服务都完蛋了。所以综上所叙述,我们当前主要存在两个问题,调用方没法感知被调用方的状态,被调用方没法知道需要调用的服务,以上这些问题我们就统称为服务治理的问题。那么要想解决这些问题,就需要用到注册中心这门技术,所以现在我们来学习一下注册中心。
注册中心原理
例如上面那个案例,我们cartService(购物车模块)是服务调用者,而itemService(商品模块)是服务提供者,但是注意这里是相对的,不是绝对的,任何一个服务模块都既可以是服务调用者也可以是服务提供者。其实我们注册中心就好比家政公司,一些保洁阿姨将自己信息注册到家政公司,她们属于服务提供者,如果你需要你个保洁阿姨,那么你就可以去家政公司,他们会给你一堆保洁阿姨的信息随你挑,我们就属于服务调用者。
· 但是这时注册中心会给我们一堆可以调用的实例,这时我们需要去做负载均衡来从中挑选一个。负载均衡算法很多,比如随机,轮询,加权,加权轮询等等。
另外倘若哪个服务挂掉了,这时注册中心会不会感知呢,其实会的,服务提供者和注册中心之间会定期发送一次请求,叫做心跳续约,就好比保洁阿姨哪天生病了要请假同理,注册中心如果定期没有收到服务提供者的请求,就会将其的信息删除,这样发送给服务调用者的列表当中也就不包含他了。
以上就是注册中心的基本知识点,听上去其实还是蛮复杂的,但是注册中心不需要我们自己实现,有很多框架都帮我们实现好了。
Nacos注册中心
其实市面上已经有了很多注册中心组件例如Eureka,Consul,Nacos等等,我们来学习一下Nacos,具体为什么选它呢,一方面它是国产的,另一方面就是它有些小插件功能比较强大,其实不管哪种组件他们使用方法差别都不大。下面是nacos的官网。
Nacos其实是一种服务,所以在使用之前,我们需要基于Docker来部署Nacos的注册中心,还需要一个数据库表来存储Nacos的数据。另外Docker部署这一块有不了解的可以看我的这一篇文章,里面有图文讲解和动手实操。
我们先通过nacos官网把数据库脚本拿下来执行一下,接着我们用下面这行代码区部署一下nacos。
docker run -d \
--name nacos \
--env-file ./nacos/custom.env \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
--restart=always \
nacos/nacos-server:v2.1.0-slim
执行完毕后,我们就可以docker ps 或者docker logs -f nacos 都可以查看nacos是否在运行中。 检查无误后,我们就可以到浏览器,去输入下面这个网址。
// 192.168.181.138 换成你的ip地址
http://192.168.181.138:8848/nacos
接着我们就来到nacos的主页,用户名和密码默认都是nacos。
进去之后我们就可以看到左边导航栏有服务列表和订阅者列表,接下来我们就可以去实现服务治理了。
服务注册
实现服务注册,我们只需要两步,第一步引入nacos discovery的依赖,第二步配置nacos地址,这样我们就实现了将当前服务注册到nacos中心。
第一步引入nacos discovery的依赖
<!--nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
第二步配置nacos地址,我们这一块要去yml配置文件。,在spring下的cloud下配置nacos地址
cloud:
nacos:
server-addr: 192.168.181.138
这时我们已经实现了服务注册,我们可以启动项目去nacos看一下,是不是成功了。
显然当项目一启动,就会去nacos中心注册服务,注意这里我itemService是注册了两份服务,实例数和健康实例数都是2,端口8081和8083也是都识别出来了,没有问题。另外nacos是通过服务名称去管理服务的。
服务发现和负载均衡
以上我们已经实现了服务注册,商品服务已经注册到了服务中心,现在我们购物车服务想要调用商品服务,我们就要去注册中心拉取商品服务,这个动作就叫做服务发现。
要实现服务发现,我们有三个步骤,第一步引入nacos discovery依赖,第二步配置nacos地址,第三步编写服务发现的代码,其实前两部和服务注册都是一样的,因为都是连接nacos。
第一二步都是和上面一样的也就不多说了,我们来看看第三步,nacos给我们提供了一套API叫做DiscoveryCIient,我们可以利用它的getInstances方法取获取服务列表实例,参数就填写服务的名称,因为nacos是通过服务名称管理实例的。获取到实例列表后,我们就需要通过负载均衡来获取到一个实例,这里我就用了随机算法,主要是好实现。得到一个单例服务ServiceInstance后我们就可以用getXXX等方法获取实例的信息,这里我们只需要它的url地址来拼接就好了。
// 2.查询商品
// 2.1 根据服务名称获取服务的实例列表
List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
if (CollUtil.isEmpty(instances)) {
return;
}
// 2.2 手写负载均衡,从实例列表中挑选一个实例
ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));
// 2.3 利用RestTemplate发起http请求,得到http的响应
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
instance.getUri() + "/items?ids={ids}",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<ItemDTO>>() {
},
Map.of("ids", CollUtil.join(itemIds, ","))
);
// 2.4 解析响应
if (!response.getStatusCode().is2xxSuccessful()) {
// 查询失败,直接结束
return;
}
List<ItemDTO> items = response.getBody();
以上我们就实现了服务发现,虽然只多了四行代码。但是从现在开始我们服务调用者再也不需要去记住服务提供者的ip信息,每一次都可以动态获取列表,然后通过负载均衡随机获取一个,真的是不要太优雅。
接下来我们就可以测试一下。这一块怎么测试呢?其实我们可以把控制台清空,然后去查询购物车多次次,看看是不是两个实例都有查询数据。很显然两个服务都被访问到了。同样我们也可以把其中一个关掉,再来测试也是没有问题的。
以上就是远程调用以及服务治理的相关知识,但是上面这个代码写起来还是有点复杂的,下一篇文章我会分享如何简化代码。
带着决心起床,带着满意入睡。