分布式主键 详解
文章目录
- 雪花算法结合分库分表的问题
- 问题出现
- 原因分析
- 解决思路
- 分布式主键要考虑的问题
- 主键生成策略
- 雪花算法详解
- 时间戳位问题
- 工作进程位问题
- 序列号位问题
- 根据雪花算法扩展基因分片法
雪花算法结合分库分表的问题
问题出现
使用ShardingSphere框架自带的雪花算法生成分布式主键,如果和分片算法结合使用不当就很有可能造成数据分布不均匀的情况
如下所示,我使用分布式主键生成策略是SNOWFLAKE,分表策略是常见的取模运算
rules:sharding:# 分片键生成策略key-generators:# 雪花算法alg_snowflake:type: SNOWFLAKEprops:worker:id: 1sharding-algorithms:# 定义分库策略db_alg:type: MODprops:sharding-count: 2# 分表策略sys_user_tab_alg:type: INLINEprops:algorithm-expression: sys_user$->{((uid+1)%4).intdiv(2)+1}
进行新增操作后,这里并没有均匀的分布在四个数据表中,只存在两个数据表中
原因分析
造成这个问题的原因是,ShardingSPhere自带的雪花算法的序列号位是一个单位时间内自增,如果不是一个单位时间内那么就重置为0重新自增。
// 全类名 org.apache.shardingsphere.sharding.algorithm.keygen.SnowflakeKeyGenerateAlgorithm@Override
public synchronized Long generateKey() {long currentMilliseconds = timeService.getCurrentMillis();// 处理雪花算法41bit时间戳位 时钟回拨 if (waitTolerateTimeDifferenceIfNeed(currentMilliseconds)) {currentMilliseconds = timeService.getCurrentMillis();}// 处理12bit序列号位if (lastMilliseconds == currentMilliseconds) {// 单位时间内 sequence序列号位每次都是自增if (0L == (sequence = (sequence + 1) & SEQUENCE_MASK)) {currentMilliseconds = waitUntilNextTime(currentMilliseconds);}} else {// 如果不是一个单位时间内,序列号位就又重置vibrateSequenceOffset();sequence = sequenceOffset;}lastMilliseconds = currentMilliseconds;// 时间戳位 | 工作进程位 | 序列号位return ((currentMilliseconds - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (getWorkerId() << WORKER_ID_LEFT_SHIFT_BITS) | sequence;
}
进一步编写测试类验证
取出插入数据库中的uid值,进行位& 运算,最终就可以发现序列号位基本上都是 0 或者是1 那么这样我们就进行简单的 %2 %4 运算所以就导致了上方的数据分布不均匀问题
public void testSequence(List<Long> uidList) {int mask = (1 << 3) - 1;for (Long uid : uidList) {log.info("uid:{} 的后3位的结果为 {}", uid, uid & mask);}
}
uid:1027514136331812864 的后3位的结果为 0
uid:1027514137086787584 的后3位的结果为 0
uid:1027514137128730624 的后3位的结果为 0
uid:1027514137166479360 的后3位的结果为 0
uid:1027514137204228096 的后3位的结果为 0
uid:1027514137057427457 的后3位的结果为 1
uid:1027514137107759105 的后3位的结果为 1
uid:1027514137149702145 的后3位的结果为 1
uid:1027514137183256577 的后3位的结果为 1
uid:1027514137216811009 的后3位的结果为 1
解决思路
修改现有的雪花算法,让序列号位不要每次都重置。这种方式的确能解决当前业务功能。
@Override
public synchronized Long generateKey() {long currentMilliseconds = timeService.getCurrentMillis();if (waitTolerateTimeDifferenceIfNeed(currentMilliseconds)) {currentMilliseconds = timeService.getCurrentMillis();}if (lastMilliseconds == currentMilliseconds) {
// if (0L == (sequence = (sequence + 1) & SEQUENCE_MASK)) {currentMilliseconds = waitUntilNextTime(currentMilliseconds);
// }} else {vibrateSequenceOffset();
// sequence = sequenceOffset;// SEQUENCE_MASK = (1 << 12L) - 1sequence = sequence >= SEQUENCE_MASK ? 0:sequence+1;}lastMilliseconds = currentMilliseconds;return ((currentMilliseconds - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (getWorkerId() << WORKER_ID_LEFT_SHIFT_BITS) | sequence;
}
但是带来的问题是分布式场景下,雪花算法的序列号位相当于没用了。因为雪花算法的初始目标就是分布式主键生成不冲突,它是靠 时间戳位+工作进程位+序列号位保证的。
如果在单位时间内,并且没有设置工作进程位,多台服务器中的序列号位相同了,那么也就导致了后续生成的id都可能出现重复的情况。
分布式主键要考虑的问题
- 主键除了要标识数据的唯一性之外,还通常会要求主键与业务不直接相关。因为这样不管业务如何变化,都不会影响主键来控制数据的生命周期
- 但是,另外一个方面,我们通常又会要求主键包含一部分的业务属性,这样可以加速对数据的检索。
- 主键也需要考虑安全性,让别人无法通过规律猜出主键来。
所以,对于主键,一方面,要求他与业务不直接相关。这就要求分配主键的服务要足够稳定,足够快速。不能说我辛辛苦苦把业务给弄完了,然后等着分配主键的时候,还要等半天,甚至等不到。另一方面,要求他能够包含某一些业务特性。这就要求分配主键的服务能够进行一定程度的扩展。
主键生成策略
数据库策略
业务层面不设置主键,直接使用数据库的自增长。这种方式实现简单,但是分库分表场景下就会造成主键冲突。当然也可以通过为各个数据库设置自增长步长来解决。但是又不能满足分库分表扩缩容的场景。
应用单独生成
数据库生成的主键不靠谱,那么就应用层面自己来生成。比较常见的算法就是:UUID、NANOID、SnowFlaks雪花算法
优点:简单使用。比如UUID使用JDK自带的工具类即可、SnowFlaks按照它定义的规则自行组合。比较容易扩展,可以随意组合生成主键。
缺点:
- 算法不能太复杂,会消耗cpu计算资源与内存空间
- 要考虑多线程并发安全问题,不能主键冲突。
- 考虑数据库产品结合因素,比如mysql的InnoDB存储引擎,B+树索引,需要使用趋势递增的主键来避免B+树的分裂。UUID这类无序字符串主键就不能满足
第三方服务统一生成
借助第三方服务来生成主键,比如redis、zookeeper、MongoDB
redis:通过incr指令,配合lua脚本比较容易防并发
zookeeper:使用它的序列号节点;或者是在apache提供的Zookeeper客户端Curator中,提供了DistributedAtomicInteger,DistributedAtomicLong等工具,可以用来生成分布式递增的ID。
MongoDB:使用MongoDB的ObjectID。
缺点:
- 这些原生的方式大都不是为了分布式主键场景而设计的,所以,如果要保证高效以及稳定,在使用这些工具时,还是需要非常谨慎。
- 每一次生成主键都需要调用第三方服务,效率问题也需要考虑
与第三方结合的segment策略
还是从第三方获取主键,只不过是一次获取一批主键,缓存在本地,当使用完后再去申请一批。
比如我们设计下面这种数据表
biz_tag表示具体的某一业务标识,用户或订单他们都对应的一整个集群服务。max_id表示当前已经分配的最大id,step表示每次分配的步长。max_id会随着每一次分配id而增加。
这种方式的缺点是应用向第三方申请id有网络消耗,这段时间内应用会出现无主键可用的情况。
与第三方结合的多segment策略
双Buffer写入,向第三方服务申请两个segment放入本地缓存。避免出现应用在向第三方申请主键这段期间没有主键可用的情况
扩展点:
- 比较依赖DB,上方max_id 和step这两个字段都是保存在数据库的。我们可以保存在其他存储方式
- 第三方应用宕机,在我服务中双buffer中的id使用完之前重新提供服务,这样问题都不大。所以我服务中保存的buffer个数可以多一些,进而提高容错性
雪花算法详解
雪花算法使用8字节的二进制序列来生成一个主键
-
41bit的时间戳为主体,时间戳位保证趋势递增,放在最高位;
-
10bit的工作进程位标识服务器中运行的进程,而不是标识机器,需要应用自行扩展;
-
12bit序列号位是用来区分单位时间内 单机器内的自增序列。
而在具体实现时,雪花算法实际上只是提供了一个思路,并没有提供现成的框架。比如ShardingSphere中的雪花算法就是这样生成的。
时间戳位问题
41bit的时间戳为主体,时间戳位保证趋势递增,放在最高位;
时间戳位存在时钟回拨的问题。
一台服务器上,获取时钟只能依赖于内核的电信号维护,而电信号很难保持稳定。在高并发场景下获取高精度的时间戳有时会往前跳,有时会往回拨。
一旦时钟回拨就有可能产生重复的id。
各个框架的雪花算法都有对时钟回拨的问题做相应的处理,基本思路就是记录上一次生成主键的时间lastMilliseconds,和当前时间进行比较,如果lastMilliseconds大于当前时间就表示出现了时钟回拨,处理方式要么sleep()一段时间,要么直接抛异常。就比如ShardingSPhere的SnowFlake雪花算法就是sleep(),而cosID_SnowFlake就是抛异常
// 全类名 org.apache.shardingsphere.sharding.algorithm.keygen.SnowflakeKeyGenerateAlgorithm
@SneakyThrows(InterruptedException.class)
private boolean waitTolerateTimeDifferenceIfNeed(final long currentMilliseconds) {// 上一次生成主键的时间 和 当前时间进行比较if (lastMilliseconds <= currentMilliseconds) {return false;}long timeDifferenceMilliseconds = lastMilliseconds - currentMilliseconds;Preconditions.checkState(...);// 解决时钟回拨的方式采用sleep()Thread.sleep(timeDifferenceMilliseconds);return true;
}
// 全类名 me.ahoo.cosid.snowflake.AbstractSnowflakeId
public synchronized long generate() {long currentTimestamp = this.getCurrentTime();// 时钟回拨if (currentTimestamp < this.lastTimestamp) {// 抛异常throw new ClockBackwardsException(this.lastTimestamp, currentTimestamp);} else {// 序列号处理 不是同一个单位时间点&&值>2^12 就重置0if (currentTimestamp > this.lastTimestamp && this.sequence >= this.sequenceResetThreshold) {this.sequence = 0L;}// 序列号位自增this.sequence = this.sequence + 1L & this.maxSequence;if (this.sequence == 0L) {currentTimestamp = this.nextTime();}this.lastTimestamp = currentTimestamp;long diffTimestamp = currentTimestamp - this.epoch;if (diffTimestamp > this.maxTimestamp) {throw new TimestampOverflowException(this.epoch, diffTimestamp, this.maxTimestamp);} else {// 时间戳 | 进程号 | 序列位return diffTimestamp << (int)this.timestampLeft | this.machineId << (int)this.machineLeft | this.sequence;}}
}
上方只能处理单台服务器上的时钟回拨,如果是多台服务器一个集群,就无法保证时间戳的统一了。
可以为每个应用配置一个工作进程位来防止不同服务器之间的主键冲突。但是万一应用没有配置嘞?大部分应用不会为了一个雪花算法去单独考虑如何分配工作进程位。
也可以使用ntpd这样的时间同步服务来把多个服务器的时间同步一下。但同样的不会有人仅仅为了雪花算法去这么做
第三种方案是将时间戳从本地扔到第三方服务上去,比如zookeeper,这样多个服务就可以根据共同的时间戳往前推进,省了服务器之间同步时间的麻烦。美团的leaf就是这么做的。缺点是:给雪花算法添加强绑定;降低了效率,因为多了一个访问第三方的步骤。
这有变为了方案取舍、实现优化的头疼环节。就像整个分布式主键一样。
工作进程位问题
10bit的工作进程位标识服务器中运行的进程,而不是标识机器,需要应用自行扩展;它是分布式场景下,保证id不重复很重要的字段。
因为一台服务器上面可以运行多个进程实例,所以这个标识是工作进程的,而不能简单理解为标识机器。工作进程是需要各个应用自己设定值,所以这里就分为了手动指定和自动获取两种方式。
对于一些小集群,就可以简单使用手动指定的方式在配置文件中为当前应用指定一个工作进程。但如果是大型集群,上百个微服务肯定就不能手动指定了。
现在就有一个思路,把MachineId(比如ip+端口)当做一个短的并发不是很高的分布式主键来处理,用其他分布式主键生成的方式生成工作进程位。我们现在需要依赖于一个工作进程位来生成分布式唯一主键,然后现在又要依赖于一个分布式唯一主键生成策略来生成工作进程位,完美闭环!鸡生蛋 蛋生鸡问题。
首先工作进程位是不需要考虑高并发问题,通常工作进程位只需要在一个应用启动时分配一个就可以了,应用的运行过程中不需要什么变化。所以工程进程位每次单步推进,申请一次,分配一个就行
其次工程进程位有一个天然的就带有唯一性的因素,比如使用ip地址,如果服务器运行多个应用那么也可以使用ip+端口区分。所以工作进程位天生就有很多唯一性因素,不需要像雪花算法那样去设计复杂的结构。
最后工作进程位的分配需要保持稳定。工作进程要与一个应用建立绑定关系。给一个应用分配工作进程号位之后如果应用崩溃了,重启服务之后所生成的雪花算法还是需要保持一个稳定的区分度。所以可以使用一个本地缓存保存这个应用的工作进程位,应用重启后,还需要保持,那么这个缓存可以持久化到本地文件中。
其实把这几个方面想明白了,Cosid当中的工作进程位分配机制也就大致成型了。
序列号位问题
序列号位就是一个连续性问题。就比如上文中的结合分库分表的问题。
如果不是一个单位时间内,那么就将序列为重置为0,进而导致了我们使用取模分片策略使得数据分配不均匀。
根据雪花算法扩展基因分片法
业务场景,对User用户表进行分库分表,最简单的处理就是根据userId作为分片键,之后每次查询都根据userId这个分片键来进行查询。但用户登录这个场景嘞?此时只有一个用户名,你甚至都不知道这个用户名是否存在,难道就只能全路由查询?这肯定是不能接受的。
此时就可以使用基因法的分片算法来解决这个问题。它的基础思想是再给用户分配userId时,就把用户名当中的某种序列信息插入到userId当中。从而保证userId和用户名可以按照某一种对应规则分到同一个分片上。这样就可以根据用户名确定对应的用户信息在哪一个分片上
具体在实现时,可以参照雪花算法的实现。
@Test
public void testGene() {// 初始值Long userId = 1257458L;String userName = "testroy";// 基因序列位数int dataSize = 3;//掩码,二进制表述为全部是1. 111int mask = ((1 << dataSize) - 1);// 只取 datasize 个bit位long userGene = userName.hashCode() & mask;// 给ID添加用户名的基因片段后的新ID,保持了原id的一致性long newUserId = (userId << dataSize) | userGene;// 对新用户id对8取模进行数据分片long actualNode = newUserId % 8;System.out.println("用户信息实际保存的分片:" + actualNode);long userNode = (userName.hashCode() & mask) % 8;System.out.println("根据用户名判断,用户信息可能的分片:" + userNode);
}
用户信息实际保存的分片:2
根据用户名判断,用户信息可能的分片:2
相关文章:

分布式主键 详解
文章目录 雪花算法结合分库分表的问题问题出现原因分析解决思路 分布式主键要考虑的问题主键生成策略雪花算法详解时间戳位问题工作进程位问题序列号位问题根据雪花算法扩展基因分片法 雪花算法结合分库分表的问题 问题出现 使用ShardingSphere框架自带的雪花算法生成分布式主…...

synchronzed为什么要升级为重量级锁,轻量级锁不好吗?
轻量级锁和重量级锁各有其适用场景和优缺点。轻量级锁旨在减少在无竞争情况下的同步开销,而重量级锁则在竞争激烈的情况下确保线程的同步。以下是为什么在某些情况下需要将轻量级锁升级为重量级锁的原因,以及轻量级锁的不足之处: 为什么需要…...

.NET 项目中发送电子邮件异步处理和错误机制的解决方案
在 .NET 中处理电子邮件,可以使用多种技术和库来实现高效的电子邮件发送、接收和管理。以下是一些常见的解决方案和最佳实践: 目录 1. 使用 SMTP 发送电子邮件 2. 使用 IMAP/POP3 接收电子邮件 3. 异步处理电子邮件 4. 处理大型邮件队列 5. 错误处…...

如何在银河麒麟操作系统上搭建 Electron (含 Electron 打包指南)
本次教程所用版本 Eletron版本:31.3.1 Electron-packager版本:17.1.2 VScode版本:1.92.0 Node版本:18.19.0 npm版本:10.2.3 前言: 随着跨平台应用开发的需求日益增长,Electron 和 Qt 成为…...

小怡分享之数据结构基础知识准备
前言: 🌈✨之前小怡给大家分享了JavaSE的知识,今天小怡要给大家分享一下数据结构基础知识。 一、初识集合框架 1.什么是集合框架 Java集合框架Java Collection Framework, 又称为容器container,是定义在Java.util 包…...

Linux安全与高级应用(三)深入探索MySQL数据库:安装、管理与安全实践
文章目录 深入探索MySQL数据库:安装、管理与安全实践MySQL数据库简介MySQL的安装与配置编译安装MySQL配置MySQL服务 MySQL数据库的基本操作数据库的创建与删除表的创建与管理数据记录的增删改查 MySQL用户管理与权限设置MySQL数据库的备份与恢复数据库备份数据库恢复…...

基于jsp的宠物领养与服务管理系统(源码+论文+部署讲解等)
博主介绍:✌全网粉丝10W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术栈介绍:我是程序员阿龙ÿ…...

基于STM32F407+NBIOT+华为云IOT平台设计的环境检测系统
基于STM32F407NBIOT华为云IOT平台设计的环境检测系统实现的功能: 【1】能够采集本地环境的温度、湿度、烟雾浓度,火光信息,在OLED显示屏上显示。 如果检测到烟雾、温度、火光超过阀值会触发蜂鸣器报警。 【2】能够通过NBIOT将本地设备采集的信…...

工具方法 - 如何表扬小孩子
赞扬小朋友的表现可以通过多种方法来进行,以鼓励他们的积极行为和努力,增强他们的自信心和动力。以下是一些有效的赞扬方法: 1. 具体表扬:指出具体的行为或成就,而不是泛泛地说“你很棒”。例如,“你今天很…...

【扒模块】DySample
逐行注释 import torch import torch.nn as nn import torch.nn.functional as F import warnings# 忽略警告信息,这通常用于开发过程中,避免警告干扰输出结果 warnings.filterwarnings(ignore)# 定义一个函数,用于对神经网络模块的权重进行…...

数学建模之数据分析【四】:变量及其分析
文章目录 一、单变量数据1.1 单变量数据1.2 单变量分析的要点: 二、双变量数据2.1 双变量数据2.2 双变量分析的要点 三、多元数据3.1 多元数据3.2 多元分析的要点 四、单变量,双变量和多变量数据之间的区别 公众号/小红书: 快乐数模 CSDN: 清上尘 本文&a…...

iOS ------ UIKit相关
UIView和CALayer UIView UIView表示屏幕上的一块矩形区域,它是基本上iOS中所有可视化控件的父类。UIView可以管理矩形区域里的内容,处理矩形区域的事件,包括子视图的管理以及动画的实现。 UIKit相关类的继承关系 UIView继承自UIResponde…...

24/8/9算法笔记 随机森林
"极限森林"(Extremely Randomized Trees,简称ERT)是一种集成学习方法,它属于决策树的变体,通常被归类为随机森林(Random Forest)的一种。极限森林的核心思想是在构建决策树时引入极端…...

如何在前后端分离项目中,使用Spring Security
使用 WebSecurityConfigurationAdapter 在前后端分离的架构中,通常使用 Token 进行认证和授权是一种常见的做法。Token 可以是 JSON Web Token(JWT),用于在客户端和服务器之间传递身份信息和访问控制信息。下面我将详细介绍如何在…...

c#怎么折叠代码快捷
在C#中,你可以使用快捷键来折叠或展开代码,以便更好地管理和浏览代码。以下是一些常用的快捷键: 折叠所有方法:使用Ctrl M O。折叠或展开当前方法:使用Ctrl M M。展开所有方法:使用…...

数据库篇--八股文学习第十七天| 什么是慢查询?原因是什么?可以怎么优化?;undo log、redo log、binlog 有什么用?
1、什么是慢查询?原因是什么?可以怎么优化? 答: 数据库查询的执行时间超过指定的超时时间时,就被称为慢查询。 原因: 查询语句比较复杂:查询涉及多个表,包含复杂的连接和子查询&…...

插件、cookie存储,json,ajax详解
1.插件 下载地址:http://github.com/carhartl/jquery-cookie/zipball/v1.4.1 使用文档:jquery-cookie(github.com) 2.存储 初学前端用的是localStorage和sessionStorage,后来又引入了cookie进行存储。 localStorage使用如下 sessionStor…...

快速上手Spring Boot
快速上手Spring Boot (qq.com)...

思路超清晰的 LVS-NAT 模式实验部署
目录 一、实验原理 1、实验基础配置图 2、实验原理 二、实验环境准备 1、准备四台红帽9的主机 2、四台主机的基础配置 (1)client 1)配置主机名:client 2)配置ip:172.25.254.200 (2)lv…...

Android实时通信:WebSocket与WebRTC的应用与优化
文章目录 一、WebSocket在Android中的应用1.1 简介1.2 示例 二、WebRTC在Android中的应用2.1 简介2.2 示例 三、Android实时通信的优化策略3.1 网络优化3.2 延迟降低 四、Android实时通信的安全问题五、实时通信协议的比较六、总结 在现代移动应用中,实时通信已经成…...

力扣刷题之3131.找出与数组相加的整数I
题干描述 给你两个长度相等的数组 nums1 和 nums2。 数组 nums1 中的每个元素都与变量 x 所表示的整数相加。如果 x 为负数,则表现为元素值的减少。 在与 x 相加后,nums1 和 nums2 相等 。当两个数组中包含相同的整数,并且这些整数出现的频…...

非线性表之堆的实际应用和二叉树的遍历
目录 前言:前一篇我已经介绍过了二叉树和堆的介绍和相关代码的实现 一、堆的实现 1.1堆向上调整算法 1.2堆向下调整算法 二、堆的应用 2.1堆的排序 2.2TOP-K问题 三、二叉树的遍历 3.1 二叉树的创建 3.2遍历介绍 3.3前序遍历 3.4中序遍历 3.5后序遍历 …...

os.path库学习之splitext函数
os.path库学习之splitext函数 一、简介 os.path.splitext 是 Python 标准库 os.path 模块中的一个函数,用于将文件名分割成两部分:文件名和扩展名。这个函数非常有用,特别是在处理文件路径和文件扩展名时。 二、语法和参数 语法: os.path…...

Python知识点:如何使用Sqlmap进行SQL注入测试
使用 Sqlmap 进行 SQL 注入测试是一个非常有效的方法,它可以帮助你自动化地检测和利用 SQL 注入漏洞。以下是使用 Sqlmap 进行 SQL 注入测试的详细步骤: 1. 安装 Sqlmap 首先,你需要安装 Sqlmap。Sqlmap 是一个 Python 工具,因此…...

Android Gradle开发与应用 (一) : Gradle基础
Gradle基础 Gradle 是一个基于 Apache Ant 和 Apache Maven 概念的项目自动化构建工具。它使用一种基于 Groovy 的特定领域语言(DSL)来声明项目设置,而不是传统的 XML。Gradle 提供了灵活的构建脚本和强大的依赖管理功能,使其成为…...

Linux驱动开发—设备树分析:GPIO,中断,时钟信息,CPU信息
书接上回:Linux驱动开发—设备树基本概念,语法详解-CSDN博客 文章目录 使用设备树描述中断使用设备树描述CPU节点CPU 节点缓存节点总结 使用设备树描述时钟总结 使用设备树描述GPIO示例设备树节点逐行解析GPIO 单元 使用设备树描述中断 在NXP 官方中截…...

Java全栈解密:从JVM内存管理到Spring框架,揭秘垃圾回收、类加载机制与Web开发精髓的全方位旅程
JVM内存划分 在JVM中,每个线程有自己的虚拟机栈,而整个JVM实例共享一些内存区域。JVM的内存划分主要包括四个部分:程序计数器、虚拟机栈、堆区和方法区(元数据区)。 程序计数器:程序计数器用于存储当前线程…...

【探索Linux】P.46(高级IO —— 五种IO模型简介 | IO重要概念)
阅读导航 引言一、五种IO模型1. 阻塞IO(1)定义(2)特点 2. 非阻塞IO(1)定义(2)特点 3. IO多路复用(1)定义(2)特点 4. 信号驱动IO&#…...

【MongoDB 】MongoDB 介绍及应用,设计到4个案例
MongoDB 介绍概述 基础概念 MongoDB 是非关系型数据库,也就是nosql,存储json数据格式会非常灵活,要比数据库mysql/MariaDB更好,同时也能为mysql/MariaDB分摊一部分的流量压力。 对于经常读写的数据他会存入内存,如此…...

AI浪潮下的程序员生存指南:如何在智能时代锻造不可替代的核心竞争力
人工智能时代,程序员如何保持核心竞争力? 随着AIGC(如chatgpt、midjourney、claude等)大语言模型接二连三的涌现,AI辅助编程工具日益普及,程序员的工作方式正在发生深刻变革。有人担心AI可能取代部分编程工…...