首页 文章 精选 留言 我的

精选列表

搜索[思考模型],共10000篇文章
优秀的个人博客,低调大师

每日一博 | 业务数据迁移上云的一些技术思考

前言 在支持京东集团内部及京东云外部客户的业务迁移到京东公有云及京东私有云、京东政务云的过程中,京东科技-京东云事业群-技术服务组积累了相关业务系统数据迁移的一些管理和技术经验,以案例的形式分享给大家,希望对大家的业务迁移工作有所帮助。 迁移前的准备工作 业务迁移上云涉及到的业务数据种类繁多,主要类型包括: 数据库: 关系型数据库 MySQL 、PG、Oracle等 对象存储: 标准S3接口对象存储迁移中间件数据:ES、mongoDB、redis等 文件存储: 文档、图片等非结构化数据 大数据: HBASE、HDFS files等 京东云的内外部客户在上云过程中,大部分业务均涉及到以上多种数据类型,基于相关迁移的案例所积累的经验,数据迁移需要在迁移启动前至少做好如下准备工作。 1、可执行的数据迁移技术方案制定完成,包含明确的迁移操作步骤(迁移前准备工作,迁移操作、迁移后校验工作)、执行人、确认人。 2、制定迁移应急预案及回切方案,明确责任矩阵,确认异常情况的决策条件及决策人。 3、确认数据安全等级,确认数据迁移的方案合规安全,通过相关业务安全部门审核。 4、迁移时长及割接数据同步窗口的评估(以POC验证数据信息为基础制定),确认各个业务及数据迁移可选的第二方案。 5、 确认网络带宽及质量满足迁移需求。 案例分析 下面是几个案例,涉及到了不同数据迁移的场景。 一、关系型数据库 MySQL: MySQL在迁移中最为常见,也有很成熟的迁移工具和迁移方案,包括官方工具和相关开源工具,如mysqldump等,各个云厂商也都有各自的DTS迁移工具。 DTS工具: DTS服务在传输及同步、数据校验等步骤都实现了一定的抽象化,具有相对友好的交互界面,同时可以实现多个任务并行进行,对要求平滑迁移的场景,具有自动化优势,节省大量人力,有部分DTS工具可以实现跨版本迁移。 DTS的限制是: (1)源端数据库与目标端数据库与DTS管理服务IP网络互通,并具备稳定的网络连接。 (2)数据库需要满足一定的前提条件才能实现迁移后的增量同步功能,通常的需求是权限需求,比如REPLICATION SLAVE,REPLICATION等,同时存储过程及函数在全量+增量的场景下不会被包含,在全量迁移阶段,不支持 Alter Table、Drop TableDDL操作,**不同厂家的工具限制条件可能不同,需要仔细阅读产品说明,并通过POC验证功能。 mysqldump工具: 适合的场景,数据库源端与目的端没有良好网络连接或无网络连接的情况下,允许有一定的业务中断时间,则在停机窗口完成数据导出、导入是比较适合的方案(如果具有主机级别的管理和控制能力,直接将数据库主机整体以镜像方式迁移也是一个可行的迁移方法)。 Mysqldump导出导入速度相对DTS要快(本地操作,而且与DTS相比,少了一些中间环节),但是多了一个数据文件压缩及通过网络或移动介质传送的时间。 其他开源及商业工具,如streamset等,可以支持mysql到异构数据库的同步,功能比较强大,同时限制也比较多。 迁移时长的估算: 业务割接过程中,业务数据的迁移及同步是切换前的重要步骤,也是割接过程中耗时较长,容易出现错误并导致割接延时或失败的环节,因此要对数据迁移及同步耗时做出靠谱的估算。 数据库同步,是表级别的并发来迁移全量数据,因此,DTS得结合实际的数据类型、数据行数、网络带宽、网络延迟、同步实例规格,库表的数量、单库表的大小等因素评估时长。 举例来说,数据库大小 500G,有5张表,其中一个单表400G,剩下4张表 各25G,因单表400G相对较大,迁移时长会拉长。如果是5个100G的表,迁移时长会缩减。 在正式迁移生产数据前,一般会有对测试环境的迁移POC,来验证和评估生产环境的切换流程及耗时,制定生产业务割接的计划时,要以这个时间为数据库迁移的时长依据。 京东云DTS数据迁移同步架构如下图: 案例一 从友商公有云迁移到京东公有云云,由于源端binlog问题导致的一次DTS迁移到手动迁移方式的转换。 项目条件,业务具有8小时的停服时间,因此在迁移技术方案DTS及手动导数据库都是可选方案,鉴于DTS的不停服及数据增量功能特性,我们选择在停服前开始通过京东云DTS服务同步历史数据,并开启DTS增量同步功能,基于停机窗口,我们给数据库在线迁移及增量同步的时间为4小时,DTS服务不影响在线业务,基于测试环境的迁移经验及评估。 在停服前的下午,为了给迁移留出足够的时间缓冲,我们提前启动了主数据库的DTS服务,数据库迁移进程正常进行,预计迁移时长为4小时,但是在DTS服务最后阶段,因源端binlog问题,出现了一个致命错误,导致DTS任务失败。 Migration Task Run Error ERROR 1236 (HY000): The slave is connecting using CHANGE MASTER TO MASTER_AUTO_POSITION = 1, but the master has purged binary logs containing GTIDs that the slave requires. Region: cn-north-1 ClusterGID: dts-r1rroa ERROR 1236 (HY000): The slave is connecting using CHANGE MASTER TO MASTER_AUTO_POSITION = 1, but the master has purged binary logs containing GTIDs that the slave requires. 因为最终binlog错误(部分binglog丢失),DTS任务无法恢复,最终DTS传输的4小时时间被浪费,因迁移是系统工程,其他数据迁移进程也都在根据计划推进,此时大家都没有时间去分析具体原因。 因到晚上客户业务已经推送通知并停服,此时业务迁移的其他数据迁移及业务调试已经开始。 所以,当机立断决定以mysqldump模式导出文件,本地导出速度很快(20M/s),压缩后的数据库导出文件体积缩小,减少了网络传输耗时。通过网络传输到京东云侧的云主机,然后source方式导入RDS。导出、传输、导入整个过程耗时小于2小时。 导入MySQL数据后,根据迁移流程做迁移数据校验,使用checksum_table工具对源端和目的端数据库做对比。 源库信息:—src-host sourceIP —src-user user —src-pass pass 目标库信息:—dest-host targetURL —dest-user user —dest-pass pass 验证过程中,发现部分表不一致,与业务方确认为源端在迁移开始后,停止服务不彻底导致,仍然有数据写入操作,因为业务侧并没有根据迁移规范检查mq、kafka的消息产生情况,只是停止了部分服务,后经业务及研发检查新增数据,对部分数据做清理后,完成数据库的迁移工作。 根据项目经验,这种DTS服务因binlog问题导致的失败情况并非个案。 准备工作 (1)为数据库迁移准备一个备选方案并准备好应急预案。 (2)出现问题时,决策条件及决策人提前确认,在实施过程中能根据需要及时决策做出调整。 厂商改良(非原生)的数据库的迁移: 在某些云厂商的特定数据库版本中,会对标准的数据库产品如mariaDB、PG等数据库做一些定制化的开发,以满足客户的业务的某些特殊需求,这种数据库属于厂家深度绑定的类型,在做业务迁移或灾备数据同步的时候,根据时间场景做定制化的迁移及同步方案,大部分需要从研发层面做一些定制化的配置和操作。 案例二 某金融用户,原系统运行于T的金融云,使用了定制化的RDS服务,因金融行业的业务及数据灾备规范,需要做异地容灾,目标为实现业务级别灾备,将灾备系统运行于京东金融云平台。 为实现从T云定制化的TDSQL到京东云的迁移,对源端的数据库做了详细调研,因为源端是定制化的、具有自动水平拆分、Shared Nothing 架构的分布式数据库,因此使用京东云的DTS工具不适用于这个场景,同时,在两个环境,要求数据基本为实时同步才能满足业务容灾的需求。 制定方案 在制定数据同步方案时,也对传统灾备厂商的方案做了调研,因传统厂商灾备方案多以主机级别数据及IO分析或日志分析为基础,需要做一些侵入式agent的安装,与云上RDS的场景无法适配,相关厂商也表示正在做向云上灾备的转型,但尚未有成熟落地的产品(适配难度较大),因此最终方案采取了基于gtid的主从复制的方案来实现数据库的异构云同步,屏蔽了架构差异带来的问题。 注意:涉及业务信息及底层操作的部分内容已经隐去。 首先对源端做权限调整: GRANT SELECT, RELOAD, SHOW DATABASES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, SHOW VIEW ON.TO ‘user’@’192.168.%’ 对源端做全量的逻辑备份: mysqldump –h xx –uusername -p –database nx_db -f —single-transaction —master-data=2 —skip-lock-tables > /data1/bs.dmp 注意导出文件中要有gtid信息。 灾备端导入: mysql –h xx -f –uusername -p < bs.dmp 后台做复制配置: set gtid_slave_pos=’0-13381-1xxxx06’; CHANGE MASTER TO MASTER_HOST=’sourceIP’,MASTER_USER=’username’,MASTER_PASSWORD=’*‘,MASTER_USE_GTID=’slave_pos’; 同步验证 数据验证 (1)业务侧停止写操作,在T云侧和京东云侧分别通过checksum table tablename对关键表做校验。 (2)业务侧在T云/京东云两边分别执行命令,对表/view等做数量校验: select count(1) from information_schema.tables where table_schema=’nx_db’; select count(1) from information_schema.views where table_schema=’nx_db’; 业务测通过创建测试表/增删改查等操作验证ddl/dml是否正常复制。 基础功能验证完成后,还需进一步验证源端数据库主从切换以及网络中断对数据库同步的影响,对源端数据库的日志配置,要提出Binlog本地保留时长需求(不少于48小时),避免网络中断时间过长导致的日志过期 而影响数据库的同步业务。 为保障数据及业务灾备的可靠性,需要对网络专线做实时的监控及告警配置,当出现网络问题时,需要能及时收到告警,第一时间处理,避免中间专线网络中断对灾备业务的可用性的影响。 POC验证期间曾经对网络影响进行中断测试,中断2小时,后续观察数据仍能正常同步追平,能够容忍实际业务中可能出现的网络中断造成的影响。 对源库的保护 在这个异构云容灾的案例中,因为与源端云是通过专线做了网络互通,而源端的数据库是通过IP方式访问的,因此,在应用主机整体迁移到京东侧后,主机仍然可以通过网络访问到源端数据库,这样有可能对源端库的写操作,为阻断对源端数据库的访问,可以采用主机安全组方式,阻断主机对外部3306端口的访问,或通过子网级别的ACL,限制对指定网段的特定端口的访问,在应用配置调整后,数据库连接指向改变后,再调整安全组条目或ACL策略,放开对应的访问权限。 因部分数据库的子网规划问题,使用ACL可能对数据库同步造成影响,因此在此案例,为业务主机创建一个附加安全组,配置3306端口阻断策略,实现了对源端数据库的访问保护。待业务调整完成后,解除安全组,即可实现业务数据的正常写入。 二、ES迁移 ES应用越来越广泛,业务迁移中,ES数据迁移已经成为数据迁移的一个重要部分。 ES迁移技术涉及停服迁移及不停服迁移,不停服迁移对迁移的源端和目的端网络及服务有很多要求,目前实现起来尚有很多限制,目前一般只在集团内部业务做ES不停服迁移。 通常停服迁移技术路径可选择reindex或snapshot方式及logstash方式,几种方式均要参考官方对版本的要求,选择满足版本要求的迁移方式。 Snapshot方式: Snapshot方式,从源ES集群创建数据快照,然后在目标ES集群中进行恢复。创建快照前必须先创建repository仓库,一个repository仓库可以包含多份快照文件。repository支持S3及共享文件存储系统等,自建的ES可以使用共享文件存储(从速度、成本等因素考虑,是最佳选择),使用公有云ES服务的建议采用支持S3协议的对象存储。 从速度和效率上来讲,快照方式优于reindex,当不需要对源端做任何变更,且网络存储条件具备时,优先选择快照方式迁移ES。 reindex是Elasticsearch提供的一个api接口,可以把数据从源ES集群导入到当前的ES集群,实现数据的迁移,reindex适用于数据量较大,有索引调整需求或无法连接共享存储的迁移场景,以及只需要迁移部分数据的场景。 reindex方式需要目标端能够访问到源端ES的服务端口。 案例三 客户业务从友商云迁移到京东云,源端ES为K8S集群自建服务,服务访问方式为nodeport方式,因为安全原因,限制访问方式为内部业务主机访问,服务未通过互联网对外开放。 选择迁移技术方案,源端自建的ES未安装S3插件,考虑到快照方式迁移需要源端安装S3插件,而通过POD方式部署的业务需要重新制作镜像并更新应用,从时间及工作量上考虑不是最佳选择,因此采取reindex方式来做业务数据的迁移。 为实现从京东云侧对ES的数据拉取,在源端配置一个nginx反向代理,实现了通过公网对内部ES接口的访问,同时配置白名单,限制访问IP为京东侧NAT网关出口的公网IP,确保数据的访问安全。 在京东云侧,因生产环境子网未配置公网出口,为临时拉取数据,满足迁移需求,调整路由表,配置明细路由,将源端公网IP配置到对应子网的路由表中,指向NAT网关,临时打通公网连接,通过NAT网关可以拉取到源侧的ES数据,并在ES服务中对源端的公网IP做加白操作,注意加白操作会重启ES服务。 为满足网络通信需求,临时配置ES子网的明细路由,完成数据迁移后需删除明细路由。 迁移前,确认相关迁移条件已经具备: 源端及京东云ES服务均创建对应索引,需要确认云上索引是新建的,源端与目的端的mapping可以一样,也可以不同,通过reindex,可以实现修改mapping后的字段类型。 可以从京东云侧ES访问到源端云侧服务的ES的服务端口,验证方式,telnet或curl -XGET http://:9200方式验证均可。 在源端创建索引: 源ES集群 1.创建索引(示例) PUT 11_index_11 { “mappings”: { “user”:{ “properties”:{ “name”:{“type”:”text”}, “title”:{“type”:”text”}, “age”:{“type”:”integer”} } } } } 2.写入数据(示例) POST 11_index_11/user { “title”:”manager”, “name”:”XP”, “age”:22 } 目标端ES配置 1.创建索引(示例) PUT 11_index_11 { “mappings”: { “user”:{ “properties”:{ “name”:{“type”:”text”}, “title”:{“type”:”text”}, “age”:{“type”:”integer”} } } } } 2、配置 reindex.remote.whitelist 参数,指明能够reindex 的远程集群的白名单,并重启目标端集群服务。 3 、reindex迁移数据 POST _reindex(示例) { “source”: { “remote”: { “host”: “http://sourceIP:9200“, “socket_timeout”: “1m”, “connect_timeout”: “10s” }, “index”: “11_index_11” }, “dest”: { “index”: “11_index_11” } } 迁移后,做数据校验,完成ES的数据迁移。 三、对象存储的迁移 对兼容S3协议的对象存储数据迁移,各个公有云厂商(包括部分传统灾备厂商)均有迁移工具或脚本,迁移技术难度不高。但是,因为不同厂家的对象存储在不同region可能存在底层版本及配置差异。 因此,同一个工具或脚本,在处理不同区域的对象存储数据时,可能会出现文件访问问题,在做迁移前后,需要对迁移的数据做完整性和可用性校验。 对象存储迁移的一般顺序: 1、目标端配置镜像回源,保证读404可以回源拿到数据 2、使用迁移工具迁移源端的历史数据 3、同步后的数据校验 实际迁移中,因为涉及到增量数据的同步(迁移工具支持对transfer.coverFile的参数设置,是否覆盖文件,因此也可以做到增量复制),因此,应根据项目实际的数据存储量、业务访问特性、业务停机窗口等信息,综合考虑迁移流程和选择技术方案。 案例四 某业务从友商云迁移到京东云,涉及到对象存储迁移,源端文件数量约为1000万级别,迁移前,先对源端对象存储做文件list,检查迁移工具list数据与对象存储实际数据能够匹配,然后通过迁移工具做迁移操作,因文件量较大,而且业务每天都有新数据上传,要保证所有文件都正确同步。因此采用的的历史和增量数据同步方案为提前一周做全量迁移,然后通过镜像回源同步新增文件。 割接前一周做全量迁移 1、 在割接一周前,利用京东云的osstransfer迁移工具进行全量迁移。 2、 迁移后会生成一个以源oss的bucket命名的.list.txt的文件,此文件里含用源oss bucket的所有文件的清单。 3、 迁移日志会生成在迁移的工具包log目录下,相关log说明(log文件很重要,迁移完成后做了一个异地备份): 迁移的所有文件将记录在audit-0.log中 迁移成功的文件记录在audit.success中 可以用命令grep “1$” audit-0.log查看迁移失败的文件 用生成的源oss的清单文件列表的txt文件中的文件数量和audit.success文件中的文件数量进行对比,如果数量一致说明全部迁移成功。 文件list获取配置示例: 割接当天进行全量迁移后的增量迁移 1、 利用osstransfer工具生成源oss的清单文件列表。 2、 从文件列表清单中找出全量迁移至增量迁移这段时间内新增加的文件。 3、 开启oss的镜像回源。 4、 使用curl访问新增加的文件(要访问目标端oss),进行新增文件的镜像回源。 实际迁移中遇到了问题: 在POC阶段,做测试环境数据迁移时,采用这个方案验证一切顺利,但是在生产环境割接时,遇到了问题,判断增量文件所需的list清单出现循环错误,导致list任务一直运行中,而list的清单有大量重复内容。 迁移软件版本与测试环境迁移使用的版本一致,而生产环境中,迁移软件在一周前的全量同步的使用也是一切正常。数据也正常同步到了京东云的对象存储中,割接时需要的是通过回源方式获取一周内新增的文件,如果list文件不正确,会导致增量的数据同步无法进行。 问题处理 业务割接时间有限,迅速升级问题,将问题反馈到工具软件的研发同事,研发同事迅速投入排查(已是凌晨,为京东研发同学的敬业精神点个赞)。经过研发同事排查,发现源端的友商云上,测试环境使用的对象存储与生产环境的对象存储位于不同区域(zone),而友商云的生产环境对象存储所在区域的OSS接口在本周做了调整,导致原有工具list出现错误。 研发同事紧急更新一个工具包提供给迁移现场同事,解决了对象存储文件list循环错误问题,顺利完成了文件list检查,通过对比前后生成的list文件,获取到新增的文件列表。配置镜像回源,通过脚本同步了一周的新增文件,校验无误后,配置业务应用启用对象存储,业务启动及验证工作继续正常进行。 四、redis迁移 业务中Redis使用有两种场景,一种是仅作为缓存,不做数据持久化,这种业务场景,迁移后在新环境部署业务后直接调整业务指向新的redis实例即可,一种是有数据持久化,这种业务在迁移到云上时,需要根据业务需求,做redis数据的迁移操作。 Redis有rdb(point-in-time snapshot)和aof两种持久化方案,其中rdb模式是二进制格式,类似于快照,恢复直接覆盖,aof保存的是命令(文本格式),类似于追加模式,如果需要保留目标端的redis的数据,可以使用aof方式,aof方式需要把源端redis做停写操作。Redis加载RDB恢复数据速度快于AOF的方式,但要注意老版本Redis服务不兼容新版RDB格式,因此RDB模式不适用降级的迁移或恢复。 在业务迁移时,需要根据redis的使用场景、源端与目的端版本要求,数据存储、网络条件等选择适用的redis迁移工具。 Rdb及aof的迁移,官方有详细的说明(bgrewriteaof/bgsave/redisdump等),使用也相对简单,因此本文不多做介绍。 京东云研发了redis的迁移工具redissycner(目前支持自建redis业务迁移),通过模拟redis的replication协议,提供redis迁移及同步。 Redissycner通过docker部署方式: git clonehttps://github.com/TraceNature/redissyncer.git 进入目录docker-compose up –d 下载客户端软件:wgethttps://github.com/TraceNature/redissyncercli/releases/download/v0.1.0/redissyncer-cli-0.1.0-linux-amd64.tar.gz 调整配置文件:.config.yaml syncserver:http://x.x.x.x:8080(docker服务地址) token: xxx 注意token文件内容需要在容器中确认。 编辑配置文件后即可启动服务,通过编写要执⾏的任务json来配置操作环境。 { “sourcePassword”: “xxxx”, “sourceRedisAddress”: “10.0.1.101:6379”, “targetRedisAddress”: “10.0.1.102:6379”, “targetPassword”: “xxxx”, “taskName”: “testtask”, “targetRedisVersion”: 4.0, “autostart”: true, “afresh”: true, “batchSize”: 100 } redissyncer-cli -i redissyncer-cli > task create source ./task.json 五、数据备份的重要性 数据备份是在业务迁移的全生命周期怎么强调都不过分的环节(也包括迁移后的一段时期),因数据备份不充分导致数据丢失、业务受损的教训很多,但是,在迁移实施过程中,因忽视数据备份而导致出现问题的事件仍然很常见。 问题可能来自客户,可能来自我们实施团队,也可能来自ISV或者其他可能操作数据的团队或个人。有些问题是因为迁移各个责任方的沟通不充分、宣贯不到位或技术不过关导致,有些是误操作导致。 实际场景中数据备份实施的压力或阻力,主要来自存储空间不足以及备份过程可能造成的对性能的影响。 除了备份的数据文件需要占用存储空间,数据库文件的可用性、一致性验证,同样会占用大量的存储空间(临时),因此在实际迁移过程中,对存储空间的需求,可能会大于现有数据库的数据量的2倍(源端及目标端都是如此)。因此,在重要业务场景中,迁移前,需要对数据备份所需的存储空间做好评估并考虑备份空间的成本。 案例五 某局委办业务由vmware环境迁移到政务云,在迁移前,笔者为客户做了所有迁移主机的整机备份(导出到vmware外部的外部存储中),事实证明这些环节(准备存储环境、沟通vmware运维方、数据导出耗时等)导致的成本增加是值得的。 迁移过程很顺利,在业务迁移到政务云正常服务完成业务交接大约1个月后,接到客户电话,希望能够通过迁移前备份的主机恢复数据。 问题原因 原因是一个ISV在新环境做业务升级时,安排了一个没有经验的新人,把数据库软件做了重新安装,并删除了原有数据。在协助客户通过备份的镜像恢复数据后(历史数据,新增数据部分由ISV做了增补操作),客户购买了政务云提供的灾备服务,开始定期对重要主机和数据做全量及增量备份,通过云上的容灾服务来避免或减少业务错误或误操作造成的损失。 六、业务数据迁移总结 1、提前做好备份,有了备份数据,迁移过程的压力会减小,相对宽松的迁移氛围对迁移实施很有利。 2、迁移技术及工具的选择,现在数据迁移工具越来越多,各个大厂也都有自己的工具,但是产品的限制及兼容程度各有不同,需要根据业务性质选择并验证。 3、准备回退预案,做好POC验证,POC能发现部分问题,提前准备解决方案。 4、做好流程手册,明确操作责任人,联系相关部门做好迁移的切换阶段的护航准备。产品和服务类的问题一定要能找到人支持。 5、明确责任矩阵、进行全面沟通,沟通能够发现技术层面很难发现的问题,越早建立迁移组织并形成有限的沟通机制,对迁移的顺利实施越有利。

优秀的个人博客,低调大师

元宇宙爆红后的冷思考:安全问题不容忽视

WOT全球技术创新大会2022,门票6折抢购中!购票立减2320元! 元宇宙,一个出自30年前的科幻小说的概念,在2021年迅速蹿红,并吸引了科技公司以及消费领域企业的争相布局。彭博行业研究甚至预测,到2024年,元宇宙市场规模将达到8000亿美元。不过,各行各业在积极拥抱元宇宙的同时,有一个问题不可忽视——安全。 2021年,元宇宙彻底引爆全球,受到众多资本和企业的追逐,社交巨头Facebook宣布更名为Meta,并将发展重心放在元宇宙上;英伟达以其Omniverse平台为基础,构建了一个元宇宙愿景;微软有计划通过整合虚拟环境的新程序来实现元宇宙;腾讯首席执行官马化腾在业绩电话会议上表示,公司拥有大量探索和开发元宇宙的技术和能力…… 除此以外,国内诸多企业在元宇宙相关商标赛道上的竞争也是如火如荼。数据显示,截至2021年12月8日,名称中含“元宇宙”的商标申请总量已超过7700条,共有1040余家公司参与了申请。 多种技术的综合运用 元宇宙概念最早出现于1992年的一部科幻小说《雪崩》,在小说中,人类戴上耳机和目镜,找到连接终端,以虚拟化身的方式在一个虚拟三维空间中生活,作者将这个虚拟空间称为元宇宙。 元宇宙被定义为一个结合了虚拟现实和增强现实的数字世界,它可能与现实世界相同,也可能基于想象,或者两者的某种结合。总的来说,元宇宙被视为互联网发展的新方向,将融合应用游戏机制、大规模互动直播活动、支持区块链的数字商品和虚拟商务,并将用于工作、学习、购物、休闲等各种活动,所有这些都以人际互动为核心。 近年来,随着科技的发展,人们对元宇宙这个概念有了更深的理解。元宇宙并不是某一种技术,而是一种整合了5G、云计算、大数据、物联网、人工智能、增强现实、虚拟现实、边缘计算等数字技术的互联网应用。 发展面临安全挑战 可以说,元宇宙概念大火的背后,是相关技术日益走向成熟作为支撑。但这也意味着,元宇宙的发展所面临的安全隐患可能会变得更加突出、更加复杂,具体来看: 一、技术漏洞。除了我们熟悉的普通网络钓鱼、恶意软件和黑客攻击之外,由于其基础架构,元宇宙可能会带来全新的网络犯罪。元宇宙采用的技术集成模式令其可能蕴藏更多的设计缺陷或漏洞。这些漏洞既有可能破坏系统本身的正常运行,也有可能被攻击者所利用。 例如,5G技术作为实现元宇宙的网络基础设施,实现了通信和计算的融合,基于大数据、人工智能的网络运维降低了人为差错,提高了网络安全的防御水平。不过,5G的虚拟化和软件定义能力以及协议的互联网化、开放化也带来了新的安全挑战,很有可能给网络带来更多的攻击。 物联网中的通信方式主要采用无线通信,以及大量使用电子标签和无人值守设备进行通信不过,由于成本、性能等方面的限制,使得物联网大部分所使用的终端属于弱终端,极易遭到非法入侵。这些技术遇到的问题,同样也有可能是元宇宙未来要面临的问题。 二、数据风险。元宇宙作为一个虚拟空间,需要对用户的身份属性、行为路径、社会关系、财产资源、所处场景等信息进行深度挖掘及实时同步。显然,元宇宙收集到的个人数据的数量以及种类丰富程度将是前所未有的,而这些数据在元宇宙中可能被盗用或被滥用。 三、硬件攻击。元宇宙依赖于硬件。元宇宙以外部数字设备为中心,如虚拟现实耳机,如果不加以保护,很容易成为黑客攻击的目标。通过这些头戴式设备或任何未来肯定会推出的可穿戴设备获取的数据本质上是非常敏感的。数据落入不法之徒手中,很容易成为网络罪犯的讹诈威胁的导火索。此外,当人类和企业组织不仅生活在现实世界中,而且还生活在元宇宙中时,知识产权可能更难保护。 值得注意的是,在元宇宙中,伤害一家公司并不像在网上留下负面评论那么简单。由于多层增强或虚拟现实隐藏了攻击者的真实身份,遭受到盗窃或骚扰的受害者很难或不可能诉诸法律。 写在最后: 虽然元宇宙现在可能只是一个流行词,但它是一个越来越受欢迎的概念。假设元宇宙被广泛采用,它将在很大程度上改变人类之间的互动方式。然而,这种新的媒介,人们可以通过增强的方式在线连接,这将为网络罪犯打开新的攻击面。 因此,加强对元宇宙的监管,有助于让整个行业发展得更健康、可持续。与此同时,了解到元宇宙存在的风险,并部署正确的网络安全资源进行防御,也是在这个新时代保证安全的关键。 目前,元宇宙尚处于初步探索阶段,离设想的互联网的终极形态还相去甚远。未来,元宇宙将如何改变世界,值得期待。

优秀的个人博客,低调大师

离散与提炼——一些关于向量召回算法优化方法的思考

✏️ 作者介绍: 周语馨,高级云智能工程师 最近做的很多向量召回的相关工作,主要集中在优化Faiss里面常用的几个算法,包括 IVFFlat 和 IVFPQ,并且针对这两个算法都做出了专门的优化。 前一阵子灵光乍现,想到了一种与具体算法无关的(或者更严格地说,与具体算法相关性较小的)优化方法,可以优化诸如 Flat、IVFFlat 或者 HNSW 等算法。我称之为“方法”而不是“算法”,是因为它遵从原有算法的逻辑,只是在计算过程中大幅降低了内存带宽,从而提升性能。因此经过优化的算法在召回率等方面完全不变。我把这种方法称为“离散与提炼(Discretize and Refine)”,以类比于 Google 的“Map and Reduce”,强调其方法论的地位。 开门见山,先来说说“离散与提炼”最核心的思想: 离散:使用 int8 和 bfp16 来表达原始向量,计算距离过程中使用这些压缩后的向量,从而降低内存带宽; 提炼:使用压缩后的向量计算得到的距离存在误差,但是足以剔除距离太远的点,之后在很小的一个候选集中使用原始向量计算精确距离。 接下来开始让我帮你一步步理解。 1. 背景 向量召回其实就是经典的KNN(k-NearestNeighbor)问题。KNN问题的表述为:假设现在有N个向量yi,当给定一个查询向量x时,找出距离x最近的K个yi。为了方便理解,我以最简单的暴力搜索算法(Flat)为例。那么当给定一个查询向量x时,需要把x到每一个yi的距离都计算出来。 在Faiss以及其他的ANN(近似近邻)搜索库中,向量都是使用32位浮点数(下文中简称为“fp32”)表达的,因此每个维度占用4个字节。比如一个128维度的向量就占用512字节。把向量维度记作d。那么一次查询需要访问的内存容量有d*4*N,而且是顺序访问。不管是通过实验还是Faisswiki都可以得知,Flat类(纯Flat或者IVFFlat等)算法的性能瓶颈就在于内存带宽,大多数时候CPU的计算单元都在等待yi被送到cache中。而且通过统计发现,当压力较大时,内存读的时间占到了整个算法的90%以上。 因此,如果能够使用int8来表达向量的每一维,那么一次查询的内存访问量会降低到d*1*N,即本来的四分之一,理论上性能会提升到接近原来的4倍。这就是“DiscretizeandRefine”的动机。 2. 最简单的离散化 先来考虑最容易处理的情况,即所有yi的每一维都落在区间 [-128.0, 127.0)中。 此时,把yi的每一维都四舍五入到最接近的整数上,即-128 到 127 这 256 个整数,那么就可以使用 int8 存储。把 yi 经过四舍五入(下称为“离散化”)后使用int8 表达的向量记作 zi,并且计算 yi 到 zi 的距离,记作 ei。下图是一个简单的例子来理解 zi 和 ei 的几何含义。 很直观,yi 就是实数(暂且把 fp32 看作实数)空间中的点,而 zi 就是其最接近的格点(坐标均为整数的点),而ei就是两者的距离。 那么经过这样的预处理后,我们有了 N 个 yi,N个 zi 和 N 个 ei,分别使用 fp32、int8和 fp32 表达,占用 d*4*N 字节、d*1*N 字节和 4*N 字节。 当给定一个x时,先计算x到每一个zi的距离,记作bi。这里需要强调两点: x是用户传入的、未经离散化的、使用 fp32 表达的原始向量; zi的每一维int8被依次读入寄存器后,再通过 CPU 指令在寄存器中转成 fp32,与x的每一维 fp32 计算差的平方,进行累加,最后开方。 因此,从内存到CPU的总线上传输的都是int8,而计算的结果是 fp32,即 x 到 zi 的精确距离。把 bi 减去 ei,得到 Li。至此,搜索的第一步可以用数学语言表达: Li=|x−zi|−ei 把x 到yi 的精确距离记作di,那么Li 是di 的下界。为什么呢?看下图: 图中的绿线即为 bi = |x − zi| ,是 x到 zi 的精确距离。红线为 ei = |yi − zi| ,是 yi 到 zi的精确距离。根据三角不等式,|x − yi| ≥ |x − zi| − |yi − zi|,因此有di ≥ bi − ei = Li。至此,我们使用离散化后的数据得到了 N 个 Li。 3. 最简单的提炼 如何根据 Li 来得到最小的 K 个 di 呢?这就是整个方法的第二步——提炼(Refine)。 先来看一个生活中的例子。有 100 个运动员赛跑,选出前三名。终点处的裁判手里有个秒表,每一个运动员到达终点线,裁判都会掐表来记录时间。由于裁判是人,存在几毫秒的反应时间,因此第 i 个运动员过线的客观时间 Ti,与秒表记录下的测量时间 ti 之间存在误差。但是,ti 与 Ti 之间存在非常大的正相关性,如果 ti 远比 tj 小,那么几乎可以断定 Ti 比 Tj 小。只有在 ti 和 tj都比较小且很接近时(比如第三名、第四名几乎同时过线),才需要调取录像来获取 Ti 和 Tj 再进行比较。这个例子很直观地表达了提炼的思路——通过估算距离就能排除掉绝大部分不可能入选 topK 的点,而在剩余的少数可能入选 topK 的点中使用精确距离进行最后的筛选。 这里,我们选用Li 作为估算距离进行提炼。为什么不选 bi 呢?后续算法会展现Li的妙用。 既然是最简单情况,这里的提炼算法也是力求最容易理解的,只为讲清数学原理,在性能上并不是最优的,后续会给出更优化的实现方法。 首先,将 N 个 i 与 Li 打包成(i,Li)这样的元组,并且按照 Li 从小到大排序,把排好序的元组数组记作 S。比如 S = [ (14,21.5),(31,25.2),(16,33,7), ...],含义即为到 x 的距离至少为 21.5,到 x 的距离至少为25.2,到 x 的距离至少为 33.7…… 然后,初始化一个最大堆 topK。topK 的最大容量为 K,往里面丢入若干个(label, distance),只会保留最多 K 个 distance 最小的(label, distance)。并且把 topK 的门槛值记为 T(当 topK 中的项数小于 K 时,T 为+∞,否则 T 为 topK 中最大的 distance)。T 的数学含义为:如果试图丢入(label, distance),而 distance>=T,那么(label, distance)不可能成为 topK 其中一项。 for(label,lower_bound)in S: iflower_bound>=topK.T: break distance=|x-y[label]| topK+=(label,distance) 最后,对 S 和 topK 执行如下循环: 代码依次遍历S中的每一对(label, lower_bound),如果lower_bound 不小于topK 的门槛值,那么算法终止,此时的topK 即为所求。否则,计算x 与yi 的精确距离distance,并且把(label, distance)丢入topK 中。 代码非常简单。但是,为什么代码第 2 行的判断可以确保当前的 topK就是最后的结果呢? 我们知道,S 是根据(i, Li)中的Li 从小到大排序的,如果其中第 i 项满足Si. lower_bound ≥ topK. T ,那么第 i 项后续的任一项都大于T,即: Sj.lower_bound≥Si.lower_bound≥topK.T,∀j≥i 又因为 L 作为 d 的下界,有: dSj.label≥Sj.lower_bound 联立两式,有: dSj.label≥topK.T,∀j≥i 所以,一旦代码第 2 行的判断成立,那么 S 中后续的所有 yi 都不可能入选 topK。 计算 N 个 Li 时,顺序访问了 N 个 zi 和 ei,共 d*1*N+4*N 字节。而提炼过程中,只需要访问 Li 最小的 M 个 yi,共 d*4*M 字节。一次查询的内存访问量,从 d*4*N 字节变成了 d*N+4*N+d*4*M 字节。经验上,M 大约在 K 到4*K 之间(因不同数据集而异)。以 SIFT1M 数据集(d=128)、K=100 为例,内存访问量从 128*4*1M=512MB 降 到 128*1M+4*1M+128*4*4*100≈132MB 。事实上,计算 Li 的过程中,因为需要在寄存器中把 int8 转回 fp32 ,所以计算指令反而变多了。但是由于 Flat 类算法瓶颈几乎完全在内存访问上,因此以增加 CPU 指令为代价来换内存带宽的降低,是非常值得的。至此,离散与提炼的加速原理已经明了了。 4.基于 heap 的提炼 提炼步骤中有一个明显可以优化的地方,即对 N 个 Li 排序。上文提到,S 中只有前 M 项会被访问,而 M 通常远小于 N。算法只要求前M项按照 Li排序即可,而之后的 N-M 项既然都不会被访问,那么花费大部分 CPU 时间排序后 N-M 项就很不划算。 改进方法是使用堆(优先队列同理)。如果把 N 个 (i, Li) 放入最小堆中,每次从中取出一个当前 L 最小的 (i, Li) ,那么就可以避免完全排序。用伪代码表述就是: min_heap = build_min_heap ((1, L[1]), (2, L[2]), ... , (N, L[N]))whileTrue: (label,lower_bound)=min_heap.pop() iflower_bound>=topK.T: break distance=|x-y[label]|topK+=(label,distance) 建堆过程复杂度是 O(N),进行 M 次取出操作复杂度是 O(M*logN),加起来就是 O(N+MlogN),比原本的完全排序的 O(NlogN) 的复杂度要低很多。 5.基于 nth_element的提炼 仔细观察,会发现基于最小堆的提炼仍然不是最优的。在提炼过程中,后 N-M 完全不需要排序,然而堆算法会建堆,尽管不是完全排序,但是会调整至小根二叉树的结构,可以看作一种“浅排序“,也就是仍然浪费了不必要的 CPU 时间。 既然经验上 M 大约为 K 到 4K,那么是否可以使用 nth_element 算法把 N个项中最小的 4K 个项取到数组最开头处,然后只对这 4K 个项做完全排序呢?如果很不巧,遍历完这 4K 个仍然无法结束算法,那么就继续从剩下的 N-4K 个项中抽取出 4K 个……如此循环。算法用伪代码表示如下: 6. 使用线性变化的离散化 上文中,我们假定 yi 的每一维都落在 int8 的表达访问之内,因此离散化操作只是将浮点数近似到最接近的整数。但是,如果 yi 的各个维度的取值区间很大呢,比如[-10000, 10000)?或者不是关于原点对称的,比如[0, 10000)?这个时候,我们需要利用欧几里得距离的两个特性: 坐标系的平移不会改变两点之间的距离; 所有的维度同时做相同比例的线性缩放,则两点之间的距离也被按该比例缩放。 这两点都很直观,当然,如果需要数学证明也非常容易。 因此,对于一个超出 int8 表达范围的空间,先对每一个维度分别使用一个恰当的偏置,把取值区间平移到关于原点对称。之后选取一个恰当的比例,把每个维度做一个缩放,使得每一维都落入 int8 的表达范围。该操作用数学语言表达就是: y′ = ky + b 实际操作中,对于每一个加入的 yi,都先通过线性变换得到 yi',然后对yi'做离散化得到zi和ei。 搜索时,对于用户给定的x,也要先执行x′ = kx + b ,将x映射到int8空间中。之后计算Li 的过程中使用x'、zi 和ei 作为参数。需要注意的时候,最后Li 需要除以k。搜索的第一步用数学语言表达为: 由于后续的提炼过程中,计算的精确距离都是基于原始空间的,所以需要将Li除以 k,映射回原始空间中去。 7. 使用残差的离散化 真实的数据集往往有一定的聚类特征,很可能表现为下图所示的样子: 这样的聚类特征会导致上文中的离散化方法产生较大的信息损失。比如,上图中的所有绿色点可能都被离散化到了(100,80)这个格点上,或者附近少数几个格点上。那么搜索时,Li 就过于“粗糙”,点与点之间的区分度不大,以至于不能高效地提炼。 在 ANN 算法中,IVF 类算法特别适用于这种具有聚类特征的数据集。IVF 算法在构建索引时,将原始数据聚类成 nlist 个类(每个类的聚类中心记作Ci),每个点属于其中一个类。当给定一个待搜索的 x 时,找到距离 x 最近的 nprobe个 Ci,在这 nprobe 个聚类中的点中执行暴力搜索。IVF 算法利用了聚类特征大幅减少了候选集的大小(原本的 nprobe/nlist),从而牺牲一定精度来换取性能的大幅提升。 基于IVF 算法,离散化操作可以进一步优化。对于每一个yi,假设其所在聚类的中心点为Cy 。使用ri = yi − Cy ,即yi 的残差作为“基于线性变换的离散化”的输入,得到zi 与ei。该过程的数学本质是,以每个聚类中心为原点建立一个坐标系,在该“局部坐标系”中对属于该聚类的点做离散化。如此即可解决信息损失的问题。 当给定 x 时,按照 IVF 算法找出最近的 nprobe 个聚类。对于每一个聚类,计算 x 的残差 q = x − Cx ,然后在该聚类中计算 Li。该操作本质上就是把 x 分别映射到每一个“局部坐标系”中计算距离下界。 8. 使用 bfp16 应对出界点 上文中所有的讨论,都是基于一个假设:向量的取值范围都是有界的。在有界的情况下,可以通过线性变换的方法将向量映射到int8的表达范围中。但是,如果: 向量的取值范围是无界的; 绝大多数向量都落在某个有限范围内,可是极少数向量远远地出界。 第一种情况显然无法利用 int8 来离散化。 第二种情况中,如果选用绝大多数向量所在的那个有限范围,就不得不剔除那些出界的向量。而如果为了这极少数的向量而扩大取值范围,就会使得离散化的信息损失变大,从而降低提炼效率。 这两种情况,都可以利用 bfp16 来离散化。fp32 使用最高位表达符号,之后的 8 位表达指数,最低的 23 位表达小数部分。而 bfp16 的符号位、指数位与 fp32 相同,唯一的区别是只使用 7 位来表达小数。 bfp16 拥有与 fp32 一样的非常宽阔的表达范围,只是精度会更低。把 fp32变成 bfp16 的过程,其实就是把一个 23 位小数保留到一个 7 位小数(当然,这里的小数是指二进制中的概念)。使用 bfp16之后,由于带宽可以降低到原本的一半,因此性能仍然有明显提升(接近原本的两倍)。 在上述的第一种情况中,可以全部使用bfp16做离散化,虽然性能不及使用int8,但是性能提升到接近两倍仍然非常有诱惑力。 而在上述的第二种情况中,可以混合使用 int8 和 bfp16,即绝大多数能够落入有限范围的向量,使用 int8 做离散化,而少部分“出界”的向量,使用bfp16 做离散化。这样,即能够享受到 int8 的性能,又能保证"出界点"也被正确处理。 9. 实测性能 测试平台为 Intel(R) Xeon(R) Platinum 8268 CPU @ 2.90GHz,单个 socket 上 6 个内存通道全部插满了内存条。选用的算法是 IVF1024,Flat (nprobe=20),测试数据集分别为SIFT1M、GIST1M。测试结果如下: 横坐标为执行搜索的线程数。由于 8268 单 socket 有 24 个物理核心,48 个超线程,因此线程数从 1 逐渐增加到 48。图中共用 6 条线,深蓝色线、灰色现和浅蓝色线分别是原始的 IVF1024, Flat 算法在使用 SSE4 、 AVX2 和 AVX-512 时的总吞吐量( QPS ,即 query per second ),而橙色线、黄色线和绿色线分别是优化后 IVF1024, Flat 算法在使用 SSE4 、 AVX2 和 AVX-512 时的总吞吐量。观察这六条线的走势,可以得出如下结论: 对于原始算法,随着线程数的增加,QPS 很快就触顶,不再增加; 对于原始算法,更先进的计算指令集几乎发挥不了优势; 对于优化后的算法,随着线程数增加,总吞吐量逐步上升,最多能达到 3~3.5 倍的性能; 对于优化后的算法,更先进的计算指令集可以充分发挥优势。 很显然,优化后的算法缓解了内存带宽瓶颈问题,使得多核平台与先进的向量化指令集可以充分发挥性能优势。事实上,对于 SIFT1M 数据集,原始算法需要的内存带宽高达 91GB/s,而优化后的算法仅需要 20GB/s,这使得使用 DCPMM 等廉价存储器来降低内存采购成本成为可能。 欢迎加入 Milvus社区 github.com/milvus-io/milvus|源码 milvus.io|官网 milvusio.slack.com|Slack 社区 zhihu.com/org/zilliz-11|知乎 zilliz.blog.csdn.net|CSDN博客 space.bilibili.com/478166626|Bilibili 本文分享自微信公众号 - ZILLIZ(Zilliztech)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

记一次HEX和RGB互换算法的思考及应用

由于笔者最近在开发可视化平台,所以对动态编辑器这块做了一段时间的研究, 发现其中有个小模块的知识点比较有意思,所以在这里分享一下. 作为前端工程师, 我们平时在对接设计稿的时候, 是不是经常会涉及到颜色值的转换呢? 比如从HEX值转化到RGB值, 亦或者是从RGB值转换到HEX值, 这块在PhotoShop等设计软件中非常常见, 在做类似于画板, 设计类的IDE的时候也经常会用到它们的互相转换, 还有一种场景是,为了满足老板对高大上特效的要求, 我们要让动画在不同的时间显示不同的颜色,而且有过渡效果(过渡效果虽然可以通过transiton来实现),如下: 所以笔者在这里就分享一下HEX与RGB之间相互转换的原理和算法, 并且实现随机生成HEX值和随机生成RGB值的函数,最后带着大家深度理解和掌握颜色领域的应用. 1 文章摘要 HEX与16进制 HEX转RGB算法 RGB转HEX算法 应用场景 2 HEX(16进制) 十六进制(英文名称:Hexadecimal),是计算机中数据的一种表示方法。和我们平常的表示法不一样。它由0-9,A-F组成,字母不区分大小写。与10进制的对应关系是:0-9对应0-9;A-F对应10-15;N进制的数可以用0~(N-1)的数表示,超过9的用字母A-F。 以上概念非常重要, 这也是我们转换RGB的关键. 还有一个知识点需要我们掌握的就是进制转换. 在计算机基础中我们都知道如何将二进制转化为十进制, 10进制数转换成16进制的方法,和转换为2进制的方法类似,唯一的变化:除数由2变成16. 举个例子, 我们拿140来举例: 被除数 计算过程 商 余数 140 140/16 8 14 8 8/16 0 8 所以140转换为16进制,结果为:7E (由十六进制的定义我们知道14对应的字母为E) 以上就是掌握HEX和RGB互相转换的核心知识点, 接下来我们来看看互相转换的算法实现. 3 HEX转RGB算法 从 HEX 颜色值转换成 RGB 颜色值,本质上是HEX的第一位数乘以16加上第二位数. 举个例子: 转换颜色为 #1821DD的 HEX 值到 RGB 值. #1821DD ----------> rgb:18 ----> r: r的值就是: 1 * 16 + 8 = 2421 ----> g: g的值就是: 2 * 16 + 1 = 33DD ----> b: b的值就是: 13 * 16 + 13 = 221 以上转换的结果为rgb: (24, 33, 221), 怎么样, 是不是很简单? 接下来我们来看看具体的算法实现: const hex2rgb = (hex: string = ''):string => { // 针对于传入错误的hex,即长度不等于7或者不等于4的 if(![4,7].includes(hex.length)) { throw new Error('格式错误') } let result = hex.slice(1) // 如果是颜色叠值, 统一转换成6位颜色值 if(result.length === 3) { result = result.split('').map(a => `${a}${a}`).join('') } const rgb:number[] = [] // 计算hex值 for(let i=0, len = result.length; i< len; i+=2) { rgb[i / 2] = getHexVal(result[i]) * 16 + getHexVal(result[i+1]) } function getHexVal(letter:string):number { let num:number = -1; switch(letter.toUpperCase()) { case "A": num = 10 break; case "B": num = 11 break; case "C": num = 12 break; case "D": num = 13 break; case "E": num = 14 break; case "F": num = 15 break; } if(num < 0) { num = Number(letter) } return num } return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`} 当然还有更其他方法可以实现REX转RGB, 大家可以自行探索. 4 RGB转HEX算法 对于RGB转HEX, 方法类似, 只不过相当于上述方法的逆运算, 笔者实现一种思路, 大家可以参考学习: const rgb2hex = (rgb: string):string => { let str = rgb.replace(/rgb\((.*)\)/g, '$1') let strArr = str.split(',').map(t => t.trim()) let result:string[] = [] for(let i:number = 0, len:number = strArr.length; i < len; i++) { let curVal = Number(strArr[i]) let left = getHexStr(String(Math.floor(curVal / 16))) let right = getHexStr(String(Math.floor(curVal % 16))) result[i] = left + right } function getHexStr(v:string):string { let str:string = ''; switch(v) { case '10': str = "A" break; case '11': str = "B" break; case '12': str = "C" break; case '13': str = "D" break; case '14': str = "E" break; case '15': str = "F" break; } if(!str) { str = v } return str } return `#${result.join('')}`} 以上就是反转的算法, 大家掌握了吗?接下来我们来聊聊它的应用场景. 5 应用场景 其实颜色单位互换应用在很多领域, 笔者先罗列几个实际场景: 单位换算工具 WEB IDE调色板 动态背景 其实还有很多应用, 大家可以自行发挥哈, 今天的学习就到这了, 请问今天你又博学了吗? 更多推荐 深度剖析github上15.1k Star项目:redux-thunk TS核心知识点总结及项目实战案例分析 轻松使用纯css3打造有点意思的故障艺术(附React加强组件版) 本文分享自微信公众号 - 趣谈前端(beautifulFront)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册