springcloud alibaba 1(微服务入门、nacos、ribbon、fegin、jmeter、sentinel)

写在前面

由于spring cloud体系的部分组件已经闭源或停止维护,
spring cloud alibaba是一个更好的解决方案,且性能较好

1、系统架构的演变

单体应用架构 --> 垂直应用架构 --> 分布式架构 --> SOA架构 --> 微服务架构。
后面还有一个正在悄然兴起的service mesh(服务网格化)。

1、单体和垂直的对比:
在这里插入图片描述
2、分布式架构和SOA架构对比
在这里插入图片描述
3、微服务架构(在soa的基础上将服务分得更彻底)
在这里插入图片描述

2、开始搭建环境

在这里插入图片描述
模块间调用关系
在这里插入图片描述
新建maven父工程mycloud:

父工程不需要src文件夹,删掉

添加依赖(这里主要注意springboot、springcloud、springcloud alibaba版本要对应)

spring cloud alibaba的父工程和子工程的pom.xml 配置可参考:https://blog.csdn.net/a__int__/article/details/111996865

<!-- 父工程 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <version>2.1.13.RELEASE</version>
        <artifactId>spring-boot-starter-parent</artifactId>
    </parent>

    <!-- 版本依赖的锁定 -->
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
        <spring-cloud-alibaba.version>2.1.3.RELEASE</spring-cloud-alibaba.version>
    </properties>

    <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>

注意springboot和spring cloud alibaba 的版本要匹配
在这里插入图片描述
在这里插入图片描述

具体参考官方:https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E

2.1、shop-common

在父工程里创建模块shop-common(里面只放实体类)
在这里插入图片描述

需要的相关依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.56</version>
        </dependency>
    </dependencies>

新建三个实体类
在这里插入图片描述
在这里插入图片描述
User

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

// 用户
@Entity(name = "shop_user")
@Data
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer uid;

    private String username;
    private String password;
    private String telephone;
}

Product

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

// 用户
@Entity(name = "shop_product")
@Data
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer pid;

    private String pname;
    private Double pprice; // 价格
    private String stock; // 库存
}

Order

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

// 用户
@Entity(name = "shop_order")
@Data
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long oid;

    // 用户
    private Integer uid;
    private String username;
    // 商品
    private Integer pid;
    private Double pprice; // 价格
    private String pname;
    // 数量
    private Integer number;
}
2.2、shop-user、shop-product、shop-order

在父工程里创建模块shop-user
引入相关依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.haha</groupId>
            <artifactId>shop-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

这里面上一个模块的引入需要注意
在这里插入图片描述
新建内容与结构如下
在这里插入图片描述
配置文件application.yaml (记得在数据库新建库shop)

server:
  port: 8071
spring:
  application:
    name: service-user
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Hongkong
    username: root
    password: root
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

UserApplication.java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

剩下几个java文件
在这里插入图片描述
同理shop-product、shop-order两个板块内容几乎的shop-uer一样
在这里插入图片描述

配置文件里需要注意一下
在这里插入图片描述
shop-product和shop-order中
引入相关依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.haha</groupId>
            <artifactId>shop-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
2.3、不使用服务治理,直接调用

这里以客户下单购买东西为例:shop-order调用shop-product

在这里插入图片描述
然后为product里面添加一个findByPid()的查询
在这里插入图片描述
接口ProductService.java

public interface ProductService {
    Product findByPid(Integer pid);
}

ProductServiceImpl.java

@Service
public class ProductServiceImpl implements ProductService {
    @Autowired
    private ProductDao productDao;
    @Override
    public Product findByPid(Integer pid) {
        return productDao.findById(pid).orElse(null);
    }
}

ProductController.java

@RestController
@Slf4j
public class ProductController {
    @Autowired
    private ProductService productService;
    //商品信息查询
    @RequestMapping("/product/{pid}")
    public Product product(@PathVariable("pid") Integer pid){
        log.info("接下来要进行{}号商品信息的查询",pid);
        Product product = productService.findByPid(pid);

        log.info("查询结果{}", JSON.toJSONString(product));
        return product;
    }
}

然后记得在数据库里面新建一个叫shop的库,之后运行ProductApplication
会看到库里自动生成了这些表
在这里插入图片描述
往product表里添加写数据
在这里插入图片描述
测试一下127.0.0.1:8081/product/1
在这里插入图片描述
接下来往订单微服务shop-order中添加代码
先在OrderApplication.java里面添加RestTemplate,用来调用shop-product的接口
在这里插入图片描述
接下来通过pid调用产品,然后生成订单

在这里插入图片描述
接口OrderService.java

public interface OrderService {
    void createOrder(Order order);
}

OrderServiceImpl.java

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderDao orderDao;
    @Override
    public void createOrder(Order order) {
        orderDao.save(order);
    }
}

OrderController.java

@RestController
@Slf4j
public class OrderController {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private OrderService orderService;
    // 下单
    @RequestMapping("/order/prod/{pid}")
    public Order order(@PathVariable("pid") Integer pid){
        log.info("接收到{{}号商品的下单请求,接下来调用微服务查询",pid);
        //调用,查询商品信息
        Product product = restTemplate.getForObject(
                "http://127.0.0.1:8081/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;
    }
}

然后启动OrderApplication.java
注意这时是两个服务都在启动着
在这里插入图片描述
访问http://127.0.0.1:8091/order/prod/1
在这里插入图片描述
通过RestTemplate成功调用product的服务

这样调用发现的几个缺点:
1、服务ip、端口需要手动填写
2、其中一个服务掉了不容易发现

3、nacos 服务治理(注册中心)

nacos为微服务间提供服务治理,相当于服务的注册中心
在这里插入图片描述

在这里插入图片描述
nacos下载地址:https://github.com/alibaba/nacos/releases/tag/1.3.2
下载完进入bin目录
在这里插入图片描述
cmd进入nacos\bin目录下,输入启动指令:startup.cmd -m standalone
然后进入浏览器访问:http://127.0.0.1:8848/nacos/index.html(账号密码都是nacos)
在这里插入图片描述
接下来在两个微服务上安装nacos-cline

下面几个步骤消费者服务者都需要

第一步添加依赖

		<!--nocos客户端-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

第二步在主类上添加注解
在这里插入图片描述

第三步:在application.yaml配置nacos服务地址(shop-order和shop-product都需要)
在这里插入图片描述

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

配置完之后如果运行报错,可能是springboot和springcloud alibaba版本不匹配
第四步:启动shop-order和shop-product微服务,就能看看服务列表了
在这里插入图片描述
接下来开始使用调用

首先在OrderController注入DiscoveryClient,将ip和端口用DiscoveryClient获取
在这里插入图片描述

    @Autowired
    private DiscoveryClient discoveryClient;
    .
    .
    .
        List<ServiceInstance> instances = discoveryClient.getInstances("service-product");
        ServiceInstance instance = instances.get(0);
    .
    .
    .
        Product product = restTemplate.getForObject(
                "http://"+instance.getHost()+":"+instance.getPort()+"/product/"+pid ,Product.class
        );

然后重新启动项目,就可以进行服务调用了
在这里插入图片描述

4、Ribbon 负载均衡(接口选择)

现在我们要将shop-product启动两个微服务,注册到注册中心

在idea里找到Edit Configurations…–>springboot -->填写name、Main class、VM options
在这里插入图片描述
在这里插入图片描述
然后启动ProductionApplication2
在这里插入图片描述
相当于现在有三个微服务在运行
在这里插入图片描述
在这里插入图片描述
相当于现在有两个product服务,接下来我们让order随机选择调用其中一个微服务

4.1、自定义负载均衡

在这里插入图片描述

int index = new Random().nextInt(instances.size());

然后重新启动就可以了

4.2、基于Ribbon负载均衡

第一步:在restTemplate()上添加注解@LoadBalanced

在这里插入图片描述
第二步:把 “ IP+端口 ” 改为服务名称service-product
在这里插入图片描述
然后重启就可以了
在这里插入图片描述

4.3、Ribbon负载均衡 策略调整

ribbon总共有七种负载均衡策略

策略类描述
RandomRule 随机策略随机选择server
RoundRobinRule 轮询策略按照顺序选择server(ribbon默认策略)
RetryRule 重试策略在一个配置时间段内,当选择server不成功,则一直尝试选择一个可用的server
BestAvailableRule 最低并发策略逐个考察server,如果server断路器打开,则忽略,再选择其中并发链接最低的server
AvailabilityFilteringRule 可用过滤策略过滤掉一直失败并被标记为circuit tripped的server,过滤掉那些高并发链接的server(active connections超过配置的阈值)
ResponseTimeWeightedRule 响应时间加权重策略根据server的响应时间分配权重,响应时间越长,权重越低,被选择到的概率也就越低。响应时间越短,权重越高,被选中的概率越高,这个策略很贴切,综合了各种因素,比如:网络,磁盘,io等,都直接影响响应时间
ZoneAvoidanceRule 区域权重策略综合判断server所在区域的性能,和server的可用性,轮询选择server并且判断一个AWS Zone的运行性能是否可用,剔除不可用的Zone中的所有server

默认使用的是RoundRobinRule 轮询策略

在application.yaml里面配置策略
在这里插入图片描述

service-product:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

重启OrderApplication试一下

在这里插入图片描述

5、Fegin http客户端(接口调用工具)

Nacos很好的兼容了fegin,fegin内部集成了Ribbon。

第一步:往shop-order的pom.xml加依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

第二步:往OrderApplication.java添加fegin的注解@EnableFeignClients
在这里插入图片描述
第三步:添加一个ProductFeignService

import com.haha.domain.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@FeignClient(value = "service-product")
public interface ProductFeignService {
    @RequestMapping("/product/{pid}")
    public Product findByPid(@PathVariable Integer pid);
}

在这里插入图片描述

第四步:修改OrderController
在这里插入图片描述
重启运行试试
在这里插入图片描述

6、Sentinel 服务容错(解决高并发问题)

下面我们来模拟测试高并发

6.1、jmeter高并发测试工具 安装、启动

需要用到工具jmeter,下载地址:https://jmeter.apache.org/
在这里插入图片描述
下载完解压,进入其bin目录,修改jmeter.properties
在大概39行下面添加language=zh_CN
在这里插入图片描述
然后双击jmeter.bat启动

在这里插入图片描述

6.2、修改OrderController模拟高并发

先在shop-order中修改application.yaml配置tomcat的线程为10
在这里插入图片描述
再将OrderController复制一个OrderController2

注释掉OrderController的@RestController
在这里插入图片描述
修改OrderController2内容如下

package com.haha.controller;

import com.alibaba.fastjson.JSON;
import com.haha.domain.Order;
import com.haha.domain.Product;
import com.haha.service.OrderService;
import com.haha.service.ProductFeignService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class OrderController2 {

    @Autowired
    private OrderService orderService;
    @Autowired
    private DiscoveryClient discoveryClient;
    @Autowired
    private ProductFeignService productFeignService;

    // 下单
    @RequestMapping("/order/prod/{pid}")
    public Order order(@PathVariable("pid") Integer pid){
        log.info("接收到{{}号商品的下单请求,接下来调用微服务查询",pid);

        //调用,查询商品信息
        Product product = productFeignService.findByPid(pid);

        //模拟调用商品微服务需要2秒
        try{
            Thread.sleep(20001);
        }catch (InterruptedException e){
            e.printStackTrace();
        }


        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;
    }

    @RequestMapping("/order/msg")
    public String msg(){
        return "测试高并发";
    }
}

重启OrderApplication.java
先试试能访问不
在这里插入图片描述

6.3、jmeter模拟高并发

先添加一个线程组
在这里插入图片描述
线程数20、循环100次
在这里插入图片描述
在该线程组下创建一个取样器
在这里插入图片描述
填写请求内容
在这里插入图片描述
再添加一个监听器
在这里插入图片描述
然后点击启动
在这里插入图片描述
然后再发起访问
在这里插入图片描述
我们发现访问响应速度慢了很多
在这里插入图片描述

6.4、Sentinel 服务容错思路(可跳过)

参考文章:https://www.jianshu.com/p/3c1b8562e95b

这就是高并发带来的问题,会带来服务雪崩,也就是整个服务链上的服务可能都会堵塞在这里。
在这里插入图片描述

对于这样的问题无法从源头上解决,但是我们可以做好足够的容错机制,
保证一个服务出现问题时,不会影响其他服务。

常见的容错思路有隔离、超时、限流、熔断、降级 这几种。

1、隔离,常见的隔离方式有:线程池隔离和信号量隔离。

在这里插入图片描述
2、超时,获取服务时超出时间就断开线程。
3、限流,限制系统的输入和输出流量。
4、熔断,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。
服务熔断三种状态:

  • 熔断关闭状态(Closed):服务没有故障时,熔断器关闭。
  • 熔断开启状态(Open):后续对该服务接口的调用不再经过网络,直接执行本地的fallback方法
  • 半熔断状态(Half-Open):尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断关闭状态。

5、降级,就是使用备用方案。

常见的容错组件:Sentinel、Resilience4J、Hystrix

6.5、Sentinel 开始使用

现在我们要做下单功能,也就是使用order调用product,所以在上游服务order中部署Sentinel
在这里插入图片描述
进入shop-order
1、添加依赖

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

2、然后我们把OrderController2的@RestController也注释掉,新建一个OrderController3
在这里插入图片描述
OrderController3内容如下

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class OrderController3 {

    @RequestMapping("/order/msg1")
    public String msg1(){
        return "测试高并发1";
    }
    @RequestMapping("/order/msg2")
    public String msg2(){
        return "测试高并发2";
    }
}

3、下载并启动sentinel 控制台

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

在这里插入图片描述

如果从github上下载太慢,可以直接百度jar包的名字,一般有网站可以下载

启动指令

java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.jar

在这里插入图片描述
浏览器访问登陆:http://localhost:8080/
用户名、密码都是sentinel
在这里插入图片描述
sentinel 的版本1.8依赖关系:fastjson 升级到 1.2.71、nacos-client 升级到 1.3.0( sentinel-datasource-nacos 模块)
4、在shop-order配置文件中加入配置

spring:    
  cloud:
    sentinel:
      transport:
        port: 9999 #跟控制台交流的端口,随意指定一个未使用的端口即可
        dashboard: localhost:8080 # 指定控制台服务的地址

然后重启order微服务(nacos保持启动)
访问:localhost:8091/order/msg1 (必须至少访问一次,sentinel才能发现它)
在这里插入图片描述
在这里插入图片描述
如果想关闭sentinel的懒加载在配置中输入

##取消Sentinel控制台懒加载,即项目启动即连接
sentinelspring.cloud.sentinel.eager = true

接下来为接口http://localhost:8091/order/msg2添加一个限流
在这里插入图片描述
在这里插入图片描述
接下来如果我们不断刷新访问这个端口,就会发现被流控了
在这里插入图片描述

6.6、Sentinel 的一些概念

资源:Sentinel 要保护的东西
规则:以什么样的方式保护资源

容错的三个核心思想:

  • 1、保证自己不被上游压垮
  • 2、保证自己不被下游拖垮
  • 3、保证外界环境良好

容错的三个核心思想实现措施:

1、流量控制:将随机的请求调整成合适的排队请求。(针对资源)

比如第一分钟有8个请求进来,第二分钟有2个请求进来,流量控制可以把它分成第一分钟请求5个,剩下3个放到第二分钟才让进来

2、熔断降级:让有故障或不稳定的请求快速失败。(针对资源)

  • 控制并发线程数让熔断降级
  • 控制响应时间让熔断降级

3、系统负载保护:集群环境下,本系统承载过高,转移流量到其他系统(针对系统)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值