远程调用:在分布式系统中,我们使用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模块:
- 添加Feign依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 开启feign注解
@EnableFeignClients // 开启feign注解
@EnableDiscoveryClient
@SpringBootApplication
@MapperScan("com.lihao.mapper")
public class ShopOrderApplication {
public static void main(String[] args) {
SpringApplication.run(ShopOrderApplication.class, args);
}
}
- 创建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);
}
- 通过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