SpringCloud——关于Nacos

Nacos

理解nacos首先要清楚微服务的概念。

首先,微服务是什么

微服务架构, 简单的说就是将单体应用进一步拆分,拆分成更小的服务,每个服务都是一个可以独立运行的项目。

所以,微服务架构就会面临一些问题?

1、服务间的服务调用需要ip+端口号,一旦修改,相关代码就要全部修改

2、无法实现负载均衡

什么是负载均衡

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

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

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

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

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

所以为了解决上述问题,就需要通过注册中心动态实现服务治理!!!

什么是服务治理

服务治理是微服务架构中最核心最基本的模块。用于实现各个微服务的 自动化注册与发现 。

服务注册: 在服务治理框架中,都会构建一个注册中心,每个服务单元向注册中心登记自己提供服务的详细信息。并在注册中心形成一张服务的清单,服务注册中心需要以心跳的方式去监测清单中的服务是否可用,如果不可用,需要在服务清单中剔除不可用的服务。

服务发现: 服务调用方向服务注册中心咨询服务,并获取所有服务的实例清单,实现对具体服务实例的访问。

通过上面的调用图会发现,除了微服务,还有一个组件是 服务注册中心 ,它是微服务架构非常重要的一个组件,在微服务架构里主要起到了协调者的一个作用。注册中心一般包含如下几个功能:

1. 服务发现:

服务注册:保存服务提供者和服务调用者的信息

服务订阅:服务调用者订阅服务提供者的信息,注册中心向订阅者推送提供者的信息

2. 服务配置:

配置订阅:服务提供者和服务调用者订阅微服务相关的配置

配置下发:主动将配置推送给服务提供者和服务调用者

3. 服务健康检测

检测服务提供者的健康情况,如果发现异常,执行服务剔除

Nacos就是微服务架构里的服务治理组件!!可以作为注册中心,负责注册管理架构中各个微服务

💡 其实可以将Nacos想象成为一个HashMap,注册的服务名称作为map的键,服务的ip+端口号作为它的值,通过访问key就可以获得服务的ip和端口号。

💡 zookeeper也是一种服务治理组件

Nacos实现

1、首先要下载nacos,打开/bin目录下的startup.cmd运行nacos

https://github.com/alibaba/nacos/releases

nacos默认端口号是8848,此时可以通过localhost:8848访问nacos,用户名和密码都是naocs

2、在pom.xml中添加nacos依赖

<!--这个是添加在你要注册的微服务模块的pom.xml中的 nacos客户端-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

==========================================

 <spring-cloud-alibaba.version>2.1.0.RELEASE</spring-cloud-alibaba.version>

dependencyManagementd的dependencies

<!--            注意解开 父pom中的这个管理alibaba cloud 组件版本的管理依赖-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

3、在启动类上添加注解@EnableDiscoveryClient

4、在application.yml中写入nacos访问网址,以及此服务名称

# nacos将会把application.name作为注册中心的代表该服务的键
spring:
  application:
    name: shop-product
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

5、修改Controller代码实现微服务调用

/**
     * 2.0版本, 通过discoveryClient, 动态的获取服务提供方(商品)的ip+port
     * @param pid
     * @return
     */
    @GetMapping("/order/{productid}")
    public ShopOrder addOrder(@PathVariable("productid") Integer pid){

        //创建一个订单
        ShopOrder order = new ShopOrder();
        order.setPid(pid);

        RestTemplate restTemplate = new RestTemplate();

        //根据服务名称, 想nacos注册中心索要对应的ip+port信息
        List<ServiceInstance> shopProductInfoList = discoveryClient.getInstances("shop-product");
        ServiceInstance serviceInstance = shopProductInfoList.get(0);
        String ip = serviceInstance.getHost();
        int port = serviceInstance.getPort();
        System.out.println("ip = " + ip);
        System.out.println("port = " + port);

        ResponseEntity<ShopProduct> productResponseEntity = restTemplate.getForEntity("http://"+ip + ":" + port+"/product/" + pid, ShopProduct.class);
        ShopProduct product = productResponseEntity.getBody();
        System.out.println("我是服务调用方, 我查询到了服务提共方返回给我的商品所有数据" + product);

        order.setPprice(product.getPprice());
        order.setPname(product.getPname());

        return order;
    }

最基础的Nacos服务就实现了!!

但此时的代码仅解决了我们一开始提出的第一个问题,第二个负载均衡的问题还没有解决。

实现负载均衡

基于Ribbon实现负载均衡

Ribbon是SpringCloud的一个组件,可以通过一个注解实现负载均衡

1、在RestTemplate生成类上添加注解@LoadBalanced

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

2、修改Controller代码

package com.itszt20.springclouddemo.shoporder.controller;

import com.itszt20.springclouddemo.shopcommon.entity.Order;
import com.itszt20.springclouddemo.shopcommon.entity.Product;
import com.itszt20.springclouddemo.shoporder.service.IShopOrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@RestController
public class OrderController {

    @Resource
    private IShopOrderService shopOrderService;

    @Resource
    private RestTemplate restTemplate;

//    @Resource
//    private DiscoveryClient discoveryClient;

//    private static AtomicInteger i = new AtomicInteger(0);

//    private static final Object lock = new Object();

    @GetMapping("/order/product/{id}")
    public ResponseEntity addOrder(@PathVariable("id") Integer id){

        Order order = new Order();
        order.setPid(id);

        //当前操作用户的id从session当中取到
        order.setUid(123);

        //本次订单中该商品买了多少个 默认1个
        order.setNumber(1);

        //从代表服务的集合中挑选出来一个 就是所谓的当前环境下的负载均衡
        // 1. 随机  2. 轮询

        //1. 随机
//        Random random = new Random();
//        int serviceIndex = random.nextInt(serviceInstanceList.size());
//        ServiceInstance serviceInstance = serviceInstanceList.get(serviceIndex);
//
//        String host = serviceInstance.getHost();
//        int port = serviceInstance.getPort();

        //2.轮询
        //  标记本次被调用的服务 然后每次只调用未被标记服务  在所有服务均被标记后 一次性全部清除标记

//        final List[] listArray = new List[1];
//        synchronized (lock) {
//            listArray[0] = discoveryClient.getInstances("shop-product");
//        }
//
//        int newIndex = i.getAndUpdate((serviceIndex) -> {
//            if (serviceIndex + 1 >= listArray[0].size()) {
//                return 0;
//            } else {
//                return serviceIndex + 1;
//            }
//        });
//
//        List<ServiceInstance> serviceInstanceList = listArray[0];
//        ServiceInstance serviceInstance = serviceInstanceList.get(newIndex);
//
//        String host = serviceInstance.getHost();
//        int port = serviceInstance.getPort();
//
//        log.info("调用服务的ip[{}],端口[{}]", host, port);

//        todo 商品的名称和价格 需要通过http的方式调用商品的根据id查找商品这个服务
        Product goods = restTemplate.getForObject(String.format("<http://shop-product/product/%d>", id), Product.class);
        order.setPname(goods.getPname());
        order.setPprice(goods.getPprice());

        shopOrderService.saveOrder(order);
        return ResponseEntity.ok().build();
    }
}

Ribbon支持的负载均衡策略

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

我们可以通过修改配置来调整Ribbon的负载均衡策略,具体代码如下

#写在服务调用方的配置
shop-product: # 调用的提供者的名称
    ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
ribbon:
 ConnectTimeout: 2000 #超时时间
 ReadTimeout: 5000 #请求处理的超时时间

基于Feign实现负载均衡

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

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

Feign的使用 [重要]

注意: feign是http调用, 自然是配置在http的调用方

1 加入Fegin的依赖

        <!--fegin组件-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

<!--加在父pom的dependencymanagement中-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

2 在主类上添加Fegin的注解@EnableFeignClients

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients//开启Fegin
public class OrderApplication {}

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

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


@FeignClient("shop-product")//声明调用的提供者的name
public interface ProductService {
    //指定调用提供者的哪个方法
//@FeignClient+@GetMapping 就是一个完整的请求路径 <http://shop-product/product/{pid}>
    @GetMapping(value = "/product/{pid}")
    Product findByPid(@PathVariable("pid") Integer pid);
}

@RestController
@Slf4j
public class OrderController {
    @Autowired
    private IOrderService orderService;
    @Autowired
    private IProductService productService;

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值