【分布式技术专题】「分布式ID系列」百度开源的分布式高性能的唯一ID生成器UidGenerator

UidGenerator是什么

UidGenerator是百度开源的一款分布式高性能的唯一ID生成器,更详细的情况可以查看官网集成文档

uid-generator是基于Twitter开源的snowflake算法实现的一款唯一主键生成器(数据库表的主键要求全局唯一是相当重要的)。要求java8及以上版本。

snowflake算法

Snowflake算法描述:指定机器 & 同一时刻 & 某一并发序列,是唯一的。据此可生成一个64 bits的唯一ID(long)。

将long的64位分为3部分,时间戳、工作机器id和序列号,位数分配如下:

时间戳部分的时间单位一般为毫秒,也就是说1台工作机器1毫秒可产生4096个id(2的12次方)。

UidGenerator算法

与原始的snowflake算法不同,uid-generator支持自定义时间戳、工作机器id和序列号等各部分的位数,以应用于不同场景。

  • sign(1bit):固定1bit符号标识,即生成的UID为正数。
  • delta seconds (28 bits):当前时间,相对于时间基点"2016-05-20"的增量值,单位:秒,最多可支持约8.7年
  • worker id (22 bits):机器id,最多可支持约420w次机器启动。内置实现为在启动时由数据库分配,默认分配策略为用后即弃,后续可提供复用策略。
  • sequence (13 bits):每秒下的并发序列,13 bits可支持每秒8192个并发。

这些字段的长度可以根据具体的应用需要进行动态的调整,满足总长度为64位即可。

Snowflake和UidGenerator的对比

百度的worker id的生成策略和美团的生成策略不太一样,美团的snowflake主要利用本地配置的port和IP来唯一确定一个workid,美团的这种生成方式还是可以由于手工配置错误造成port重复,最终产生重复ID的风险,百度的这种生成方式每次都是新增的,可能会一段时间后worker id用完的情况,人工配置错误的可能性很小了。

源码分析

DefaultUidGenerator

DefaultUidGenerator的产生id的方法与基本上就是常见的snowflake算法实现,仅有一些不同,如以秒为为单位而不是毫秒。DefaultUidGenerator的产生id的方法如下。

 protected synchronized long nextId() {long currentSecond = getCurrentSecond();if (currentSecond < lastSecond) {long refusedSeconds = lastSecond - currentSecond;throw new UidGenerateException("Clock moved backwards. Refusing for %d seconds", refusedSeconds);}if (currentSecond == lastSecond) {sequence = (sequence + 1) & bitsAllocator.getMaxSequence();if (sequence == 0) {currentSecond = getNextSecond(lastSecond);}} else {sequence = 0L;}lastSecond = currentSecond;return bitsAllocator.allocate(currentSecond - epochSeconds, workerId, sequence);}

nextId方法主要负责ID的生成,这种实现方式很简单,如果毫秒数未发生变化,在序列号加一即可,毫秒数发生变化,重置Sequence为0(Leaf文章中讲过,重置为0会造成如果利用这个ID分表的时候,并发量不大的时候,sequence字段会一直为0等,会出现数据倾斜)

CachedUidGenerator

CachedUidGenerator支持缓存生成的id。

  • 【采用RingBuffer来缓存已生成的UID, 并行化UID的生产和消费】
  • 【UidGenerator通过借用未来时间来解决sequence天然存在的并发限制】
基本实现原理

正如名字体现的那样,这是一种缓存型的ID生成方式,当剩余ID不足的时候,会异步的方式重新生成一批ID缓存起来,后续请求的时候直接的时候直接返回现成的ID即可。

在实现上, UidGenerator通过借用未来时间来解决sequence天然存在的并发限制; 采用RingBuffer来缓存已生成的UID, 并行化UID的生产和消费, 同时对CacheLine补齐,避免了由RingBuffer带来的硬件级「伪共享」问题. 最终单机QPS可达600万。

使用RingBuffer缓存生成的id。RingBuffer是个环形数组,默认大小为8192个,里面缓存着生成的id。

CachedUidGenerator采用了双RingBuffer,Uid-RingBuffer用于存储Uid、Flag-RingBuffer用于存储Uid状态(是否可填充、是否可消费)

由于数组元素在内存中是连续分配的,可最大程度利用CPU cache以提升性能。但同时会带来「伪共享」FalseSharing问题,为此在Tail、Cursor指针、Flag-RingBuffer中采用了CacheLine 补齐方式。

获取id

会从ringbuffer中拿一个id,支持并发获取

public long getUID() {try {return ringBuffer.take();} catch (Exception e) {LOGGER.error("Generate unique id exception. ", e);throw new UidGenerateException(e);}}
RingBuffer缓存已生成的id

RingBuffer为环形数组,默认容量为sequence可容纳的最大值(8192个),可以通过boostPower参数设置大小。几个重要的数据结构,采用了RingBuffer的方式来缓存相关UID信息。

tail指针、Cursor指针用于环形数组上读写slot:

Tail指针

指向当前最后一个可用的UID位置:表示Producer生产的最大序号(此序号从0开始,持续递增)。Tail不能超过Cursor,即生产者不能覆盖未消费的slot。当Tail已赶上curosr,此时可通过rejectedPutBufferHandler指定PutRejectPolicy

Cursor指针

指向下一个获取UID的位置,其一定是小于Tail:表示Consumer消费到的最小序号(序号序列与Producer序列相同)。Cursor不能超过Tail,即不能消费未生产的slot。当Cursor已赶上tail,此时可通过rejectedTakeBufferHandler指定TakeRejectPolicy

Tail - Cursor表示的是现在可用的UID数量,当可用UID数量小于一定阈值的时候会重新添加一批新的UID在RingBuffer中。

填充id
  • RingBuffer填充时机
    • 程序启动时,将RingBuffer填充满,缓存着8192个id
    • 在调用getUID()获取id时,检测到RingBuffer中的剩余id个数小于总个数的50%,将RingBuffer填充满,使其缓存8192个id。
    • 定时填充(可配置是否使用以及定时任务的周期)

因为delta seconds部分是以秒为单位的,所以1个worker 1秒内最多生成的id书为8192个(2的13次方)。从上可知,支持的最大qps为8192,所以通过缓存id来提高吞吐量。

为什么叫借助未来时间?

因为每秒最多生成8192个id,当1秒获取id数多于8192时,RingBuffer中的id很快消耗完毕,在填充RingBuffer时,生成的id的delta seconds 部分只能使用未来的时间。(因为使用了未来的时间来生成id,所以上面说的是,【最多】可支持约8.7年)

注意:这里的RingBuffer不是Disruptor框架中的RingBuffer,但是借助了很多Disruptor中RingBuffer的设计思想,比如使用缓存行填充解决伪共享问题。

填充RingBuffer
public void paddingBuffer() {LOGGER.info("Ready to padding buffer lastSecond:{}. {}", lastSecond.get(), ringBuffer);if (!running.compareAndSet(false, true)) {LOGGER.info("Padding buffer is still running. {}", ringBuffer);return;}boolean isFullRingBuffer = false;while (!isFullRingBuffer) {List uidList = uidProvider.provide(lastSecond.incrementAndGet());for (Long uid : uidList) {isFullRingBuffer = !ringBuffer.put(uid);if (isFullRingBuffer) {break;}}}running.compareAndSet(true, false);LOGGER.info("End to padding buffer lastSecond:{}. {}", lastSecond.get(), ringBuffer);}

生成id(上面代码中的uidProvider.provide调用的就是这个方法)

protected List nextIdsForOneSecond(long currentSecond) {int listSize = (int) bitsAllocator.getMaxSequence() + 1;List uidList = new ArrayList<>(listSize);long firstSeqUid = bitsAllocator.allocate(currentSecond - epochSeconds, workerId, 0L);for (int offset = 0; offset < listSize; offset++) {uidList.add(firstSeqUid + offset);}return uidList;}

RingBuffer的代码

public class RingBuffer {private static final Logger LOGGER = LoggerFactory.getLogger(RingBuffer.class);private static final int START_POINT = -1;private static final long CAN_PUT_FLAG = 0L;private static final long CAN_TAKE_FLAG = 1L;public static final int DEFAULT_PADDING_PERCENT = 50;private final int bufferSize;private final long indexMask;private final long[] slots;private final PaddedAtomicLong[] flags;private final AtomicLong tail = new PaddedAtomicLong(START_POINT);private final AtomicLong cursor = new PaddedAtomicLong(START_POINT);private final int paddingThreshold;private RejectedPutBufferHandler rejectedPutHandler = this::discardPutBuffer;private RejectedTakeBufferHandler rejectedTakeHandler = this::exceptionRejectedTakeBuffer;private BufferPaddingExecutor bufferPaddingExecutor;

代码层面的优化

代码中通过字节的填充,来避免伪共享的产生。

多核处理器处理相互独立的变量时,一旦这些变量处于同一个缓存行,不同变量的操作均会造成这一个缓存行失效,影响缓存的实际效果,造成很大的缓存失效的性能问题。下面图中线程处理不同的两个变量,但这两个变量的修改都会造成整个整个缓存行的失效,导致无效的加载、失效,出现了伪共享的问题

RingBuffer中通过定义一个PaddedAtomicLong来独占一个缓存行,代码中的实现填充可能需要根据具体的执行系统做一些调整,保证其独占一个缓存行即可。

take先关id的源码

下面我们来看下如何获取相关的UID

public long take() {long currentCursor = cursor.get();long nextCursor = cursor.updateAndGet(old -> old == tail.get() ? old : old + 1);Assert.isTrue(nextCursor >= currentCursor, "Curosr can't move back");long currentTail = tail.get();if (currentTail - nextCursor < paddingThreshold) {LOGGER.info("Reach the padding threshold:{}. tail:{}, cursor:{}, rest:{}", paddingThreshold, currentTail,nextCursor, currentTail - nextCursor);bufferPaddingExecutor.asyncPadding();}if (nextCursor == currentCursor) {rejectedTakeHandler.rejectTakeBuffer(this);}int nextCursorIndex = calSlotIndex(nextCursor);Assert.isTrue(flags[nextCursorIndex].get() == CAN_TAKE_FLAG, "Curosr not in can take status");long uid = slots[nextCursorIndex];flags[nextCursorIndex].set(CAN_PUT_FLAG);return uid;}

通过AtomicLong.updateAndGet来避免对整个方法进行加锁,获取一个可以访问的UID的游标值,根据这个下标获取slots中相关的uid直接返回 缓存中可用的uid(Tail - Cursor)小于一定阈值的时候,需要启动另外一个线程来生成一批UID UID 的生成

public synchronized boolean put(long uid) { long currentTail = tail.get(); long currentCursor = cursor.get();

long distance = currentTail - (currentCursor == START_POINT ? 0 : currentCursor);if (distance == bufferSize - 1) {rejectedPutHandler.rejectPutBuffer(this, uid);return false;}int nextTailIndex = calSlotIndex(currentTail + 1);if (flags[nextTailIndex].get() != CAN_PUT_FLAG) {rejectedPutHandler.rejectPutBuffer(this, uid);return false;}slots[nextTailIndex] = uid;flags[nextTailIndex].set(CAN_TAKE_FLAG);tail.incrementAndGet();return true;
}

获取Tail的下标值,如果缓存区满的话直接调用RejectedPutHandler.rejectPutBuffer方法 未满的话将UID放置在slots数组相应的位置上,同时将Flags数组相应的位置改为CAN_TAKE_FLAG CachedUidGenerator通过缓存的方式预先生成一批UID列表,可以解决UID获取时候的耗时,但这种方式也有不好点,一方面需要耗费内存来缓存这部分数据,另外如果访问量不大的情况下,提前生成的UID中的时间戳可能是很早之前的,DefaultUidGenerator应该在大部分的场景中就可以满足相关的需求了。

填充缓存行解决"伪共享"

关于伪共享,可以参考这篇文章《伪共享(false sharing),并发编程无声的性能杀手》

private final PaddedAtomicLong[] flags;private final AtomicLong tail = new PaddedAtomicLong(START_POINT);private final AtomicLong cursor = new PaddedAtomicLong(START_POINT)
PaddedAtomicLong的设计

public class PaddedAtomicLong extends AtomicLong {private static final long serialVersionUID = -3415778863941386253L;public volatile long p1, p2, p3, p4, p5, p6 = 7L;public PaddedAtomicLong() {super();}public PaddedAtomicLong(long initialValue) {super(initialValue);}public long sumPaddingToPreventOptimization() {return p1 + p2 + p3 + p4 + p5 + p6;}}

Spring Boot工程集成全局唯一ID生成器 UidGenerator

基础工程创建

官网集成文档

创建数据表

执行如下SQL

DROP TABLE IF EXISTS WORKER_NODE;
CREATE TABLE WORKER_NODE
(
ID BIGINT NOT NULL AUTO_INCREMENT COMMENT 'auto increment id',
HOST_NAME VARCHAR(64) NOT NULL COMMENT 'host name',
PORT VARCHAR(64) NOT NULL COMMENT 'port',
TYPE INT NOT NULL COMMENT 'node type: ACTUAL or CONTAINER',
LAUNCH_DATE DATE NOT NULL COMMENT 'launch date',
MODIFIED TIMESTAMP NOT NULL COMMENT 'modified time',
CREATED TIMESTAMP NOT NULL COMMENT 'created time',
PRIMARY KEY(ID)
)COMMENT='DB WorkerID Assigner for UID Generator',ENGINE = INNODB;

在使用的数据库中创建表WORKER_NODE。(如果数据库版本较低,需要将TIMESTAMP类型换成datetime(3),一劳永逸的做法就是直接将TIMESTAMP换成datetime(3))

引入Maven依赖

<dependencies><dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-webartifactId>dependency><dependency><groupId>org.mybatis.spring.bootgroupId><artifactId>mybatis-spring-boot-starterartifactId><version>2.1.0version>dependency><dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-testartifactId><scope>testscope>dependency><dependency><groupId>mysqlgroupId><artifactId>mysql-connector-javaartifactId><scope>runtimescope><version>8.0.12version>dependency><dependency><groupId>com.alibabagroupId><artifactId>druid-spring-boot-starterartifactId><version>1.1.9version>dependency><dependency><groupId>com.baidu.fsggroupId><artifactId>uid-generatorartifactId><version>1.0.0-SNAPSHOTversion>dependency>
dependencies>

互联网jar包引入(本文用的是此方式)

在maven仓库只找到了一个jar包。

<dependency><groupId>com.xfvape.uidgroupId><artifactId>uid-generatorartifactId><version>0.0.4-RELEASEversion>
dependency>
排除冲突的依赖

uid-generator中依赖了logback和mybatis。一般在项目搭建过程中,springboot中已经有了logback依赖,mybatis会作为单独的依赖引入。如果版本和uid-generator中的依赖不一致的话,就会导致冲突。为了防止出现这些问题,直接排除一劳永逸。

<dependency><groupId>com.baidu.fsggroupId><artifactId>uid-generatorartifactId><version>1.0.0-SNAPSHOTversion><exclusions><exclusion><groupId>org.mybatisgroupId><artifactId>*artifactId>exclusion>exclusions>
dependency>

排除冲突的依赖如下:(使用本地项目引入的方式也需要排除以下依赖)

<dependency><groupId>com.xfvape.uidgroupId><artifactId>uid-generatorartifactId><version>0.0.4-RELEASEversion><exclusions><exclusion><groupId>org.mybatisgroupId><artifactId>*artifactId>exclusion>exclusions>
dependency>

我这里用的是mybatis-plus,mybatis-plus官方要求的是,如果要使用mybatis-plus,就不能再单独引入mybatis了,所以我这里也是必须排除mybatis的。

配置SpringBoot核心配置

修改配置文件application.properties(注意MySQL地址、数据库名称账户等于之前建表的保持一致)

server.port=9999
spring.datasource.url=jdbc:mysql://*.*.*.*:3306/baiduUidGenerator?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=*
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.configuration.map-underscore-to-camel-case=true

@MapperScan的dao层接口扫描:

核心对象装配为spring的bean。

uid-generator提供了两种生成器: DefaultUidGenerator、CachedUidGenerator。

如对UID生成性能有要求, 请使用CachedUidGenerator。这里装配CachedUidGenerator,DefaultUidGenerator装配方式是一样的。

自定义DisposableWorkerIdAssigner

将源码DisposableWorkerIdAssigner类加入到自己的项目中,并将其中的mapper方法修改成自己项目中的方法与启动类同级目录新建DisposableWorkerIdAssigner内容如下


public class DisposableWorkerIdAssigner implements WorkerIdAssigner {private static final Logger LOGGER = LoggerFactory.getLogger(DisposableWorkerIdAssigner.class);private WorkerNodeMapper workerNodeMapper;public long assignWorkerId() {WorkerNodeEntity workerNodeEntity = buildWorkerNode();workerNodeMapper.addWorkerNode(workerNodeEntity);LOGGER.info("Add worker node:" + workerNodeEntity);return workerNodeEntity.getId();}private WorkerNodeEntity buildWorkerNode() {WorkerNodeEntity workerNodeEntity = new WorkerNodeEntity();if (DockerUtils.isDocker()) {workerNodeEntity.setType(WorkerNodeType.CONTAINER.value());workerNodeEntity.setHostName(DockerUtils.getDockerHost());workerNodeEntity.setPort(DockerUtils.getDockerPort());} else {workerNodeEntity.setType(WorkerNodeType.ACTUAL.value());workerNodeEntity.setHostName(NetUtils.getLocalAddress());workerNodeEntity.setPort(System.currentTimeMillis() + "-" + RandomUtils.nextInt(100000));}return workerNodeEntity;}
}

public class UidGeneratorConfig {public DisposableWorkerIdAssigner disposableWorkerIdAssigner(){DisposableWorkerIdAssigner disposableWorkerIdAssigner = new DisposableWorkerIdAssigner();return  disposableWorkerIdAssigner;
}public CachedUidGenerator initCachedUidGenerator(WorkerIdAssigner workerIdAssigner) {CachedUidGenerator cachedUidGenerator = new CachedUidGenerator();cachedUidGenerator.setWorkerIdAssigner(workerIdAssigner);cachedUidGenerator.setBoostPower(3);cachedUidGenerator.setPaddingFactor(50);cachedUidGenerator.setScheduleInterval(60L);return cachedUidGenerator;}
}
详细配置信息控制
public DefaultUidGenerator defaultUidGenerator(WorkerIdAssigner disposableWorkerIdAssigner) {DefaultUidGenerator defaultUidGenerator = new DefaultUidGenerator();defaultUidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner);defaultUidGenerator.setTimeBits(32);defaultUidGenerator.setWorkerBits(22);defaultUidGenerator.setSeqBits(9);defaultUidGenerator.setEpochStr("2020-01-01");return defaultUidGenerator;}public CachedUidGenerator cachedUidGenerator(WorkerIdAssigner disposableWorkerIdAssigner) {CachedUidGenerator cachedUidGenerator = new CachedUidGenerator();cachedUidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner);cachedUidGenerator.setTimeBits(32);cachedUidGenerator.setWorkerBits(22);cachedUidGenerator.setSeqBits(9);cachedUidGenerator.setEpochStr("2020-01-01");cachedUidGenerator.setBoostPower(3);cachedUidGenerator.setScheduleInterval(60L);return cachedUidGenerator;}

mapper服务接口

与启动类同级目录新建WorkerNodeMapper内容如下


public interface WorkerNodeMapper {WorkerNodeEntity getWorkerNodeByHostPort( String host,  String port);void addWorkerNode(WorkerNodeEntity workerNodeEntity);
}

WorkerNodeMapper


<mapper namespace="org.zxp.uidgeneratortest.WorkerNodeMapper"><resultMap id="workerNodeRes"type="com.baidu.fsg.uid.worker.entity.WorkerNodeEntity"><id column="ID" jdbcType="BIGINT" property="id"/><result column="HOST_NAME" jdbcType="VARCHAR" property="hostName"/><result column="PORT" jdbcType="VARCHAR" property="port"/><result column="TYPE" jdbcType="INTEGER" property="type"/><result column="LAUNCH_DATE" jdbcType="DATE" property="launchDate"/><result column="MODIFIED" jdbcType="TIMESTAMP" property="modified"/><result column="CREATED" jdbcType="TIMESTAMP" property="created"/>resultMap><insert id="addWorkerNode" useGeneratedKeys="true" keyProperty="id"parameterType="com.baidu.fsg.uid.worker.entity.WorkerNodeEntity">INSERT INTO WORKER_NODE(HOST_NAME,PORT,TYPE,LAUNCH_DATE,MODIFIED,CREATED)VALUES (#{hostName},#{port},#{type},#{launchDate},NOW(),NOW())insert><select id="getWorkerNodeByHostPort" resultMap="workerNodeRes">SELECTID,HOST_NAME,PORT,TYPE,LAUNCH_DATE,MODIFIED,CREATEDFROMWORKER_NODEWHEREHOST_NAME = #{host} AND PORT = #{port}select>
mapper>

创建UidGenService逻辑类


public class UidGenService {private UidGenerator uidGenerator;public long getUid() {return uidGenerator.getUID();}
}

分享资源

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Yf1KiFl-1692670384862)(https://pic.imgdb.cn/item/64d0dc6a1ddac507cc857b30.png)]
获取以上资源请访问开源项目 点击跳转

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

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

相关文章

YOLO目标检测——动漫头像数据集下载分享

动漫头像数据集是用于研究和分析动漫头像相关问题的数据集&#xff0c;它包含了大量的动漫风格的头像图像。动漫头像是指以动漫风格绘制的虚构人物的头像图像&#xff0c;常见于动画、漫画、游戏等媒体。 数据集点击下载&#xff1a;YOLO动漫头像数据集50800图片.rar

【论文阅读】POIROT:关联攻击行为与内核审计记录以寻找网络威胁(CCS-2019)

POIROT: Aligning Attack Behavior with Kernel Audit Records for Cyber Threat Hunting CCS-2019 伊利诺伊大学芝加哥分校、密歇根大学迪尔伯恩分校 Milajerdi S M, Eshete B, Gjomemo R, et al. Poirot: Aligning attack behavior with kernel audit records for cyber thre…

idea http request无法识别环境变量

问题描述 创建了环境变量文件 http-client.env.json&#xff0c;然后在*.http 文件中引用环境变量&#xff0c;运行 HTTP 请求无法读取环境变量文件中定义的变量。 事故现场 IDEA 版本&#xff1a;2020.2 2021.2 解决步骤 2020.2 版本环境变量无法读取 2021.2 版本从 2020.…

lesson9: C++多线程

1.线程库 1.1 thread类的简单介绍 C11 中引入了对 线程的支持 了&#xff0c;使得 C 在 并行编程时 不需要依赖第三方库 而且在原子操作中还引入了 原子类 的概念。要使用标准库中的线程&#xff0c;必须包含 < thread > 头文件 函数名 功能 thread() 构造一个线程对象…

探工业互联网的下一站!腾讯云助力智造升级

引言 数字化浪潮正深刻影响着传统工业形态。作为第四次工业革命的重要基石&#xff0c;工业互联网凭借其独特的价值快速崛起&#xff0c;引领和推动着产业变革方向。面对数字化时代给产业带来的机遇与挑战&#xff0c;如何推动工业互联网的规模化落地&#xff0c;加速数字经济…

【C语言】数组概述

&#x1f6a9;纸上得来终觉浅&#xff0c; 绝知此事要躬行。 &#x1f31f;主页&#xff1a;June-Frost &#x1f680;专栏&#xff1a;C语言 &#x1f525;该篇将带你了解 一维数组&#xff0c;二维数组等相关知识。 目录&#xff1a; &#x1f4d8;前言&#xff1a;&#x1f…

KaiwuDB CTO 魏可伟:回归用户本位,打造“小而全”的数据库

8月16日&#xff0c;KaiwuDB 受邀亮相第十四届中国数据库技术大会 DTCC 2023。KaiwuDB CTO 魏可伟接受大会主办方的采访&#xff0c;双方共同围绕“数据库架构演进、内核引擎设计以及不同技术路线”展开深度探讨。 以下是采访的部分实录 ↓↓↓ 40 多年前&#xff0c;企业的数…

html动态爱心代码【三】(附源码)

目录 前言 特效 内容修改 完整代码 总结 前言 七夕马上就要到了&#xff0c;为了帮助大家高效表白&#xff0c;下面再给大家带来了实用的HTML浪漫表白代码(附源码)背景音乐&#xff0c;可用于520&#xff0c;情人节&#xff0c;生日&#xff0c;表白等场景&#xff0c;可直…

OrienterNet: visual localization in 2D public maps with neural matching 论文阅读

论文信息 题目&#xff1a;OrienterNet: visual localization in 2D public maps with neural matching 作者&#xff1a;Paul-Edouard Sarlin&#xff0c; Daniel DeTone 项目地址&#xff1a;github.com/facebookresearch/OrienterNet 来源&#xff1a;CVPR 时间&#xff1a…

win10 下运行 npm run watch-poll问题

背景&#xff1a;在本地练习laravel项目&#xff0c;windows 宝塔环境&#xff08;之前装过ubuntu子系统&#xff0c;很慢&#xff0c;就放弃了。有知道的兄弟说下&#xff0c;抱拳&#xff09;。以下命令我是在本地项目中用git bash里运行的&#xff0c;最好用管理员权限打开你…

Django实现音乐网站 ⒀

使用Python Django框架制作一个音乐网站&#xff0c; 本篇主要是推荐页-推荐排行榜、推荐歌手功能开发。 目录 推荐页开发 推荐排行榜 单曲表增加播放量 表模型增加播放量字段 执行表操作 模板中显示外键对应值 表模型外键设置 获取外键对应模型值 推荐排行榜视图 推…

函数式编程

函数式编程 函数式编程思想&#xff1a;对方法中的数据进行了什么操作 优点&#xff1a;代码简介、便于理解、易于并发编程 1.Lambda表达式 JDK8中的语法糖&#xff0c;可以对某些匿名内部类的写法进行简化 使用条件&#xff1a;匿名内部类是一个接口&#xff0c;并且接口只…

GraphScope,开源图数据分析引擎的领航者

文章首发地址 GraphScope是一个开源的大规模图数据分析引擎&#xff0c;由Aliyun、阿里巴巴集团和华为公司共同开发。GraphScope旨在为大规模图数据处理和分析提供高性能、高效率的解决方案。 Github地址&#xff1a; https://github.com/alibaba/GraphScope GraphScope 的重…

【电商领域】Axure在线购物商城小程序原型图,抖音商城垂直电商APP原型

作品概况 页面数量&#xff1a;共 60 页 兼容软件&#xff1a;Axure RP 9/10&#xff0c;不支持低版本 应用领域&#xff1a;网上商城、品牌自营商城、商城模块插件 作品申明&#xff1a;页面内容仅用于功能演示&#xff0c;无实际功能 作品特色 本作品为品牌自营网上商城…

logstash配置文件

input { kafka { topics > “xxxx” bootstrap_servers > “ip:port” auto_offset_reset > “xxxx” group_id > “xxxx” consumer_threads > 3 codec > “json” } } filter { grok { match > { “message” > ‘%{IP:client_ip} - - [%{HTTPDATE:…

线性代数的学习和整理---番外1:EXCEL里角度,弧度,三角函数

目录 1 角的度量&#xff1a;角度和弧度 1.1 角度 angle 1.1.1 定义 1.1.2 公式 1.1.2 角度取值范围 1.2 弧长和弦长 1.3 弧度 rad 1.3.1 弧长和弧度定义的原理 1.3.2 定义 1.3.3 取值范围 1.3.4 取值范围 1.4 角度&#xff0c;弧度的换算 1.5 EXCEL里进行角度和…

STL list基本用法

目录 list的使用构造函数和赋值重载迭代器(最重要)容量相关插入删除元素操作reversesortuniqueremovesplice list的底层实际是双向链表结构 list的使用 构造函数和赋值重载 构造函数说明list()无参构造list (size_type n, const value_type& val value_type())构造的li…

安全学习DAY18_信息打点-APP资产搜集

信息打点-APP资产&静态提取&动态抓包&动态调试 文章目录 信息打点-APP资产&静态提取&动态抓包&动态调试本节知识&思维导图本节使用到的链接&工具 如何获取目标APP从名称中获取APP从URL获取APP APP搜集资产信息APP提取信息分类信息提取方式信息…

怎么管理运营私域流量?

私域流量管理是当今企业运营的重要议题&#xff0c;对于企业发展和品牌建设具有不可忽视的作用。然而&#xff0c;管理私域流量并不是一项轻松的任务&#xff0c;需要我们采取科学有效的措施&#xff0c;才能取得良好的效果。 首先&#xff0c;私域流量管理需要建立清晰的目标。…

Linux系统安全——NAT(SNAT、DNAT)

目录 NAT SNAT SNAT实际操作 DNAT DNAT实际操作 NAT NAT: network address translation&#xff0c;支持PREROUTING&#xff0c;INPUT&#xff0c;OUTPUT&#xff0c;POSTROUTING四个链 请求报文&#xff1a;修改源/目标IP&#xff0c; 响应报文&#xff1a;修改源/目标…