9. SpringCloudAlibaba 实践笔记:负载均衡项目集成
使用负载均衡的前提是每一个服务提供者服务有多个实例,所以第一步,应该对用户微服务和商品微服务启动多个实例
启动多服务实例
启动多个用户微服务
启动用户微服务时,默认监听的端口为806,如果需要启动多个服务,需要在启动时设置自定义的监听端口。
在 IDEA 的 Edit Configuration 中点击 modify options,添加 VM Options,然后在 VM Options 加上服务端口

启动后,刷新 Nacos,发现 server-user 已经有两个实例

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

实现自定义负载均衡
在 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("库存扣减失败");
}

浙公网安备 33010602011771号