当前位置: 首页 > news >正文

SpringCloud API Gateway2.0如何解决docker中应用间IP漂移的正确手法

前言

SpringCloud API Gateway大家都知道可以限流、可以拦截流量、可以抓到请求,可是它的最重要作用是:充当docker中的Load Balance。而这一特性几乎有90%的开发者们都被前3个特性所蒙蔽了双眼而忽略了。

Spring Cloud API Gateway诞生的真正的目的

Docker化进程带来的影响

优势

docker化带来的是开机快速、资源节约、云原生。

问题

Docker化带来的另一个问题就是:每次重启或者是重新布署后docker内的镜像ip是“漂移”的。

譬如说:

docker内有一镜像:

  • 镜像名:TestWeb1;
  • 端口号:运行在9081端口上
  • IP:刚布署完时TestWeb1位于172.0.1.52

当你每次发布时你会发觉TestWeb1一会跑到了:172.0.1.55上一会又变成了172.0.1.21了。

而api gateway里如果我们配置路由是这样的话:

可能一开始我们是可以访问得了的,而当每次镜像一被重新布署,那么ip地址就会发生“漂移”。

我们总不见得每次都要改hosts或者是DNS,这哪是云原生

api gateway对于docker容器中的镜像代理的正确做法

即配置成:

lb://TestWeb1

*注:上述api gateway叫:venus,是基于spring cloud api gateway2.0做的带有图形化界面可动态配置的企业级api gateway,它跟随着我打过双11和618应对过10万级每秒并发,目前已经完全开源在git了,需要的人可自行下载。

api gateway中的lb的用法

什么是lb://协议,它有什么作用?

我们假设有以下这种架构

我们可以看到一旦当:

  1. 所有的微服务以nacos discovery把自己当成服务注册进nacos里;
  2. 同时当spring cloud api gateway也用nacos discovery把自己注册进nacos里;

此时,nacos服务器就是一个load balance。或者说nacos本身就是具备load balance功能的。它本身就可以记住当前使用nacos discovery注册进它服务列表的服务的真实ip到底是在哪里的。因此通过spring cloud api gateway里的lb://服务名就相当于访问了:http://一个服务的ip:端口号/sample/hello这样 的一个调用了。

比如说:我们的TestWeb1上有一个服务:http://localhost:9081/sample/hello。只要按照上述这样的架构把TestWeb1做成镜像包放于docker中,我们只需要在spring cloud api gateway上配置成:

当访问spring cloud api gateway所在服务器(它位于http://localhost:9080上):http://localhost:9080/sample/hello时,spring cloud api gateway会自动路由到docker上的TestWeb1上去的。

NACOS里怎么做到路由的?

这是我们在nacos里的两个服务。

  • TestWeb1位于docker容器内;
  • apigateway运行在我们的本地的intellij上;

现在,我们点击具体的apigateway这个服务,进入查看详情

我们可以看到spring cloud api gateway的ip是:169.254.57.189。这是我本地的IP。

我们再点击TestWeb1这个服务,进入查看详情。

我们可以看到它的IP是:host.docker.internal,由于host.docker.internal等于:本地docker安装的那台机器的IP就等于:169.254.57.189。

因此,api gateway会通过: lb://服务名自动取得相应的服务的具体ip和端口到底在哪里。

以上就是lb://协议通过nacos的服务名获取当前服务的ip和端口号的寻址路径。

要知道nacos这东西一出现后,在大厂内部当时瞬间就让万台nginx以及F5成为了历史,这是因为nacos本身就是一个load balance,这一把直接就可以省出一笔巨款来,同时又因为只要你的服务使用了nacos discovery client,哪怕服务本身的ip马上发生了变更nacos也可以马上知道当前你的服务跑到了哪个IP上去了。

是不是很强大?

动手实现api gateway 2.0通过注册nacos动态获得docker内境像实例的例子

我们这个例子里用的api gateway是标准的spring cloud api gateway2.0,我使用了api gateway的动态实时路由并使用了vue3做了界面,项目名叫:venus,已经完全开源在git了,需要的人可自行下载。

我们来看我们的api gateway2.0的启动方式。

spring cloud api gateway2.0

spring cloud启动文件:FountainGatewayApplication.java

package com.mkyuan.fountaingateway;import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.redisson.spring.starter.RedissonAutoConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import javax.annotation.PostConstruct;@SpringBootApplication
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class, RedisAutoConfiguration.class, RedissonAutoConfiguration.class, RedisRepositoriesAutoConfiguration.class
})
@EnableDiscoveryClient
public class FountainGatewayApplication {protected Logger logger = LogManager.getLogger(this.getClass());public static void main(String[] args) {SpringApplication.run(FountainGatewayApplication.class, args);}}

关键在于这一行:@EnableDiscoveryClient

pom.xml

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.mkyuan</groupId><artifactId>apigateway</artifactId><version>1.0</version><packaging>jar</packaging><name>apigateway</name><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.4.2</version><relativePath/></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><java.version>11</java.version><spring-cloud.version>2020.0.1</spring-cloud.version><spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version><log4j2.version>2.17.1</log4j2.version><spring.data.redis>1.8.14-RELEASE</spring.data.redis><redisson.version>3.19.1</redisson.version><common.langs3.version>3.17.0</common.langs3.version><fastjson.version>1.2.59</fastjson.version><jedis.version>3.9.0</jedis.version></properties><dependencies><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>${fastjson.version}</version></dependency><!-- redis --><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><!-- <exclusions> <exclusion> <groupId>org.redisson</groupId> <artifactId>redisson-spring-data-23</artifactId></exclusion> </exclusions> --><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></exclusion></exclusions><version>${redisson.version}</version></dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-data-24</artifactId><version>${redisson.version}</version></dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>${jedis.version}</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>${common.langs3.version}</version></dependency><!-- Spring Cloud Gateway --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><!-- Nacos Discovery --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><!-- Nacos Config --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><!-- Bootstrap --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><!-- LoadBalancer --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><dependency><groupId>com.lmax</groupId><artifactId>disruptor</artifactId><version>3.4.4</version></dependency><!-- 排除了老版本log4j2后升级到最新的2.15.0 --><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>${log4j2.version}</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>${log4j2.version}</version></dependency><!-- 排除了老版本log4j2后升级到最新的2.15.0 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><!-- Spring Cloud --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><!-- 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><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>

bootstrap.yml

spring:application:name: apigatewaycloud:nacos:discovery:server-addr: ${NACOS_HOST:localhost}:${NACOS_PORT:8848}username: nacospassword: nacosnamespace: localconfig:server-addr: ${NACOS_HOST:localhost}:${NACOS_PORT:8848}username: nacospassword: nacosnamespace: localgroup: DEFAULT_GROUPfile-extension: yamlprefix: apigatewayrefresh-enabled: true

TestWeb1工程

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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.mkyuan.sample</groupId><artifactId>TestWeb1</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.4.2</version><relativePath/></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><java.version>11</java.version><spring-cloud.version>2020.0.1</spring-cloud.version><spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version><log4j2.version>2.17.1</log4j2.version><spring.data.redis>1.8.14-RELEASE</spring.data.redis><redisson.version>3.19.1</redisson.version><common.langs3.version>3.17.0</common.langs3.version><fastjson.version>1.2.59</fastjson.version><jedis.version>3.9.0</jedis.version></properties><dependencies><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>${fastjson.version}</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>${common.langs3.version}</version></dependency><!-- Spring Cloud Gateway --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><!-- Nacos Discovery --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><!-- Nacos Config --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><!-- Bootstrap --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><!-- LoadBalancer --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><dependency><groupId>com.lmax</groupId><artifactId>disruptor</artifactId><version>3.4.4</version></dependency><!-- 排除了老版本log4j2后升级到最新的2.15.0 --><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>${log4j2.version}</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>${log4j2.version}</version></dependency><!-- 排除了老版本log4j2后升级到最新的2.15.0 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><!-- Spring Cloud --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><!-- 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><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency></dependencies></dependencyManagement><build><plugins><!-- Spring Boot Maven Plugin --><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><mainClass>com.mkyuan.sample.Application</mainClass><layout>JAR</layout></configuration><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build>
</project>

application.yml

server:port: 9081
logging:config: classpath:log4j2.xmllevel:root: infocom.mkyuan: debugorg.springframework.cloud.gateway: debug

bootstrap.yml

spring:application:name: TestWeb1cloud:nacos:discovery:server-addr: ${NACOS_HOST:localhost}:${NACOS_PORT:8848}username: nacospassword: nacosnamespace: localconfig:server-addr: ${NACOS_HOST:localhost}:${NACOS_PORT:8848}username: nacospassword: nacosnamespace: localgroup: DEFAULT_GROUPfile-extension: yamlprefix: TestWeb1refresh-enabled: true

spring cloud启动文件:Application.java

package com.mkyuan.sample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@ComponentScan(basePackages = "com.mkyuan")
@EnableDiscoveryClient
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}

关键同样有一行:@EnableDiscoveryClient。

对外暴露API-HelloApi.java


package com.mkyuan.sample.hello.controller;import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;@RestController
@RequestMapping("/sample")
public class HelloApi {@PostMapping("/hello")@ResponseBodypublic String sayHello() {return "{\"message\": \"hello\"}";}
}

很简单。

运行TestWeb1

它只是返回一个hello。

把TestWeb1做成Docker镜像

在TestWeb1根目录下建立两个文件,它们分别为:

  1. docker-compose.yml
  2. Dockerfile

docker-compose.yml

      - SPRING_CLOUD_NACOS_DISCOVERY_IP=host.docker.internal  # 使用宿主机地址或同apigateway服务ip网段

这儿需要注意的两点
image: localhost:9991/testweb1:1.0

此处的localhost:9991是我docker的本地镜像仓库所在。

这是因为,我的docker基础镜像用的是:openjdk:11-jre-slim,它是一个自带openjdk11的linux镜像。它有一两百兆左右。

每次构建时都会:从docker.io去下载这个镜像文件。那么你的构建过程所耗时间就会很长。

而用了docker本地镜像仓库后,每次只会构建你本地的应用镜像而不会再去docker.io去重复下载这个基础镜像了。

SPRING_CLOUD_NACOS_DISCOVERY_IP=host.docker.internal  # 使用宿主机地址或同apigateway服务ip网段

这儿可不是nacos服务器所在的ip,而是指的是:当nacos根据请求的服务名获得需要路由到的服务的ip地址和端口所在的TestWeb1真实的服务地址所在。

比如说:我们的TestWeb1的docker以及api gateway还有一系列微服务运行在:192.168.0.1这台宿主机上,那么此时这儿的SPRING_CLOUD_NACOS_DISCOVERY_IP就要设成192.168.0.1上。

这边由于我用的宿主机上用的是本地的windows docker deskstop,因此我设成了:host.docker.internal(相当于localhost的意思)。

Dockerfile

# 使用更小的openjdk基础镜像
FROM openjdk:11-jre-slim# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone# 设置工作目录
WORKDIR /app# 创建一个非root用户来运行应用
RUN groupadd -r spring && useradd -r -g spring spring# 创建日志目录并设置权限
RUN mkdir -p /app/logs && chown -R spring:spring /app# 复制配置文件到指定位置
COPY --chown=spring:spring src/main/resources/application.yml /app/config/
COPY --chown=spring:spring src/main/resources/bootstrap.yml /app/config/
COPY --chown=spring:spring src/main/resources/log4j2.xml /app/config/# 切换到非root用户
USER spring:spring# 复制jar文件
COPY --chown=spring:spring target/TestWeb1-1.0-SNAPSHOT.jar app.jar# 设置JVM参数
ENV JAVA_OPTS="-Xms512m -Xmx512m -XX:+UseG1GC"# 暴露应用端口
EXPOSE 9081# 启动应用,指定配置文件位置
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -Dspring.config.location=file:/app/config/ -Dlogging.config=file:/app/config/log4j2.xml -jar app.jar"]

构建本地镜像仓库

创建目录
mkdir E:\docker_images\docker-registry\data
创建或修改Docker配置文件

必须使用docker desktop里的settings->docker engine里列出的daemon.json图形界面来修改

加的是这一句:

  "insecure-registries": ["localhost:9991"]

这就是我们的本地镜像仓库。

更改完后点击docker desktop右下角的【Apply & Restart】按钮。如果是在mac或者是ubuntu/centos里记得要重启docker的服务以使得配置生效

运行以下命令来构建本地仓库
docker run -d --name local-registry --restart=always -p 9991:5000 -v E:/docker_images/docker-registry/data:/var/lib/registry registry:2

此处的:5000为镜像仓库内部的默认端口号,外部的9991就是我们推送镜像的端口号。

验证本地镜像仓库registry是否正常运行
docker ps | grep local-registry
curl http://localhost:5001/v2/_catalog

开始构建TestWeb1应用镜像

构建应用

在TestWeb1根目录下即有docker-compose.yml和Dockerfile的那一层目录键入以下命令

docker compose build

大约5分钟左右,TestWeb1应用构建完毕。

构建完应用后把基础镜像推送到本地docker镜像仓库中去
docker push localhost:9991/testweb1:1.0

我们可以看到如下这样的输出就代表基础镜像已经成功推送到了本地镜像仓库中去了。

启动应用镜像
docker compose up -d

然后我们通过:http://localhost:9081/sample/helo访问,当看到:

那么就代表我们的应用docker启动成功了。

启动api gateway(venus)

它启动后位于9080端口。

配置一条路由

通过api gateway访问位于docker内的testweb1

http://localhost:9080/sample/hello

同样是成功返回了TestWeb1应用的api的。

再观察api gateway的控台,看下方红色箭头所指之处。

这就代表了api gateway成功的把请求转到了docker内的TestWeb1应用上去了。

好了,结束这次的教程,关键还是大家自己要多动动手并亲自实验才能真正掌握这些精髓!

http://www.dtcms.com/a/437958.html

相关文章:

  • 鸿蒙Next中使用mDNS发现局域网服务:完整指南与实战
  • 长泰建设局网站注册网站多久
  • 孝感网站开发江苏建设服务信息网站
  • 数据分析概述与环境搭建
  • 易语言网站怎么做帕绍网站建设
  • vue3父组件和子组件之间传递数据
  • Coze源码分析-资源库-编辑工作流-前端源码-核心流程/API/总结
  • Netty服务器监听读写超时
  • PHP 中的正则表达式
  • Linux的Socket编程之UDP
  • 环境没有tomcat怎么演示自己做的网站动漫设计专业就业方向
  • 180课时吃透Go语言游戏后端开发7:Go语言中的函数
  • Python核心架构深度解析:从解释器原理到GIL机制全面剖析
  • 数据结构_哈夫曼编码(Huffman)完整指南:从原理到实现,附考研真题详解
  • 怎样做网站吸引客户网站开发专业就业前系军
  • 四川建站模板网站公司有哪些做任务网站
  • 藏语自然语言处理入门 - 5 文本归类
  • Stanford CS336 assignment1 | Transformer Language Model Architecture
  • 告别人工出题!PromptCoT 2.0 让大模型自己造训练难题,7B 模型仅用合成数据碾压人工数据集效果!
  • Prompt Programming - 用文字重构AI智能体系
  • 基于提示学习的多模态情感分析系统:从MULT到PromptModel的华丽升级
  • Node.js 图形渲染库对比:node-canvas 与 @napi-rs/canvas
  • 【LangChain】P10 LangChain 提示词模板深度解析(一):Prompt Template
  • C# TCP 服务端开发笔记(TcpListener/TcpClient)
  • 180课时吃透Go语言游戏后端开发6:Go语言的循环语句
  • wordpress+vps建站关键词语有哪些
  • 网站建设基本标准野花高清中文免费观看视频
  • hadoop-hdfs
  • VB6.0找不到该引用word,excel“Microsoft Excel 16.0 Object Library”解决方法
  • 读者-写者问题实现真正的写优先