一、前言

上一章芝法酱躺平攻略(12)展望了微服务下常见的技术需求与常见解决方案,本期来讲解第一部分,Nginx与SpringCloud-Gateway。
本章将实践在nginx和spring-cloud-gateway的简单使用。

二、nginx的安装与简单使用

2.1 nginx的安装

首先,去官网查找最新稳定版,把下载的nginx包放到DOWNLOAD文件夹下。
而后把该包解压到SOFTWARE文件夹下

mkdir ~/SOFTWARE/nginx
tar -xzvf nginx-1.22.1.tar.gz -C ~/SOFTWARE/nginx

而后安装编译所需的依赖

sudo apt-get install gcc
sudo apt-get install libpcre3 libpcre3-dev
sudo apt-get install zlib1g zlib1g-dev
sudo apt-get install openssl 
sudo apt-get install libssl-dev

而后运行编译安装

sudo ./configure --prefix="/home/hataksumo/SOFTWARE/nginx/bin"
sudo make
sudo make install

安装后,目录结构是这样的:

springboot nginx 远程上传文件 nginx springcloud_spring cloud


conf 是存放配置文件的地方

logs是存放log的地方

sbin是存放nginx运行程序的地方

进入conf,打开nginx.conf,做合适的配置,而后进入sbin,运行nginx。

从浏览器打开localhost:配置监听的ip,就可以看到nginx的页面

springboot nginx 远程上传文件 nginx springcloud_gateway_02

2.2 nginx常用的命令

sudo ./nginx  #运行nginx
sudo ./nginx -s reload #重新加载nginx的配置
sudo ./nginx -s stop #关闭nginx

更多的命令可以在官网上查看

2.3 nginx的反向代理配置

我们先看一个简单的配置,实现简单的负载均衡

upstream test{
        server 172.25.0.1:8001  weight=3;
        server 172.25.0.1:8002  weight=2;
        server 172.25.0.1:8003  weight=1;
    }

    server {
        listen       80;
        server_name  localhost;

        location /test{
            proxy_pass http://test/api;
        }
	}
}

其中upstream 表示一个负载均衡,可以配置不同服务的权重
server 段表示配置一个服务器,既可以配置http,也可以配置https。

java端口,按芝法酱躺平攻略(13)那样配置打包程序,并添加如下controller测试代码:

@Api(tags = "test-测试接口")
@RequestMapping("/api/test")
@Slf4j
@ZfRestController
@RequiredArgsConstructor
public class TestApi {

    private final AppProperty mAppProperty;

    @GetMapping("/hello")
    public String sayHello(){
        return mAppProperty.getMyName();
    }

}

从浏览器访问http://localhost/test/test/hello,反复刷新,查看现象

springboot nginx 远程上传文件 nginx springcloud_gateway_03

2.4 nginx的静态资源配置

location /img {
    root /home/hataksumo/res;

    proxy_store on;
    access_log /home/hataksumo/res/img/log;
    proxy_temp_path /home/hataksumo/res/img/cache;
    proxy_redirect          off;

    gzip on;
    gzip_comp_level 9;
}

三、spring-cloud-gateway的简单使用

3.1 网关简介

网关的英文是router,直译过来就是路由。网关的主要职责就是根据请求,做一些处理后转发到其他服务器,当其他服务器信息处理好后,再把处理好的信息返回给客户端。
其他服务器处理信息是需要时间的,网关服务器不应当在这个时间中忙等,所以需要一个不同的架构。在Java中是基于netty的nio模式。
spring-cloud-gateway使用拦截器来处理从前端发给后端得信息,也可以处理从第三方服务返回的信息。

3.2 响应式编程

小编私以为,gateway这块实在没那么多好讲的,核心的知识点反倒是响应式编程。
SpringBoot 提供了Mono机制做响应式编程,当一个请求过来后,并不是开启一个新线程,而是由几个固定线程依次处理过来的请求。当该请求需要请求第三方服务时,将会被跳过处理其他请求。当第三方消息返回后,再加入处理队列依次处理。
在gateway中的controller写法和普通服务基本一致,只是需要返回一个Mono对象,做异步处理。

3.2.1 从helloworld开始

先上代码:

@RequestMapping("/api/test")
@ZfRestController
public class HelloController {

    @GetMapping("hello")
    Mono<RestResponse<String>> hello(){
        RestResponse<String> r = RestResponse.ok("hello");
        return Mono.just(r);
    }
}

Mono.just是一种阻塞的模式,直接返回数据。

3.2.2 我们来异步一下

@GetMapping("/fibonacii/{n}")
    Mono<RestResponse<BigInteger>> fibonacii(@PathVariable(name = "n") int n){
        CompletableFuture<RestResponse<BigInteger>> future = CompletableFuture.supplyAsync(()->{
            log.info("计算"+n+" 当前线程 "+Thread.currentThread().getId());
            BigInteger res =  getFibonacii(n);
            log.info("计算"+n+" 当前线程 "+Thread.currentThread().getId()+"已完成===========================");
            return res;}).thenApply(v->RestResponse.ok(v));
        log.info("^_^返回"+n+" 当前线程 "+Thread.currentThread().getId());
        return Mono.fromFuture(future);
    }

为了方便测试,我们把netty的worker线程调到2

@SpringBootApplication(
        scanBasePackages = {"indi.zhifa.recipe.bailan.framework","indi.zhifa.recipe.bailan5.gateway"}
)
public class GatewayApplication {
    public static void main(String[] args){
        System.setProperty("reactor.netty.ioWorkerCount","2");
        ApplicationContext context = SpringApplication.run(GatewayApplication.class, args);
    }
}

浏览器请求,http://localhost:9000/api/test/fibonacii/521003,不断刷新,查看控制台,可以观察到以下现象:

springboot nginx 远程上传文件 nginx springcloud_gateway_04


返回线程总是129和101。当然,我们的例子是开了个新线程跑斐波那契数列,如果是从第三方服务拉去数据,或者调用复杂的数据库查询,毫无疑问这就能省去大量的等待时间。

3.2.3 关于db的操作

在异步模式下,目前不是很推荐操作mysql数据库,mongodb倒很推荐。
可以引入下列库做响应式

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>

3.3 网关简单负载均衡测试

其实这部分非常简单,没有太多好说的。
日常开发可以直接接入nacos,把微服务全开放了。其实体量有限的线上项目也可以这么干。
如果是个百人的大项目,可以开发一个动态路由,其实也不难写。

这里给出网关的简单配置:

server:
  # 服务端口
  port: 9000
spring:
  application:
    name: "bailan5-gateway"
  servlet:
    multipart:
      enabled : true
      max-file-size: "256MB"
      max-request-size: "256MB"
  cloud:
    nacos:
      server-addr: localhost:8848
      config:
        namespace: tmpTest
        name: gateway
        file-extension: yml
        group: bailan5
      discovery:
        namespace: tmpTest
        register-enabled: true
    gateway:
      discovery:
        locator:
          enabled: true

启动之前的test服务,通过访问localhost:9000/test/api/test/hello即可访问test服务的对应接口

3.3 网关的拦截器配置

官方默认提供了大量默认的拦截器,可以通过yml配置。
这里仅仅讲解自定义的拦截器。下面用接口执行时间为例做代码展示。

@Slf4j
@Component
public class CustomFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 前置处理,直接写就行
        String info = String.format("Method:{%s} Host:{%s} Path:{%s} Query:{%s}",
                exchange.getRequest().getMethod().name(),
                exchange.getRequest().getURI().getHost(),
                exchange.getRequest().getURI().getPath(),
                exchange.getRequest().getQueryParams());
        log.info(info);
        exchange.getAttributes().put("startTime", System.currentTimeMillis());
        // 后置处理,放到then
        return chain.filter(exchange).then( Mono.fromRunnable(() -> {
            Long startTime = exchange.getAttribute("startTime");
            if (startTime != null) {
                Long executeTime = (System.currentTimeMillis() - startTime);
                log.info(exchange.getRequest().getURI().getRawPath() + " : " + executeTime + "ms");
            }
        }));
    }
    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}