SpringCloud微服务即是由一系列细小的服务构成的,而服务是由SpringBoot创建构成的。因此,在项目的开发过程中我们将各个模块划分出来分别使用SpringBoot进行开发,例如 我们 将卖家端的 商品管理模块 和 订单管理模块 划分成两个SpringBoot微服务项目。此时就会出现问题,订单管理模块中 客户下单时,需要进行对 商品管理模块的 商品库存信息进行数据操作,那么 订单模块 如何调用 商品管理模块 的服务的?
两大阵营:HTTP和RPC,其中SpringCloud使用的是HttpRestful方式,Dubbo是RPC框架。(二者兼容性较差)
SpringCloud应用间通信的两种实现方式:RestTemplate、Feign。(HttpClient在这里就不提了)。
例1:RestTemplate直接请求 (例3最优)
1. 在服务端创建 一个简单的接口,客户端服务需要调用它。
@RestController
public class ServerController {
@GetMapping("/msg")
public String msg(){
return "this is product's message";
}
}
2. 客户端服务进行调用
@RestController
@Slf4j
public class ClientController {
@GetMapping("/getProductMsg")
public String getProductMsg(){
//第一种方式
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject("http://127.0.0.1:8081/msg",String.class);
log.info("response={}",response);
return response;
}
}
3. 缺点:接口的url是写死的。在多个地址(服务端负载均衡时的多个服务实例)的情况下,这种调用是不合理的。
例2:使用LoadBalancerClient动态获取到接口的url地址,再采用例1方法调用。 (例3最优)
服务端(Product)不需修改,客户端调用方式修改即可。
@RestController
@Slf4j
public class ClientController {
@Resource
private LoadBalancerClient loadBalancerClient;
@GetMapping("/getProductMsg")
public String getProductMsg(){
// 选择应用的名称,以便创建服务实例。应用名在application配置中定义
ServiceInstance serviceInstance = loadBalancerClient.choose("PRODUCT");
String url = String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort()+"/msg");
RestTemplate template = new RestTemplate();
String response = template.getForObject(url,String.class);
log.info("response={}",response);
return response;
}
}
例3:在RestTemplate配置类中应用注解@LoadBalanced,然后在客户端请求中直接调用服务名。最优。
1. 服务端(Product)不需修改,在客户端项目中 新建一个RestTemplate的配置类 用于创建Bean对象。
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
2. 客户端请求中,直接 通过 服务名进行服务调用。
@RestController
@Slf4j
public class ClientController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/getProductMsg")
public String getProductMsg(){ // 直接通过服务名进行调用
String response = restTemplate.getForObject("http://PRODUCT/msg",String.class);
log.info("response={}",response);
return response;
}
}
实际上,例2中的 LoadBalancerClient 对象 和 @LoadBalanced注解,均是Ribbon的组件。
Ribbon:客户端负载均衡器,实现应用间通信。
Ribbon实现负载均衡的核心:服务发现;服务选择原则;服务监听。
主要组件:ServerList、IRule、ServerListFilter。
应用间通信流程:通过ServerList获取所有可用服务的列表——>ServerListFilter过滤掉部分的服务地址——>IRule选择服务实例作为最终的目标结果。
注:当有多个服务实例时,可以配置application.yml进行轮询调用,例如:
PRODUCT:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
Fegin:声明式Rest客户端(伪RPC);采用基于接口的注解;
实例:订单服务模块 调用 商品服务 模块。
1. 添加pom依赖
org.springframework.cloud
spring-cloud-starter-openfeign
2. 在启动类中添加注解:@EnableFeignClients。
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {SpringApplication.run(OrderApplication.class, args);}
}
3. 在客户端 定义 需要调用的服务方的接口
@FeignClient(name = "product")
public interface ProductClient {
@GetMapping("/msg")
String productMsg();
}
4. 在客户端的中 调用 服务。(注入第三步定义的接口,直接调用)
@RestController
@Slf4j
public class ClientController {
@Resource
private ProductClient productClient;
@GetMapping("/getProductMsg")
public String getProductMsg(){
// 第四种方式 使用feign调用
String response = productClient.productMsg();
log.info("response={}",response);
return response;
}
}
实例2:在订单服务中 获取商品列表
1. 在商品模块的服务中,创建根据ID查询商品的一系列dao层和service层,以及最终controller层,以便给订单服务提供接口。注意 controller层中,需要使用@RequestBody注解修饰入参。
/**
* 专门用于 —— 订单服务调用商品列表。需要使用 @RequestBody 注解注释入参
*/
@PostMapping("/listForOrder")
public List listForOrder(@RequestBody List productIdList){
return infoService.findByProductIdIn(productIdList);
}
2. 在订单模块,引入商品模块的实体类,以及订单模块需要的接口服务。注意 方法的入参需要使用 @RequestBody 注解修饰。另:@RequestBody注解修饰参数时,必须使用POST方式请求。
@FeignClient(name = "product")
public interface ProductClient {
@GetMapping("/msg")
String productMsg();
/**
* 接收参数时,需要使用 @RequestBody
* 由于使用了RequestBody注解,则必须是POST请求。
* RequestBody:请求参数为JSON格式
* @param productIdList
* @return
*/
@PostMapping("/product/listForOrder")
List listForOrder(@RequestBody List productIdList);
}
3. 订单模块 调用 商品模块的服务
@RestController
@Slf4j
public class ClientController {
@Resource
private ProductClient productClient;
@GetMapping("/getProductInfo")
public ResultVO getProductInfo(){
List productInfoList =
Optional.ofNullable(productClient.listForOrder(Arrays.asList("157875196366160022","164103465734242707","157875227953464068")))
.orElse(new ArrayList<>());
log.info("response={}",productInfoList);
productInfoList.forEach(System.out::println);
return ResultVOUtil.success(productInfoList);
}
}
现阶段订单服务和商品服务存在的问题:
多模块改造:
1. 创建服务配置中心——https://blog.csdn.net/haoxin963/article/details/82350985;https://blog.51cto.com/zero01/2171735。
简述:a. 先将配置文件存储到git中。b. 配置中心服务中,进行配置并读取存储到git中的配置文件。
2. 创建多模块的Product服务和Order服务——https://blog.csdn.net/github_39577257/article/details/81842234,https://blog.csdn.net/yangshangwei/article/details/88809468。
注:在本地引入其他服务的jar包时,可以先将其他服务的jar包强制安装到本地,然后在客户端 项目的依赖引入jar包。强制打包安装命令为:mvn -U clean install -Dmaven.test.skip=true
在服务器中安装服务jar包:步骤待续。
其他博客:https://blog.csdn.net/qq_35275233/article/details/89041647;
https://juejin.im/post/5c2b7301e51d45342a256504;
https://www.cnblogs.com/linlf03/p/10180005.html
feign服务:https://blog.csdn.net/github_39577257/article/details/81842234
bootstrap.yml和application.yml配置文件的区别:https://www.cnblogs.com/BlogNetSpace/p/8469033.html