Tair简介

news2024/11/18 11:27:28

概述

Tair是淘宝团队开源的高可用分布式KV存储引擎,采用服务端自动负载均衡方式,使客户端逻辑简单。Tair,即TaoBao Pair缩写,Pair表示一对、一双等意思,即Key-Value数据对。

Tair分为持久化和非持久化两种方式。非持久化Tair可看成是一个分布式缓存;持久化Tair将数据存放于磁盘中。支持以下4种存储引擎:

  • 非持久化:mdb
  • 持久化:fdb、kdb和ldb

这4种存储引擎分别基于四种开源的KV数据库:Memcached、Firebird、Kyoto Cabinet和LevelDB。Firebird是关系型数据库,另外三个是NoSQL数据库。

架构

Tair架构图
在这里插入图片描述
包括Client、ConfigServer和DataServer三个不同的应用。ConfigServer通过和DataServer的心跳(即HeartBeat)维护集群中可用的节点,并根据可用的节点,构建数据的在集群中的分布信息。Client在初始化时,从ConfigServer处获取数据的分布信息,根据分布信息和相应的DataServer交互完成用户的请求。DataServer负责数据的存储,并按照ConfigServer的指示完成数据的复制和迁移工作。

ConfigServer

ConfigServer维护集群内DataServer信息,可用DataServer的信息,以及用户配置的桶数量、副本数、机房信息等,构建数据分布的对照表,以达到负载均衡和高可用的目标。ConfigServer和client相互配合根据对照表决定数据的具体分布。如果DataServer宕机或扩容,ConfigServer负责协调数据迁移、管理进度,将数据迁移到负载较小的节点上。

Tair客户端和ConfigServer的交互主要是为了获取数据分布的对照表,客户端从ConfigServer拿到对照表后,会在本地缓存对照表,在需要存储/获取数据时根据对照表查找数据在哪个DataServer上。由此也可以看出,数据访问请求不需要和ConfigServer交互,所以ConfigServer本身的性能高低并不会形成集群的瓶颈。

ConfigServer维护的对照表有版本概念,由于集群变动或管理触发,构建新的对照表后,对照表的版本号递增,并通过DataServer的心跳,将新表同步给数据节点。

客户端和DataServer交互时,DataServer每次都把自己缓存的对照表版本号放入response结构中,返回给客户端,客户端将DataServer的对照表版本号和自己缓存的对照表版本号比较,如果不相同,会主动和ConfigServer通信,请求新的对照表。

ConfigServer使客户端使用时,不需要配置数据节点列表,也不需要处理节点的状态变化,这使得Tair对最终用户来说使用和配置都很简单。

DataServer

DataServer负责数据的物理存储,并根据ConfigServer构建的对照表完成数据的复制和迁移工作。DataServer具备抽象的存储引擎层,可以很方便地添加新存储引擎。DataServer还有一个插件容器,可以动态地加载/卸载插件。

DataServer逻辑架构图
在这里插入图片描述

自动复制和迁移

为了增强数据的安全性,Tair支持配置数据的备份数。比如你可以配置备份数为3,则每个数据都会写在不同的3台机器上。得益于抽象的存储引擎层,无论是作为cache的mdb,还是持久化的fdb,都支持可配的备份数。

当数据写入一个DataServer主节点后,主节点会根据对照表自动将数据写入到其他备份节点,整个过程对用户是透明的。

当有新DataServer加入或有DataServer不可用时,ConfigServer会根据当前可用的DataServer列表,重新构建对照表。DataServer获取到新的对照表时,会自动将在新表中不由自己负责的数据迁移到对照表中相应的DataServer。迁移完成后,客户端可以从ConfigServer同步到新的对照表,完成扩容或者容灾过程。整个过程对用户是透明的,服务不中断。

插件容器

Tair内置一个插件容器,支持热插拔插件。插件由ConfigServer配置,ConfigServer会将插件配置同步给各个数据节点,数据节点会负责加载/卸载相应的插件。插件分为request和response两类,可以分别在request和response时执行相应的操作,如在put前检查用户的quota信息等。插件容器也让Tair在功能方便具有更好的灵活性。

DataServer最主要组成模块有tair_serverrequest_processortair_managerstorage_manager和各种存储的具体实现实现。

源码

目录结构良好:
在这里插入图片描述

ConfigServer

ConfigServer源代码目录下主要有下面几个cpp文件:

  • tair_cfg_svr.cppserver_conf_thread.cpp:ConfigServer的主文件,tair_cfg_svr被执行后,会检查参数和配置,然后启动几个主要线程:
    • task_queue_thread:处理请求的具体线程;
    • packet_transport:发送和接收命令数据包的线程,其中引用tbnet公共包处理epoll;
    • server_conf_thread:ConfigServer的主要业务逻辑实现线程。包括ConfigServer之间的心跳保持,根据心跳维持DataServer存活列表,维护对照表,数据迁移过程管理等逻辑;
    • heartbeat_transport:发送和接收心跳数据包的线程,其中引用tbnet公共包处理epoll。
  • conf_server_table_manager.cpp:管理对照表的辅助类,提供对照表持久化、取得一些元信息等功能,还提供打印对照表的功能,方便调试。
  • table_builder.cpp:包括3个文件,实际的对照表构建过程是由server_conf_thread::table_builder_thread::build_table触发,其中:
    • table_builder:基类,定义构造对照表的主体逻辑,其中有几个虚函数:rebuild_tableset_available_serveris_this_node_OKcaculate_capableget_tokens_per_node用于不同的构造实现扩展不同的逻辑;
    • table_builder1:构建负载均衡策略对照表的实现类,继承table_builder类,对几个虚函数进行基于负载均衡优先的逻辑实现;
    • table_builder2:构建多数据中心策略对照表的实现类,继承table_builder类,对几个虚函数进行基于位置和负载均衡双因子的逻辑实现。
  • group_info.cppgroup_info负责处理group.conf和持久化文件$TAIR_DATA_DIR/data/group_1_server_table,通过读取配置和持久化的信息,构建DataServer位置信息,供ConfigServer主逻辑使用。
  • server_info:记录DataServer存活信息的主要数据结构,server_info会被持久化到$TAIR_DATA_DIR/data/server_info.0中。server_info由下面几个部分组成:
    • serverid:DataServer在集群里的唯一标识,由ip和port构成;
    • last_time:记录最后一次接收到该DataServer心跳时间;
    • status:表示该DataServer的状态,有三种状态:ALIVE、DOWN、FORCE_DOWN。
  • server_info_file_mapper.cpp、server_info_allocator.cpp:实现server_info持久化逻辑。持久化的文件存放在$TAIR_DATA_DIR/data目录下,server_info_allocator维护server_info持久化化文件集合和其中包含的server_info数量。如果当前文件没有空间来存储新server_info,则新建一个序列化文件。
  • stat_info_detail.cpp:存储统计信息,主要的数据结构vector<u64> data_holder,包含GETCOUNT,PUTCOUNT,EVICTCOUNT,REMOVECOUNT,HITCOUNT,DATASIZE,USESIZE,ITEMCOUNT。

DataServer

通过重载的process函数,处理put、get、range等请求。request_processor.cpp定义每种请求的最高层执行流程,每个请求的大体流程都相似,request_processor处理流程:
在这里插入图片描述
如上图,Tair接收到请求后,会循环处理每一个key,如果key在迁移,会发送数据迁移的响应给客户端,客户端重新获取数据分布后,到新的DataServer操作相应的数据。处理过程中会调用性能监控工具,统计相应的性能数据。

数据结构

mdb的存储数据结构:

struct mdb_item {
	uint64_t h_next;
	uint64_t prev;
	uint64_t next;
	uint32_t exptime;
	uint32_t key_len;
	uint32_t data_len;
	uint16_t version;
	uint32_t update_time;
	uint64_t item_id;
	char data[0];
};

mdb的存储数据结构:

struct LdbItemMetaBase {
	uint8_t  meta_version_;
	uint8_t  flag_;
	uint16_t version_;
	uint32_t cdate_; // create time
	uint32_t mdate_; // modify time
	uint32_t edate_; // expired time
};

kdb的存储数据结构:

struct kdb_item_meta {
	uint8_t  flag;
	uint8_t  reserved;
	uint16_t version;
	uint32_t cdate;
	uint32_t mdate;
	uint32_t edate;
};

fdb的存储数据结构:

typedef struct fdb_item_meta {
	uint16_t magic;
	uint16_t checksum;
	uint16_t keysize; // key size max: 64KB
	uint16_t version;
	uint32_t prefixsize;
	uint32_t valsize: 24;
	uint8_t  flag; // for extends
	uint32_t cdate;
	uint32_t mdate;
	uint32_t edate;
};

高可用

Tair的高可用,主要通过对照表和数据迁移两大功能进行支撑。

对照表

分布式系统的一个核心问题:数据在集群中的分布策略,好的策略应该能将数据均衡地分布到所有节点上,且能适应集群节点的增减变化。

对照表将数据分为若干个桶,并根据机器数量、机器位置进行负载均衡和副本放置,确保数据分布均匀,并且在多机房有数据副本。在集群发生变化时,会重新计算对照表,并进行数据迁移。

Tair基于一致性Hash算法存储数据,根据配置建立固定数量的bucket,将这些bucket尽量均衡分配到DataServer节点上,并建立副本。

ConfigServer启动后,会等待4秒,然后根据有连接和心跳的状态,检查DataServer是否在线,然后决定是否重建对照表,DataServer需要在ConfigServer启动之前启动。

对照表的初始化

过程如下:在tair_cfg_svr程序启动后,会调用tair_config_server::start(),这个方法会调用my_server_conf_thread.start()my_server_conf_thread有个属性table_builder_threadbuilder_thread,这个类在构造方法里,会调用自己的start方法,把自己启动为一个线程。这个线程会每秒钟检查一次是否需要重新构造对照表。如果需要重新构造,就调用组对象的rebuild方法重新构建对照表。

ConfigServer会定期调用server_conf_thread::check_server_status()方法检查是否需要重建对照表或有节点变动。第一次启动时,由于没有原有的对照表,所以check_server_status调用group_info::is_need_rebuild()时,会固定返回true。因此第一次启动时会根据在线的服务器列表重构对照表。

重建对照表有三种策略选择,可通过group.conf中的_build_strategy=num的配置项进行配置:

  • num=1:表示所有机器不分机房;
  • num=2:表示按照机房分组;
  • num=3:表示让ConfigServer自动决定使用哪种模式。

在设置为自动选择模式时,根据_pos_mask设置的值,检测DataServer所在机房,如果有机器分布在不同机房,且不同机房的服务器数量差不大于_build_diff_ratio配置项指定的差异率,则使用策略类型2,否则使用策略类型1;如果没有分布在不同机房的机器,则使用策略类型1。

机房之间的差异算法:假设有两个机房A和B,配置差异比率_build_diff_ratio=0.5。假设机房A有8台DataServer,机房B有4台DataServer,差异比率=(8-4)/8=0.5。此时满足条件。如果后续对机房A进行扩容,增加一台DataServer,扩容后的差异比率=(9-4)/9=0.556,即对DataServer多的机房扩容会扩大差异比率。如果_build_diff_ratio配置值是0.5,那扩容后ConfigServer会拒绝再继续build新表。如果正在做数据迁移,则调用p_table_builder->build_quick_table(),否则调用p_table_builder->rebuild_table()重建对照表。

负载均衡策略

由于允许数据存放多备份,某个桶最多会存储copyCount次(本例用Y表示)​,也就是说集群中存在Y个bucket的内容是完全一样的,这些一样的数据桶,我们将其中的一个叫作主桶,下面的推演实例为方便和代码对照,主桶都存放在line0中。

采用负载均衡策略(_build_strategy=1)建出的对照表将使bucket会均衡分布到集群中的DataServer上。假设共有X个桶,Y个副本,N个节点,那么在负载均衡优先的策略下,每个节点存放桶最少的个数为:​(XY)/N。如果(XY)%N等于0,每个节点就会存放相同数量的桶。如果(XY)%N不等于0,那么将有(XY)%N个节点将负载(X*Y)/N+1个桶。也就是说,如果使用这种策略,任意两个节点存放的桶数量至多相差1。

在使用负载均衡策略构建对照表的时候,会按照约束级别调用table_builder1::is_this_node_OK函数,决定一个DataServer是否适合存储某个桶。约束级别有四种:

  • CONSIDER_ALL = 0;
  • CONSIDER_POS = 1;
  • CONSIDER_BASE = 2;
  • CONSIDER_FORCE = 3。

函数的四个约束:

  • c1:如果DataServer存储主桶的个数,超过计算出来的主桶容量,就会返回TOOMANY_MASTER;
  • c2:如果DataServer存储的桶的总个数,超过计算出来的容量,就会返回TOOMANY_BUCKET;
  • c3:如果DataServer存储的桶数量超过 M / N M/N M/N,且存储 M / N + 1 M/N+1 M/N+1个桶的DataServer数量超过计算出的最大个数+1,会返回TOOMANY_BUCKET;
  • c4:存储相同数据的任何两个桶不能在同一个DataServer上,如果违反此条,会返回SAME_NODE。

主桶和副本桶检查约束的规则如表

正在迁移的是主副桶?ALLPOSBASEFORCE
主桶c1、c4c1、c4c1
副本桶c2、c4c2、c4c3、c4c4

主桶没有宕机的情况下,检查当前DataServer上的主桶数量是否过多,如果数量过多,则轮询每个节点,查看如果主桶迁移到该DataServer,是否引起数量过多,或者和自己的副本在同一个节点;主数据桶宕机的情况下,副本桶进行提升,如果即将存放主桶的DataServer存放的主桶数量过多,则轮询每个节点,查看如果主桶迁移到该DataServer,是否引起数量过多,或者和自己的副本在同一个节点。

多机架支持

Tair的设计考虑多机架支持,在机架/机房灾难时,确保异地有数据备份。假设搭建Tair集群后,配置数据副本数为3,搭建5个DataServer分布在两个机架上。Tair在建立对照表时,会确保每一份数据至少在两个机架的DataServer上至少有一个副本,如果数据在某一个机架上有两份副本,Tair会尽量使这两份副本分布在不同的DataServer上。

多机架情况下,调用is_this_node_OK判断某个副本是否适合存放在某个DataServer上时,Tair会考虑机架信息和所在机架各个DataServer之间的负载均衡。主要考虑点如下:

  • DataServer存储主副本数量不超过master_server_capable中计算的上限;
  • DataServer存储副本总数量不超过server_capable中计算的上限;
  • 某个机架上总共存储N个副本,共有C个DataServer,存储 N / C + 1 N/C+1 N/C+1个副本的DataServer个数不超过上限;
  • 主副本与其备份副本不能存储在同一个DataServer上;
  • 主副本与其备份副本不能存储在同一个机架上。

数据迁移

Tair每次重新构造对照表之后,会将新的对照表发送给DataServer,DataServer拿到新的对照表后,通过计算,如果发现需要迁移的数据列表不为空,则通过migrate_manager::set_migrate_server_list方法,把迁移列表写入migrate_manager的迁移列表里。迁移完成后,DataServer向ConfigServer发送迁移完成的消息。

migrate_manager是DataServer启动后就启动的一个线程,不断扫描自己的迁移表,发现迁移表不为空的时候,就进行数据迁移工作。具体迁移逻辑在migrate_manager::do_migrate_one_bucket方法里,主要逻辑是,开始迁移数据时,设置current_migrating_bucket为当前正在迁移的桶id,之后DataServer写入这个桶时,都会写入redolog。然后migrate_manager开始迁移内存中桶的数据(或ldb文件中的数据)​。数据迁移完成后,迁移redolog。redolog迁移完成后,将这个桶标记为迁移完成,并把迁移完成信息发送给ConfigServer。

存储引擎

storage_manager,Tair的抽象存储引擎层​,只要满足存储引擎需要的接口,便可以很方便地替换Tair底层的存储引擎。如有需要,可对bdb、tc甚至MySQL进行包装作为存储引擎,而同时使用Tair的分布式、同步等特性。

Tair默认包含四个存储引擎:

  • mdb:高效的缓存存储引擎,它有着和Memcached类似的内存管理方式。mdb支持使用share memory,使得在重启Tair数据节点的进程时不会导致数据丢失,使应用升级更平滑,不会导致命中率的较大波动。
  • fdb:简单高效的持久化存储引擎,使用树的方式根据数据key的Hash值索引数据,加快查找速度。索引文件和数据文件分离,尽量保持索引文件在内存中,以便减小IO开销。使用空闲空间池管理被删除的空间。
  • ldb:LevelDB是Google开源的快速轻量级的单机KV存储引擎。基本特性:
    • 提供KV支持,key和value是任意的字节数组;
    • 数据按key内部排序;
    • 支持批量修改(原子操作)​。
  • kdb:Kyoto Cabinet是一个数据库管理的lib,是Tokyo Cabinet的改进版本。数据库是一个简单的包含记录的数据文件,每个记录是一个键值对,key和value都是变长的字节序列。key和value可以是二进制、文本字符串。数据库中的key必须唯一。数据库既没有表的概念,也不存在数据类型。所有的记录被组织为Hash表或B+树。Kyoto Cabinet的运行速度非常快,例如保存一百万记录到Hash数据库中只需要0.9秒,保存到B+ tree数据库只需要1.1秒。且数据库本身还非常小。Hash数据库的每个记录头只有16字节,B+ tree数据库是4字节。Kyoto Cabinet的伸缩性非常好,数据库大小可以增长到8EB​。

mdb

Tair默认使用MDB存储数据,MDB是一个内存K/V存储引擎,有着类似Memcached的内存管理模式。

mdb结构图
在这里插入图片描述
四个主要的数据结构为:

  • mem_pool:用于管理内存;
  • mem_cache:用于管理slab;
  • cache_hash_map:用于存储Hash表;
  • mdb_area_stat:用于维护area状态。

mem_pool主要用于内存管理,Tair通过将内存分为若干个page管理内存。每个page的大小是1MB,page的个数由Tair根据slab_mem_size配置设置,单位MB。上图中的例子设置为2048,即2GB。Tair代码中定义最大page数量MAX_PAGES_NO = 65536限制,单个DataServer节点最多可使用64GB内存。mem_pool里存储当前已经占用的page、未分配的page。

mem_cache中主要存放slab_manager列表。slab_manager主要用于管理各个item,每个slab_manager中管理相同大小的数据块,存储在Tair中的数据,最终存储在这些块里,也就是item。

Tair中限制最大slab个数为100(TAIR_SLAB_LARGEST)​,每个slab的数据块大小按照mdb_param::factor(值为1.1)递增。最小slab中数据块大小cache_info->base_size=ALIGN(sizeof(mdb_item)+16),为64字节,可存储16字节数据,slab最大可存储881920约800kB字节每个item的数据。slab_manager中会分配page,然后将数据写入page中。

cache_hash_map,主要存储一个HashTable,按照数据key进行Hash,Hash冲突时,产生一个链表。

mdb_area_stat:维护area的相关数据,主要记录area的数据量限制和属于某个area的所有数据的链表。在写入数据时,会检查area数据量限制,如果数据量达到上限,则会循环50次;检查是否有过期数据,如果找到,则逐出。如果50次都没有找到过期数据,则将最后一个数据逐出。

被逐出的数据,有两种可能:

  • 如果配置evict_data_path选项,被逐出的数据会记入文件;
  • 没有配置,数据直接被逐出。

area中记录的所有数据链表,用于执行clear操作。

API

Tair为客户端提供丰富的API支持,主要分为:

  • KV操作API:普通KV操作,和Redis操作很相似;
  • Prefix操作API:类似Redis Hash数据结构。

KV

几个示例(没必要完全列举):

  • getHidden(short ns, byte[​] key, TairOption opt):用于获取被标记为隐藏的key;
  • put(short ns, byte[​] key, byte[​] value, TairOption opt):设置KV;
  • hideByProxy(short ns, byte[​] key, TairOption opt):隐藏某个key。

解读:ns表示namespace或area,K和V都是byte数组,TairOption表示参数设置(如version、expire)。

Prefix

几个示例:

  • prefixPut(short ns, byte[​] pkey, byte[​] skey, byte[​] value,TairOption opt):设置KV;
  • prefixGetHidden(short ns, byte[​] pkey, byte[​] skey,TairOption opt):取得隐藏的KV;

原理:Tair在接收到含有prefix的请求后,会按照prefix计算Hash,因此同一个namespace下的同一个prefix,会Hash到同一个HashTable位置,形成一个链表。后续通过prefix操作时,都是操作这个链表。

Range

几个示例:

  • getRange(short ns, byte[​] pkey, byte[​] begin, byte[​] end, int offset, int maxCount, boolean reverse, TairOption opt):按照前缀匹配取得prefix的子KV;
  • getRangeKey(short ns, byte[​] pkey, byte[​] begin, byte[​] end, int offset, int maxCount, boolean reverse, TairOption opt):按照前缀匹配取得prefix的子key;
  • getRangeValue:参数同上,按照前缀匹配取得prefix的子key对应的value。

mdb、fdb、kdb引擎不支持range操作,需要更换为ldb引擎。

Version

Tair中的每个数据都包含版本号,版本号在每次更新后都会递增。这个特性可防止数据的并发更新导致的问题。

Tair使用不同的存储引擎时,存储的数据结构里,都会有一个版本号。参考上面的数据结构部分。

在执行put操作时,会首先把原来存储的数据拿出来,对比version如果version不匹配,返回错误;如果version匹配,则更新数据,并增加版本号。如果不希望使用version匹配,可以传入0:

else if(version_care && version ! = 0
        && it->version ! = static_cast<uint32_t> (version)) {
	TBSYS_LOG(WARN, "it->version(%hu) ! = version(%hu)", it->version, key.get_version());
	return TAIR_RETURN_VERSION_ERROR;
}

参考

  • 深入分布式缓存:从原理到实践

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

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

相关文章

C++ 发布包问题汇总

C 发布包问题汇总 1、64位系统 拷贝 C:\Windows\SysWOW64 文件夹下面的DLL 2、32位系统 拷贝C:\Windows\System32文件夹下面的DLL 3、 程序所需dll问题 使用vs调试&#xff0c;在调试界面会打印出加载的dll 4、拷贝dll问题 64位拷贝了32位的dll就会出现加载失败的问题&a…

Java语言之数据类型与变量

Java的数据类型主要分为两类 基本数据类型&#xff1a;整形&#xff08;包括&#xff1a;字节型&#xff1a;byte、1个字节,短整型&#xff1a;short、两个字节&#xff0c;整形&#xff1a;int、4个字节&#xff0c;长整型&#xff1a;long、8个字节&#xff09;&#xff0c;…

swagger v2默认访问地址

SpringBoot项目启动默认访问地址&#xff1a;swagger-ui.html 2024-09-27 08:40:59.744 INFO 248900 --- [ main] o.a.coyote.http11.Http11NioProtocol : Starting ProtocolHandler ["http-nio-9090"] 2024-09-27 08:40:59.756 INFO 248900 --- […

三数之和为0

1. 问题描述 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元…

Vscode超好看的渐变主题插件

样式效果&#xff1a; 插件使用方法&#xff1a; 然后重启&#xff0c;之后会显示vccode损坏&#xff0c;不用理会&#xff0c;因为这个插件是更改了应用内部代码&#xff0c;直接不再显示即可。

GUPAO-AI大模型实战训练营-大模型原理及训练技巧、大模型微调的核心原理

在当今科技日新月异的时代&#xff0c;大模型已经成为人工智能领域的重要支柱。GUPAO-AI大模型实战训练营&#xff0c;正是为了深入解析这些庞然大物背后的原理&#xff0c;以及如何有效利用它们进行实际操作和微调。本文将带你走进大模型的殿堂&#xff0c;揭示其原理&#xf…

【重学 MySQL】四十二、单行子查询

【重学 MySQL】四十二、单行子查询 单行子查询的基本用法示例1&#xff1a;查找薪资高于公司平均水平的员工示例2&#xff1a;查找没有分配项目的员工示例3&#xff1a;使用单行子查询进行等值比较 注意事项 在MySQL中&#xff0c;子查询&#xff08;Subquery&#xff09;是一种…

VUE 整合 ECharts

一、vue 引入 ECharts依赖 npm install echarts --save 二、创建盒子 <div ref"chars" style"height: 500px;width:800px;"></div> 解释说明 ref"chars" 是 Vue.js 中一个非常有用的特性&#xff0c;用于给 DOM 元素或组件实例…

CrossOver24支持的游戏有那些

CrossOver刚刚更新了24版本&#xff0c;支持《地平线零之曙光》、《以撒的结合&#xff1a;重生》等游戏。一起来看看它有哪些更新吧&#xff01; 一、功能优化 - 更新 Wine 至最新的稳定版 Wine 9.0&#xff0c;引入了 7000多个更新和针对各种软件游戏的优化。 - 更新 Wine M…

Android平台GB28181实时回传流程和技术实现

规范解读 GB28181 中的 “INVITE” 是会话初始协议&#xff08;SIP&#xff09;中的一种请求方法&#xff0c;主要用于邀请一个或多个参与者加入特定的会话。在 GB28181 标准中&#xff0c;“INVITE” 请求通常用于发起媒体流的传输请求。当一个设备想要接收来自另一个设备的媒…

Linux上安装Jenkins并展示allure报告

1. 确认安装正确的java版本 到官网War Jenkins Packages查看Jenkins版本匹配的java版本&#xff0c;我这里选择安装java11 使用java --version命令是否已安装java版本 java --version 如上图所示&#xff0c;暂未安装java版本&#xff0c;我这里选择安装java11&#xff08;je…

基于SpringBoot+Vue+MySQL的在线酷听音乐系统

系统展示 用户前台界面 管理员后台界面 系统背景 随着互联网技术的飞速发展&#xff0c;网络已成为人们日常生活中不可或缺的一部分。在线音乐服务因其便捷性和丰富性&#xff0c;逐渐成为用户获取音乐内容的主要渠道。然而&#xff0c;传统的音乐播放平台往往存在歌曲资源有限…

进程组、会话、守护进程和线程的概念

1.进程组和会话 1.1 概念和特性 进程组&#xff0c;也称之为作业。BSD于1980年前后向Unix中增加的一个新特性。代表一个或多个进程的集合。每个进程都属于一个进程组。在waitpid函数和kill函数的参数中都曾使用到。操作系统设计的进程组的概念&#xff0c;是为了简化对多个进…

微信小程序-数据模型与动态赋值

首先新建一个小程序项目. 这边有创建基础项目的流程:从0新建一个微信小程序实现一个简单跳转_小白开发小程序源代码-CSDN博客 一共两步: 1.建立页面的 数据模型 和 默认赋值: 默认赋值: 2.接收输入框的新文案,动态替换上面的文案展示 //文件 testUI.js增加方法:onInputChan…

当 ucx --with-cuda 时做了什么

1&#xff0c;找一只活麻雀&#xff0c;下载编译 ucx git clone https://github.com/openucx/ucx.git cd ucx/ git checkout v1.16.0 ./autogen.sh ./autogen.sh mkdir build cd build ../contrib/configure-devel --with-cuda/usr/local/cuda --without-rocm --without-java …

JavaScript 知识点 - 作用域(变量提升、垃圾回收机制、闭包)

一、作用域 1、基本概念 是什么? 指变量、对象和函数在【代码中的可访问性范围】。 有什么用? 理解作用域对【编写高效和无错误的代码】至关重要 分类 局部作用域&#xff08;函数作用域、块作用域&#xff09;、全局作用域 涉及到那些知识点 作用域链、JS垃圾回收机…

在线支付系统

一、系统概述 本在线支付系统基于 Servlet 技术构建&#xff0c;旨在为用户提供安全、便捷的支付服务。系统具备简洁的用户界面和高效的支付处理能力&#xff0c;满足用户在各种场景下的支付需求。 二、主要功能 首页登录注册&#xff1a; 用户可以在首页进行登录和注册操作。注…

MacBook 使用 brew 安装 MySQL

目录 &#xff08;1&#xff09;准备工作1.1 更新 brew &#xff08;2&#xff09;正式安装2.1 安装MySQL&#xff1a;2.2 启动mysql &#xff08;3&#xff09;初始化数据库3.1 选择验证密码组件3.2 密码强度3.3 删除匿名用户3.4 禁用root用户远程连接3.5 删除test数据库3.6 重…

C语言 17 宏定义

前面认识了#include指令&#xff0c;接着来看#define指令&#xff0c;它可以实现宏定义。宏是啥意思&#xff1f; 把参数批量替换到文本中&#xff0c;这种实现通常称为宏&#xff08;macro&#xff09;或定义宏 (define macro) 可以通过#define来定义宏&#xff0c;规则如下&a…

Cyber Weekly #26

赛博新闻 1、Meta发布最强AR眼镜 Meta Connect 2024大会展示了多款新产品和技术&#xff0c;包括更便宜的Quest 3S系列AR眼镜、新功能丰富的Meta Rayban眼镜、OrionAR眼镜原型机&#xff0c;以及月活5亿用户的Meta AI。其中&#xff0c;OrionAR眼镜以其先进的交互体验和强大的…