深入了解Alibaba Nacos

Nacos

提示:蓝色字体均为超链接

一定要先搭建好微服务环境点击–>微服务环境搭建
其他相关博客:
微服务介绍博客点击–>微服务介绍
Spring Cloud快速入门博客点击–>Spring Cloud快速入门
服务治理博客点击–>服务治理


# 2. Nacos简介

https://nacos.io/zh-cn/

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。

Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。

2.1 什么是 Nacos?

服务(Service)是 Nacos 世界的一等公民。Nacos 支持几乎所有主流类型的“服务”的发现、配置和管理:

Kubernetes Service

gRPC & Dubbo RPC Service

Spring Cloud RESTful Service

Nacos 的关键特性包括:

  • 服务发现和服务健康监测

    Nacos 支持基于 DNS 和基于 RPC 的服务发现。服务提供者使用 原生SDKOpenAPI、或一个独立的Agent TODO注册 Service 后,服务消费者可以使用DNS TODOHTTP&API查找和发现服务。

    Nacos 提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求。Nacos 支持传输层 (PING 或 TCP)和应用层 (如 HTTP、MySQL、用户自定义)的健康检查。 对于复杂的云环境和网络拓扑环境中(如 VPC、边缘网络等)服务的健康检查,Nacos 提供了 agent 上报模式和服务端主动检测2种健康检查模式。Nacos 还提供了统一的健康检查仪表盘,帮助您根据健康状态管理服务的可用性及流量。

  • 动态配置服务

    动态配置服务可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。

    动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。

    配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。

    Nacos 提供了一个简洁易用的UI (控制台样例 Demo) 帮助您管理所有的服务和应用的配置。Nacos 还提供包括配置版本跟踪、金丝雀发布、一键回滚配置以及客户端配置更新状态跟踪在内的一系列开箱即用的配置管理特性,帮助您更安全地在生产环境中管理配置变更和降低配置变更带来的风险。

  • 动态 DNS 服务

    动态 DNS 服务支持权重路由,让您更容易地实现中间层负载均衡、更灵活的路由策略、流量控制以及数据中心内网的简单DNS解析服务。动态DNS服务还能让您更容易地实现以 DNS 协议为基础的服务发现,以帮助您消除耦合到厂商私有服务发现 API 上的风险。

    Nacos 提供了一些简单的 DNS APIs TODO 帮助您管理服务的关联域名和可用的 IP:PORT 列表.

  • 服务及其元数据管理

    Nacos 能让您从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的 SLA 以及最首要的 metrics 统计数据。

3 Nacos实战入门

3.1 下载安装Nacos

Nacos 依赖 Java 环境来运行。如果您是从代码开始构建并运行Nacos,还需要为此配置 Maven环境,请确保是在以下版本环境中安装使用:

  1. 64 bit OS,支持 Linux/Unix/Mac/Windows,推荐选用 Linux/Unix/Mac。
  2. 64 bit JDK 1.8+;下载 & 配置
  3. Maven 3.2.x+;下载 & 配置

您可以从 最新稳定版本 下载 nacos-server-$version.zip 包。

我们下载:https://github.com/alibaba/nacos/releases/download/2.0.2/nacos-server-2.0.2.zip

# 解压nacos-server-2.0.2.zip
# 进入解压目录,进入bin目录
cd nacos/bin
#命令启动
startup.cmd -m standalone

3.1.1 访问nacos

打开浏览器,输入http://localhost:8848/nacos 即可访问服务,默认密码 nacos/nacos

在这里插入图片描述

3.2 将商品微服务注册到Nacos

3.2.1 添加依赖

 <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  </dependency>

3.2.2 添加@EnableDiscoveryClient

主类上添加

package com.yf.shop.goods;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class GoodsApp {
    
    

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

3.2.3 配置中添加nacos的地址

application.yml

spring:
  application:
    name: shop-goods
  datasource:
    password: root
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
server:
  port: 8002

mybatis-plus:
  global-config:
    db-config:
      table-prefix: shop_

3.2.4 启动服务

在这里插入图片描述

3.3 将用户微服务注册到Nacos

步骤同上:

  <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
package com.yf.shop.user;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class UserApp {
    
    

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

}

spring:
  application:
    name: shop-user
  datasource:
    password: root
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
server:
  port: 8001

mybatis-plus:
  global-config:
    db-config:
      table-prefix: shop_

在这里插入图片描述

3.4 将订单微服务注册到Nacos

 <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
package com.yf.shop.order;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class OrderApp {
    
    

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

spring:
  application:
    name: shop-order
  datasource:
    password: root
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
server:
  port: 8003

mybatis-plus:
  global-config:
    db-config:
      table-prefix: shop_

在这里插入图片描述

3.5 在APP应用服务中添加Nacos

3.5.1 添加依赖

    <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

3.5.2 配置

package com.yf.shop.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class App {
    
    

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

spring:
  application:
    name: shop-app
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
server:
  port: 8004

3.5.3 使用nacos调用微服务

package com.yf.shop.app.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestConfig {
    
    

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(ClientHttpRequestFactory factory){
    
    
        RestTemplate restTemplate = new RestTemplate(factory);
        return restTemplate;
    }
    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
    
    
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(5000);//单位为ms
        factory.setConnectTimeout(5000);//单位为ms
        return factory;
    }

}

package com.yf.shop.app.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.yf.common.Result;
import com.yf.common.goods.GoodsBO;
import com.yf.common.order.params.OrderParams;
import com.yf.common.user.UserBO;
import com.yf.shop.app.model.params.BuyParams;
import com.yf.shop.app.service.BuyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.math.BigDecimal;
import java.util.Map;

@Service
public class BuyServiceImpl implements BuyService {
    
    
    @Autowired
    private RestTemplate restTemplate;

    private String shopUserName = "shop-user";
    private String shopGoodsName = "shop-goods";
    private String shopOrderName = "shop-order";

    @Override
    public Result submitOrder(BuyParams buyParams) {
    
    
        String userResult = restTemplate.getForObject("http://"+shopUserName+"/user/findUser/" + buyParams.getUserId(), String.class);
        Result<UserBO> userBOResult = JSON.parseObject(userResult,new TypeReference<Result<UserBO>>(){
    
    });
        if (userBOResult == null || !userBOResult.isSuccess() || userBOResult.getData() == null){
    
    
            return Result.fail(10001,"用户不存在");
        }
        UserBO userBO = userBOResult.getData();

        String goodsResult = restTemplate.getForObject("http://"+shopGoodsName+"/goods/findGoods/" + buyParams.getGoodsId(), String.class);
        Result<GoodsBO> goodsBOResult = JSON.parseObject(goodsResult,new TypeReference<Result<GoodsBO>>(){
    
    });

        if (goodsBOResult == null || !goodsBOResult.isSuccess() || goodsBOResult.getData() == null){
    
    
            return Result.fail(10002,"商品不存在");
        }
        GoodsBO goodsBO = (GoodsBO) goodsBOResult.getData();
        Integer goodsStock = goodsBO.getGoodsStock();
        if (goodsStock < 0){
    
    
            return Result.fail(10003,"商品库存不足");
        }
        BigDecimal goodsPrice = goodsBO.getGoodsPrice();
        BigDecimal account = userBO.getAccount();
        if (account.compareTo(goodsPrice) < 0){
    
    
            return Result.fail(10004,"余额不足");
        }
        OrderParams orderParams = new OrderParams();
        orderParams.setUserId(userBO.getId());
        orderParams.setGoodsId(goodsBO.getId());
        orderParams.setGoodsPrice(goodsBO.getGoodsPrice());
        String orderResult = restTemplate.postForObject("http://"+shopOrderName+"/order/createOrder", orderParams, String.class);
        Result<String> orderResultString = JSON.parseObject(orderResult,new TypeReference<Result<String>>(){
    
    });
        if (orderResultString == null || !orderResultString.isSuccess()){
    
    
            return Result.fail(10005,"下单失败");
        }
        String orderId =  orderResultString.getData();

        return Result.success(orderId);
    }
}

通过以上的代码,我们发现各个微服务的地址,不需要硬编码在代码中了,通过在nacos中注册的微服务名称即可访问微服务。

在RestTemplate上 我们加了一个@LoadBalanced注解,熟悉英文的小伙伴们,一定猜出来了,这是做负载均衡的,也就是说 如果微服务部署集群的话,服务调用方通过nacos可以很轻松的访问服务提供者,而不需要考虑到底应该访问哪台机器,nacos帮我们做了这件事情。

3.6 负载均衡

3.6.1 什么是负载均衡?

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

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

服务端负载均衡指的是发生在服务提供者一方,比如常见的nginx负载均衡。

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

在这里插入图片描述

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

3.6.2 基于Ribbon实现负载均衡

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

上面例子中,我们使用的@LoadBalanced 就是使用了Ribbon的负载均衡。

我们再起一个商品的微服务,验证一下负载均衡:

  1. 将原有的商品微服务Controller 打印一个语句,便于验证
package com.yf.shop.goods.controller;

import com.mszlu.common.Result;
import com.mszlu.shop.goods.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("goods")
public class GoodsController {
    
    

    @Autowired
    private GoodsService goodsService;

    @GetMapping("findGoods/{id}")
    public Result findGoods(@PathVariable("id") Long id){
    
    
        System.out.println("调用了111111111的商品微服务");
        return goodsService.findGoodsById(id);
    }
}

  1. 更改打印语句,并更改端口号,在启动一个商品微服务:

    package com.yf.shop.goods.controller;
    
    import com.yf.common.Result;
    import com.yf.shop.goods.service.GoodsService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("goods")
    public class GoodsController {
          
          
    
        @Autowired
        private GoodsService goodsService;
    
        @GetMapping("findGoods/{id}")
        public Result findGoods(@PathVariable("id") Long id){
          
          
            System.out.println("调用了22222222222的商品微服务");
            return goodsService.findGoodsById(id);
        }
    }
    
    
    
    
 server:
   port: 8012
  1. 启动,并查看nacos

在这里插入图片描述
在这里插入图片描述

  1. 启动App并进行多次调用

在这里插入图片描述
在这里插入图片描述

我们发现,执行http://localhost:8004/buy/submit四次,两个商品微服务分别调用了两次,四次的访问被两个商品微服务进行了分摊。

3.6.3 Ribbon支持的负载均衡策略

Ribbon内置了多种负载均衡策略,内部负载均衡的顶级接口为com.netflix.loadbalancer.IRule,具体的负载策略如下所示:

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

我们可以通过配置来调整Ribbon的负载均衡策略:

加在app端

shop-goods:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

3.7 基于Feign实现服务调用

3.7.1 什么是Feign?

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

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

3.7.2 Feign的使用

  1. 加入Feign的依赖

    <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
    
  2. 在主类上添加Feign的注解

    package com.yf.shop.app;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableFeignClients
    public class App {
          
          
    
        public static void main(String[] args) {
          
          
            SpringApplication.run(App.class,args);
        }
    }
    
    
  3. 创建各个微服务的feign接口,实现feign的微服务调用

    package com.yf.shop.app.feign;
    
    import com.yf.common.Result;
    import com.yf.common.user.UserBO;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    @FeignClient("shop-user")
    public interface UserFeign {
          
          
    
        //调用路径同http访问路径
        @GetMapping("/user/findUser/{id}")
        public Result<UserBO> findUser(@PathVariable("id") Long id);
    }
    
    
    package com.yf.shop.app.feign;
    
    import com.yf.common.Result;
    import com.yf.common.goods.GoodsBO;
    import com.yf.common.user.UserBO;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    @FeignClient("shop-goods")
    public interface GoodsFeign {
          
          
    
        //调用路径同http访问路径
        @GetMapping("/goods/findGoods/{id}")
        public Result<GoodsBO> findGoods(@PathVariable("id") Long id);
    }
    
    
    package com.yf.shop.app.feign;
    
    import com.yf.common.Result;
    import com.yf.common.goods.GoodsBO;
    import com.yf.common.order.params.OrderParams;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    
    @FeignClient("shop-order")
    public interface OrderFeign {
          
          
    
        //调用路径同http访问路径
        @PostMapping("/order/createOrder")
        public Result<String> createOrder(@RequestBody OrderParams orderParams);
    }
    
    
  4. 更改BuyService的实现,使用feign来完成调用

    @Autowired
        private UserFeign userFeign;
        @Autowired
        private GoodsFeign goodsFeign;
        @Autowired
        private OrderFeign orderFeign;
    
        @Override
        public Result submitOrder(BuyParams buyParams) {
          
          
            Result<UserBO> userBOResult = userFeign.findUser(buyParams.getUserId());
            if (userBOResult == null || !userBOResult.isSuccess() || userBOResult.getData() == null){
          
          
                return Result.fail(10001,"用户不存在");
            }
            UserBO userBO = userBOResult.getData();
            log.info("userBo:{}",userBO);
    
            Result<GoodsBO> goodsBOResult = goodsFeign.findGoods(buyParams.getGoodsId());
            log.info("goodsBOResult:{}",goodsBOResult);
            if (goodsBOResult == null || !goodsBOResult.isSuccess() || goodsBOResult.getData() == null){
          
          
                return Result.fail(10002,"商品不存在");
            }
            GoodsBO goodsBO = goodsBOResult.getData();
            Integer goodsStock = goodsBO.getGoodsStock();
            if (goodsStock < 0){
          
          
                return Result.fail(10003,"商品库存不足");
            }
            BigDecimal goodsPrice = goodsBO.getGoodsPrice();
            BigDecimal account = userBO.getAccount();
            if (account.compareTo(goodsPrice) < 0){
          
          
                return Result.fail(10004,"余额不足");
            }
            OrderParams orderParams = new OrderParams();
            orderParams.setUserId(userBO.getId());
            orderParams.setGoodsId(goodsBO.getId());
            orderParams.setGoodsPrice(goodsBO.getGoodsPrice());
            Result<String> orderResultString = orderFeign.createOrder(orderParams);
            log.info("orderResultString:{}",orderResultString);
            if (orderResultString == null || !orderResultString.isSuccess()){
          
          
                return Result.fail(10005,"下单失败");
            }
            String orderId =  orderResultString.getData();
    
            return Result.success(orderId);
        }
    
  5. 重启APP服务,测试

    整合上feign之后,我们发现,整个微服务的开发变的开始舒服起来。

猜你喜欢

转载自blog.csdn.net/weixin_51250404/article/details/120646816