kotlin半生对象_如何在Kotlin中使用Actor实现对象池

kotlin半生对象

by osha1

由osha1

如何在Kotlin中使用Actor实现对象池 (How to implement an Object-Pool with an Actor in Kotlin)

We use object pool in jasync-sql to manage connections to the database. In this post, I will share how it is done in a performant, lock-free manner using Kotlin coroutines with an Actor.

我们在jasync-sql中使用对象池来管理与数据库的连接。 在这篇文章中,我将分享如何通过与Actor一起使用Kotlin协程以高性能,无锁的方式完成操作。

An object pool has a very simple API to work with. It is a pool of objects with two methods: take() and return().

对象池具有非常简单的API。 它是具有两种方法的对象池: take()return()

On first sight it looks like a very simple problem. The main catch here is that it has to be both performant and thread-safe, and that’s what makes it interesting and tricky to implement.

乍看之下,这似乎是一个非常简单的问题。 这里的主要问题是它必须兼具高性能和线程安全性,这就是实现它有趣且棘手的原因。

但是,嘿! 为什么我们仍然需要一个对象池? (But hey! Why do we need an object pool anyway?)

jasync-sql is a library to access relational databases like MySQL and PostgreSQL. Database connections are a great example of the need for object pools. The access to the database is done by obtaining a connection from a Connection-Pool, using it and returning it back to the pool.

jasync-sql是一个用于访问关系数据库(如MySQL和PostgreSQL)的库。 数据库连接是需要对象池的一个很好的例子。 通过从Connection-Pool获取连接,使用数据库并将其返回给 ,可以完成对数据库的访问。

With a connection pool we get a couple of advantages over creating connections per each SQL query:

使用连接池,与每个SQL查询创建连接相比,我们有两个优点:

  • Reusing connections — since the overhead of initiating a connection to the database is high (handshake, etc), connection pools allow keeping connections alive, thus reducing that overhead.

    重用连接 -由于启动与数据库的连接的开销很高(握手等),因此连接池允许保持连接处于活动状态,从而减少了开销。

  • Limiting resources — creating a DB connection per user request can be overwhelming to the DB. Using a pool effectively adds a barrier, limiting the number of maximum number of concurrent connections.

    限制资源 -为每个用户请求创建数据库连接可能会使数据库不堪重负。 使用池有效地增加了障碍,限制了并发连接的最大数量。

Well, I am sold, but…
好吧,我被卖了,但是…

连接池在Java世界中解决不了吗? (Isn’t a Connection Pool a solved problem in the Java world?)

Yes it is a solved problem if you’re using JDBC. In that case HikariCP is an excellent choice from my experience, but there are a lot of others. In the case of jasync-sql it is not possible to use HikariCP, because HikariCP works with the JDBC API, and the jasync-sql driver is not implementing that full-fledged API, only a subset of it.

是的,如果您使用JDBC,这是一个已解决的问题。 在这种情况下,从我的经验来看 , HikariCP是一个很好的选择,但还有很多其他选择。 在jasync-sql的情况下,无法使用HikariCP ,因为HikariCP与JDBC API一起使用,并且jasync-sql驱动程序未实现该完整的API,仅实现了一部分。

What about other Object pools in Java world?
Java世界中的其他对象池又如何呢?

There are numerous implementations, but it turns out that you usually find some specific requirement that was not implemented by that pool you’re using.

有许多实现,但是事实证明,您通常会发现一些特定要求,而该要求不是您所使用的池所实现的。

In our case, that requirement was non-blocking. In our pool, all operations have to be non-blocking since the library is async. For example, the take() operation in most implementations returns an object immediately or blocks until an object is ready. Our take() returns a Future<Connection>, which will be completed and continued when the connection is ready to use.

在我们的情况下,该要求是无障碍的。 在我们的池中,由于库是异步的,因此所有操作都必须是非阻塞的。 例如,大多数实现中的take()操作立即返回一个对象或阻塞直到对象准备就绪。 我们的take()返回Future<Connecti on>,当连接准备就绪时,它将完成并继续。

I haven’t seen such an implementation in the wild.

我还没有在野外看到这样的实现。

I really like this answer from Stack Exchange:

我真的很喜欢来自Stack Exchange的答案:

Is object pooling a deprecated technique?Software Engineering Stack Exchange is a question and answer site for professionals, academics, and students working…softwareengineering.stackexchange.com

对象池是否已被弃用? 软件工程堆栈交换是一个为专业人士,学者和工作的学生提供的问答网站 。softwareengineering.stackexchange.com

Another requirement that makes it hard to find an alternative is the need to try and stay compatible as much as possible with the current implementation we have.

另一个很难找到替代方法的要求是,需要尝试与现有的实现尽可能保持兼容。

In case you want to see other implementations you can check here:

如果您想查看其他实现,可以在这里查看:

object pool in java - Google Searchobject pool is a collection of a particular object that an application will create and keep on hand for those…www.google.co.il

Java中的对象池-Google搜索 对象池是应用程序将创建的特定对象的集合,并将这些对象保存在手中…… www.google.co.il

那么我们如何实现对象池呢? (So how did we implement Object Pool?)

Before we dive into the details, let’s observe other requirements from the object pool that were omitted above for clarity but are necessary details.

在深入研究细节之前,让我们观察一下对象池中的其他需求,为清晰起见,这些需求在上面已被省略,但它们是必要的细节。

介面 (Interfaces)

The Object pool interface looks like this:

对象池接口如下所示:

interface AsyncObjectPool<T&gt; {  fun take(): CompletableFuture&lt;T>  fun giveBack(item: T): CompletableFuture<AsyncObjectPool<T>>  fun close(): CompletableFuture<AsyncObjectPool<T>>
}

In addition, when a pool wants to create new objects (connections) it will call the ObjectFactory. The factory has a couple more methods to handle the object lifecycle:

另外,当池要创建新对象(连接)时,它将调用ObjectFactory 。 工厂有更多其他方法来处理对象生命周期:

  • validate — a method to check that the object is still valid. The method should be fast and check only in-memory constructs. For connections we usually check that the last query did not throw an exception and did not get a termination message from netty.

    validate验证对象仍然有效的方法。 该方法应该是快速的,并且仅检查内存中的构造。 对于连接,我们通常检查最后一个查询没有引发异常,也没有从netty获得终止消息。

  • test — similar to validate, but a more exhaustive check. We allow test method to be slow and access the network etc. This method is used to check that idle objects are still valid. For connections, that will be something similar to select 0.

    测试 -与验证类似,但检查更为详尽。 我们允许测试方法变慢并访问网络等。此方法用于检查空闲对象是否仍然有效。 对于连接,这类似于select 0

  • destroy — called to clean up the object when the pool is not using it anymore.

    销毁 -在池不再使用该对象时调用以清除该对象。

The complete interface is:

完整的界面是:

interface ObjectFactory<T> {  fun create(): CompletableFuture<;out T>  fun destroy(item: T)  fun validate(item: T): Try<T>  fun test(item: T): CompletableFuture<T>
}

For pool configuration we have the following properties:

对于池配置,我们具有以下属性:

  • maxObjects — maximum number of connections we allow.

    maxObjects —我们允许的最大连接数。

  • maxIdle — time that we leave the connection open without use. After that time it will be reclaimed.

    maxIdle —我们不使用连接而保持打开状态的时间。 在那之后,它将被回收。

  • maxQueueSize — when a request for a connection arrives and no connection is available, we put the request on hold in a queue. In case the queue is full (its size passed maxQueueSize) it will not wait but instead return an error.

    maxQueueSize —当一个连接请求到达并且没有可用的连接时,我们将该请求置于队列中。 如果队列已满(其大小通过maxQueueSize传递),它将不等待而是返回一个错误。

  • createTimeout — maximum time to wait for a new connection to be created.

    createTimeout —等待创建新连接的最长时间。

  • testTimeout — maximum time to wait for a test query on an idle connection. If it passes we will consider the connection as erroneous.

    testTimeout —在空闲连接上等待测试查询的最长时间。 如果通过,我们将认为连接错误。

  • validationInterval — on this interval, we will test if the idle connections are active and free up connections that passed maxIdle. We will also remove connections that passed testTimeout.

    validationInterval -在此期间,我们将测试如果空闲连接有效且腾出通过连接maxIdle 。 我们还将删除通过testTimeout连接。

原始实施 (Original implementation)

The first implementation of object pool was single threaded. All operations were sent to a worker thread that was responsible to execute them. This method is known as thread-confinement. Object creation and test operations were blocking and query execution itself was non-blocking.

对象池的第一个实现是单线程的。 所有操作都发送到负责执行这些操作的工作线程。 此方法称为线程约束 。 对象创建和测试操作处于阻塞状态,而查询执行本身是非阻塞的。

This method is problematic because operations are done one after another. On top of that, there are a couple of operations that are blocking as mentioned above. There were various cases of high latency when working in some scenarios and use cases (like here for example).

这种方法有问题,因为操作是一个接一个地完成的。 最重要的是,如上所述,有几项操作正在阻塞。 在某些情况和用例中工作时,存在各种高延迟情况(例如,例如此处 )。

As a workaround PartitionedPool was introduced. This is a workaround to the block issue with the above single-threaded approach. The partitioned pool creates multiple SingleThreadedObjectPools, each with its own worker. When a connection is requested, a pool is selected by a modulus on the thread id. The partitioned pool is actually a pool of pools ;-)

作为解决方法,引入了PartitionedPool 。 这是上述单线程方法解决问题的一种解决方法。 分区池创建多个SingleThreadedObjectPools ,每个都有其自己的工作程序。 当请求连接时,将通过线程ID上的模数来选择一个池。 分区池实际上是池的池;-)

I mentioned this is a workaround since it has its own problems: you might still be blocking, but at a lower rate — plus it consume more threads and resources.

我提到了这是一种解决方法,因为它有其自身的问题:您可能仍在阻塞,但是速率较低-而且它消耗更多的线程和资源。

基于Actor的实现 (Actor based implementation)

An Actor is an entity that has a mailbox. It receives messages to its mailbox and processes them one after the other. The mailbox is a sort of a channel to pass events from the outside world to the actor.

Actor是具有邮箱的实体。 它接收到其邮箱的消息,然后一个接一个地处理它们。 邮箱是一种将事件从外界传递给演员的渠道。

A coroutines actor employs lock-free algorithms to allow fast and performant execution of events without the need for locks and synchronized blocks.

协程演员使用无锁算法来快速,高效地执行事件,而无需锁和synchronized块。

You can see an elaborated explanation here.

您可以在此处看到详细的说明。

In our case those events will be take and giveBack. In addition to those, we will have internal messages that the actor sends to itself like objectCreated etc. That allows the actor to have states that does not suffer from concurrency problems, as it is always confined to the same sequential execution. In addition the channel that passes those events is a queue that is using lock-free algorithms so it is very efficient, avoids contention, and generally has very high performance.

在我们的情况下,这些事件将为takegiveBack 。 除此之外,我们还将拥有objectCreated发送给自己的内部消息,例如objectCreated等。这使得objectCreated具有不受并发问题困扰的状态,因为它始终限于同一顺序执行。 另外,传递这些事件的通道是使用无锁算法的队列,因此它非常高效,避免争用并且通常具有很高的性能。

There is an excellent video explaining how this was implemented (note that this is “heavy” algorithmic staff):

有一个精彩的视频解释了如何实现(请注意,这是“繁重的”算法工作人员):

Let’s recap what we have until now:

让我们回顾一下到目前为止所拥有的:

  • An actor receives messages and processes them one by one.

    演员接收消息并对其进行逐一处理。
  • Usually messages will contain a CompletableFuture that should be completed when the actor processes it.

    通常,消息将包含CompletableFuture ,当actor处理该消息时应将其完成。

Messages will be completed immediately or delayed (like in case we are waiting for a connection to be created). If it is delayed the actor will put the Future in a queue, and will use a callback mechanism to notify itself when the original future can be completed.

消息将立即完成或延迟(例如,在我们等待连接建立的情况下)。 如果延迟,则参与者将把Future放在队列中,并将使用回调机制通知自己何时可以完成原始的Future

  • Message processing in the actor should not be blocked or delay the actor. If this happens, it will delay all messages waiting to be processed in the queue and will slow down the entire actor operation.

    actor中的消息处理不应被阻塞或延迟actor。 如果发生这种情况,它将延迟所有等待在队列中处理的消息,并且会减慢整个actor操作的速度。

That’s why, in case we have long running operations inside the actor, we use the callback mechanism.

这就是为什么如果我们在actor中长时间运行操作的原因,我们使用回调机制。

让我们看一下用例的更多细节 (Let’s see more details on the use cases)

Take — someone wants an object from the pool. It will send a message with a callback to the actor. The actor will do one of the following things:

Take -有人要从游泳池里Take东西。 它将带有回调的消息发送给参与者。 演员将执行以下操作之一:

  • If the object is available — the actor will simply return it.

    如果对象可用-演员将简单地将其返回。
  • If the pool hasn’t passed the limit of created objects — the actor will create a new object and return it when the object is ready.

    如果池尚未通过创建对象的限制,则actor将创建一个新对象,并在对象准备就绪时将其返回。

In such a case, object creation can take time, so the actor will connect the callback from the object creation to the original take request callback.

在这种情况下,对象创建可能会花费一些时间,因此参与者会将回调从对象创建连接到原始的获取请求回调。

  • Will put the request in a queue for an available object (unless the queue is full and in that case will just return an error).

    将请求放入可用对象的队列中(除非队列已满,在这种情况下只会返回错误)。

GiveBack — someone wants to give an object back to the pool (release it). This is also done by a message to the actor. The actor will do one of the following:

GiveBack有人想将对象还给池(释放它)。 这也可以通过发送给演员的消息来完成。 演员将执行以下操作之一:

  • If someone is waiting on the wait queue — it will borrow the object to it.

    如果有人在等待队列中等待,它将向其借用该对象。
  • In other cases it will just keep the object on the pool for requests to come, so the object remains idle.

    在其他情况下,它将对象仅保留在池中以等待请求,因此该对象保持空闲状态。

Test — periodically, someone from outside will notify the actor to test connections:

Test -定期,外部人员会通知参与者测试连接:

  • The actor will release the idle connection that hasn’t been used for a long time (it’s configurable).

    actor将释放很长时间未使用的空闲连接(它是可配置的)。
  • The actor will test other idle objects using the ObjectFactory. It will send a callback to the factory and mark those objects as In Use, to prevent from borrowing them until the test is completed.

    参与者将使用ObjectFactory测试其他空闲对象。 它将向工厂发送一个回调并将这些对象标记为In Use ,以防止在测试完成之前借用它们。

  • The actor will check for timeouts in tests and destroy time-outed objects.

    参与者将检查测试中的超时并破坏超时的对象。

Those are the main use cases.

这些是主要的用例。

泄漏 (Leaks)

There can be all sort of leaks in an object pool. Some are internal bugs which I hope are easier to spot and fix, and others are objects that were taken but not returned due to some user error. In such cases, objects might remain in the “In Use” queue forever.

对象池中可能存在各种泄漏。 我希望一些内部错误更容易发现和修复,而其他一些则是由于某些用户错误而被拿回但未返回的对象。 在这种情况下,对象可能永远保留在“ 使用中”队列中。

To avoid such cases, the “In Use” Map is using Java’s WeakHashMap. So if a user lost a connection it will be automatically removed from the map when it is cleaned by Java’s Garbage-Collector.

为了避免这种情况, “使用中”地图使用Java的WeakHashMap 。 因此,如果用户失去了连接,当Java的Garbage-Collector清理连接时,它将自动从地图中删除。

In addition we added a log message in such cases that says: “LEAK-DETECTED”.

此外,在这种情况下,我们添加了一条日志消息,内容为: “泄漏检测到”

而已! (That’s it!)

The full Kotlin source code of the object pool is available here:

对象池的完整Kotlin源代码在此处提供:

jasync-sql/jasync-sqlJava async database driver for MySQL and PostgreSQL written in Kotlin - jasync-sql/jasync-sqlgithub.com

jasync-sql / jasync-sql 用Kotlin编写的用于MySQL和PostgreSQLJava异步数据库驱动程序-jasync-sql / jasync-sql github.com

In an upcoming post I will compare performance metrics of the different implementations.

在下一篇文章中,我将比较不同实现的性能指标。

If you want to read more about Kotlin there is a nice introduction here:

如果您想了解有关Kotlin的更多信息,这里有一个不错的介绍:

And for coroutines in general check out this video:

对于一般的协程,请观看以下视频:

Finally if you want to learn more about Actors implementation using coroutines in Kotlin, then head over here:

最后,如果您想了解有关Kotlin中使用协程的Actor实现的更多信息,请前往此处:

Kotlin/kotlinx.coroutinesLibrary support for Kotlin coroutines . Contribute to Kotlin/kotlinx.coroutines development by creating an account on…github.com

Kotlin / kotlinx.coroutines库特林 协同程序的库支持。 通过在 github.com 上创建一个帐户,为Kotlin / kotlinx.coroutines开发做出贡献

Thanks for reading! ❤️

谢谢阅读! ❤️

翻译自: https://www.freecodecamp.org/news/how-to-implement-an-object-pool-with-an-actor-in-kotlin-ed06d3ba6257/

kotlin半生对象

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/393055.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux运行apktool签名,解决Linux中使用ApkTool遇到问题

8种机械键盘轴体对比本人程序员&#xff0c;要买一个写代码的键盘&#xff0c;请问红轴和茶轴怎么选&#xff1f;遇到问题在Linux中使用IntelliDroid工具时&#xff0c;按要求配置好环境之后&#xff0c;始终无法成功运行该工具内部的ApkTool&#xff0c;导致后续的安卓静态分析…

python 脚本学习(二)

task1&#xff1a; 在一个文件中&#xff0c;单词之间使用空格、分号、逗号或者句号分隔&#xff0c;请提取全部单词。 代码实例&#xff1a; 1234567891011#!/usr/local/python27/bin/python2.7import sys import re words [] with open(sys.argv[1]) as f: for line in f: #…

2.2 Consumer API官网剖析(博主推荐)

不多说&#xff0c;直接上干货&#xff01; 一切来源于官网 http://kafka.apache.org/documentation/ 2.2 Consumer API 2.2、消费者API 随着0.9.0版本&#xff0c;我们已经增加了一个新的Java消费者替换我们现有的基于zookeeper的高级和低级消费者。这个客户端还是测试版的质量…

leetcode1053. 交换一次的先前排列(贪心算法)

给你一个正整数的数组 A&#xff08;其中的元素不一定完全不同&#xff09;&#xff0c;请你返回可在 一次交换&#xff08;交换两数字 A[i] 和 A[j] 的位置&#xff09;后得到的、按字典序排列小于 A 的最大可能排列。 如果无法这么操作&#xff0c;就请返回原数组。 示例 1&a…

mybatis-generator-gui如何打包成exe

快速阅读&#xff1a; ​ 用wix和inno setup把mybatis-generator-gui 打包成exe和安装文件。 以后使用的时候方便&#xff0c;不用每次打开eclipse运行。 使用inno setup 5 和wix 3.11 基于mybatis generator开发一款界面工具, 非常容易及快速生成Mybatis的Java POJO文件及数据…

分步表单如何实现 html_HTML表单入门的分步指南

分步表单如何实现 htmlby Abhishek Jakhar通过阿比舍克贾卡(Abhishek Jakhar) HTML表单入门的分步指南 (A step-by-step guide to getting started with HTML forms) 总览 (Overview) HTML forms are required when you want to collect some data from the person who visits…

linux网络服务偶尔失效,判断linux下的网络服务是否正常启动

# 自动判断samba,http,named,dovecot,tomcat等服务是否正常启动##作者&#xff1a;胡昌文#时间&#xff1a;2008-09-28#MSN&#xff1a;[email]hucw_rhcehotmail.com[/email]###!/bin/shSAMBA1netstat -nutlp | grep :137 | grep smbdSAMBA2netstat -nutlp | grep :138 | grep …

leetcode809. 情感丰富的文字

有时候人们会用重复写一些字母来表示额外的感受&#xff0c;比如 “hello” -> “heeellooo”, “hi” -> “hiii”。我们将相邻字母都相同的一串字符定义为相同字母组&#xff0c;例如&#xff1a;“h”, “eee”, “ll”, “ooo”。 对于一个给定的字符串 S &#xff…

NeHe OpenGL教程 第三十课:碰撞检测

转自【翻译】NeHe OpenGL 教程 前言 声明&#xff0c;此 NeHe OpenGL教程系列文章由51博客yarin翻译&#xff08;2010-08-19&#xff09;&#xff0c;本博客为转载并稍加整理与修改。对NeHe的OpenGL管线教程的编写&#xff0c;以及yarn的翻译整理表示感谢。 NeHe OpenGL第三十课…

andorid手机电脑操作

之前一直使用androidscreencast在pc上对手机进行操作,好久都没用了,前些天再次用的时候,提演示样例如以下: 决定还是自己写一个吧,由于7月份要做一个小分享,打算讲一些android的东西,须要在电脑上显示手机这边的画面,提供一定的操作. 花了一点时间做好了,给大家截一个图,代码放…

struct.error: cannot convert argument to integer解决办法

更新Python包转载于:https://www.cnblogs.com/long5683/p/11086768.html

sphinx_Sphinx之谜:如何轻松地编写代码

sphinx为什么我在这里&#xff1f; (Why Am I Here?) You, the reader, are here because you wrote some awesome tool in Python, and you want to make it accessible and easy to use.读者之所以在这里&#xff0c;是因为您使用Python编写了一些很棒的工具&#xff0c;并且…

linux贪吃蛇c程序,Linux环境下C语言实现贪吃蛇游戏

Linux环境下C语言实现贪吃蛇游戏[liultest snake]$ more snake.c#include #include #include #include #include #define NUM 60struct direct //用来表示方向的{int cx;int cy;};typedef struct node //链表的结点{int cx;int cy;struct node *back;struct node *next;}node;v…

Java正则表达式的使用和详解(上)

1.匹配验证-验证Email是否正确 public static void main(String[] args) {// 要验证的字符串String str "servicexsoftlab.net";// 邮箱验证规则String regEx "[a-zA-Z_]{1,}[0-9]{0,}(([a-zA-z0-9]-*){1,}\\.){1,3}[a-zA-z\\-]{1,}";// 编译正则表达式P…

在组策略中使用脚本为域用户添加网络打印机

使用脚本为用户添加网络打印机 如果你想让培训部门的用户登录后就能添加网络打印机&#xff0c;就可以使用登录脚本来实现。其中DCServer是域控制&#xff0c;MarketPC1是市场部门的计算机&#xff0c;韩立辉用户是培训部门的用户。下面就验证使用组策略为培训部门的用户添加网…

leetcode257. 二叉树的所有路径(回溯算法)

给定一个二叉树&#xff0c;返回所有从根节点到叶子节点的路径。 说明: 叶子节点是指没有子节点的节点。 示例: 输入: 1 / 2 3 5 输出: [“1->2->5”, “1->3”] 解释: 所有根节点到叶子节点的路径为: 1->2->5, 1->3 代码 /*** Definition for a b…

英特尔神经计算棒_如何设置英特尔Movidius神经计算棒

英特尔神经计算棒by Rishal Hurbans由Rishal Hurbans 如何设置英特尔Movidius神经计算棒 (How to set up the Intel Movidius Neural Compute Stick) In 2017 I was approached by Intel to join their Innovator Programme. After a couple interviews I was inducted as an …

linux 脚本中的push,linux shell之pushd、popd和dirs的使用讲解

1 问题我们有时候需要保存多个路径&#xff0c;上下键切换不方便&#xff0c;用cd-只能到上个目录&#xff0c;我们可以用dirs和pushd和popd2 dirs、pushd、popddirs: 这个命令显示栈里面所有的路径&#xff0c;一定会包含当前路径,常用参数如下dirs -v 显示栈里面的所有路径和…

为什么我从 Git Flow 开发模式切换到了 Trunk Based 开发模式?

我已经使用 Git Flow 构建我的 Git 分支有几年了。但是&#xff0c;我遇到了 Git Flow 的一些问题&#xff0c;其中大部分来自长期存在的分支。解决这些问题的方案就是 Trunk Based Development。这是一个非常简单的技术&#xff0c;也是有效的持续交付的基础。在这篇文章中&am…

DedeCMS 提示信息! ----------dede_addonarticle

把数据保存到数据库附加表 dede_addonarticle 时出错&#xff0c;请把相关信息提交给DedeCms官方。Duplicate entry ’2532′ for key ‘PRIMARY’出现这种情况其实是你的主键是不可重复的&#xff0c;现在重复插入值为2532的主键了。可以去掉主键唯一&#xff0c;或是设成自增…