Netty重试一定次数后调用System.exit(n)退出应用程序(二)

news/2024/10/22 16:52:16/

 ============================System.exit()方法=============================

原型:System.exit(int status)

其功能主要是调用Runtime.getRuntime().exit(status);

作用是终止当前正在运行的Java虚拟机,这个status表示退出的状态码,非零表示异常终止。(可以返回给其他进程的调用者一个调用的返回码,以根据返回码采取不同的策略。)

注意:不管status为何值程序都会退出,和return 相比有不同的是:return是回到上一层,而System.exit(status)是回到最上层。

System.exit(0): 

  • 正常退出,程序正常执行结束退出,Java GC进行垃圾回收,直接退出。
  • 在Swing开发中,一般用于Swing窗体关闭按钮。(重写windowClosing方法时调用System.exit(0)来终止程序,Window类的dispose()方法只是关闭窗口,并不会让程序退出)。

System.exit(1): 

  • 是非正常退出,就是说无论程序正在执行与否,都退出.
  • 如果为非0的话,如果这个方法被调用后,虚拟机已开始关闭序列如果关闭钩子正在运行,此方法将无限期阻塞。如果关将钩子运行完成,并且未调用的finalizers,在finalization-on-exit允许的情况下启动回收完成,虚拟机停止。
  • 一般在catch块中会使用(例如使用Apache的FTPClient类时,源码中推荐使用System.exit(1)告知连接失败),当程序会被脚本调用、父进程调用发生异常时需要通过System.exit(1)来告知操作失败,默认程序最终返回的值返是0,即然发生异常默认还是返回0,因此在这种情况下需要手工指定返回非零。

===============================Nettty代码===================================

Netty客户端:

package org.jy.sso.websocket.stomp.push.netty.chat.system.chatcenter;import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.time.DateFormatUtils;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chapter.handler.FirstClientSendMsgHandler;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chatcommond.ChatRetryCommand;
import org.jy.sso.websocket.stomp.push.netty.chat.system.constant.ChatClientConstant;import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;/*** IM聊天系统客户端* <p>* 工具主要是封装了java.util.Random对象,提供了一些产生随机数或者随机字符串的方法。随机工具类是RandomUtil,里面的方法都是静态方法。*/
@Slf4j
public class ChatCenterClient {public static final ConcurrentHashMap<Byte,Bootstrap> bootStrapMap = new ConcurrentHashMap();@SneakyThrowspublic static void main(String[] args) {Bootstrap bootstrap = new Bootstrap();NioEventLoopGroup group = new NioEventLoopGroup();bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<Channel>() {@Overrideprotected void initChannel(Channel channel) {// 在客户端添加一遍逻辑处理器,在客户建立连接成功后,向服务器端写数据channel.pipeline().addLast(new FirstClientSendMsgHandler());}});connect(bootstrap, ChatClientConstant.CHAT_SERVER_HOST_IP, ChatClientConstant.CHAT_SERVER_PORT, ChatClientConstant.CHAT_MAX_RETRY_NUM);// 重连指令与对应客户端启动的实例映射关系bootStrapMap.put(ChatRetryCommand.CHAT_RETRY_CONNECTION, bootstrap);}/*** @param bootstrap 客户端启动器* @param host      主机* @param port      端口* @param retryNum  指数退避重新连接的次数* @author yh19166* @deprecated 连接和重连机制,实现了指数退避重连*/private static void connect(Bootstrap bootstrap, String host, int port, int retryNum) {bootstrap.connect(host, port).addListener(future -> {// 通过future.isSuccess() 判断是否连接成功if (future.isSuccess()) {System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " -->> 连接成功....");} else if (retryNum == ChatClientConstant.CHAT_INI_RETRY_NUM) {System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " -->> 重连次数已用完,放弃连接....");/*** 原型:System.exit(int status)** 其功能主要是调用Runtime.getRuntime().exit(status);** 作用是终止当前正在运行的Java虚拟机,这个status表示退出的状态码,非零表示异常终止。(可以返回给其他进程的调用者一个调用的返回码,以根据返回码采取不同的策略。)** 注意:不管status为何值程序都会退出,和return 相比有不同的是:return是回到上一层,而System.exit(status)是回到最上层。*=======================================================================================================================================================* System.exit(0):** 正常退出,程序正常执行结束退出,Java GC进行垃圾回收,直接退出。* 在Swing开发中,一般用于Swing窗体关闭按钮。(重写windowClosing方法时调用System.exit(0)来终止程序,Window类的dispose()方法只是关闭窗口,并不会让程序退出)。* System.exit(1):** 是非正常退出,就是说无论程序正在执行与否,都退出.* 如果为非0的话,如果这个方法被调用后,虚拟机已开始关闭序列如果关闭钩子正在运行,此方法将无限期阻塞。如果关将钩子运行完成,并且未调用的finalizers,在finalization-on-exit允许的情况下启动回收完成,虚拟机停止。* 一般在catch块中会使用(例如使用Apache的FTPClient类时,源码中推荐使用System.exit(1)告知连接失败),当程序会被脚本调用、父进程调用发生异常时需要通过System.exit(1)来告知操作失败,默认程序最终返回的值返是0,即然发生异常默认还是返回0,因此在这种情况下需要手工指定返回非零。*/System.exit(0);} else {// 第几次重连int order = (ChatClientConstant.CHAT_MAX_RETRY_NUM - retryNum) + ChatClientConstant.CHAT_STEP_RETRY_LENGTH;// 客户考虑重新连接的逻辑,分梯度连接......System.out.println("第 " + order + " 连接失败......");// 本次重连的间隔: 通过左移操作快速计算出重连时间间隔int delay = ChatClientConstant.CHAT_STEP_RETRY_LENGTH << order;System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " -->> 连接失败,第" + order + "次重新连接....");// 实现定时任务逻辑bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retryNum - ChatClientConstant.CHAT_STEP_RETRY_LENGTH), delay, TimeUnit.SECONDS);}});}/*** @param bootstrap 客户端启动类* @param host      主机* @param port      端口* @param retryNum  指数退避重新连接的次数* @author yh19166* @deprecated 连接和重连机制,实现了指数退避重连*/@Deprecatedpublic static void retryConnect(Bootstrap bootstrap, String host, int port, int retryNum) {bootstrap.connect(host, port).addListener(future -> {if (future.isSuccess()) {log.info("连接服务器成功!");} else if (retryNum == ChatClientConstant.CHAT_INI_RETRY_NUM) {log.error("重连次数已用完,放弃连接!");/*** 原型:System.exit(int status)** 其功能主要是调用Runtime.getRuntime().exit(status);** 作用是终止当前正在运行的Java虚拟机,这个status表示退出的状态码,非零表示异常终止。(可以返回给其他进程的调用者一个调用的返回码,以根据返回码采取不同的策略。)** 注意:不管status为何值程序都会退出,和return 相比有不同的是:return是回到上一层,而System.exit(status)是回到最上层。*=======================================================================================================================================================* System.exit(0):** 正常退出,程序正常执行结束退出,Java GC进行垃圾回收,直接退出。* 在Swing开发中,一般用于Swing窗体关闭按钮。(重写windowClosing方法时调用System.exit(0)来终止程序,Window类的dispose()方法只是关闭窗口,并不会让程序退出)。* System.exit(1):** 是非正常退出,就是说无论程序正在执行与否,都退出.* 如果为非0的话,如果这个方法被调用后,虚拟机已开始关闭序列如果关闭钩子正在运行,此方法将无限期阻塞。如果关将钩子运行完成,并且未调用的finalizers,在finalization-on-exit允许的情况下启动回收完成,虚拟机停止。* 一般在catch块中会使用(例如使用Apache的FTPClient类时,源码中推荐使用System.exit(1)告知连接失败),当程序会被脚本调用、父进程调用发生异常时需要通过System.exit(1)来告知操作失败,默认程序最终返回的值返是0,即然发生异常默认还是返回0,因此在这种情况下需要手工指定返回非零。*/System.exit(0);} else {// 第几次重连int order = (ChatClientConstant.CHAT_MAX_RETRY_NUM - retryNum) + ChatClientConstant.CHAT_STEP_RETRY_LENGTH;// 本次重连的间隔int delay = ChatClientConstant.CHAT_STEP_RETRY_LENGTH << order;log.error(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + ": 连接失败,第" + order + "次重新连接....");bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retryNum - ChatClientConstant.CHAT_STEP_RETRY_LENGTH), delay, TimeUnit.SECONDS);}});}
}

 代码中用到的常量类:

package org.jy.sso.websocket.stomp.push.netty.chat.system.constant;public class ChatClientConstant {// 主机IPpublic static final String CHAT_SERVER_HOST_IP = "127.0.0.1";// 连接的端口public static final int CHAT_SERVER_PORT = 8000;// 最大重试次数public static final int CHAT_MAX_RETRY_NUM = 4;// 重试初始值public static final int CHAT_INI_RETRY_NUM = 0;// 步长public static final int CHAT_STEP_RETRY_LENGTH = 1;
}

 客户端处理器:

package org.jy.sso.websocket.stomp.push.netty.chat.system.chapter.handler;import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.time.DateFormatUtils;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chatcenter.ChatCenterClient;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chatcommond.ChatRetryCommand;
import org.jy.sso.websocket.stomp.push.netty.chat.system.constant.ChatClientConstant;import java.nio.charset.StandardCharsets;
import java.util.Date;/*** 客户端向服务器端发送消息第一个处理器*/
@Slf4j
public class FirstClientSendMsgHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelActive(ChannelHandlerContext ctx) {System.out.println("=========================向服务器端写数据============================");System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + ": 客户端写出数据");// 1.获取数据ByteBuf buffer = getByteBuf(ctx);// 2.写数据ctx.channel().writeAndFlush(buffer);}private ByteBuf getByteBuf(ChannelHandlerContext ctx) {// 1.获取二进制抽象 ByteBufByteBuf buffer = ctx.alloc().buffer();// 2.准备传递给服务器端的数据,并指定字符编码为UTF-8byte[] bytes = "你好,这是Netty客户端传递的数据".getBytes(StandardCharsets.UTF_8);// 3.填充数据到ByteBufbuffer.writeBytes(bytes);return buffer;}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {System.out.println("=========================读取到服务器端的数据============================");ByteBuf byteBuf = (ByteBuf) msg;System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " : 服务器端读到的客户端传递过来的数据 -> "+ byteBuf.toString(StandardCharsets.UTF_8)); // 去掉这个编码转换结果为:}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) {ctx.flush();}/*** 程序的健壮性考虑** @param ctx 通道处理器上下文* @param cause 抛出的错误信息*/@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {// 客户端没有正常退出导致的异常: 远程主机强迫关闭了一个现有的连接// 服务器端突然关闭,抛出该异常: 远程主机强迫关闭了一个现有的连接ctx.channel().flush();ctx.channel().closeFuture();log.info("FirstClientSendMsgHandler.exceptionCaught : [{}]", cause.getMessage());Bootstrap Bootstrap = ChatCenterClient.bootStrapMap.get(ChatRetryCommand.CHAT_RETRY_CONNECTION);ChatCenterClient.retryConnect(Bootstrap, ChatClientConstant.CHAT_SERVER_HOST_IP, ChatClientConstant.CHAT_SERVER_PORT, ChatClientConstant.CHAT_MAX_RETRY_NUM);}
}

==================================Netty服务器端==============================

package org.jy.sso.websocket.stomp.push.netty.chat.system.chatcenter;import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.SneakyThrows;
import org.jy.sso.websocket.stomp.push.netty.chat.system.chapter.handler.FirstServerReceiveDataHandler;/*** IM服务器端*/
public class ChatCenterServer {@SneakyThrowspublic static void main(String[] args) {// 服务器端启动类ServerBootstrap serverBootstrap = new ServerBootstrap();// boss主线程(父线程)NioEventLoopGroup bossLoopGroup = new NioEventLoopGroup();// 工作线程(子线程)NioEventLoopGroup workerLoopGroup = new NioEventLoopGroup();serverBootstrap // 父子线程建立组连接.group(bossLoopGroup, workerLoopGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) {ch.pipeline().addLast(new FirstServerReceiveDataHandler());}}).bind(8000).sync();}
}

 服务器端Pipeline对应的通道处理器handler:

package org.jy.sso.websocket.stomp.push.netty.chat.system.chapter.handler;import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateFormatUtils;import java.nio.charset.StandardCharsets;
import java.util.Date;/*** 服务器端接收客户端数据的处理器:* 服务器端接收客户端数据* 响应客户端请求,并向客户端写数据*/
@Slf4j
public class FirstServerReceiveDataHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object requestContent) {//接收客户端的数据逻辑System.out.println("============================接收客户端的数据=============================");ByteBuf byteBuf = (ByteBuf) requestContent;System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " : 服务器端读到的客户端传递过来的数据 -> "+ byteBuf.toString(StandardCharsets.UTF_8)); // 去掉这个编码转换结果为:System.out.println("============================向客户端写数据=============================");ByteBuf response = getByteBuf(ctx);ctx.channel().writeAndFlush(response);}/*** 向客户端写响应数据** @param ctx 通道处理器上下文* @return {@link io.netty.buffer.ByteBuf}*/private ByteBuf getByteBuf(ChannelHandlerContext ctx) {byte[] responseBytes = "你好,欢迎关注杨哥哥的微信公众号,<<财务自由耕耘者>>!".getBytes(StandardCharsets.UTF_8);ByteBuf buffer = ctx.alloc().buffer();buffer.writeBytes(responseBytes);return buffer;}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) {ctx.flush();}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {// 客户端未正常的关闭,抛出异常: 主机强迫关闭了一个现有的连接ctx.channel().flush();ctx.channel().closeFuture();log.info("exception: [{}]",cause.getMessage());// 可以考虑重启服务端,重新启动|重连区别}
}

测试效果日下:

====================== 先运行客户端代码============不运行服务器端代码=====================
20:04:33.435 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.tinyCacheSize: 512
20:04:33.435 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.smallCacheSize: 256
20:04:33.435 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.normalCacheSize: 64
20:04:33.435 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxCachedBufferCapacity: 32768
20:04:33.435 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.cacheTrimInterval: 8192
20:04:33.448 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
20:04:33.448 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 65536
20:04:33.448 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
第 1 连接失败......
2023-05-24 20:04:34 -->> 连接失败,第1次重新连接....
第 2 连接失败......
2023-05-24 20:04:37 -->> 连接失败,第2次重新连接....
第 3 连接失败......
2023-05-24 20:04:42 -->> 连接失败,第3次重新连接....
第 4 连接失败......
2023-05-24 20:04:51 -->> 连接失败,第4次重新连接....
2023-05-24 20:05:08 -->> 重连次数已用完,放弃连接....Process finished with exit code 0
=============================客户端输出的日志=======================================
======先运行服务器端代码,在运行客户端代码,待客户端连接上服务端后,再关闭服务器端==============
20:27:31.730 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.normalCacheSize: 64
20:27:31.730 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxCachedBufferCapacity: 32768
20:27:31.730 [main] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.cacheTrimInterval: 8192
20:27:31.748 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
20:27:31.748 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 65536
20:27:31.748 [main] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
2023-05-24 20:27:31 -->> 连接成功....
=========================向服务器端写数据============================
2023-05-24 20:27:31: 客户端写出数据
20:27:31.801 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxCapacityPerThread: 32768
20:27:31.801 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxSharedCapacityFactor: 2
20:27:31.801 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.linkCapacity: 16
20:27:31.809 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.ratio: 8
20:27:31.841 [nioEventLoopGroup-2-1] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.bytebuf.checkAccessible: true
20:27:31.841 [nioEventLoopGroup-2-1] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@5f1d79
=========================读取到服务器端的数据============================
2023-05-24 20:27:31 : 服务器端读到的客户端传递过来的数据 -> 你好,欢迎关注杨哥哥的微信公众号,<<财务自由耕耘者>>!
20:27:37.561 [nioEventLoopGroup-2-1] INFO org.jy.sso.websocket.stomp.push.netty.chat.system.chapter.handler.FirstClientSendMsgHandler - FirstClientSendMsgHandler.exceptionCaught : [远程主机强迫关闭了一个现有的连接。]
20:27:38.569 [nioEventLoopGroup-2-2] ERROR org.jy.sso.websocket.stomp.push.netty.chat.system.chatcenter.ChatCenterClient - 2023-05-24 20:27:38: 连接失败,第1次重新连接....
第 2 连接失败......
2023-05-24 20:27:41 -->> 连接失败,第2次重新连接....
第 3 连接失败......
2023-05-24 20:27:46 -->> 连接失败,第3次重新连接....
第 4 连接失败......
2023-05-24 20:27:55 -->> 连接失败,第4次重新连接....
2023-05-24 20:28:12 -->> 重连次数已用完,放弃连接....Process finished with exit code 0
===============================客户端输出的日志==============================


http://www.ppmy.cn/news/79061.html

相关文章

shiro环境搭建

源码部署 这种方法相对复杂&#xff0c;如果不需要分析源码直接用docker就行 前置条件&#xff1a;Maven Ideal Tomcat 下载方式1&#xff1a;https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4&#xff0c;然后将文件夹导入ideal下载方式2&#xff1a;将shiro…

Java中链表的实现(超详细)

在Java中&#xff0c;链表可以通过创建节点和链接节点来实现。以下是一个简单的链表实现示例&#xff1a; public class LinkedList {Node head; // 头结点// 节点类class Node {int data;Node next;Node(int d) {data d;next null;}}// 在链表头部插入节点public void push…

Linux - 第17节 - 网络基础(应用层三)

1.HTTPS协议 1.1.HTTPS简介 HTTPS也是⼀个应⽤层协议&#xff0c;是在HTTP协议的基础上引⼊了⼀个加密层。 HTTP协议内容都是按照文本的方式明文传输的&#xff0c;明文传输是不安全的&#xff0c;所以现在主流的解决 方式是在http所在的应用层和tcp所在的传输层之间加一层SSL…

OS7安装rabbitmq

1.卸载存在的rabbitmq 停止rabbitmq服务&#xff1a; systemctl stop rabbitmq-server 查看rabbitmq安装的相关列表: yum list | grep rabbitmq 卸载rabbitmq已安装的相关内容: yum -y remove rabbitmq-server.noarch 查看erlang安装的相关列表: yum list | grep erlang 卸…

数据库知识点

1索引 介绍 索引本质上是一张表&#xff0c;保存了主键与索引字段&#xff0c;在对数据做频繁的查询或排序时&#xff0c;可在某些字段上添加索引&#xff0c;提高检索的的效率&#xff0c;降低IO成本&#xff0c;并可以使用索引列&#xff0c;对数据进行排序&#xff0c;降低…

10 对象和类

1.访问控制和数据隐藏 公有成员函数是程序和对象的私有成员之间的桥梁&#xff0c;提供了接口 2.类和结构 结构的默认访问类型是&#xff1a;public 类的默认访问类型是&#xff1a;private 3. 成员函数的内联 定义位于类声明中的函数&#xff0c;为内联函数 也可以现在类内进行…

LRU Cache

前言 哈喽&#xff0c;各位小伙伴大家好&#xff0c;本章内容为大家介绍计算机当中为了提高数据相互传输时的效率而引进的一种重要设计结构叫做LRU Cache,下面将为大家详细介绍什么是LRU Cache,以及它是如何是实现的&#xff0c;如何提升效率的。 1.什么是LRU Cache? LRU是L…

测牛学堂:2023软件测试学习教程之sql的分页查询

查重 所谓的查重&#xff0c;就是过滤返回数据中重复的记录。 语法关键字&#xff1a;distinct select distinct 字段列表 from tableName [where 子句]注意&#xff1a; distinct的位置有两个地方&#xff0c;一个是放在select后面&#xff0c;对筛选出来的数据去重。 一个是…