springcloud第一讲

目录

一、SpringCloud01

1.1 单体应用框架

1.2 微服务应用

1.2.1 微服务介绍

1.2.2 微服务架构的优势

1.2.3 微服务架构面临的挑战

1.3 SpringCloud与微服务的关系

1.4 SpringBoot和SpringCloud关系

1.5 搭建微服务架构

1.5.1 创建父工程

1.5.2 创建公共模块

1.5.3 创建商品微服务模块

1.5.4 创建订单微服务模块

1.6 服务治理组件

1.6.1 服务治理组件介绍

1.6.2 常见的服务治理组件

1.6.3 使用nacos搭建服务端[注册中心]

1.6.4 微服务接入到注册中心

1.6.5 通过注册中心解决硬编码问题

1.7 负载均衡

1.7.1 如何实现负载均衡

1.7.2 ribbon负载均衡组件

1.7.3 ribbon的负载均衡策略

1.8 OpenFeign完成服务调用

​编辑

1.8.1 OpenFeign介绍

1.8.2 如何使用OpenFeign

一、SpringCloud01

1.1 单体应用框架

单体框架结构如下

单体应用架构:将项目所有模块(功能)打成jar或者war,然后部署一个进程

优点: 1:部署简单:由于是完整的结构体,可以直接部署在一个服务器上即可。 2:技术单一:项目不需要复杂的技术栈,往往一套熟悉的技术栈就可以完成开发。

缺点: 1:系统启动慢,一个进程包含了所有的业务逻辑,涉及到的启动模块过多,导致系统的启动、重启时间周期过长;

2:系统错误隔离性差、可用性差,任何一个模块的错误均可能造成整个系统的宕机;

3:可伸缩性差:系统的扩容只能只对这个应用进行扩容,无法结合业务模块的特点进行伸缩。

4: 线上问题修复周期长:任何一个线上问题修复需要对整个应用系统进行全面升级。

5: 跨语言程度差

6: 不利于安全管理,所有开发人员都拥有全量代码

1.2 微服务应用

1.2.1 微服务介绍

在国外,习惯是先出论文再做产品,微服务架构也不例外

微服务架构论文: Microservices

译文: 微服务译文理解_微服务架构译文_发了个版的博客-CSDN博客

In short, the microservice architectural style [1] is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.

简单来说,微服务架构风格[1]是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级通信机制(通常用HTTP资源API)。这些服务围绕业务能力构建并且可通过全自动部署机制独立部署。这些服务共用一个最小型的集中式的管理,服务可用不同的语言开发,使用不同的数据存储技术。

微服务的特点:

1:微服务是一种项目架构思想(风格)

2:微服务架构是一系列小==服务==的组合(组件化与多服务)

3:任何一个微服务,都是一个独立的进程(独立开发、独立维护、独立部署)

4:轻量级通信http协议(跨语言,跨平台)

5:服务粒度(围绕业务功能拆分)

6:去中心化管理(去中心化地治理技术、去中心化地管理数据)

1.2.2 微服务架构的优势

1.易于开发和维护 一个微服务只关注一个特定的业务功能,所以它的业务清晰、代码量较少。开发和维护单个微服务相对比较简单,整个应用是由若干个微服务构建而成,所以整个应用也会维持在可控状态;

⒉.单个微服务启动较快 单个微服务代码量较少,所以启动会比较快;

3.局部修改容易部署 单体应用只要有修改,就要重新部署整个应用,微服务解决了这样的问题。一般来说,对某个微服务进行修改,只需要重新部署这个服务即可;

4.技术栈不受限 在微服务中,我们可以结合项目业务及团队的特点,合理地选择技术栈

5.按需伸缩

如果需要搭建集群,那么就只需要对某个服务搭建集群即可

1.2.3 微服务架构面临的挑战

1、服务太多,导致服务间的依赖错综复杂,运维难度大

2、微服务放大了分布式架构的系列问题

  • 分布式事务(seata)

  • 分布式锁怎么处理(redisson) ,

  • 服务注册与发现(nacos) .

  • 依赖服务不稳定(sentinel)导致服务雪崩怎么办?

3、运维复杂度陡增,部署数量多、监控进程多导致整体运维复杂度提升。

1.3 SpringCloud与微服务的关系

1) Springcloud为微服务思想提供了完美的解决方案

2) Springcloud是一些列框架的集合体(服务的注册与发现【注册中心】、服务间远程调用、服务降级、服务熔断、服务限流、分布式事务等)

一般我们说springcloud 其实指的是Springc1oud-netflix[netflix],Springcloud并不是造轮子,只是把Netflix公司的组件做二次开发. netflix对这些组件不在维护了,停止更新。 springclou-alibaba,也是把阿里巴巴公司的组件做了二次开发。

1.4 SpringBoot和SpringCloud关系

SpringBoot专注于快速方便的开发单个个体微服务。

SpringCloud是关注全局的微服务协调、整理、治理的框架,它将SpringBoot开发的单体整合并管理起来。

SpringBoot可以离开SpringCloud独立使用开发项目,但是SpringCloud离不开SpringBoot,属于依赖关系。

1.5 搭建微服务架构

使用springboot+springcloud+alibaba+mybatis+maven搭建一个微服务架构

其中有两个服务:订单微服务---->商品微服务

(多个服务的关系也和两个服务的关系一样)

1.5.1 创建父工程

1、创建一个SpringBoot项目

删除src目录:父工程用来加载jar包

修改pom文件中的打包方式为pom方式:父工程会把子工程统一打包或清理

<!--该工程为父工程打包方式就是pom模式-->
    <packaging>pom</packaging>

2、引入相关依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.aaa</groupId>
    <artifactId>springcloud</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <!--该工程为父工程打包方式就是pom模式-->
    <packaging>pom</packaging>
    <name>springcloud</name>
    <description>springcloud</description>
​
    <!--定义版本号-->
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <!--springcloud的版本号-->
        <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
        <!--阿里巴巴的版本号-->
        <spring-cloud-alibaba.version>2.2.3.RELEASE</spring-cloud-alibaba.version>
        <!--cloud和alibaba以及boot之间的版本一定要符合要求-->
    </properties>
​
    <!--dependencyManagement:它只负责jar的版本号管理 不负责jar的下载-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <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>
        </dependencies>
    </dependencyManagement>
​
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

在这项目里,SpringBoot、springcloud、alibaba的版本号不能相差太多

下表为按时间顺序发布的 Spring Cloud Alibaba 以及对应的适配 Spring Cloud 和 Spring Boot 版本关系(由于 Spring Cloud 版本命名有调整,所以对应的 Spring Cloud Alibaba 版本号也做了对应变化)

1.5.2 创建公共模块

公共模块里面放置工具类、微服务模块之间的公共实体类等

1、创建一个子模块当做公共模块

子模块名字为springcloud-common

创建完子模块之后,父模块的pom文件中会自动添上子模块

 <modules>
     <module>springcloud-common</module>
 </modules>

2、添加依赖

    <!--加入依赖-->
    <dependencies>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.56</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>

3、添加实体类

因为是微服务项目,所以每个不同的微服务模块都对应着自己的数据库

数据库:springcloud-order

DROP TABLE IF EXISTS `shop_order`;
CREATE TABLE `shop_order`  (
  `oid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单id',
  `uid` int(11) NULL DEFAULT NULL COMMENT '用户id',
  `username` varchar(255) CHARACTER SET utf8mb4  NULL DEFAULT NULL COMMENT '用户名',
  `pid` bigint(20) NULL DEFAULT NULL COMMENT '商品id',
  `pname` varchar(255) CHARACTER SET utf8mb4  NULL DEFAULT NULL COMMENT '商品名称',
  `pprice` decimal(10, 2) NULL DEFAULT NULL COMMENT '商品价格',
  `number` int(11) NULL DEFAULT NULL COMMENT '购买数量',
  PRIMARY KEY (`oid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 0 CHARACTER SET = utf8mb4 ;

数据库:springcloud-product

DROP TABLE IF EXISTS `shop_product`;
CREATE TABLE `shop_product`  (
  `pid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品id',
  `pname` varchar(255) CHARACTER SET utf8mb4  NULL DEFAULT NULL COMMENT '商品名',
  `pprice` decimal(10, 2) NULL DEFAULT NULL COMMENT '商品价格',
  `stock` varchar(255) CHARACTER SET utf8mb4  NULL DEFAULT NULL COMMENT '商品库存',
  PRIMARY KEY (`pid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 0 CHARACTER SET = utf8mb4 ;
​
INSERT INTO `shop_product` VALUES (1, '华为手机', 3999.22, '100');
INSERT INTO `shop_product` VALUES (2, 'vivo手机', 2999.00, '100');
INSERT INTO `shop_product` VALUES (3, '小敏', 2222.00, '1000');
​
SET FOREIGN_KEY_CHECKS = 1;

ShopProduct实体类:

import java.io.Serializable;
​
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
​
@TableName(value = "shop_product")
@Data
//@ApiModel(value = "ShopProduct)", description = "")
public class ShopProduct implements Serializable {
 private static final long serialVersionUID = 715658691036740509L;
 /**
 * 商品id
 */
@TableId(value = "pid",type = IdType.AUTO)
private Long pid;
​
//@ApiModelProperty(value = "商品名")
private String pname;
​
//@ApiModelProperty(value = "商品价格")
private Double pprice;
​
//@ApiModelProperty(value = "商品库存")
private String stock;
}

ShopOrder实体类:

package com.aaa.entity;
​
import java.io.Serializable;
​
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
​
@TableName(value = "shop_order")
@Data
//@ApiModel(value = "ShopOrder)", description = "")
public class ShopOrder implements Serializable {
 private static final long serialVersionUID = -65703217521155950L;
 /**
 * 订单id
 */
 @TableId(value = "oid",type = IdType.AUTO)
private Long oid;
//@ApiModelProperty(value = "用户id")
private Integer uid;
​
//@ApiModelProperty(value = "用户名")
private String username;
​
//@ApiModelProperty(value = "商品id")
private Long pid;
​
//@ApiModelProperty(value = "商品名称")
private String pname;
​
//@ApiModelProperty(value = "商品价格")
private Double pprice;
​
//@ApiModelProperty(value = "购买数量")
private Integer number;
}

1.5.3 创建商品微服务模块

对于商品表的操作业务

1、创建子模块

创建一个springcloud-product子模块当做商品微服务模块

创建完子模块之后,父模块里面又会多一个子模块

 <modules>
     <module>springcloud-common</module>
     <module>springcloud-product</module>
 </modules>

2、添加依赖

    <dependencies>
        <!--拉取公共模块中的依赖-->
        <dependency>
            <groupId>com.aaa</groupId>
            <artifactId>springcloud-common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!--添加SpringBoot-web的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

3、编写配置文件

因为创建的是maven工程,所以需要自己在resources目录下创建一个file文件,名字为application.properties

#端口号范围为8080~8089,方便后面对某个微服务搭载集群
server.port=8080
#数据库信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=密码
spring.datasource.url=jdbc:mysql:///springcloud-product?serverTimezone=Asia/Shanghai&characterEncoding=utf-8
#配置日志信息
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

4、编写启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
​
/**
 * 启动类
 */
@SpringBootApplication
@MapperScan(basePackages = "com.aaa.product.dao")
public class ProductApp {
    public static void main(String[] args) {
        SpringApplication.run(ProductApp.class,args);
    }
}

5、dao层

import com.aaa.entity.ShopProduct;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
​
/**
 * mapper接口
 */
public interface ProductMapper extends BaseMapper<ShopProduct> {
    
}

6、service层

public interface ProductService {
​
    /**
     * 根据商品id查询商品信息
     * @param pid  商品id
     * @return  商品信息
     */
     ShopProduct getProductById(Integer pid);
}
​
​
​
实现类
@Service
public class ProductServiceImpl implements ProductService {
    
    @Autowired
    private ProductMapper productMapper;
​
    /**
     * 根据商品id查询商品信息
     * @param pid  商品id
     * @return
     */
    @Override
    public ShopProduct getProductById(Long pid) {
        return productMapper.selectById(pid);
    }
}

7、controller层

@RestController
@RequestMapping("product")
public class ProductController {
​
    @Autowired
    private ProductService productService;
​
    @GetMapping("getProductById/{pid}")
    public ShopProduct getProductById(@PathVariable Long pid){
        return productService.getProductById(pid);
    }
}

8、开启项目进行测试

访问controller中的路径,应该能在数据库中查询到信息

访问路径为:http://localhost:8080/product/getProductById/1

如果使用的是上面提供的数据库结果应该是:

{"pid":1,"pname":"华为手机","pprice":3999.22,"stock":"100"}

1.5.4 创建订单微服务模块

操作订单表的业务

1、创建子模块

创建一个springcloud-order1子模块当做订单微服务模块

创建完子模块之后,父模块中也会出现该子模块

 <modules>
     <module>springcloud-common</module>
     <module>springcloud-product</module>
     <module>springcloud-order1</module>
 </modules>

2、添加依赖

    <dependencies>
        <!--拉取公共模块中的依赖-->
        <dependency>
            <groupId>com.aaa</groupId>
            <artifactId>springcloud-common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!--添加SpringBoot-web的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

3、配置文件

因为创建的是maven工程,所以需要自己在resources目录下创建一个file文件,名字为application.properties

#端口号范围为8090~8099,方便后面对某个微服务搭载集群
server.port=8090
#数据库信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=密码
spring.datasource.url=jdbc:mysql:///springcloud-order?serverTimezone=Asia/Shanghai&characterEncoding=utf-8
#配置日志信息
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

4、启动类

@SpringBootApplication
@MapperScan(basePackages = "com.aaa.order.dao")
public class OrderApp {
    public static void main(String[] args) {
        SpringApplication.run(OrderApp.class,args);
    }
}

5、mapper接口

public interface OrderMapper extends BaseMapper<ShopOrder> {
}

6、service层

public interface OrderService{
​
    /**
     * 根据订单对象添加订单
     * @param shopOrder
     * @return 
     */
    public ShopOrder saveOrder(ShopOrder shopOrder);
}
​
​
​
@Service
public class OderServiceImpl implements OrderService {
​
    @Autowired
    private OrderMapper orderMapper;
​
​
    @Override
    public ShopOrder saveOrder(ShopOrder shopOrder) {
        int insert = orderMapper.insert(shopOrder);
        if (insert>=0){
            return shopOrder;
        }else {
            throw new RuntimeException("订单添加失败");
        }
    }
}

7、controller层

package com.aaa.order.controller;
​
import com.aaa.entity.ShopOrder;
import com.aaa.entity.ShopProduct;
import com.aaa.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
​
import javax.annotation.Resource;
​
@RestController
@RequestMapping("order")
public class OrderController {
​
    @Autowired
    private OrderService orderService;
​
    /**
     * 注入远程调用工具类
     * 这个工具类是默认没有交给spring容器进行管理
     * 这里选择在启动类中,添加RestTemplate进入spring容器
     */
    @Resource
    private RestTemplate restTemplate;
​
    @GetMapping("createOrder/{pid}/{num}")
    public ShopOrder createOrder(@PathVariable Long pid,@PathVariable Integer num){
        ShopOrder shopOrder = new ShopOrder();
        //用户的信息可以从token令牌中解析出来,这里暂时设置为定值
        shopOrder.setUid(2);
        shopOrder.setUsername("lwl");
        //购买数量也应该和库存做个判断
        shopOrder.setNumber(num);
        //product的其他信息,可以远程调用商品微服务的接口来获取
        /**
         * 对于服务之间的远程调用,提供了两种模式
         * 第一种:基于http协议:用于微服务之间的调用
         *        1、可以自定义工具类完成远程调用
         *        2、使用spring提供的工具类(RestTemplate)
         * 第二种:基于TCP协议:用于dubbo分布式系统之间
         */
        /** 将RestTemplate注入spring容器中之后,调用方法
         * getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)
         * url:远程调用的路径
         * responseType:返回值类型的反射类
         * uriVariables:参数,比如访问路径中?后面的参数
         */
        shopOrder.setPid(pid);
        ShopProduct shopProduct = restTemplate.getForObject("http://localhost:8080/product/getProductById/" + pid, ShopProduct.class);
        shopOrder.setPname(shopProduct.getPname());
        shopOrder.setPprice(shopProduct.getPprice());
        //将订单信息存入数据库中
        ShopOrder shopOrder1 = orderService.saveOrder(shopOrder);
        return shopOrder1;
    }
}

启动类中添加的bean

@SpringBootApplication
@MapperScan(basePackages = "com.aaa.order.dao")
public class OrderApp {
    public static void main(String[] args) {
        SpringApplication.run(OrderApp.class,args);
    }
​
    /**
     * 将RestTemplate交给spring容器管理
     */
    @Bean
    public RestTemplate template(){
        return new RestTemplate();
    }
}

8、测试

启动两个微服务项目[springcloud-order1,springcloud-product]

访问ordercontroller中的请求路径:http://localhost:8090/order/createOrder/1/10

成功的结果为:

{"oid":1,"uid":2,"username":"lwl","pid":1,"pname":"华为手机","pprice":3999.22,"number":10}

1.6 服务治理组件

上面我们通过RestTemplate完成了服务与服务之间的远程调用,我们消费端把提供者的地址以及端口号写死在代码中[硬编码问题]。 这种写法会带来哪些问题?

l 一旦服务提供者地址变化,就需要手工修改代码

l 一旦是多个服务提供者,无法实现负载均衡功能

我们可以使用服务治理组件来解决上述问题。

1.6.1 服务治理组件介绍

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

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

1.6.2 常见的服务治理组件

Zookeeper

zookeeper是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决分布式

应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。

Eureka

Eureka是Springcloud Netflix中的重要组件,主要作用就是做服务注册和发现。但是现在已经闭源 ,停更不停用。

Consul

Consul是基于GO语言开发的开源工具,主要面向分布式,服务化的系统提供服务注册、服务发现和配置管理的功能。Consul的功能都很实用,其中包括:服务注册/发现、健康检查、Key/Value 存储、多数据中心和分布式一致性保证等特性。Consul本身只是一个二进制的可执行文件,所以 安装和部署都非常简单,只需要从官网下载后,在执行对应的启动脚本即可。

Nacos (服务治理 配置中心)

Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它是 Spring Cloud Alibaba 组件之一,负责==服务注册发现==和服务配置。

1.6.3 使用nacos搭建服务端[注册中心]

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

nacos官网:Releases · alibaba/nacos · GitHub

1、下载nacos并解压

修改为单机模式,

需要修改/bin/startup.cmd脚本

大概是第26行,修改为set MODE="standalone"

2、启动nacos

双击 /bin/startup.cmd 启动nacos

默认端口号是8848

3、访问nacos

访问nacos:http://localhost:8848/nacos

用户名:nacos

密码:nacos

4、查看主页

1.6.4 微服务接入到注册中心

这里是想要将springcloud-product微服务模块接入到注册中心,所以这些操作都是在对应的微服务模块中进行操作的

1、引入依赖

        <!--引入nacos的依赖:一定要注意依赖的名称,这个discovery既可以做配置中心,也可以做服务管理
        这里不添加版本号是因为父工程中已经定义了alibaba产品的版本号-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--这个是nacos的依赖,但是这个依赖只能做配置中心-->
        <!--<dependency>-->
        <!--    <groupId>com.alibaba.cloud</groupId>-->
        <!--    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>-->
        <!--</dependency>-->

2、修改配置文件

#配置nacos服务端的信息
spring.cloud.nacos.discovery.server-addr=localhost:8848
#配置该服务的在的名字
spring.application.name=springcloud-product

3、刷新服务端

如果服务详情里面的ip地址是一个虚拟机ip[xxx.xxx.x.1/2],是因为虚拟机的缘故,在本机网络设置中,找到更改适配器设置,把虚拟机给禁用掉再次刷新即可

当一个服务有多个实例时,有些实例可能是不健康的,这个时候就会遵循心跳30s,90s原则

超过30s没有响应,就是不健康的,

超过90s没有响应,那么服务端就会强制让该实例下线

4、配置文件中配置组名

#配置该服务的组名
spring.cloud.nacos.discovery.group=qy160-spring-cloud

1.6.5 通过注册中心解决硬编码问题

上面已经将商品微服务接入到注册中心中,这里可以改写订单微服务中controller代码来解决硬编码问题

    import com.lwl.entity.ShopOrder;
    import com.lwl.entity.ShopProduct;
    import com.lwl.order.service.OrderService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cloud.client.ServiceInstance;
    import org.springframework.cloud.client.discovery.DiscoveryClient;
    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;
    import org.springframework.web.client.RestTemplate;
    
    import javax.annotation.Resource;
    import java.util.List;
    
    @RestController
    @RequestMapping("order")
    public class OrderController {
    
        @Autowired
        private OrderService orderService;
    
        @Resource
        private RestTemplate restTemplate;
        
        /**
        * 在springcloud依赖中存在一个DiscoveryClient,该类可以从注册中心拉取指定的服务列表清单
        */
        @Autowired
        private DiscoveryClient discoveryClient;
    
        @GetMapping("createOrderDis/{pid}/{num}")
        public ShopOrder createOrderByDis(@PathVariable Long pid,@PathVariable Integer num){
            ShopOrder shopOrder = new ShopOrder();
            /**
             * 用户的信息从token令牌中获取
             */
            shopOrder.setUid(2);
            shopOrder.setUsername("陆xx");
            shopOrder.setNumber(num);
            shopOrder.setPid(pid);
            /**
             * 使用注册中心解决硬编码问题
             */
            //通过服务名获取服务的所有信息,因为一个服务名下可能有多个实例(集群),所以这里返回的是一个数组
            List<ServiceInstance> instances = discoveryClient.getInstances("spring-product");
            //获取当前数组中第一个实例
            ServiceInstance serviceInstance = instances.get(0);
            //获取该实例的uri  uri=http://192.168.0.112:8080 [uri=协议+IP地址+端口号]
            String uri = serviceInstance.getUri().toString();
            /**
             * 商品的信息远程调用商品微服务进行实现
             */
            ShopProduct forObject = restTemplate.getForObject(uri+"/product/getProById/" + pid, ShopProduct.class);
            shopOrder.setPname(forObject.getPname());
            shopOrder.setPprice(forObject.getPprice());
            ShopOrder order = orderService.createOrder(shopOrder);
            return order;
        }
    }

更改订单微服务的controller层代码后,再次启动两个项目,访问订单微服务中的路径进行测试

访问路径为:http://localhost:8090/order/createOrderDis/1/10

结果为:

{"oid":4,"uid":2,"username":"陆xx","pid":1,"pname":"华为手机","pprice":3999.22,"number":10}

1.7 负载均衡

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

1.7.1 如何实现负载均衡

模拟搭建商品微服务2台(这里使用两台代替多台)

手动实现负载均衡[随机负载均衡]

package com.aaa.order.controller;
​
import com.aaa.entity.ShopOrder;
import com.aaa.entity.ShopProduct;
import com.aaa.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
​
import javax.annotation.Resource;
import java.net.URI;
import java.util.List;
import java.util.Random;
​
@RestController
@RequestMapping("order")
public class OrderController {
​
    @Autowired
    private OrderService orderService;
​
    /**
     * 注入远程调用工具类
     * 这个工具类是默认没有交给spring容器进行管理
     * 这里选择在启动类中,添加RestTemplate进入spring容器
     */
    @Resource
    private RestTemplate restTemplate;
​
    /**
     *
     * @param pid
     * @param num
     * @return
     */
    @Resource
    private DiscoveryClient discoveryClient;
​
    @GetMapping("createOrder/{pid}/{num}")
    public ShopOrder createOrder(@PathVariable Long pid,@PathVariable Integer num){
        ShopOrder shopOrder = new ShopOrder();
        //用户的信息可以从token令牌中解析出来,这里暂时设置为定值
        shopOrder.setUid(2);
        shopOrder.setUsername("lwl");
        //购买数量也应该和库存做个判断
        shopOrder.setNumber(num);
        //product的其他信息,可以远程调用商品微服务的接口来获取
        /**
         * 对于服务之间的远程调用,提供了两种模式
         * 第一种:基于http协议:用于微服务之间的调用
         *        1、可以自定义工具类完成远程调用
         *        2、使用spring提供的工具类(RestTemplate)
         * 第二种:基于TCP协议:用于dubbo分布式系统之间
         */
        /** 将RestTemplate注入spring容器中之后,调用方法
         * getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)
         * url:远程调用的路径
         * responseType:返回值类型的反射类
         * uriVariables:参数,比如访问路径中?后面的参数
         */
        shopOrder.setPid(pid);
        //通过服务名获取服务的所有信息,因为一个服务名下可能有多个实例(集群),所以这里返回的是一个数组
        List<ServiceInstance> instances = discoveryClient.getInstances("springcloud-product");
        //根据数组长度获取一个随机数
        int index = new Random().nextInt(instances.size());
        //随机获取数组中的一个实例
        ServiceInstance serviceInstance = instances.get(index);
        //获取该实例的uri  uri=http://192.168.0.112:8080 [uri=协议+IP地址+端口号]
        URI uri = serviceInstance.getUri();
        System.out.println("uri = " + uri);
​
        ShopProduct shopProduct = restTemplate.getForObject(uri+"/product/getProductById/" + pid, ShopProduct.class);
        shopOrder.setPname(shopProduct.getPname());
        shopOrder.setPprice(shopProduct.getPprice());
        //将订单信息存入数据库中
        ShopOrder shopOrder1 = orderService.saveOrder(shopOrder);
        return shopOrder1;
    }
}

可以先将service控制台调出来,更好进行观察【Alt+8】

启动服务进行测试

访问:http://localhost:8090/order/createOrder/1/10

多次访问,并观察另外两个商品微服务的控制台,会发现两台商品微服务是随机的响应订单微服务的请求

上面我们可以自己实现负载均衡,但是如果我们想修改负载均衡的策 略,需要修改源代码。而springcloud提供了一个负载均衡的组件- ribbon

1.7.2 ribbon负载均衡组件

Ribbon是Netflix发布的一个负载均衡器,有助于控制 HTTP 和 TCP客户端行为。在 SpringCloud 中,nacos一般配合Ribbon客户端负载均衡的功能,Ribbon利用从nacos中读取到的服务信息,在调用服务节点提供的服务时,会合理(策略)的进行负载。 在SpringCloud中可以将注册中心和Ribbon配合使用,Ribbon自动的从注册中心中获取服务提供者的列表信息,并基于内置的负载均衡算法,请求服务。

1、在主启动类的RestTemplate上添加LoadBalanced注解

@SpringBootApplication
@MapperScan(basePackages = "com.aaa.order.dao")
@EnableFeignClients //开启openfeign注解驱动
public class OrderApp {
    public static void main(String[] args) {
        SpringApplication.run(OrderApp.class,args);
    }
​
    /**
     * 将RestTemplate交给spring容器管理
     */
    @Bean
    @LoadBalanced //扫描到该注解,使用ribbon的完成负载均衡
    public RestTemplate template(){
        return new RestTemplate();
    }
}

2、修改订单微服务controller层代码

package com.aaa.order.controller;
​
import com.aaa.entity.ShopOrder;
import com.aaa.entity.ShopProduct;
import com.aaa.order.service.OrderService;
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;
import org.springframework.web.client.RestTemplate;
​
import javax.annotation.Resource;
​
@RestController
@RequestMapping("order")
public class OrderController_ribbon {
​
    @Autowired
    private OrderService orderService;
​
    /**
     * 注入远程调用工具类
     * 这个工具类是默认没有交给spring容器进行管理
     * 这里选择在启动类中,添加RestTemplate进入spring容器
     */
    @Resource
    private RestTemplate restTemplate;
    
    @GetMapping("createOrder/{pid}/{num}")
    public ShopOrder createOrder(@PathVariable Long pid,@PathVariable Integer num){
        ShopOrder shopOrder = new ShopOrder();
        //用户的信息可以从token令牌中解析出来,这里暂时设置为定值
        shopOrder.setUid(2);
        shopOrder.setUsername("lwl");
        //购买数量也应该和库存做个判断
        shopOrder.setNumber(num);
        //product的其他信息,可以远程调用商品微服务的接口来获取
        shopOrder.setPid(pid);
        /**
         * 使用restTemplate模板时,会查看启动类中是否有@LoadBalanced注解,
         * 如果有就会使用ribbon会拉取服务中心的服务名为springcloud-product的微服务,并进行负载均衡的调用
         * 格式:restTemplate.getForObject("http://服务提供者名称/资源路径")
         */
        ShopProduct shopProduct = restTemplate.getForObject("http://springcloud-product/product/getProductById/" + pid, ShopProduct.class);
        shopOrder.setPname(shopProduct.getPname());
        shopOrder.setPprice(shopProduct.getPprice());
        //将订单信息存入数据库中
        ShopOrder shopOrder1 = orderService.saveOrder(shopOrder);
        return shopOrder1;
    }
}

启动项目,

访问:http://localhost:8090/order/createOrder/1/10

观察两个商品微服务的控制台,可以观察到两个商品微服务是轮询的响应订单微服务的请求,事实上,ribbon默认使用的就是轮询的负载均衡策略

1.7.3 ribbon的负载均衡策略

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

策略类命名描述实现说明
RandomRule随机策略随机选择server在index上随机,选择index对应位置的server
RoundRobinRule轮询策略按照顺序选择server(ribbon默认策略)轮询index,选择index对应位置的server
RetryRule重试策略在一个配置时间段内,当选择server不成功,则一直尝试选择一个可用的server在一个配置时间段内当选择server不成功则一直尝试使用subRule的方式选择一个可用的server
BestAvailableRule最低并发策略逐个考察server,如果server断路器打开,则忽略,再选择其中并发链接最低的server逐个考察Server,如果Server被 tripped了,则忽略,在选择其中 ActiveRequestsCount最小的的server
AvailabilityFilteringRule可用过滤策略过滤掉一直失败并被标记为circuit tripped的server,过滤掉那些高并发链接的server(active connections超过配置的阈值)使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查 status里记录的各个server的运行状态
ResponseTimeWeightedRule响应时间加权重策略根据server的响应时间分配权重,响应时间越长,权重越低,被选择到的概率也就越低。响应时间越短,权重越高,被选中的概率越高,这个策略很贴切,综合了各种因素,比如:网络,磁盘,io等,都直接影响响应时间一个后台线程定期的从status里面读取评价响应时间,为每个server计算一个weight。Weight的计算也比较简单responsetime减去每个server自己平均的responsetime是server的权重。当刚开始运行,没有形成statas时,使用roubine策略选择server。
ZoneAvoidanceRule区域权重策略综合判断server所在区域的性能,和server的可用性,轮询选择server并且判断一个AWS Zone的运行性能是否可用,剔除不可用的Zone中的所有server使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个zone的运行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用于过滤掉连接数过多的Server。

如果这些策略无法满足需求,那么也可以继承AbstractLoadBalancerRule类,来自定义负载均衡策略

修改订单微服务的配置文件

#配置ribbon负载均衡策略[这里是配置的随机策略]
#服务名.ribbon.NFLoadBalancerRuleClassName=随机策略的路径
springcloud-product.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

配置完之后启动项目

访问:http://localhost:8090/order/createOrder/1/10

观察两个商品微服务的控制台,可以观察到两个商品微服务是随机的响应订单微服务的请求

1.8 OpenFeign完成服务调用

因为使用RestTemplate完成远程调用是不符合我们的编程习惯的,所以这里需要使用一个别的组件完成远程调用

1.8.1 OpenFeign介绍

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

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

1.8.2 如何使用OpenFeign

1、引入OpenFeign依赖

        <!--引入openfeign依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

2、订单微服务中创建一个微服务接口

/**
 *@FeignClient(value = "springcloud-product"):声明客户端,值是要调用的微服务的名字
 * 
 * 方法声明:把要调用的方法【除了方法体以外的所有东西都拿过来】
 */
@FeignClient(value = "springcloud-product")
public interface ProductFeign {
​
    //这里记得要添上调用类中完整的路径,包括类上@RequestMapping注解中的路径
    @GetMapping("product/getProductById/{pid}")
    public ShopProduct getProductById(@PathVariable Long pid);
}

3、在主启动类中开启OpenFeign注解驱动

@SpringBootApplication
@MapperScan(basePackages = "com.aaa.order.dao")
@EnableFeignClients //开启openfeign注解驱动
public class OrderApp {
    public static void main(String[] args) {
        SpringApplication.run(OrderApp.class,args);
    }
}

4、修改订单微服务controller层代码

import com.aaa.entity.ShopOrder;
import com.aaa.entity.ShopProduct;
import com.aaa.order.feign.ProductFeign;
import com.aaa.order.service.OrderService;
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;
​
import javax.annotation.Resource;
​
@RestController
@RequestMapping("order")
public class OrderController_Openfeign {
​
    @Autowired
    private OrderService orderService;
    //将feign接口注入进来,这里springcloud会为该接口创建一个代理实现类。
    @Resource
    private ProductFeign productFeign;
​
    @GetMapping("createOrder/{pid}/{num}")
    public ShopOrder createOrder(@PathVariable Long pid,@PathVariable Integer num){
        ShopOrder shopOrder = new ShopOrder();
        //用户的信息可以从token令牌中解析出来,这里暂时设置为定值
        shopOrder.setUid(2);
        shopOrder.setUsername("lwl");
        //购买数量也应该和库存做个判断
        shopOrder.setNumber(num);
        //打印springcloud为feign接口生成的代理实现类
        System.out.println("*********"+productFeign+"***********");
        /**
         * *********HardCodedTarget(type=ProductFeign, name=springcloud-product, url=http://springcloud-product)***********
         * 这里就相当于是在ProductFeign接口中,拿到springcloud-product的url为 http://springcloud-product
         * 然后再拼接在接口中方法的路径
         */
        //product的其他信息,可以远程调用商品微服务的接口来获取
        shopOrder.setPid(pid);
        //调用openfeign接口里面服务方法,根据pid查询product服务中的资源
        ShopProduct shopProduct = productFeign.getProductById(pid);
        //使用返回的实体类
        shopOrder.setPname(shopProduct.getPname());
        shopOrder.setPprice(shopProduct.getPprice());
        //将订单信息存入数据库中
        ShopOrder shopOrder1 = orderService.saveOrder(shopOrder);
        return shopOrder1;
    }
}

5、配置文件

#配置ribbon负载均衡策略[这里是配置的随机策略]
#服务名.ribbon.NFLoadBalancerRuleClassName=随机策略的路径
springclou-product.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

这里的配置文件,配置的负载均衡策略是随机策略

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值