五 远程调用
1 创建模型层 – model
各个微服务的实体类模型只存在于自己的服务中,当相互调用时,每个服务的模型,可能都被其他微服务所需要,因此抽取model层,所有实体类模型都在这里。位于父工程下。
2 导入模型
① 复制订单与商品实体模型到模型层后,删除每个微服务的实体类模型,此后微服务统一依赖模型层,其中囊括了所有数据模型,便于交叉调用,而微服务本身不需要再定义实体类模型。
② 导入lombok依赖 – model.pom
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>annotationProcessor</scope>
</dependency>
</dependencies>
③ 微服务都需要模型层,因此将model
导入service
依赖中 – services.pom
<dependency>
<groupId>cn.tj</groupId>
<artifactId>model</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
3 新增配置类 – service-product ,service-order
为了给远程发送数据,Spring提供了组件RestTemplate
他是线程安全的,不需要每次使用都new,因此创建配置类后,通过注入的方式获取
① ProductServiceConfig
@Configuration
public class ProductServiceConfig {
// 放入容器中 想用直接调
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
② OrderServiceConfig配置类
@Configuration
public class OrderServiceConfig {
// 放入容器中 想用直接调
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
4 远程调用方法 – OrderServiceImpl
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
DiscoveryClient discoveryClient;
@Autowired
RestTemplate restTemplate;
@Override
public Order createOrder(Long productId, Long userId) {
// 远程调用获取商品数据
Product product = getProductFromRemote(productId);
Order order = new Order();
order.setId(1L);
// 总金额 = 商品单价 * 数量
order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
order.setUserId(userId);
order.setNickname("huang");
order.setAddress("涠洲岛");
// 远程查询商品列表
order.setProductlist(Arrays.asList(product));
return order;
}
// 远程调用获取商品数据方法
private Product getProductFromRemote(Long productId) {
// 通过注册中心获取存在商品服务的所有地址(根据对方微服务的名字)
List<ServiceInstance> instances = discoveryClient.getInstances("service-product");
// 取第一个实例 随意取的
ServiceInstance instance = instances.get(0);
// 远程URL 获取ip端口
String url="http://"+instance.getHost()+":"+instance.getPort()+"/product/"+productId;
// 日志记录
log.info("远程请求:{}",url);
// 将返回的 json转为 Product实体
Product product = restTemplate.getForObject(url, Product.class);
return product;
}
}
5 精确订单实体类模型层 – Order
public class Order {
private Long id;
private BigDecimal totalAmount;
private Long userId;
private String nickname;
private String address;
// Object 改为 Product
private List<Product> productlist;
}
6 订单调用商品测试
7 若有服务下线
复制ProductMainApplication
启动9001端口
由于订单服务每次调用前都会询问注册中心,获取商品服务的所有地址,从中挑选一个进行调用,若有服务器实例宕机下线,注册中心就不会存在该实例,获取的地址列表取第一个也不会取到宕机的服务器地址,通过其他服务器实例完成调用
六 负载均衡
每次都取地址列表的第一个,就会一直给一个固定的服务器地址发送远程调用,不能够合理利用服务器资源,此时需要使用负载均衡
1 引入负载均衡依赖 – service-order.pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
2 测试类 – LoadbalancerTest
@SpringBootTest
public class LoadbalancerTest {
// 负载均衡组件API
@Autowired
LoadBalancerClient loadBalancerClient;
@Test
public void testLoadbalancer() {
// DiscoveryClient 是获取所有实体 但负载均衡需自己负责
// 选择某一个微服务的地址 返回单个实体 轮询规则
ServiceInstance choose = loadBalancerClient.choose("service-product");
System.out.println("choose: " + choose.getServiceId() + "; " + choose.getHost() + "; " + choose.getPort());
choose = loadBalancerClient.choose("service-product");
System.out.println("choose: " + choose.getServiceId() + "; " + choose.getHost() + "; " + choose.getPort());
choose = loadBalancerClient.choose("service-product");
System.out.println("choose: " + choose.getServiceId() + "; " + choose.getHost() + "; " + choose.getPort());
choose = loadBalancerClient.choose("service-product");
System.out.println("choose: " + choose.getServiceId() + "; " + choose.getHost() + "; " + choose.getPort());
}
}
// 测试结果 轮询
choose: service-product; 192.168.44.1; 9000
choose: service-product; 192.168.44.1; 9001
choose: service-product; 192.168.44.1; 9000
choose: service-product; 192.168.44.1; 9001
3 改造负载均衡的远程调用方法 – OrderServiceImpl
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
DiscoveryClient discoveryClient;
@Autowired
RestTemplate restTemplate;
// 使用负载均衡组件
@Autowired
LoadBalancerClient loadBalancerClient;
@Override
public Order createOrder(Long productId, Long userId) {
// 远程调用获取商品数据
Product product = getProductFromRemoteWithLoadBalancer(productId);
Order order = new Order();
order.setId(1L);
// 总金额 = 商品单价 * 数量
order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
order.setUserId(userId);
order.setNickname("huang");
order.setAddress("涠洲岛");
// 远程查询商品列表
order.setProductlist(Arrays.asList(product));
return order;
}
// 负载均衡远程调用获取商品数据
private Product getProductFromRemoteWithLoadBalancer(Long productId) {
// 通过注册中心以负载均衡方式获取存在商品服务的地址
ServiceInstance choose = loadBalancerClient.choose("service-product");
// 远程URL 获取ip端口
String url="http://"+choose.getHost()+":"+choose.getPort()+"/product/"+productId;
// 日志记录
log.info("远程请求:{}",url);
// 将返回的 json转为 Product实体
Product product = restTemplate.getForObject(url, Product.class);
return product;
}
}
4 多次测试 – F5
5 注解式负载均衡 – OrderServiceConfig
将@OrderServiceConfig
注解贴到RestTemplate
远程调用上,即可实现负载均衡
@Configuration
public class OrderServiceConfig {
// 注解式负载均衡
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
6 改造负载均衡方法 – OrderServiceImpl
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
// 远程调用
@Autowired
DiscoveryClient discoveryClient;
@Autowired
RestTemplate restTemplate;
@Override
public Order createOrder(Long productId, Long userId) {
// 远程调用获取商品数据
Product product = getProductFromRemoteWithLoadBalancerAnnotation(productId);
Order order = new Order();
order.setId(1L);
// 总金额 = 商品单价 * 数量
order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
order.setUserId(userId);
order.setNickname("huang");
order.setAddress("涠洲岛");
// 远程查询商品列表
order.setProductlist(Arrays.asList(product));
return order;
}
// 注解负载均衡远程调用获取商品数据
private Product getProductFromRemoteWithLoadBalancerAnnotation(Long productId) {
// ip与端口号通过微服务名称进行动态替换
// restTemplate在发送远程调用前会动态的将服务名称替换为ip与端口号
String url="http://service-product/product/"+productId;
// 日志记录
log.info("远程请求:{}",url);
// 将返回的 json转为 Product实体
Product product = restTemplate.getForObject(url, Product.class);
return product;
}
}
7 测试注解式负载均衡
测试是哪个商品微服务被调用 – ProductController
@RestController
public class ProductController {
@Autowired
ProductService productService;
// 根据ID获取商品数据
@GetMapping("/product/{id}")
public Product getProduct(@PathVariable("id") Long productId) {
// 查看控制台是否按照负载均衡进行输出
System.out.println("welcome");
Product product=productService.getProductById(productId);
return product;
}
}
七 实例缓存
1 介绍
该流程实际是发送了两次请求,1.请求注册中心获取对方服务地址,2.给对方服务地址发送请求,很是消耗性能,因此引入实例缓存
第一次远程调用到注册中心拿到所有上线实例的服务地址后,存入实例缓存,之后的调用从实例缓存中选择 ip和端口号 发送请求即可,而实例缓存需要和注册中心进行实时同步,若有服务下线,注册中心将数据同步给实例缓存
此时若注册中心宕机,面对之前调用过的服务,可以继续调用(实例缓存中有地址),没调用过的服务(未经过第一次调用),则无法访问。
2 测试
关闭Nacos依旧可以继续进行调用
重启Nacos,重启服务,所有实例上线后关闭Nacos,进行第一次调用
报500异常,微服务没有可用的实例