9. SpringCloudAlibaba 实践笔记:负载均衡项目集成

使用负载均衡的前提是每一个服务提供者服务有多个实例,所以第一步,应该对用户微服务和商品微服务启动多个实例

启动多服务实例

启动多个用户微服务

启动用户微服务时,默认监听的端口为806,如果需要启动多个服务,需要在启动时设置自定义的监听端口。
在 IDEA 的 Edit Configuration 中点击 modify options,添加 VM Options,然后在 VM Options 加上服务端口
image
启动后,刷新 Nacos,发现 server-user 已经有两个实例
image

启动多个商品微服务

以启动多个用户服务为例,启动两个商品微服务。
启动后,刷新 Nacos,发现 server-product 已经有两个实例。
image

实现自定义负载均衡

在 shop-order 的 OrderServiceImpl 类的方法 getServiceUrl() 添加负载均衡逻辑。
修改前:

private String getServiceUrl(String serviceName){
    ServiceInstance serviceInstance = discoveryClient.getInstances(serviceName).get(0);
    return serviceInstance.getHost() + ":" + serviceInstance.getPort();
}

修改后:

private String getServiceUrl(String serviceName){
    List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
    int index = new Random().nextInt(instances.size());
    ServiceInstance serviceInstance = instances.get(index);
    String url = serviceInstance.getHost() + ":" + serviceInstance.getPort();
    log.info("负载均衡后的服务地址为:{}", url);
    return url;
}
  • 首先通过服务的名称在Nacos中获取到所有的服务实例列表,然后使用随机函数随机生成一个0到服务列表长度减1的整数,而这个随机生成的整数恰好在服务实例列表的下标范围内。
  • 然后以这个整数作为下标获取服务列表中的某个服务实例。从而以随机的方式实现了负载均衡,并从获取到的服务实例中获取到服务实例所在服务器的IP地址和端口号,拼接成IP:PORT的形式返回。

修改完成后,启动服务,调用多次提交订单接口,得到的日志如下:

#第一次调用
m.c.s.o.service.impl.OrderServiceImpl    : 负载均衡后的服务地址为:192.168.1.108:8060
m.c.s.o.service.impl.OrderServiceImpl    : 负载均衡后的服务地址为:192.168.1.108:8070
#第二次调用
m.c.s.o.service.impl.OrderServiceImpl    : 负载均衡后的服务地址为:192.168.1.108:8061
m.c.s.o.service.impl.OrderServiceImpl    : 负载均衡后的服务地址为:192.168.1.108:8070
  • 两次调用用户服务端端口不一样。

使用 Ribbon 实现负载均衡

修改 LoadBalanceConfig 代码

在订单微服务中的 LoadBalanceConfig 类的 restTemplate()方法上添加@LoadBalanced注解。

@Bean
@LoadBalanced
public RestTemplate restTemplate(){
    return new RestTemplate();
}

修改 OrderService 代码

在 OrderServiceImpl 类中删除自定义负载均衡的代码:

@Autowired
private DiscoveryClient discoveryClient;

private String getServiceUrl(String serviceName){
    List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
    int index = new Random().nextInt(instances.size());
    ServiceInstance serviceInstance = instances.get(index);
    String url = serviceInstance.getHost() + ":" + serviceInstance.getPort();
    log.info("负载均衡后的服务地址为:{}", url);
    return url;
}

在 saveOrder()方法中删除获取服务 URL 的代码,交给 Ribbon 来实现负载均衡:

//从Nacos服务中获取用户服务与商品服务的地址
String userUrl = this.getServiceUrl(userServer);
String productUrl = this.getServiceUrl(productServer);

修改 saveOrder() 方法调用微服务的方法

User user = restTemplate.getForObject("http://" + userServer + "/user/get/" + orderParams.getUserId(), User.class);
if (user == null){
    throw new RuntimeException("未获取到用户信息: " + JSONObject.toJSONString(orderParams));
}
Product product = restTemplate.getForObject("http://" + productServer + "/product/get/" + orderParams.getProductId(), Product.class);
if (product == null){
    throw new RuntimeException("未获取到商品信息: " + JSONObject.toJSONString(orderParams));
}

//#####################此处省略N行代码#########################

Result<Integer> result = restTemplate.getForObject("http://" + productServer + "/product/update_count/" + orderParams.getProductId() + "/" + orderParams.getCount(), Result.class);
if (result.getCode() != HttpCode.SUCCESS){
    throw new RuntimeException("库存扣减失败");
}
  • 将 userUrl 改成 userServer ,productUrl 改成 productServer。直接使用 userServer,Ribbon 会从 Nacos 获取服务列表并选择其中的服务进行服务调用。

使用 Feign 实现负载均衡

Feign 是一个声明式的 Web 服务客户端,它使得编写 Web 服务客户端变得更加简单。Feign 通过使用注解的方式来定义和调用远程服务,使得客户端可以通过调用本地服务的方式实现远程服务调用。无需手动编写大量的 HTTP 请求代码。

添加依赖

在订单微服务的pom.xml文件中添加Fegin相关的依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

修改启动类

在订单微服务的启动类 OrderStarter 上添加 @EnableFeignClients 注解,启动 Feign 客户端

@SpringBootApplication
@EnableTransactionManagement(proxyTargetClass = true)
@MapperScan(value = { "io.binghe.shop.order.mapper" })
@EnableDiscoveryClient
@EnableFeignClients
public class OrderStarter {
    public static void main(String[] args){
        SpringApplication.run(OrderStarter.class, args);
    }
}  

定义上游服务接口

在订单微服务新建 UserService 接口,并在 UserService 接口上标注@FeignClient("server-user")注解,其中注解的value属性为用户微服务的服务名称。

@FeignClient("server-user")
public interface UserService {
    @GetMapping(value = "/user/get/{uid}")
    User getUser(@PathVariable("uid") Long uid);
}

新建 ProductService 接口,并在 ProductService 接口上标注 @FeignClient("server-product") 注解,其中注解的value属性为商品微服务的服务名称。

@FeignClient("server-product")
public interface ProductService {

    /**
     * 获取商品信息
     */
    @GetMapping(value = "/product/get/{pid}")
    Product getProduct(@PathVariable("pid") Long pid);

    /**
     * 更新库存数量
     */
    @GetMapping(value = "/product/update_count/{pid}/{count}")
    Result<Integer> updateCount(@PathVariable("pid") Long pid, @PathVariable("count") Integer count);
}

修改 OrderService 代码

修改 OrderServiceImpl 类的代码,删除以下代码:

@Autowired
private RestTemplate restTemplate;

private String userServer = "server-user";
private String productServer = "server-product";

添加上游服务接口。

@Autowired
private UserService userService;
@Autowired
private ProductService productService;

修改 OrderServiceImpl 类中 saveOrder()的代码,将使用 restTemplate 调用服务的代码全部改成使用服务接口调用。

User user = userService.getUser(orderParams.getUserId()); // restTemplate -> 接口调用
if (user == null){
    throw new RuntimeException("未获取到用户信息: " + JSONObject.toJSONString(orderParams));
}
Product product = productService.getProduct(orderParams.getProductId()); // restTemplate -> 接口调用
if (product == null){
    throw new RuntimeException("未获取到商品信息: " + JSONObject.toJSONString(orderParams));
}

//##################此处省略N行代码########################

Result<Integer> result = productService.updateCount(orderParams.getProductId(), orderParams.getCount()); // restTemplate -> 接口调用
if (result.getCode() != HttpCode.SUCCESS){
    throw new RuntimeException("库存扣减失败");
}
posted @ 2024-11-06 22:56  Jacob-Chen  阅读(95)  评论(0)    收藏  举报