(一).前言
在正式介绍Eureka之前,先搞定一下配置环境以及出现的问题

上图是我创建的一个父子类项目,主要功能模块是查看订单记录和查看商品记录
这两个项目现在是相互独立的状态



如果现在直接根据订单id查订单和根据商品id查商品,只会查到对应表的记录。那么我如果现在,根据订单id查询订单的时候,查询到的数据有一个product_id,即商品id,我现在的需求是查到商品id的同时,要在商品表中找到对应的商品名称和商品价格。但是现在两个项目又是独立的,并且两个数据库也是相互独立的,我该如何操作?

如上图的效果
首先,先明确一点,在查订单的同时,查出商品的信息,那么在订单类中首先需要有商品类

那么该如何让两个项目之间建立起联系?
这就到了重点了
想让两个项目之间建立起联系,我们采用 “远程调用”的思路来解决问题
实现思路:order-service服务向product-service服务发送一个http请求,把得到的返回结果,和订单结果融合在一起,返回给调用方
实现方式:使用Spring提供的RestTemplate类来实现
这里,我们先使用,后介绍原理
1.定义RestComplate,并交给Spring进行管理
package org.com.spring.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class BeanConfig {
/**
* RestTemplate是一个支持HTTP请求的工具,是一个同步的REST API客户端
* RestTemplate会处理HTTP连接和关闭,只需要使用者提供资源的地址和参数即可
* @return
*/
//第三方的工具我们需要收到交给Spring进行管理,由于无法修改RestTemplate原码,所以只能通过方法注解来交给Spring进行管理
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
2.修改order-service的service层代码
package org.com.spring.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.com.spring.mapper.OrderMapper;
import org.com.spring.mapper.doobject.OrderDetailDO;
import org.com.spring.mapper.doobject.ProductDetailDO;
import org.com.spring.service.IOrderService;
import org.com.spring.service.dto.OrderDetailDTO;
import org.com.spring.service.dto.ProductDetailDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Slf4j
@Service
public class OrderServiceImpl implements IOrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
@Override
public OrderDetailDTO getOrderDetail(Integer id) {
//1.校验非空
if (null == id || id < 0) {
log.error("订单不存在");
}
//2.调用Mapper
OrderDetailDO orderDetail = orderMapper.getOrderDetail(id);
if (null==orderDetail){
log.error("订单不存在");
}
String url="http://127.0.0.1:7000/product/getDetail?id="+orderDetail.getProductId();
ProductDetailDO productDetailDO = restTemplate.getForObject(url, ProductDetailDO.class);
if (null==productDetailDO){
log.error("商品不存在");
}
//3.返回结果
return convertToOrderDetailDTO(orderDetail,productDetailDO);
}
private OrderDetailDTO convertToOrderDetailDTO(OrderDetailDO orderDetail,ProductDetailDO productDetailDO) {
OrderDetailDTO orderDetailDTO=new OrderDetailDTO();
orderDetailDTO.setId(orderDetail.getId());
orderDetailDTO.setUserId(orderDetail.getUserId());
orderDetailDTO.setProductId(orderDetail.getProductId());
orderDetailDTO.setNum(orderDetail.getNum());
orderDetailDTO.setPrice(orderDetail.getPrice());
ProductDetailDTO productDetailDTO=new ProductDetailDTO();
productDetailDTO.setId(productDetailDO.getId());
productDetailDTO.setProductName(productDetailDO.getProductName());
productDetailDTO.setProductPrice(productDetailDO.getProductPrice());
orderDetailDTO.setProductDetailDTO(productDetailDTO);
return orderDetailDTO;
}
}
然后进行测试

可以看到,是可以测试出来的


关于RestTemplate,它实际上是一个同步的REST API客户端,提供了常见的REST请求方案的模板。它是Spring提供的,用于封装HTTP调用,并且强制使用RESTful风格,它会处理HTTP连接和关闭,只需要使用者提供资源的地址和参数即可。
通过上面的代码,可以看到,也是包含了很多问题,例如
1.在进行远程调用的时候,url是写死的,如果地址发生了变化,此时,还需要修改代码

2.当进行多机部署的时候,该如何进行处理?
3.在进行远程调用的时候,URL非常容易写错,而且复用性也不高
4.所有的服务都可以调用上面的接口,是否会有风险?
除此之外,微服务架构还有很多问题,那么下面就来介绍如何使用SpringCloud来解决这些问题
(二).Eureka
1.背景
(1).具体问题

在进行远程调用的时候,发现,URL是写死的,一旦发生IP地址修改,那么我们还需要进行修改代码,就很麻烦。
(2).解决思路
这里通过一个例子来引出。
现在,假设 “患者A” 想去 “医院A”去看病,但是不知道医院上没上班,所以想要打电话问一下医院上没上班。但是,患者A现在不知道医院A的电话,那么此时患者A可以去“114查号台”去查找这个医院A的电话号码,当查到之后,患者A就可给医院A打电话了
但是,如果现在医院A换了一个电话号码,那么按道理说,医院A应该通知各个患者,但是医院A的使用方群体是一个巨大的,无法做到一 一通知,那么该如何处理?
此时,医院A就可以将修改之后的电话号码,上传到114平台,然后下一个患者需要联系医院A的时候,先从114平台上查询电话,查到之后,再拨打电话给医院A
可以发现,站在114平台的角度,它的功能有两个
①.服务方把电话上报给114平台
②.使用方通过114平台查找到对应的号码
114平台就保存了服务方和电话之间的关系
此时,对于①这个功能称为 “号码注册”,对于②这个功能称为“号码查询”

同样,在进行微服务开发时,也可以次啊用类似的方案
当服务启动变更时,向注册中心报道,注册中心记录应用和IP的关系
调用方进行调用的时候,先去注册中心获取服务方的IP,再去服务方进行调用
(3).注册中心的概念
随着微服务的流行与流量的激增,机器规模逐渐变大,并且机器会有频繁的上下线行为,这个时候需要运维手动地维护整个配置信息是一个很麻烦的操作。所以开发者希望有这么一个东西,他能维护一个服务列表,那个机器上线了,哪个机器宕机了,这些信息都会自动更新到服务列表上,客户端拿到这个列表,直接进行服务调用即可,这个就是注册中心
注册中心中的三个角色:
服务提供者(Server):一次业务中,被其他微服务调用的服务,也就是提供接口给其他微服务
服务消费者(Client):一次业务中,调用其他微服务的服务,也就是调用其他微服务提供的接口
服务注册中心(Registry):用于保存Server的注册信息,当Server节点发生变更时,Registry会同步编程,服务与注册中心使用一定机制通信,如果注册中心与某服务长时间无法通信,就会注销掉该实例。

服务注册:服务提供者在启动时,向注册中心注册自身服务,并向注册中心定期发送心跳汇报存货状态
服务发现:服务消费者从注册中心查询服务提供者的地址,并通过该地址调用服务提供者的接口,服务发现的一个重要作用就是提供给服务消费者一个可用的服务列表
(4).CAP理论
介绍到注册中心,就避不开CAP理论

CAP理论是分布式系统设计中,最基础,也是最关键的理论
一致性:这里指的是强一致性,指的是所有节点在同一时间具有相同的数据;弱一致性,指的是随着时间的推移,最终达到了一致性
可用性:保证每个请求都有相应
分区容错性:当出现网络分区后,系统仍然能够对外提供服务
CAP理论告诉我们:一个分布式系统不可能同时满足数据一致性,服务可用性和分区容错性这三个基本需求,最多满足其中的两个。
因为,在分布式系统中,系统间的网络不能100%保证健康,但是服务又必须对外保证服务,所以“分区容错性”不可避免,但是 “一致性” 和 “可用性” 就要选一个了。
主要原因是
想要强一致 C:必须等待隔离的节点通信恢复,在此期间部分请求无法处理,牺牲可用性 A → CP
想要可用 A:不能阻塞等待通信恢复,直接本地返回结果,不同分区数据就会不一样,牺牲强一致 C → AP
CP架构:为了保证分布式系统对外的数据一致性,于是选择不返回任何数据
AP架构:为了保证分布式系统的可用性,节点2返回V0版本的数据,即使这个数据不正确

(5).常见的注册中心
Ⅰ.Zookeeper
Zookeeper的官方并没有说它是一个注册中心,但是国内Java体系,大部分的集群环境都是依赖Zookeeper来完成注册中心的功能
Ⅱ.Eureka
Eureka是Netfix开发的基于REST的服务发现框架,主要用于服务注册,管理,负载均衡和服务故障转移
Ⅲ.Nacos
Nacos是Spring Cloud Alibaba架构中重要的组件,除了服务注册,服务发现功能之外,Nacos还支持配置管理,流量管理,DNS,动态DNS等多种特性
Ⅳ.CAP理论对比
| Zookeeper | Eureka | Nacos | |
| CAP理论 | CP | AP | CP 或 AP 默认 AP |
2.Eureka
(1).概念
Eureka是Netfix OSS套件中关于服务注册和服务发现的解决方案,SpringCloud对Eureka进行了集成,并作为优先推荐方案进行宣传。
Eureka主要分为两个部分:
Eureka Server:作为注册中心的Server端,向微服务应用程序提供服务注册,发现,健康检查等能力。
Eureka Client:服务提供者,服务启动时,会向Eureka Server 注册自己的信息(IP,端口,服务信息等),Eureka Server 会存储这些信息
(2).搭建Eureka Server
Ⅰ.创建Eureka-server子模块
Eureka-server是一个独立的微服务开发

Ⅱ.引入Eureka-server的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
Ⅲ.配置文件,增加Eureka相关的配置
server:
port: 10010
spring:
application:
name: eureka-server
eureka:
instance:
hostname: localhost
client:
fetch-registry: false # 表示是否从Eureka Server获取注册信息,默认为true.因为这是一个单点的Eureka Server,不需要同步其他的Eureka Server节点的数据,这里设置为false
register-with-eureka: false # 表示是否将自己注册到Eureka Server,默认为true.由于当前应用就是Eureka Server,故而设置为false.
service-url:
# 设置与Eureka Server的地址,查询服务和注册服务都需要依赖这个地址.
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
Ⅳ.在启动类中,开启Eureka的功能

通过@EnableEurekaServer注解,开启Eureka注册中心
Ⅴ.启动服务
当服务启动后,访问http://127.0.0.1:10010/

可以看到,Eureka-server就已经启动成功了
(3).搭建 “服务注册”
接下来,将product-server注册到eureka-server中
Ⅰ.加入Eureka依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
Ⅱ.修改配置文件
spring:
application:
name: product-service
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10010/eureka #添加Eureka地址
Ⅲ.启动测试
重启product-service,并且刷新注册中心页面

(4).服务发现
接下来,开始修改order-service,在远程调用的时候,从Eureka-server拉去product-service的服务信息,实现服务发现
Ⅰ.加入Eureka依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
Ⅱ.修改配置信息
spring:
application:
name: order-service
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10010/eureka #添加Eureka地址
Ⅲ.修改远程调用的代码
远程调用时,我们需要从Eureka-server中获取product-service的列表(可能存在多个),并选择一个进行调用
package org.com.spring.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.com.spring.mapper.OrderMapper;
import org.com.spring.mapper.doobject.OrderDetailDO;
import org.com.spring.mapper.doobject.ProductDetailDO;
import org.com.spring.service.IOrderService;
import org.com.spring.service.dto.OrderDetailDTO;
import org.com.spring.service.dto.ProductDetailDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@Slf4j
@Service
public class OrderServiceImpl implements IOrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient; //通过这个对象,可以从Eureka中拿到url,避免发生错误
@Override
public OrderDetailDTO getOrderDetail(Integer id) {
//1.校验非空
if (null == id || id < 0) {
log.error("订单不存在");
}
//2.调用Mapper
OrderDetailDO orderDetail = orderMapper.getOrderDetail(id);
if (null==orderDetail){
log.error("订单不存在");
}
//从Eureka获取服务列表(根据应用名称去获取)
List<ServiceInstance> instances = discoveryClient.getInstances("product-service");
String uri = instances.get(0).getUri().toString(); //获取到了 IP地址 和 端口号
String url=uri+"/product/getDetail?id="+orderDetail.getProductId();
log.info("远程调用地址 . url : {}",url);
ProductDetailDO productDetailDO = restTemplate.getForObject(url, ProductDetailDO.class);
if (null==productDetailDO){
log.error("商品不存在");
}
//3.返回结果
return convertToOrderDetailDTO(orderDetail,productDetailDO);
}
private OrderDetailDTO convertToOrderDetailDTO(OrderDetailDO orderDetail,ProductDetailDO productDetailDO) {
OrderDetailDTO orderDetailDTO=new OrderDetailDTO();
orderDetailDTO.setId(orderDetail.getId());
orderDetailDTO.setUserId(orderDetail.getUserId());
orderDetailDTO.setProductId(orderDetail.getProductId());
orderDetailDTO.setNum(orderDetail.getNum());
orderDetailDTO.setPrice(orderDetail.getPrice());
ProductDetailDTO productDetailDTO=new ProductDetailDTO();
productDetailDTO.setId(productDetailDO.getId());
productDetailDTO.setProductName(productDetailDO.getProductName());
productDetailDTO.setProductPrice(productDetailDO.getProductPrice());
orderDetailDTO.setProductDetailDTO(productDetailDTO);
return orderDetailDTO;
}
}
Ⅳ.启动测试
重启order-service,并刷新Eureka页面

可以发现,都加载出来了


3148

被折叠的 条评论
为什么被折叠?



