Spring Cloud Gateway 入门实战

本文主要通过代码对Cloud Gateway的熔断降级和限流进行演示

*熔断降级:我们在微服务系统中,所有请求先经网关,由网关再去找指定的服务,这个时候肯定是会出现服务异常和超时的情况,我们就针对这种情况进行熔断降级操作。

*限流:限制每秒的最大访问次数和允许在一秒钟内完成的最大请求数

想完全把整个流程跑起来测试的小伙伴需要先准备一个服务注册到注册中心(nacos或者eureka),还要准备一个可用的redis环境,想偷懒的小伙伴可以把我上篇文章的源码下载一下跑起来https://blog.csdn.net/weixin_45452416/article/details/109679818

下面直接上网关服务的代码

1.搭建基础的springboot工程,导入依赖,pom.xml如下,注意版本号

<?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.2.5.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>demo</name>
	<description>Demo project for gateway</description>

	<dependencies>

		<!--nacos 注入依赖-->
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
			<version>2.2.1.RELEASE</version>
		</dependency>

		<!--Spring Cloud Gateway默认集成了Redis限流-->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-gateway</artifactId>
			<version>2.2.3.RELEASE</version>
		</dependency>

		<!--限流-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
			<version>2.2.5.RELEASE</version>
		</dependency>

		<!--熔断 降级-->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
			<version>2.2.3.RELEASE</version>
		</dependency>

	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

我们这边没有再去nacos读取配置文件,所以只引用了nacos的discovery包,微服务的话最好还是把配置文件配到nacos,我上一篇文章介绍了如何从nacos读取配置文件,这样修改配置文件的时候就不需要再重新打包部署,直接重启服务就行了。

2.开启nacos的注解

@SpringBootApplication
@EnableDiscoveryClient
public class DemoApplication {
    
    

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

}

3.新建一个HystrixController,主要作用就是当服务异常或超时的时候,走这边我们配置的接口,第五步配置文件中会用到

package com.example.demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * 默认降级处理
 */
@RestController
public class HystrixController {
    
    

    @RequestMapping("/hystrix")
    public Map<String, String> defaultfallback() {
    
    
        System.out.println("降级操作...");
        Map<String, String> map = new HashMap<>();
        map.put("code", "500");
        map.put("message", "服务异常");
        return map;
    }

}

4. 创建bootstrap.yml

nacos地址和namespace都沿用的上一篇文章里介绍的,这里就不再介绍了

spring:
  application:
    name: cloud-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.1.54:8848 # nacos地址
        namespace:  MaoJiaFeng

logging:
  level:
    com:
      alibaba:
        nacos:
          client: error

5.配置application.yml

这里是需要配置redis参数的,注意这边配置文件中主要就是看filters,一个是熔断降级,一个就是限流,限流的配置文件需要先注释掉才能启动成功,因为我们还没写限流的配置文件,把代码22-27行注释掉即可

server:
  port: 9527

spring:
  redis:
    host: 192.168.1.52
    port: 6379
    password: KCl9HfqbVnhQ5c3n
    database: 15
  cloud:
    gateway:
      routes:
        - id: springboot-api  # 名字可以随便写
          uri: lb://springboot-api  # lb 负载均衡 这边必须是微服务名称
          predicates: # 断言 这是一个 Java 8 的 Predicate,可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。断言的输入类型是一个 ServerWebExchange
            - Path=/test/**  # 以/test开头的所有请求 都会转发到 lb://springboot-api
          filters:
            - name: Hystrix
              args:
                name: default
                fallbackUri: 'forward:/hystrix'  # /hystrix 路径是我们在HystrixController自己写的
            - name: RequestRateLimiter
              args:
                name: default
                key-resolver: "#{@remoteAddrKeyResolver}" #SPEL表达式去的对应的bean
                redis-rate-limiter.replenishRate: 1  # 每秒最大访问次数
                redis-rate-limiter.burstCapacity: 5  #令牌桶的容量,允许在一秒钟内完成的最大请求数

# hystrix 信号量隔离,3秒后自动超时
hystrix:
  command:
    default:
      execution:
        isolation:
          strategy: SEMAPHORE
          thread:
            timeoutInMilliseconds: 3000
  shareSecurityContext: true

6.注意我们上一步的配置文件中hystrix的延时是3S,所以gateway在向服务发送请求后,3S内得不到响应就会走我们自己配置的HystrixController中的接口,现在我们启动网关,注意端口是:9527

在这里插入图片描述

7.上面我们是把网关启动了,现在要启动服务,服务的话我就用上篇文章的代码了,大家也可以新建一个springboot基础模板注册到nacos就行,新增一个IndexController即可

package com.example.demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class IndexController {
    
    

    @RequestMapping("/hello")
    public String hello(){
    
    
        return "hello,gateway";
    }

    /**
     * 让线程睡眠5秒 触发hystrix
     * @return
     */
    @RequestMapping("/timeout")
    public String timeout(){
    
    
        try{
    
    
            Thread.sleep(5000);
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
        return "请求超时";
    }
}

这边主要有两个接口,一个是直接返回结果,另一个延时了5S才返回结果,现在我们也启动起来,注意是通过网关端口号访问

请求 /hello的接口,返回正常

在这里插入图片描述

现在我们访问 /timeout的接口,注意这边的几个要点

在这里插入图片描述
1.我们的服务端口是8088,但是我们访问的却是9527,这是微服务在起的作用
2.上面的服务中我们返回的是”服务超时“,实际网页里返回的是**{“code”:“500”,“message”:“服务异常”}**,这就是网关在起作用了,因为这个接口的请求5S才能得到响应,而我们配置的网关3S得不到响应就会自动返回HystrixController中接口的信息。这一步就是我们所说的熔断降级

8.现在我们验证限流

1.新建一个RateLimiterConfig类

package com.example.demo.config;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;

/**
 * 路由限流配置
 * @author MaoJiaFeng
 * @date 2020/11/14
 */
@Configuration
public class RateLimiterConfig {
    
    

    /**
     * IP限流代码
     * @return
     */
    @Bean()
    public KeyResolver remoteAddrKeyResolver() {
    
    
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }

}

2.理论上代码到这就可以了,我们可以查看一下服务是否正常启动,但是我们需要一个高并发的请求来测试我们的限流,注意postman是串行的,并不能做到并发测试,所以我们还是通过自己写代码实现,这个我就直接写在gateway项目里了,之后会一起上传到码云
先写一个能发送http的util

package com.example.demo.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

public class HttpClientOp {
    
    

    public static String doGet(String httpurl) {
    
    
        HttpURLConnection connection = null;
        InputStream is = null;
        BufferedReader br = null;
        String result = null;// 返回结果字符串
        try {
    
    
            // 创建远程url连接对象
            URL url = new URL(httpurl);
            // 通过远程url连接对象打开一个连接,强转成httpURLConnection类
            connection = (HttpURLConnection) url.openConnection();
            // 设置连接方式:get
            connection.setRequestMethod("GET");
            // 设置连接主机服务器的超时时间:15000毫秒
            connection.setConnectTimeout(15000);
            // 设置读取远程返回的数据时间:60000毫秒
            connection.setReadTimeout(60000);
            // 发送请求
            connection.connect();
            // 通过connection连接,获取输入流
            if (connection.getResponseCode() == 200) {
    
    
                is = connection.getInputStream();
                /* 封装输入流is,并指定字符集 */
                br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
                // 存放数据
                StringBuffer sbf = new StringBuffer();
                String temp = null;
                while ((temp = br.readLine()) != null) {
    
    
                    sbf.append(temp);
                    sbf.append("\r\n");
                }
                result = sbf.toString();
            }
        } catch (MalformedURLException e) {
    
    
            e.printStackTrace();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            // 关闭资源
            if (null != br) {
    
    
                try {
    
    
                    br.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }

            if (null != is) {
    
    
                try {
    
    
                    is.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }

            connection.disconnect();// 关闭远程连接
        }

        return result;
    }

}

下面就是写我们测试并发的代码了

package com.example.demo.test;

import com.example.demo.util.HttpClientOp;

import java.util.concurrent.CountDownLatch;

public class LatchTest {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    
        Runnable taskTemp = new Runnable() {
    
    

            // 注意,此处是非线程安全的
            private int iCounter;

            @Override
            public void run() {
    
    
                for (int i = 0; i < 10; i++) {
    
    
                    // 发起请求
                    System.out.println(HttpClientOp.doGet("http://localhost:9527/test/hello"));
                    iCounter++;
                    System.out.println(System.nanoTime() + " [" + Thread.currentThread().getName() + "] iCounter = " + iCounter);
                    try {
    
    
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
        };
        LatchTest latchTest = new LatchTest();
        latchTest.startTaskAllInOnce(5, taskTemp);
    }

    public long startTaskAllInOnce(int threadNums, final Runnable task) throws InterruptedException {
    
    
        final CountDownLatch startGate = new CountDownLatch(1);
        final CountDownLatch endGate = new CountDownLatch(threadNums);
        for (int i = 0; i < threadNums; i++) {
    
    
            Thread t = new Thread() {
    
    
                public void run() {
    
    
                    try {
    
    
                        // 使线程在此等待,当开始门打开时,一起涌入门中
                        startGate.await();
                        try {
    
    
                            task.run();
                        } finally {
    
    
                            // 将结束门减1,减到0时,就可以开启结束门了
                            endGate.countDown();
                        }
                    } catch (InterruptedException ie) {
    
    
                        ie.printStackTrace();
                    }
                }
            };
            t.start();
        }
        long startTime = System.nanoTime();
        System.out.println(startTime + " [" + Thread.currentThread() + "] All thread is ready, concurrent going...");
        // 因开启门只需一个开关,所以立马就开启开始门
        startGate.countDown();
        // 等等结束门开启
        endGate.await();
        long endTime = System.nanoTime();
        System.out.println(endTime + " [" + Thread.currentThread() + "] All thread is completed.");
        return endTime - startTime;
    }
}

启动我们的main()方法,注意我们配置的是每秒最大访问是1,令牌桶最大容量是5,通过控制台我们可以看到只有前五个请求成功了,下面的请求全部被网关拒绝了
在这里插入图片描述

总结一下

熔断降级是网关与服务之间的

限流是网关与请求之间的

源码地址:https://gitee.com/mao_jiafeng/cloud-gateway.git

如有不对 欢迎讨论 QQ 770850769

猜你喜欢

转载自blog.csdn.net/weixin_45452416/article/details/109693163