搭建nacos环境

安装nacos(解压缩、启动)

  1. 下载地址: https://github.com/alibaba/nacos/releases 下载zip格式的安装包,然后进行解压缩操作
  2. 启动nacos

启动方式

  1. #切换目录 cd nacos/bin
  2. #命令启动 startup.cmd -m standalone 或者直接双击startup.cmd运行

登录nacos

  1. http://localhost:8848/nacos
  2. 账号:nacos 密码:nacos

将商品服务注册nacos (服务注册

在pom.xml中添加nacos的依赖

1
2
3
4
5
<!--nacos客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

在主类上添加@EnableDiscoveryClient注解(启动类)

在application.yml中添加nacos服务的地址

1
2
3
4
5
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848

使用nacos

  1. 使用nacos需借用工具类 DiscoveryClient须在启动类上加载 @EnableDiscoveryClient

  2. 使用discoveryClient.getInstances(“service-product”)获取当前nacos中,名叫service-product的元数据(所有数据)

  3. 方法中使用

    1. nacos的一些方法

      1. getInstances(“nacos中的服务名”) 获取nacos中该服务的元数据
      2. getHost() 获取主机号(IP)
      3. getPort() 获取端口
1
2
3
4
5
6
7
8
9
10
11
12
// 从nacos中获取服务地址	
ServiceInstance serviceInstance = discoveryClient.getInstances("service-product").get(0);
String url = serviceInstance.getHost()+":"+serviceInstance.getPort();
ServiceInstance serviceInstance2 = discoveryClient.getInstances("service-user").get(0);
String url2 = serviceInstance2.getHost()+":"+serviceInstance2.getPort();
*log*.info(">>从nacos中获取到的微服务地址为:"+url);
*log*.info(">>从nacos中获取到的微服务地址为url2:"+url2);
// 通过restTemplate调用商品微服务
Product product = restTemplate.getForObject("http://" + url + "/product/" + id, Product.class);
User user = restTemplate.getForObject("http://" + url2 + "/user/" + 2, User.class);
*log*.info("查询到{}号商品的信息,内容是:{}",id, JSON.*toJSONString*(product));
*log*.info("查询到{}号用户的信息,内容是:{}",2, JSON.*toJSONString*(user));

负载均衡

通俗的讲, 负载均衡就是将负载(工作任务,访问请求)进行分摊到多个操作单元(服务器,组件)上进行执行。

根据负载均衡发生位置的不同,一般分为服务端负载均衡客户端负载均衡

服务端负载均衡指的是发生在服务提供者一方,比如常见的nginx负载均衡而客户端负载均衡指的是发生在服务请求的一方,也就是在发送请求之前已经选好了由哪个实例处理请求。

负载均衡

Namespace命名空间

命名空间用于进行隔离,Namespace 的常用场景之一是不同环境的隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。

命名空间管理

前面已经介绍过,命名空间(Namespace)是用于隔离多个环境的(如开发、测试、生产),而每个应用在不同环 境的同一个配置(如数据库数据源)的值是不一样的。因此,我们应针对企业项目实际研发流程、环境进行规划。 如某软件公司拥有开发、测试、生产三套环境,那么我们应该针对这三个环境分别建立三个namespace注册实例

1
2
3
4
5
6
7
8
9
10
11
#将商品服务注册至dev命名空间下
spring:
application:
name: server-product
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 注册中心地址
namespace: 命名空间ID # 开发环境
cluster-name: DEFAULT # 默认集群,可不填写
#注意相互依赖的服务,即有方法调用的服务必须在同一个命名空间下

硬核实现 负载均衡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//下单(负载均衡-硬核实现)
@RequestMapping("/order/prod/{pid}")
public Order order(@PathVariable("pid") Integer pid) {
log.info(">>客户下单,这时候要调用商品微服务查询商品信息");
/************************自定义负载负载均衡器***************************/
List<ServiceInstance> instances = discoveryClient.getInstances("service-product");
int index = new Random().nextInt(instances.size());
ServiceInstance serviceInstance = instances.get(index);
String url = serviceInstance.getHost() + ":" + serviceInstance.getPort();
/************************自定义负载负载均衡器***************************/
log.info(">>从nacos中获取到的微服务地址为:" + url);
//通过restTemplate调用商品微服务
Product product = restTemplate.getForObject( "http://" + url + "/product/" + pid, Product.class);
log.info("查询到{}号商品的信息,内容是:{}", pid, JSON.toJSONString(product));

//下单(创建订单)
Order order = new Order();
order.setUid(1);
order.setUsername("测试用户");
order.setPid(pid);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
order.setNumber(1);

orderService.createOrder(order);

log.info("创建订单成功,订单信息为{}", JSON.toJSONString(order));

return order;
}

Ribbon 实现负载均衡

概念

Ribbon是Spring Cloud的一个组件, 它可以让我们使用一个注解就能轻松的搞定负载均衡

就是当商品服务的负载过大时,将商品服务代码复制多份,同时部署在好几台服务器上,其spring.application.name: 都为同一名称,如有服务器性能不好,则会通过 Ribbon 来实现负载均衡策略

步骤

1.在RestTemplate 的生成方法上添加 @LoadBalanced 注解

2.修改服务调用的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Configuration
public class RestTemplateConfig {
@LoadBalanced
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
//下单(负载均衡-基于Ribbon)
@RequestMapping("/order/prod/{pid}")
public Order order(@PathVariable("pid") Integer pid) {
//直接使用微服务名字, 从nacos中获取服务地址
String url = "service-product";
//通过restTemplate调用商品微服务
Product product = restTemplate.getForObject( "http://" + url + "/product/" + pid, Product.class);

//下单(创建订单)
Order order = new Order();
order.setUid(1);
order.setUsername("测试用户");
order.setPid(pid);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
order.setNumber(1);

orderService.createOrder(order);
return order;
}

Ribbon支持的负载均衡策略

策略名 策略描述 实现说明
BestAvailableRule 选择一个最小的并发 请求的server 逐个考察Server,如果Server被tripped了,则忽略,在选择其中ActiveRequestsCount最小的server
AvailabilityFilteringRule 过滤掉那些因为一直连接失败的被标记为 circuit tripped的后端server,并过滤掉那些 高并发的的后端server(activeconnections 超过配置的阈值) 使用一个AvailabilityPredicate来包含 过滤server的逻辑,其实就就是检查 status里记录的各个server的运行状态
WeightedResponseTimeRule 根据相应时间分配一 个weight,相应时间越长,weight越小,被选中的可能性越低 一个后台线程定期的从status里面读 取评价响应时间,为每个server计算一个 weight。Weight的计算也比较简单responsetime 减去每个server自己平均的 responsetime是server的权重。当刚开始运行,没有形成statas 时,使用 roubine策略选择server
RetryRule 对选定的负载均衡策 略机上重试机制。 在一个配置时间段内当选择server不 成功,则一直尝试使用subRule的方式选择一个可用的server
RoundRobinRule 轮询方式轮询选择 server 轮询index,选择index对应位置的 server
RandomRule 随机选择一个server 在index上随机,选择index对应位置

调整Ribbon的负载均衡策略

我们可以通过修改配置来调整Ribbon的负载均衡策略,具体代码如下 不写则是默认配置: 轮询

1
2
3
service-product: # 调用的提供者的名称 
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

基于Feign实现服务调用

概念

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

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

Feign的使用

1.加入Fegin 依赖

1
2
3
4
5
<!--fegin组件--> 
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2 在主类上添加Fegin的注解 (启动类)

1
@EnableFeignClients//开启Fegin

3 创建一个service, 并使用Fegin实现微服务调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@FeignClient("service-product")//声明调用的提供者的name 就是spring.application.name 属性设置的
public interface ProductService {
//指定调用提供者的哪个方法
//@FeignClient+@GetMapping 就是一个完整的请求路径 http://service- product/product/{pid}
@GetMapping(value = "/product/{pid}")
Product findByPid(@PathVariable("pid") Integer pid);

}

说明:当请求进入对应的接口时,当调用当前业务类方法时,会先进入findByPid 方法中,通过get请求访问
该配置的路径 类似于:
//直接使用微服务名字, 从nacos中获取服务地址
String url = "service-product";
//通过restTemplate调用商品微服务
Product product = restTemplate.getForObject( "http://"+url+"/product/"+pid,Product.class);

4 修改controller代码,并启动验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Autowired
private ProductService productService;

// 下单(Feign)
@RequestMapping("/{pid}")
public Order order(@PathVariable("pid") Integer pid){
log.info(">>客户下单,这时候要调用商品微服务查询商品信息");
// 通过fegin调用商品微服务
Product product = productService.findByPid(pid);
log.info(">>商品信息,查询结果:"+JSON.toJSONString(product));
// 下单(创建订单)
Order order = new Order();
order.setUid(1);
order.setUsername("测试用户");
order.setPid(pid);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
order.setNumber(1);

orderService.saveOrder(order);

log.info("创建订单成功,订单信息为{}",JSON.toJSONString(order));
return order;
}

5 重启order微服务,查看效果

image-20230618231013707