二级索引

二级索引是从主访问路径访问数据的一种正交方式。在HBase中,你有一个索引,它按照主行键按字典顺序排序。除了通过主行之外,以任何方式访问记录都可能需要扫描表中的所有行,以便根据筛选器对它们进行测试。使用二级索引,索引的列或表达式将形成一个备用行键,从而允许沿着这个新轴进行点查找和范围扫描。

覆盖索引

Phoenix的功能特别强大,因为我们提供了覆盖索引——一旦找到索引条目,我们不需要返回到主表。相反,我们将关心的数据绑定在索引行中,从而节省了读取时间开销。 例如,下面将在v1和v2列上创建一个索引,并将v3列也包含在索引中,以避免不得不从数据表中获取它:

CREATE INDEX my_index ON my_table (v1,v2) INCLUDE(v3)

函数索引

函数索引(在4.3及以上版本中可用)不仅允许在列上创建索引,还允许在任意表达式上创建索引。然后,当查询使用该表达式时,可以使用索引检索结果,而不是数据表。例如,您可以在UPPER(FIRST_NAME|| ' ' ||LAST_NAME)上创建索引,以允许您对一个人的姓和名的组合进行不区分大小写的搜索。 例如,下面将创建这个函数索引:

CREATE INDEX UPPER_NAME_IDX ON EMP (UPPER(FIRST_NAME||' '||LAST_NAME))

有了这个索引,当发出下面的查询时,将使用索引而不是数据表来检索结果:

SELECT EMP_ID FROM EMP WHERE UPPER(FIRST_NAME||' '||LAST_NAME)='JOHN DOE'

Phoenix支持两种类型的索引技术:全局索引和本地索引。它们在不同的场景中都很有用,并且有自己的故障概要和性能特征。

全局索引

全局索引目标读取大量用例。对于全局索引,所有性能损失都发生在写时。我们在写(删除、UPSERT值和UPSERT选择)时拦截数据表更新,构建索引更新,然后将必要的更新发送到所有感兴趣的索引表。在读取时,Phoenix会选择使用的索引表,这会产生最快的查询时间,并直接扫描它,就像其他HBase表一样。索引不会用于引用不属于索引的列的查询。

本地索引

本地索引目标写重、空间受限的用例。与全局索引一样,Phoenix会在查询时自动选择是否使用本地索引。使用本地索引,索引数据和表数据共同驻留在同一服务器上,防止了写操作期间的任何网络开销。即使查询没有完全覆盖,也可以使用本地索引(例如,Phoenix通过针对数据表的point get自动检索索引之外的列)。与全局索引不同,在4.8.0版本之前,表的所有本地索引都存储在一个单独的共享表中。从4.8.0开始,我们将所有本地索引数据存储在同一个数据表中独立的影子列族中。在使用本地索引时,必须检查每个区域的数据,因为不能预先确定索引数据的确切区域位置。因此,在读取时会产生一些开销。

索引的流行性

默认情况下,在创建索引时,将在CREATE index调用期间同步填充索引。根据数据表的当前大小,这可能是不可行的。从4.5开始,索引的初始填充可以通过在索引创建DDL语句中包含ASYNC关键字来异步完成:

CREATE INDEX async_index ON my_schema.my_table (v) ASYNC

填充索引表的map reduce作业必须通过HBase命令行单独启动,如下所示:

${HBASE_HOME}/bin/hbase org.apache.phoenix.mapreduce.index.IndexTool--schema MY_SCHEMA --data-table MY_TABLE --index-table ASYNC_IDX--output-path ASYNC_IDX_HFILES

只有当map reduce作业完成时,索引才会被激活,并开始在查询中使用。这项工作对客户退出是有弹性的。输出路径选项用于指定用于将HFiles写入其中的HDFS目录。 你也可以用下面的HBase命令行开始索引填充在构建(“b”)状态中的所有索引:

${HBASE_HOME}/bin/hbase org.apache.phoenix.mapreduce.index.automation.PhoenixMRJobSubmitter

异步索引阈值

在4.16(和5.1)中,设置phoenix.index.async。如果预估的索引数据大小超过phoenix.index.async,阀值属性设置为正值将不允许同步创建索引, 阈值(以字节为单位)。

索引的使用

当确定查询效率更高时,Phoenix会自动使用索引来服务查询。但是,除非查询中引用的所有列都包含在索引中,否则不会使用全局索引。例如,以下查询将不使用索引,因为查询中引用了v2,但没有包含在索引中:

SELECT v2 FROM my_table WHERE v1 = 'foo'

在这种情况下,有两种获取索引的方法:

1、通过在索引中包含v2来创建覆盖索引:

CREATE INDEX my_index ON my_table (v1) INCLUDE (v2)

这将导致将v2列值复制到索引中,并在其更改时保持同步。这将明显增加索引的大小。

2、创建一个本地索引:

CREATE LOCAL INDEX my_index ON my_table (v1)

与全局索引不同,即使查询中引用的所有列都不包含在索引中,本地索引也将使用索引。默认情况下,本地索引是这样做的,因为我们知道表和索引数据共存于同一区域服务器上,从而确保查找是本地的。

索引删除

要删除一个索引,您需要发出以下语句:

DROP INDEX my_index ON my_table

如果数据表中删除了索引列,则该索引将自动删除。此外,如果在数据表中删除了索引包含的列,那么它也会自动从索引中删除。

索引属性

就像CREATE TABLE语句一样,CREATE INDEX语句可以通过属性来应用到底层的HBase表,包括添加盐的能力:

CREATE INDEX my_index ON my_table (v2 DESC, v1) INCLUDE (v3)SALT_BUCKETS=10, DATA_BLOCK_ENCODING='NONE'

请注意,如果对主表进行了填充,那么对索引的填充方式与对全局索引的填充方式相同。此外,相对于主表和索引表的大小,索引的MAX_FILESIZE被向下调整。想了解更多关于盐的内容,请点击这里。另一方面,对于本地索引,不允许指定SALT_BUCKETS。

一致性保证

在一次提交之后成功返回到客户机时,保证所有数据都被写到所有感兴趣的索引和主表中。换句话说,索引更新与HBase提供的强一致性保证是同步的。但是,由于索引存储在与数据表不同的表中,这取决于表的属性和索引的类型,所以在服务器端崩溃导致提交失败的情况下,表和索引之间的一致性会有所不同。这是一个由您的需求和用例驱动的重要设计考虑。下面列出了具有不同级别一致性保证的不同选项。

本地索引

因为Phoenix 4.8总是保证本地索引是一致的。

事务表上的全局索引

通过将表声明为事务性表,可以实现表和索引之间的最高级别一致性保证。在这种情况下,您提交的表突变和相关索引更新是具有强ACID保证的原子性的。如果提交失败,则不会更新任何数据(表或索引),从而确保表和索引始终同步。为什么不总是将表声明为事务性的呢?这可能很好,特别是当您的表被声明为不可变时,因为在这种情况下事务开销非常小。但是,如果数据是可变的,请确保与事务表中发生的冲突检测相关的开销和运行事务管理器的操作开销是可接受的。此外,带有辅助索引的事务性表可能会降低写入数据表的可用性,因为数据表及其辅助索引表都必须可用,否则写入将失败。

不可变表上的全局索引

对于数据只写入一次且从不就地更新的表,可以进行某些优化,以减少增量维护的写时间开销。这在日志或事件数据等时间序列数据中很常见,在这些数据中,一旦写入了一行,就永远不会更新它。为了利用这些优化,通过在DDL语句中添加IMMUTABLE_ROWS=true属性来声明你的表是不可变的:

CREATE TABLE my_table (k VARCHAR PRIMARY KEY, v VARCHAR) IMMUTABLE_ROWS=true

用IMMUTABLE_ROWS=true声明的表上的所有索引都被认为是不可变的(注意,默认情况下,表被认为是可变的)。对于全局不可变索引,索引完全在客户端上维护,并且在数据表发生更改时生成索引表。另一方面,本地不可变索引在服务器端维护。注意,没有任何保护措施可以强制声明为不可变的表实际上不会改变数据(因为那样会抵消所获得的性能增益)。如果发生这种情况,索引将不再与表同步。如果您有一个现有的表,您想从不可变索引切换到可变索引,使用ALTER table命令,如下所示

ALTER TABLE my_table SET IMMUTABLE_ROWS=false

在版本4.15(和5.1)中,不可变表的全局索引已经被完全重写

4.15(和5.1)和新版本的不可变表索引

不可变索引更新与可变索引更新经过相同的三个阶段写操作,除了删除或不验证现有索引行不适用于不可变索引。这保证了索引表始终与数据表同步。

4.14(和5.0)和旧版本的不可变表索引

非事务性、不可变表上的索引没有自动处理提交失败的机制。维护表和索引之间的一致性是由客户机来处理的。因为更新是幂等的,最简单的解决方案是客户端继续重试这批突变,直到它们成功。

可变表上的全局索引

版本4.15(和5.1)已经完全重写了可变表的全局索引。

4.15(和5.1)和新版本的可变表索引

新的强一致性全局索引特性使用三相索引算法来保证索引表始终与数据表同步。 该实现使用一个影子列来跟踪索引行的状态:

write:

①、将现有索引行的状态设置为unverified,并使用unverified状态写入新索引行

②、写入数据表的行

③、删除现有的索引行,并将新行的状态设置为已验证

read:

①、读取索引行并检查它们的状态

②、从数据表中修复未验证的行

delete:

①、 将索引表行设置为未验证状态

②、 删除数据表中的行

③、删除索引表行

译文: 有关更深入的信息,请参见参考资料。 所有新创建的表都使用新的索引算法。 使用旧Phoenix版本创建的索引将继续使用旧实现,直到使用IndexUpgradeTool进行升级

 4.14(和5.0)和旧版本的可变表索引

对于非事务性易变表,我们通过将索引更新添加到主表行的Write-Ahead-Log (WAL)条目来维护索引更新的持久性。只有在WAL条目成功地同步到磁盘之后,我们才会尝试进行索引/主表更新。默认情况下,我们并行地编写索引更新,从而获得非常高的吞吐量。如果在我们写入索引更新时服务器崩溃了,我们在WAL恢复过程中将所有的索引更新重放到索引表中,并依赖这些更新的幂等性来确保正确性。因此,非事务性可变表上的索引只是主表后面的一批编辑。

注意以下几点很重要:

①、对于非事务性表,可以看到索引表与主表不同步。

②、如上所述,这是好的,我们只是一个很小的背后,很短的时间不同步

③、每个数据行及其索引行(s)是保证书面或丢失,我们从来没有看到部分更新,因为这是原子性的保证HBase的一部分。

④、首先将数据写入表,然后写入索引表(如果禁用WAL,则相反)。

奇异写路径:

只有一个写入路径可以保证失败属性。所有写到HRegion的操作都会被协处理器拦截。然后,我们基于挂起的更新(对于批处理,则是更新)构建索引更新。然后将这些更新附加到WAL条目以进行原始更新。如果在此之前出现任何故障,我们将把故障返回给客户端,并且没有数据被持久化或使其对客户端可见。一旦写入了WAL,我们就可以确保索引和主表数据是可见的,即使在出现故障的情况下也是如此。

①、如果服务器崩溃了,我们就用通常的WAL重播机制重播索引更新。

②、如果服务器没有崩溃,我们只需要将索引更新插入到它们各自的表中。

i、如果索引更新失败,下面概述了维护一致性的各种方法。

ii、如果故障发生时无法到达Phoenix系统目录表,我们将强制服务器立即中止,如果失败,则调用system,退出JVM,迫使服务器终止。通过终止服务器,我们确保在恢复时将重播WAL,重播对相应表的索引更新。这确保了在二级索引处于已知无效状态时不会继续使用它。

不允许表写入,直到可变索引一致。

维护非事务性表和索引之间一致性的最高级别是声明,当更新索引失败时,应该临时禁止写入数据表。在这种一致性模式下,表和索引将保存在故障发生前的时间戳,并且不允许写入数据表,直到索引恢复联机并与数据表同步。索引将保持活动状态,并像往常一样继续供查询使用。

以下服务器端配置控制这种行为:

phoenix.index.failure.block.write  必须为真,才能在提交失败时使对数据表的写操作失败,直到索引能够与数据表同步。
phoenix.index.failure.handling.rebuild    重建必须为真(默认值),才能在提交失败时在后台重新构建可变索引。

在写入失败时禁用可变索引,直到一致性恢复.

可变索引的默认行为是,如果在提交时对索引的写操作失败,则将索引标记为禁用,在后台部分重新构建它们,然后在一致性恢复后再次将它们标记为活动的。在这种一致性模式下,在重新构建辅助索引时,不会阻塞对数据表的写入。但是,在重新生成时查询将不会使用辅助索引。 以下服务器端配置控制这种行为:

phoenix.index.failure.handling.rebuild 必须为真(默认值),才能在提交失败时在后台重新构建可变索引。
phoenix.index.failure.handling.rebuild.interval 控制服务器检查可变索引是否需要部分重建以赶上数据表的更新时的毫秒频率。默认值是10000或10秒。
phoenix.index.failure.handling.rebuild.overlap.time 控制从发生故障的时间戳返回到执行部分重建时返回的毫秒数。默认值是1。

在写失败时禁用可变索引,需要手动重新构建.

这是可变辅助索引的最低级别的一致性。在这种情况下,当对辅助索引的写入失败时,该索引将被标记为禁用,并手动重新构建索引,以使查询能够再次使用该索引。

以下服务器端配置控制此行为:

phoenix.index.failure.handling.rebuild   必须将rebuild设置为false,以在提交失败时禁用在后台重新生成可变索引。

BulkLoad 工具的使用限制

当已存在的记录被更新时,BulkLoadTools(例如CSVBulkLoadTool和JSONBulkLoadTool)目前不能生成对可变二级索引的正确更新。在普通的可变辅助索引写路径中,我们可以安全地为每个辅助索引计算Delete(对于旧记录)和Put(对于新记录),同时持有行锁以防止并发更新。在MapReduce作业的上下文中,我们不能有效地执行相同的逻辑,因为我们是在HBase RegionServers的带外执行这个操作的。因此,尽管这些工具为索引表生成HFiles,并对正在加载的数据进行适当的更新,但与表中相同记录对应的任何以前的索引记录都不会被删除。这种限制的结果是:如果使用这些工具将相同的记录重新摄取到一个索引表中,那么该索引表中将有重复的记录,这将导致该索引表产生不正确的查询结果。

要使用可能更新现有记录的BulkLoadTools执行数据增量加载,必须在加载数据表后删除并重新创建所有索引表。使用ASYNC选项重新创建索引,并使用IndexTool填充和启用该索引,对于非普通大小的表来说可能是必须的。

要执行CSV数据集的增量加载,不需要任何手动索引干预,可以使用psql工具代替BulkLoadTools。此外,可以编写一个MapReduce作业来解析CSV/JSON数据,并直接将其写入Phoenix;不过,Phoenix目前还没有为用户提供这样的工具。

安装

非事务性,可变索引需要在区域服务器和主服务器上的特殊配置选项运行- Phoenix确保它们是正确设置时,您启用了表上的可变索引;如果没有设置正确的属性,您将无法使用辅助索引。将这些设置添加到您的hbase网站后。xml时,需要滚动重新启动集群。

随着Phoenix的成熟,它需要的手动配置越来越少。对于旧的Phoenix版本,您需要添加该版本列出的属性,以及新版本列出的属性。

For Phoenix 4.12 and later

您需要在每个区域服务器上添加以下参数到hbase-site.xml:

<property><name>hbase.regionserver.wal.codec</name><value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
</property>

上面的属性允许编写自定义的WAL编辑,确保索引更新的正确写入/重播。这个编解码器支持通常的WALEdit选项主机,最明显的是WALEdit压缩。

For Phoenix 4.8 - 4.11

在主服务器节点和区域服务器节点上,还需要对服务器端hbase-site.xml进行以下配置更改:

<property><name>hbase.region.server.rpc.scheduler.factory.class</name><value>org.apache.hadoop.hbase.ipc.PhoenixRpcSchedulerFactory</value><description>Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates</description>
</property>
<property><name>hbase.rpc.controllerfactory.class</name><value>org.apache.hadoop.hbase.ipc.controller.ServerRpcControllerFactory</value><description>Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates</description>
</property>

通过确保索引更新处理的优先级高于数据更新,上述属性可以防止在全局索引(HBase 0.98.4+和Phoenix 4.3.1+)的索引维护期间发生死锁。它还通过确保元数据rpc调用以比数据rpc调用更高的优先级处理来防止死锁。

For Phoenix versions 4.7 and below

在主服务器节点和区域服务器节点上,还需要对服务器端hbase-site.xml进行以下配置更改:

<property><name>hbase.master.loadbalancer.class</name><value>org.apache.phoenix.hbase.index.balancer.IndexLoadBalancer</value>
</property>
<property><name>hbase.coprocessor.master.classes</name><value>org.apache.phoenix.hbase.index.master.IndexMasterObserver</value>
</property>
<property><name>hbase.coprocessor.regionserver.classes</name><value>org.apache.hadoop.hbase.regionserver.LocalIndexMerger</value>
</property>

使用本地索引需要以上属性。

调优

开箱即用,索引是相当快的。但是,为了优化特定的环境和工作负载,可以调优几个属性。

必须在hbase-site.xml中设置所有以下参数——它们对整个集群和所有索引表都适用,对同一服务器上的所有区域都适用(因此,例如,一台服务器不会同时写入太多不同的索引表)。

①、index.builder.threads.max 用于从主表更新构建索引更新的线程数。增加这个值可以克服从底层HRegion读取当前行状态的瓶颈。将这个值调优得过高只会成为HRegion的瓶颈,因为它将无法处理太多的并发扫描请求以及一般的线程交换问题。 默认值:10

②、 index.builder.threads.keepalivetime 生成器线程池中线程过期后的时间(以秒为单位)。 在这段时间之后,未使用的线程会立即释放,而不会保留核心线程(尽管最后这个问题不大,因为表预计将维持相当恒定的写负载),但如果我们没有看到预期的负载,它同时允许我们删除线程。 默认值:60

③、index.writer.threads。写入目标索引表时使用的最大线程数。第一级并行化是基于每个表的——它大致相当于默认索引表的数量:10个

④、index.write .threads。写入线程池中线程过期后的时间量(以秒为单位)。在这段时间之后,未使用的线程会立即释放,而不会保留核心线程(尽管最后这个问题不大,因为表预计将维持相当恒定的写负载),但如果我们没有看到预期的负载,它同时允许我们删除线程。默认值:60

⑤、hbase.htable.threads。每个索引HTable可用于写操作的最大线程数。增加这个值将允许更多的并发索引更新(例如跨批),从而提高总体吞吐量。默认值:2147483647

⑥、hbase.htable.threads。HTable s线程池中线程过期后的时间量(以秒为单位)。使用直接切换方法,只有在必要时才会创建新线程,并且会无限制地增长。这可能很糟糕,但HTables只创建与区域服务器数量相同的可运行表;因此,当添加新的区域服务器时,它也可伸缩。默认值:60

⑦、index.tablefactory.cache。我们应该保存在缓存中的索引htable的大小。增加这个数字可以确保我们不需要为每次写到索引表的尝试重新创建一个HTable。相反,如果这个值设置得太高,您可能会看到内存压力。默认值:10

⑧、org.apache.phoenix.regionserver.index.priority。指定索引优先级可能存在的范围的底部(包括在内)的最小值。默认值:1000

⑨、org.apache.phoenix.regionserver.index.priority。指定索引优先级可能位于的范围的最顶端(排除)的最大值。索引最小/最大范围内的较高优先级并不意味着更新处理得更快。默认值:1050

⑩、org.apache.phoenix.regionserver.index.handler。计算在为全局索引维护服务索引写请求时使用的线程数。尽管线程的实际数量由最大(调用队列的数量、处理程序计数)决定,其中调用队列的数量由标准HBase配置决定。为了进一步优化队列,您可以调整标准rpc队列长度参数(目前,没有针对索引队列的特殊旋钮),特别是ipc.server.max.callqueue。长度和ipc.server.callqueue.handler.factor。有关更多细节,请参阅HBase参考指南。默认值:30

性能

我们通过性能框架跟踪二级索引的性能。这是一个基于默认值的通用性能测试——结果将根据硬件规格和个人配置的不同而有所不同。

也就是说,我们已经看到了次要索引(不可变的和可变的)在一个小的(3个节点)基于桌面的集群上比常规写入路径< 2x的速度快。这实际上是非常合理的,因为我们必须写入多个表,并构建索引更新。

资源

已经有几个关于如何二级索引工作的报告,有一个更深入地看如何索引工作(漂亮的图片!)

  • 用于Apache Phoenix的强一致全局索引幻灯片,2019分布式SQL峰会
  • 用于Apache Phoenix 2019分布式SQL峰会的强一致性全局索引记录
  • 用于本地二级索引的幻灯片在Apache Phoenix, 2017 PhoenixCon

在某些情况下,这些旧的资源指的是过时的实现:

  • Los Anglees HBase Meetup - Sept, 4th, 2013
  • Local Indexes by Huawei
  • PHOENIX-938 and HBASE-11513 for deadlock prevention during global index maintenance.
  • PHOENIX-1112: Atomically rebuild index partially when index update fails

源文来自Phoenix 官网:http://phoenix.apache.org/secondary_indexing.html

Phoenix 二级索引 的使用相关推荐

  1. 2021年大数据HBase(十二):Apache Phoenix 二级索引

    全网最详细的大数据HBase文章系列,强烈建议收藏加关注! 新文章都已经列出历史文章目录,帮助大家回顾前面的知识重点. 目录 系列历史文章 前言 Apache Phoenix 二级索引 一.索引分类 ...

  2. HBase phoenix二级索引

    1. 为什么需要用二级索引? 对于HBase而言,如果想精确地定位到某行记录,唯一的办法是通过rowkey来查询.如果不通过rowkey来查找数据,就必须逐行地比较每一列的值,即全表扫瞄.对于较大的表 ...

  3. Phoenix二级索引那些事儿(下)

    http://www.icaijing.com/hot/article4940159/ Phoenix二级索引那些事儿(下) 作者:中兴大数据| 发表时间:2015-7-30 03:31:18 索引配 ...

  4. Hbase索引( Phoenix二级索引)

    Hbase索引( Phoenix二级索引) 1. Phoenix简介 1.1.Phoenix安装 1.2.常用命令 1.3.phoenix表映射 1.3.1.视图映射 1.3.2.表映射 1.3.3. ...

  5. Phoenix二级索引(Secondary Indexing)的使用(转:https://www.cnblogs.com/MOBIN/p/5467284.html)

    摘要 HBase只提供了一个基于字典排序的主键索引,在查询中你只能通过行键查询或扫描全表来获取数据,使用Phoenix提供的二级索引,可以避免在查询数据时全表扫描,提高查过性能,提升查询效率 测试环境 ...

  6. Phoenix 二级索引探究

    版本信息: HDP -> 3.0.0 Hadoop -> 3.0.1 HBase -> 2.0.0 Phoenix -> 5.0.0 HBASE 是 Google-Bigtab ...

  7. HBase优化之Apache Phoenix二级索引

    索引分类 全局索引 本地索引 覆盖索引 函数索引 全局索引 全局索引适用于读多写少业务 当构建了全局索引时,Phoenix会拦截写入(DELETE.UPSERT值和UPSERT SELECT)上的数据 ...

  8. HBase 集成 Phoenix 构建二级索引实践

    Phoenix 在 HBase 生态系统中占据了非常重要的地位,本文主要包括以下几方面内容: Phoenix 介绍 CDH HBase 集成 Phoenix 使用 Phoenix 创建 HBase 二 ...

  9. 阿里云EMR异步构建云HBase二级索引

    一.非HA EMR构建二级索引 云HBase借助Phoenix实现二级索引功能,对于Phoenix二级索引的详细介绍可参考https://yq.aliyun.com/articles/536850?s ...

最新文章

  1. 使用Spring MVC统一异常处理实战
  2. 修改特征码的相关知识
  3. .net core mvc 区域路由设置(配置)
  4. IntelliJ IDEA快捷键总结
  5. 视频传输专线解决方案架构特点——Vecloud
  6. Vue父子组件通信小总结
  7. (本地源)安装CDH Manager
  8. Maven私服(Nexus)搭建总结
  9. Java高级面试题!java构造方法的作用和特点
  10. window服务器cpu过高的排查_线上服务器发生CPU占用率过高应该如何排查并定位问题?...
  11. java 蓝桥杯算法训练 快速排序
  12. Java面试常问基础知识(持续更新)
  13. 商业创业计划书的21条重要事项
  14. 3.操作系统有五大功能
  15. scrapy框架爬虫
  16. Python与Julia结合使用的个人经验
  17. 皮外骨伤科病题库【1】
  18. c语言文件尾没有newline字符,关于C++:”文件末尾无新行”编译器警告“No newline at end of file”...
  19. TC297 Memory Maps 内存映射
  20. layui制作二维码

热门文章

  1. 嵌入式学习(STM32F103)-如何实现按键输入
  2. 联袂“懂行人”,打造“数字吉林”新样本
  3. 西交大项目设计实验报告(自动化系)
  4. Android 项目国际化 多国语言适配
  5. mysql左连接去重查询_mysql之单表查询、多表查询
  6. 失恋了怎么办 安慰别人失恋的话
  7. 想剑网三妹子最多服务器,224人一起捏刘亦菲?剑网三妹子刚进游戏,就整了个花木兰出来...
  8. 星露谷物语联机服务器位置已满,《星露谷物语》将于明年更新多人联机模式,年底先在Steam平台开放测试...
  9. 帝搜软件旗舰版控制台更新
  10. 宝宝1周岁生日祝福语