es(Elasticsearch) -- 入门指南

扫一扫加入大数据公众号和技术交流群,了解更多大数据技术,还有免费资料等你哦

简介

                  ES=elaticsearch简写, Elasticsearch是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。 本质上是一个分布式nosql数据库,允许多台服务器协同工作,每台服务器可以运行多个 Elastic 实例。单个 Elastic 实例称为一个节点(node)。一组节点构成一个集群(cluster)。

概念介绍

关系数据库(mysql) ⇒ 数据库 ⇒ 表 ⇒ 行 ⇒ 列(Columns)

Elasticsearch             ⇒ 索引 ⇒ 类型 ⇒ 文档 ⇒ 字段(Fields)

  1. index: es里的index相当于一个数据库,每个 Index (即数据库)的名字必须是小写。 每个索引有一或多个分片(shard),每个分片可以有多个副本(replica)
  2. type: 相当于数据库里的一个表。 在 7.0 以及之后的版本中 Type 被废弃了。一个 index 中只有一个默认的 type,即 _doc
  3. document:文档也就是指es里面的单条数据
  4. id: 唯一,相当于主键PUT数据的时候需要指定id。 
  5. node:节点是es实例,一台机器可以运行多个实例,但是同一台机器上的实例在配置文件中要确保http和tcp端口不同。 
  6. cluster:代表一个集群,集群中有多个节点,其中有一个会被选为主节点,这个主节点是可以通过选举产生的,主从节点是对于集群内部来说的。 
  7. shards:代表索引分片,es可以把一个完整的索引分成多个分片(默认5个),这样的好处是可以把一个大的索引拆分成多个,分布到不同的节点上,构成分布式搜索。分片的数量只能在索引创建前指定,并且索引创建后不能更改。 
  8. replicas:代表索引副本,es可以设置多个索引的副本,副本的作用一是提高系统的容错性,当个某个节点某个分片损坏或丢失时可以从副本中恢复。二是提高es的查询效率,es会自动对搜索请求进行负载均衡。
  9. mapping  在 Elasticsearch  中的作用就是约束,类似于mysql中的表的约束,每个type都有自己一个mapping。

document(文档)

            存储在Elasticsearch中的主要实体叫文档(document)。用关系型数据库来类比的话,一个文档相当于数据库表中的一行记录。文档由多个字段组成,每个字段可能多次出现在一个文档里,这样的字段叫多值字段(multivalued)。每个字段有类型,如文本、数值、日期等。字段类型也可以是复杂类型,一个字段包含其他子文档或者数组。字段类型在Elasticsearch中很重要,因为它给出了各种操作(如分析或排序)如何被执行的信息。幸好,这可以自动确定,然而,我们仍然建议使用映射。与关系型数据库不同,文档不需要有固定的结构,每个文档可以有不同的字段。从客户端的角度看,文档是一个JSON对象。每个文档存储在一个索引中,并有一个Elasticsearch自动生成的唯一标识符和文档类型。文档需要有对应文档类型的唯一标识符,这意味着在一个索引中,两个不同类型的文档可以有相同的唯一标识符。

端口

            ElasticSearch 客户端程序除了Java 使用TCP的方式连接ES集群以外,其他的语言基本上都是使用的Http的方式。众所周知,ES 客户端默认的TCP端口为9300,而HTTP默认端口为9200。elasticsearch-hadoop 使用的就是HTTP的方式连接的ES集群。

mapping

              Mapping)相当于数据表的表结构。ElasticSearch中的映射(Mapping)用来定义一个文档,可以定义所包含的字段以及字段的类型、分词器及属性等等,映射可以分为动态映射和静态映射。 
(1)动态映射 
我们知道,在关系数据库中,需要事先创建数据库,然后在该数据库实例下创建数据表,然后才能在该数据表中插入数据。而ElasticSearch中不需要事先定义映射(Mapping),文档写入ElasticSearch时,会根据文档字段自动识别类型,这种机制称之为动态映射。

注意:不建议使用动态mapping,因为Elasticsearch的动态Mapping并不总是精确的。动态Mapping对于入门很有用,但在某些时候您需要结合业务数据指定Mapping。

(2)静态映射 
           当然,在ElasticSearch中也可以事先定义好映射,包含文档的各个字段及其类型等,这种方式称之为静态映射。

ES 6.3 mapping 参数说明

  1.    type        : text:是数据类型一般文本使用text(可分词进行模糊查询),  keyword  :无法被分词(不需要执行分词器),存储数据时候,不会分词建立索引,用于精确查找
  2.    format     : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" ,#格式化 此参数代表可接受的时间格式 3种都接受
  3.    analyzer  : "ik_max_word", #指定分词器,一般使用最大分词:ik_max_word
  4.    index     : true, #该字段是否会被索引和可查询 默认true 不分词是:not_analyzed ,设置成  no,字段将不会被索引
  5.     store       : true, #默认情况false,其实并不是真没有存储,_source字段里会保存一份原始文档。
  6.     # 在某些情况下,store参数有意义,比如一个文档里面有title、date和超大的content字段,如果只想获取title和date
  7.   boost         : 1.5, #字段权重;用于查询时评分,关键字段的权重就会高一些,默认都是1;另外查询时可临时指定权重
  8.  normalizer   : "normalizer_name", #字段标准化规则;如把所有字符转为小写;具体如下举例
  9.   coerce       : true, #清理脏数据:1,字符串会被强制转换为整数 2,浮点数被强制转换为整数;默认为true

命名规范

      inde索引命名规范:受文件系统的限制。仅可能为小写字母,不能下划线开头。同时需遵守下列规则:

  1. 不能包括 , /, *, ?, ", <, >, |, 空格, 逗号, #
  2. 7.0版本之前可以使用冒号:,但不建议使用并在7.0版本之后不再支持
  3. 不能以这些字符 -, _, + 开头
  4. 不能包括 . 或 …
  5. 长度不能超过 255 个字符

以上这些命名限制是因为当Elasticsearch使用索引名称作为磁盘上的目录名称,这些名称必须符合不同操作系统的约定。

   类型:类型名称可以包括除了null的任何字符,不能以下划线开头。7.0版本之后不再支持类型,默认为_doc。

Elasticsearch 主要概念

               我们已经知道Elasticsearch把数据存储在一个或多个索引上,每个索引包含各种类型的文档。我们也知道了每个文档有很多字段,映射定义了Elasticsearch如何对待这些字段。但还有更多,从一开始,Elasticsearch就被设计为能处理数以亿计的文档和每秒数以百计的搜索请求的分布式解决方案。这归功于几个重要的概念,我们现在将更详细地描述。

  1. 节点和集群 :Elasticsearch可以作为一个独立的单个搜索服务器。不过,为了能够处理大型数据集,实现容错和高可用性,Elasticsearch可以运行在许多互相合作的服务器上。这些服务器称为集群(cluster),形成集群的每个服务器称为(node)。
  2.  分片:当有大量的文档时,由于内存的限制、硬盘能力、处理能力不足、无法足够快地响应客户端请求等,一个节点可能不够。在这种情况下,数据可以分为较小的称为分片(shard)的部分(其中每个分片都是一个独立的Apache Lucene索引)。每个分片可以放在不同的服务器上,因此,数据可以在集群的节点中传播。当你查询的索引分布在多个分片上时,Elasticsearch会把查询发送给每个相关的分片,并将结果合并在一起,而应用程序并不知道分片的存在。此外,多个分片可以加快索引
  3.  副本:为了提高查询吞吐量或实现高可用性,可以使用分片副本。副本(replica)只是一个分片的精确复制,每个分片可以有零个或多个副本。换句话说,Elasticsearch可以有许多相同的分片,其中之一被自动选择去更改索引操作。这种特殊的分片称为主分片(primary shard),其余称为副本分片(replica shard)。在主分片丢失时,例如该分片数据所在服务器不可用,集群将副本提升为新的主分片。
  4.  时光之门:Elasticsearch处理许多节点。集群的状态由时光之门控制。默认情况下,每个节点都在本地存储这些信息,并且在节点中同步。

集群和节点

一个集群是由一个或多个ES节点组成的集合
每一个集群都有一个唯一的名字
每一个节点都是通过集群的名字来加入集群的
每一个节点都有自己的名字
节点能够存储数据,参与集群索引数据以及搜索数据的独立服务

倒排索引

“倒排索引”是Lucene用于使数据可搜索的数据结构。一图胜千言!如下:索引、分片、分段的关系一目了然,一个index多个shard(分片),一个shard多个segment。

translog日志文件

                  为了防止elasticsearch宕机造成数据丢失保证可靠存储,es会将每次写入数据同时写到translog日志中。translog还用于提供实时CRUD。 当您尝试按ID检索,更新或删除文档时,它会首先检查translog中是否有任何最近的更改,然后再尝试从相关段中检索文档。 这意味着它始终可以实时访问最新的已知文档版本。TransLog作为事务日志,记录了所有写入信息。因为Lucene缓存中的数据默认1秒之后才生成segment文件,即使是生成了segment文件,这个segment是写到页面缓存中的,并不是实时的写到磁盘,只有达到一定时间或者达到一定的量才会强制flush磁盘。如果这期间机器宕掉,内存中的数据就丢了。如果发生这种情况,内存中的数据是可以从TransLog中进行恢复的,TransLog默认是每5秒都会刷新一次磁盘。但这依然不能保证数据安全,因为仍然有可能最多丢失TransLog中5秒的数据。这里可以通过配置增加TransLog刷磁盘的频率来增加数据可靠性,最小可配置100ms,但不建议这么做,因为这会对性能有非常大的影响。一般情况下,Elasticsearch是通过副本机制来解决这一问题的。即使主分片所在节点宕机,丢失了5秒数据,依然是可以通过副本来进行恢复的

refresh操作

               相比于Lucene的提交操作,ES的refresh是相对轻量级的操作。先将index-buffer中文档(document)生成的segment写到文件系统之中,这样避免了比较损耗性能io操作,又可以使搜索可见。默认1s钟刷新一次,所以说ES是近实时的搜索引擎,不是准实时。
注意:实际需要结合自己的业务场景设置refresh频率值。调大了会优化索引速度。注意单位:s代表秒级。

PUT /my_logs
{
  "settings": {
    "refresh_interval": "30s" 
  }
}

更新mapping

es中创建后的mapping不可修改,但是可以添加新字段

修改Mapping的字段类型

  • 新增字段
    • dynamic设置为true,一旦有新增字段的文档写入,mapping也同时被更新。
    • dynamic设置为false,mapping不会被更新,新增的字段数据无法被索引,但是信息会出现在source中.
    • dynamic设置为strict,文档写入失败
  • 已有的字段,一旦有数据写入,不支持修改(倒排索引不支持修改)
  • 希望更改字段类型,用Reindex API,重建索引
    设计原因
  • 如果修改字段数据类型,会导致已经被索引的文档不能被搜索。
  • 新增字段不存在影响。

添加新字段:

PUT /my_index/_mapping/my_type
{
  "properties": {
       "new_field_name": {
           "type":     "string"
       }
   }
}

赋值:

POST my_index/_update_by_query
{
  "script": {
    "lang": "painless",
    "inline": "ctx._source.new_field_name= '02'"
  }
}

注意 默认生成的mapping都是下面这种,默认值是text,可以.keyword转化成keyword类型


            "properties": {
               "DstIp": {
                  "type": "text",
                  "fields": {
                     "keyword": {
                        "type": "keyword",
                        "ignore_above": 256
                     }
                  }
               }

pretty

在任意的查询字符串中增加pretty参数,会让Elasticsearch美化输出(pretty-print)JSON响应以便更加容易阅读。
_source字段不会被美化,它的样子与我们输入的一致。
ES数据结构

index定义字段的分析类型以及检索方式

  • 如果是no,则无法通过检索查询到该字段;
  • 如果设置为not_analyzed则会将整个字段存储为关键词,常用于汉字短语、邮箱等复杂的字符串;
  • 如果设置为analyzed则将会通过默认的standard分析器进行分析

store定义了字段是否存储

在ES中原始的文本会存储在_source里面(除非你关闭了它)。默认情况下其他提取出来的字段都不是独立存储的,是从_source里面提取出来的。当然你也可以独立的存储某个字段,只要设置store:true即可。独立存储某个字段,在频繁使用某个特殊字段时很常用。而且获取独立存储的字段要比从_source中解析快得多,而且额外你还需要从_source中解析出来这个字段,尤其是_source特别大的时候。不过需要注意的是,独立存储的字段越多,那么索引就越大;索引越大,索引和检索的过程就会越慢....

string

字符串类型,es中最常用的类类型: 注意:5.X以上版本没有string类型了,换成了text

把string字段设置为了过时字段,引入text,keyword字段,这两个字段都可以存储字符串使用,但建立索引和搜索的时候是不太一样的

keyword:存储数据时候,不会分词建立索引

text:存储数据时候自动分词并生成索引(这是智能的,但在有些字段里面是没用的,所以对于有些字段使用text则浪费了空间)

store存储

true 独立存储 false(默认)不存储,从_source中解析

Numeric

数值类型,注意numeric并不是一个类型,它包括多种类型,比如:long,integer,short,byte,double,float,每种的存储空间都是不一样的,一般默认推荐integer和float。官方文档参考

重要的参数:

index分析    not_analyzed(默认) ,设置为该值可以保证该字段能通过检索查询到

store存储  true 独立存储    false(默认)不存储,从_source中解析

date

日期类型,该类型可以接受一些常见的日期表达方式,官方文档参考

重要的参数:

index分析  not_analyzed(默认) ,设置为该值可以保证该字段能通过检索查询到

store存储  true 独立存储   false(默认)不存储,从_source中解析

format格式化

strict_date_optional_time||epoch_millis(默认)

你也可以自定义格式化内容,比如

"date": {

  "type":   "date",

  "format": "yyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"

}

boolean

布尔类型,所有的类型都可以标识布尔类型,参考官方文档

False: 表示该值的有:false, "false", "off", "no", "0", "" (empty string), 0, 0.0

True: 所有非False的都是true

重要的参数:

index分析 not_analyzed(默认) ,设置为该值可以保证该字段能通过检索查询到 

store存储  true 独立存储  false(默认)不存储,从_source中解析

指令总结

我讲从索引(数据库)→类型(表)→文档(行)→属性(字段)一一介绍相关的指令

请求类型

Http请求内容:

PUT /customer?pretty

GET /_cat/indices?v

Curl命令

curl -XPUT ‘localhost:9200/customer?pretty&pretty’

curl -XGET ‘localhost:9200/_cat/indices?v&pretty’

x_pack加密后 我们可以在命令中添加   --user username:password  指定用户和密码

Curl命令解析

curl -X<VERB> <PROTOCOL>://<HOST>:<PORT>/<PATH>?<QUERY_STRING> -d '<BODY>'
VERB HTTP方法:GET, POST, PUT, HEAD, DELETE
  1. PROTOCOL http或者https协议(只有在Elasticsearch前面有https代理的时候可用)
  2. HOST Elasticsearch集群中的任何一个节点的主机名,如果是在本地的节点,那么就叫localhost
  3. PORT Elasticsearch HTTP服务所在的端口,默认为9200
  4. PATH API路径(例如_count将返回集群中文档的数量),PATH可以包含多个组件,例如_cluster/stats或者_nodes/stats/jvm
  5. QUERY_STRING 一些可选的查询请求参数,例如?pretty参数将使请求返回更加美观易读的JSON数据
  6. BODY 一个JSON格式的请求主体(如果请求需要的话),用-d 'BODY'添加body请求,-d之前,必需要空格。

POST和PUT的区别

POST不用加具体的id,它是作用在一个集合资源之上的(/uri),而PUT操作是作用在一个具体资源之上的(/uri/xxx)。

在ES中,如果不确定document的ID(documents具体含义见下),那么直接使用POST对应uri( “POST /website/blog” ),ES可以自己生成不会发生碰撞的UUID当做ID;如果确定document的ID,即自己设置的ID,使用PUT即可,比如 “PUT /website/blog/123”,那么执行创建或修改(修改时_version版本号提高1) 

1.每个 Index 所包含的 Type

 curl  -H "Content-Type: application/json" 'localhost:9200/_mapping?pretty=true'

2.创建一个索引

curl -XPUT 'localhost:9200/sqlcommand?pretty'

或  5.5 以后尽量使用下面的方式

curl -XPUT -H 'Content-Type: application/json' 'localhost:9200/megacorp?pretty'(5.5以后版本)

返回信息 代表创建成功

{
  "acknowledged" : true,
  "shards_acknowledged" : true
}

3.显示所有索引

curl -XGET -H 'Content-Type: application/json' 'localhost:9200/_cat/indices?v'

4.删除索引

curl -XDELETE 'localhost:9200/sqlcommand?pretty'

5.定义mapping

curl -H "Content-Type: application/json" -PUT  "http://127.0.0.1:9200/sqltest/infotest/_mapping?pretty" -d ' 
{
    "infotest": { 
             "properties": {
                          "application_id": {
                    "type": "keyword"
                },
                           "session_id": {
                    "type": "keyword"
                },
                         "user_ip_address": {
                    "type": "keyword"
                },
                         "logger_type": {
                    "type": "keyword"
                },
                         "mryxblg_command_monitoring": {
                    "type": "keyword"
                }
              }
            }
    }'

es 7.0没有type的写法:

curl -XPOST  -H "Content-Type:application/json" 'http://192.168.21.122:9200/one_meta_test/_mapping?pretty' -d ' 
{
                 
        "properties": {       
          "name":{            
            "type": "keyword"   
          },
          "message":{
            "type": "text"
          },
          "age":{
            "type": "integer"
          }
        }}'

6.导入数据

curl -H "Content-Type: application/json" -XPOST   'http://127.0.0.1:9200/productx/product/1' -d '
{
  "title": "物品",
  "description": "电脑",
  "price": 89.0
  "onSale": true
  "type": 2
  "createDate": 2018
}' 

注意:PUT一定要大写     -d之前,必需要空格

es 7.0没有type的写法:

curl -H "Content-Type: application/json" -XPOST   'http://127.0.0.1:9200/one_datasource_index/_doc/1' -d '
{
  "name": "物品",
  "message": "电脑",
  "age":1
  
}' 

权限验证写入

加个-u即可,比如用户和密码为elastic:elastic

curl -H "Content-Type: application/json" -XPOST  -uelastic:elastic  'http://127.0.0.1:9200/test_index/_doc/12' -d ' 
{
  "content": "test"
}

注意事项:向index添加数据时可以选取部分字段插入,例如只插入name字段

7.字段更新

es 7.0没有type的写法:

curl -H "Content-Type: application/json" -XPOST   'http://127.0.0.1:9200/test_index/_doc/test_id/_update' -d '
{
  "doc": {
    "manager": "xxxxxx
  }
}'

8.查看索引的type

curl -X GET 'localhost:9200/sql_command/_mapping'

获取Mapping和设置

GET /my_index_name/_mapping
GET /my_index_name/_settings

查看索引中分段信息的方法:

GET /test/_segments

设置refresh频率

PUT /my_logs
{
  "settings": {
    "refresh_interval": "30s" 
  }
}

flush操作

       新创建的document数据会先进入到index buffer之后,与此同时会将操作记录在translog之中,当发生refresh时ranslog中的操作记录并不会被清除,而是当数据从filesystem cache中被写入磁盘之后才会将translog中清空。
从filesystem cache写入磁盘的过程就是flush。

  1. 步骤1:当translog变得太大时 ,可以执行commit ponit操作。
  2. 步骤2:使用fsync刷新文件系统缓存,写入磁盘。
  3. 步骤3:旧缓冲区被清除。

flush操作如下:

POST /_flush?wait_for_ongoing 

6.检索文档

普通检索

curl -XGET  -H 'Content-Type: application/json' 'http://localhost:9200/megacorp/employee/1'

轻量检索

curl -XGET  -H 'Content-Type: application/json' 'http://localhost:9200/megacorp/employee/_search'

检索部分内容

curl -XGET  -H 'Content-Type: application/json' 'http://localhost:9200/megacorp/employee/1?_source=first_name,last_name'

只得到源的内容

curl -XGET  -H 'Content-Type: application/json' 'http://localhost:9200/megacorp/employee/1?_source'

按条件搜索

curl -XGET  -H 'Content-Type: application/json' 'http://localhost:9200/megacorp/employee/_search?q=last_name:Smith'

Q =表示匹配全部文档 *   排序=年龄表示按照年龄信息排序 ASC表示升序

注意:这个不是咱一般的准确查询,查出来的是模糊查询,不是title=elasticsearch,而是包含这个词,就返回。 

可以不加参数,查询全部文档

curl -XGET 'localhost:9200/megacorp/_search?pretty'

使用检索表达式

curl -XGET  -H 'Content-Type: application/json' 'http://localhost:9200/megacorp/employee/_search?pretty' -d '
{
    "query" : {
        "match" : {
            "last_name" : "Smith"
        }
    }
}'

复杂检索

curl -XGET  -H 'Content-Type: application/json' 'http://localhost:9200/megacorp/employee/_search?pretty' -d '
{
    "query" : {
        "bool": {
            "must": {
                "match" : {
                    "last_name" : "smith" 
                }
            },
            "filter": {
                "range" : {
                    "age" : { "gt" : 30 } 
                }
            }
        }
    }
}'

批量检索(multi-get或者mget API)

mget API要求有一个docs数组作为参数,每个元素包含需要检索文档的元数据,包括_index,_type和_id。如果你想检索一个或者多个特定的字段,那么你可以通过_source参数来指定这些字段的名字:

curl -XGET -H 'Content-Type: application/json' 'http://localhost:9200/_mget?pretty' -d '
{
   "docs" : [
      {
         "_index" : "megacorp",
         "_type" :  "employee",
         "_id" :    2
      },
      {
         "_index" : "megacorp",
         "_type" :  "employee",
         "_id" :    1
      }
   ]
}'

如果想检索的数据都在相同的_index中(甚至相同的_type中),则可以在URL中指定默认的/ _index或者默认的

文档是否存在

curl  -i -XHEAD  -H 'Content-Type: application/json' 'http://localhost:9200/megacorp/employee/4'

删除文档

curl -XDELETE  -H 'Content-Type: application/json' 'http://localhost:9200/megacorp/employee/4'

成功返回200,找不到是404,版本会加1(即使是虚假),_版本值仍然会增加。这是Elasticsearch内部记录本的一部分,用来确保这些改变在跨多节点时以正确的顺序执行)

查询删除

注意事项:POST请求

curl -H "Content-Type: application/json" -XPOST   'http://127.0.0.1:9200/test/_delete_by_query'  -d'
{
  "query": { 
    "match": {
     "etl_timestamp":"2021-11-18 16:25:49"
    }
  }
}
'

模糊查询 

wildcard执行

GET /sql_command_flink/sqldata/_search
{
  "query": {
    "wildcard": {
      "current_time": {
        "value": "*2019*"
      }
    }
  }
}

regexp 查询

假设您只想匹配以W开头,紧跟着数字的邮政编码。使用regexp查询能够让你写下更复杂的模式: 

GET /my_index/address/_search 
{ 
    "query": { 
        "regexp": { 
            "postcode": "W[0-9].+" 
        } 
    } 
}

这个正则表达式的规定了词条需要以W开头,紧跟着一个0到9的数字,然后是一个或者多个其它字符。

下面例子是所有以 wxopen 开头的正则

{ 
  "query": { 
    "regexp": { 
      "hostname": "wxopen.*" 
    } 
  } 
}

match 查询

match查询是一个标准查询,不管你需要全文本查询还是精确查询基本上都要用到它。如果你使用 match 查询一个全文本字段,它会在真正查询之前用分析器先分析match一下查询字符:

{ 
    "match": { 
        "tweet": "About Search" 
    } 
}

SQL语句

主意:libary是索引名 使用时 其他不用变

curl -X POST "127.0.0.1:9200/_xpack/sql/translate" -H 'Content-Type: application/json' -d'
{
    "query": "SELECT * FROM library ORDER BY page_count DESC",
    "fetch_size": 10
}
'

  • 42
    点赞
  • 82
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿华田512

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值