SpringCloud Alibaba-4-Feign远程调用

打工人丶 / 2024-03-02 / 原文

远程调用:在分布式系统中,我们使用springboot创建了各种各样服务,那么这些服务之间如何进行远程调用呢。如:订单微服务怎么去调用商品微服务?


Ribbon:是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法服务调用

Ribbon支持的负载均衡策略:

  • BestAvailableRule

  • AvailabilityFilteringRule

  • WeightedResponseTimeRule

  • RetryRule

  • RoundRobinRule

  • RandomRule

  • ZoneAvoidanceRule(默认)



什么是负载均衡?

负载均衡:就是将访问请求进行分摊到多个服务器上进行执行。

分类:

  • 服务端负载均衡——————Ngix
  • 客户端负载均衡——————Nacos

微服务调用关系中一般会选择客户端负载均衡,也就是在服务调用的一方来决定服务由哪个提供者执行。

    服务端负载均衡:指的是发生在服务提供者一方。
        比如:nginx负载均衡。请求到达服务器时,负载均衡器会根据预先设定的算法将请求分配到不同的服务器上,以达到均衡负载的目的。

    客户端负载均衡:指的是发生在服务请求的一方。
        比如:Ribbon负载均衡。请求会通过先某种算法来决定选择哪个可用的服务器,然后将请求发送到选定的服务器上,以达到均衡负载的目的。






1. Feign

Feign:Spring Cloud提供的一个声明式的伪Http客户端, 它使得调用远程服务就像调用本地服务一样简单, 只需要创建一个接口并添加一个注解即可。

Nacos很好的兼容了Feign, Feign默认集成了 Ribbon, 所以在Nacos下使用Fegin默认就实现了负载均衡的效果。







2. 使用Feign

Feign接口定义要点:

  • @FeignClient(name = "xxxx") 中name为服务提供者在nacos上注册的服务名, 否则报错。

  • @GetMapping("/products/{pid}") 指定接口路径,必须跟服务提供者提供接口url一样,否则报错。

  • 定义接口参数:如果使用了参数路径方式访问,需要使用@PathVariable("pid") 明确指定路径参数,否则报错。

  • 定义接口参数:如果使用普通方式访问,参数需要使用@RequestParam标记,否则报错。

  • 定义接口参数:如果是对象参数,参数需要使用@RequestBody标记(注意fegin接口,controler接口都要),否则报错。

  • 定义接口参数:如果需要进行文件上传,需要使用@RequestPart注解标记。

  • Feign接口调用默认连接时间是1s,如果电脑较慢,开发中可以配置长一点时间

feign:
  client:
    config:
      default:
        connectTimeout: 5000  #连接时间,单位毫秒
        readTimeout: 5000     #操作时间

我们以 SpringCloud Alibaba-3-注册/配置中心 为例,实现用户下单的远程调用


2.1 调整之前的代码,添加新功能

shop-user模块:

	// 根据用户名查询用户信息
	@GetMapping("/getUserInfo/{username}")
	public User getUserInfo(@PathVariable String username){
		LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
		wrapper.eq(User::getUsername, username);
		return userService.getOne(wrapper);
	}


shop-product模块:

	// 根据商品名称获取商品信息
	@GetMapping("/getProductInfo/{pname}")
	public Product getProductInfo(@PathVariable String pname){
		LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();
		wrapper.eq(Product::getPname, pname);
		return productService.getOne(wrapper);
	}
	
	// 保存或更新商品信息
	@PutMapping("/saveOrUpdate}")
	public void saveOrUpdate(@RequestBody Product product){
		productService.saveOrUpdate(product);
	}

2.2 引入Feign

shop-order模块:

  1. 添加Feign依赖
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

  1. 开启feign注解
@EnableFeignClients // 开启feign注解
@EnableDiscoveryClient
@SpringBootApplication
@MapperScan("com.lihao.mapper")
public class ShopOrderApplication {

	public static void main(String[] args) {
		SpringApplication.run(ShopOrderApplication.class, args);
	}

}

  1. 创建Feign接口
@FeignClient(name = "service-product")
public interface ProductServiceApi {
	
	// 根据商品名称获取商品信息
	@GetMapping("/product/getProductInfo/{pname}")
	public Product getProductInfo(@PathVariable String pname);
	
	// 保存或更新商品信息
	@PutMapping("/product/saveOrUpdate}")
	public void saveOrUpdate(@RequestBody Product product);
}




@FeignClient(name = "service-user")
public interface UserServiceApi {
	
	// 根据用户名查询用户信息
	@GetMapping("/user/getUserInfo/{username}")
	public User getUserInfo(@PathVariable String username);
	
}

  1. 通过Feign远程调用
@RestController
@RequestMapping("/order")
public class OrderController {
	
	@Autowired
	private OrderService orderService;
	
	@GetMapping("/list")
	public List<Order> list(){
		return orderService.list();
	}
	
	@GetMapping("/makeOrder/{username}/{pname}/{number}")
	public Order makeOrder(@PathVariable String username,
	                       @PathVariable String pname,
	                       @PathVariable Integer number){
		return orderService.makeOrder(username, pname, number);
	}
}




@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
	
	@Resource
	private ProductServiceApi productServiceApi;
	
	@Resource
	private UserServiceApi userServiceApi;
	
	/**
	 * @param username 用户名
	 * @param pname    商品名
	 * @param number   数量
	 * @return com.lihao.entity.Order
	 * @author hx
	 * @date 2024/3/1 17:22
	 * @apiNote 创建订单
	 **/
	@Override
	public Order makeOrder(String username, String pname, Integer number) {
		
		// 1. 判断产品是否还有相应数量的产品
		Product product = productServiceApi.getProductInfo(pname);
		if (product==null) {
			throw new RuntimeException("该商品不存在");
		}
		
		int count = product.getStock() - number;
		if (count<number) {
			throw new RuntimeException("产品数量不足");
		}
		
		// 2. 获取用户信息
		User userInfo = userServiceApi.getUserInfo(username);
		if (userInfo==null) {
			throw new RuntimeException("该用户不存在");
		}
		
		// 3. 生成订单
		Order order = new Order();
		order.setUid(userInfo.getUid()); // 用户ID
		order.setUsername(username); // 用户名
		order.setPname(pname); // 商品名称
		order.setPprice(product.getPprice() * number); // 商品总价
		order.setNumber(number); // 购买数量
		this.saveOrUpdate(order);
		
		// 4. 更新产品信息
		product.setStock(count - number);
		productServiceApi.saveOrUpdate(product);
		
		return order;
	}
}






3. 查看Feign负载均衡显现

前要:开启2个商品客户端(localhost:8081,localhost:8082),这里说明一下,真实开发服务器可定是部署在不同的服务器中,ip不一样,端口可以一样。此时为学习,只有一台电脑,使用不同端口模拟一下。


	@Value("${server.port}")
	private String port;
	
	// 根据商品名称获取商品信息
	@GetMapping("/getProductInfo/{pname}")
	public Product getProductInfo(@PathVariable String pname){

		System.out.println("当前服务器端口号:"+port);

		LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();
		wrapper.eq(Product::getPname, pname);
		return productService.getOne(wrapper);
	}

现象:成功实现了负载均衡。而且,Feign默认使用的是Rabbion的轮询策略。


我们可以通过修改配置来调整Ribbon的负载均衡策略。
product-service: # 调用的提供者的名称
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule