Sphinx使用及近实时索引实现

Sphinx介绍

  Sphinx是由一个开源的全文检索引擎,功能类似Lucune,用C++编写,可为其他应用提供高速、低空间占用、高结果相关度的全文搜索功能。Sphinx可以非常容易的与SQL数据库和脚本语言集成,当前系统内置MySQL和PostgreSQL数据库数据源的支持,也支持从标准输入读取特定格式的XML数据,通过修改源代码,用户可以自行增加新的数据源(例如:其他类型的DBMS的原生支持)

Sphinx特性

  • 1:Sphinx支持高速建立索引(可达10MB/秒,而Lucene建立索引的速度是1.8MB/秒)
    2:高性能的搜索(在2--4GB的文本数据上,平均每次检索响应时间小于0.1秒)
    3:高扩展性(实测最高可对100GB的文本建立索引,单一索引可包含1亿条记录)
    4:提供了优秀的相关度算法,基于短语相似度和统计(BM25)的复合Ranking方法
    5:支持分布式搜索
    6:支持短语搜索
    7:可作为MySQL的存储引擎提供搜索服务
    8:支持布尔、短语、词语相似度等多种检索模式
    9:文档支持多个全文检索字段


系统组成
整个Sphinx系统由多个可执行程序和一套api组成,这里引用Coreseek(一个基于Sphinx的开源检索引擎,提供了良好的中文支持)的一个结构图做个示例



可执行程序

  • 1:索引建立和维护程序(索引程序indexer)
    2:查询服务程序(后台服务程序searchd)
    3:辅助工具程序(search, spelldump等)

api

  • 1:应用程序api(包括ruby,C/C++, Python, php, java的程序api)
    2:Mysql的SphinxSE引擎接口
    3:SphinxQL支持

工作流程 
安装好Sphinx后,首先需要根据想要检索的场景来建立对应的配置文件,Sphinx是以sphinx.conf为配置文件,索引与搜索均以这个文件为依据进行,要进行全文检索,首先就要配置好sphinx.conf,告诉sphinx哪些字段需要进行索引,哪些字段需要在where,orderby,groupby中用到。
该文件的结构大致如下:
Java代码
  1. <![CDATA[source 源名称1{   
  2. …   
  3. }   
  4. index 索引名称1{   
  5. source=源名称1  
  6. …   
  7. }   
  8. source 源名称2{   
  9. …   
  10. }   
  11. index 索引名称2{   
  12. source = 源名称2  
  13. …   
  14. }   
  15. indexer{   
  16. …   
  17. }   
  18. searchd{   
  19. …   
  20. }   
  21. ]]>  
<![CDATA[source 源名称1{
…
}
index 索引名称1{
source=源名称1
…
}
source 源名称2{
…
}
index 索引名称2{
source = 源名称2
…
}
indexer{
…
}
searchd{
…
}
]]>

从配置文件的组成中我们可以发现Sphinx可以定义多个索引与数据源,不同的索引与数据源可以应用到不同表或不同应用的全文检索方式。
source

以MySQL为例,示范如何配置全量索引的数据源
Java代码
  1. source poi_name   
  2. {   
  3.     type            = mysql          ######数据源类型   
  4.   
  5.     sql_host        = localhost           ######mysql主机   
  6.     sql_user        = root     ######mysql用户名   
  7.     sql_pass        = ************   ######mysql密码   
  8.     sql_db          = ***     ######mysql数据库名   
  9.     sql_port        = 3306           ######mysql端口   
  10.   
  11.     sql_query_pre       = SET NAMES utf8   ###mysql检索编码,特别要注意这点,很多人中文检索不到是数据库的编码是GBK或其他非UTF8   
  12.   
  13.     sql_query       = \   
  14.         SELECT id, poi_name, poi_name as name, branch_name, city_id, district_id, biz_area_id, type_id, level, latitude/1000000 latitude, longitude/1000000 longitude, complain_status, creator_id, create_time, check_status, modify_time, deleted, link_status \   
  15.         FROM poi        ####### 获取数据的sql,这里可以指定条件查询进行过滤   
  16.   
  17.     #####以下是用来过滤或条件查询的属性,这里列出的字段将可以进行条件查询,同时不参与全文检索############   
  18.     sql_attr_uint       = city_id   
  19.     sql_attr_uint       = district_id   
  20.     sql_attr_uint       = biz_area_id   
  21.     sql_attr_uint       = type_id   
  22.     sql_attr_uint       = level   
  23.     sql_attr_uint       = complain_status   
  24.     sql_attr_uint       = creator_id   
  25.     sql_attr_uint       = create_time   
  26.     sql_attr_uint       = check_status   
  27.     sql_attr_uint       = deleted   
  28.     sql_attr_uint       = modify_time   
  29.     sql_attr_uint       = link_status   
  30.     sql_attr_float      = latitude   
  31.     sql_attr_float      = longitude   
  32.     sql_attr_string     = poi_name       ####### poi_name字段将不参与全文检索   
  33. }  
source poi_name
{
    type            = mysql          ######数据源类型

    sql_host        = localhost           ######mysql主机
    sql_user        = root     ######mysql用户名
    sql_pass        = ************   ######mysql密码
    sql_db          = ***     ######mysql数据库名
    sql_port        = 3306           ######mysql端口

    sql_query_pre       = SET NAMES utf8   ###mysql检索编码,特别要注意这点,很多人中文检索不到是数据库的编码是GBK或其他非UTF8

    sql_query       = \
        SELECT id, poi_name, poi_name as name, branch_name, city_id, district_id, biz_area_id, type_id, level, latitude/1000000 latitude, longitude/1000000 longitude, complain_status, creator_id, create_time, check_status, modify_time, deleted, link_status \
        FROM poi        ####### 获取数据的sql,这里可以指定条件查询进行过滤

    #####以下是用来过滤或条件查询的属性,这里列出的字段将可以进行条件查询,同时不参与全文检索############
    sql_attr_uint       = city_id
    sql_attr_uint       = district_id
    sql_attr_uint       = biz_area_id
    sql_attr_uint       = type_id
    sql_attr_uint       = level
    sql_attr_uint       = complain_status
    sql_attr_uint       = creator_id
    sql_attr_uint       = create_time
    sql_attr_uint       = check_status
    sql_attr_uint       = deleted
    sql_attr_uint       = modify_time
    sql_attr_uint       = link_status
    sql_attr_float      = latitude
    sql_attr_float      = longitude
    sql_attr_string     = poi_name       ####### poi_name字段将不参与全文检索
}


  增量索引的配置与之类似,只不过需要根据增量条件对获取数据进行过滤,这里以时间戳为例(也可以通过对id设置更新记录表等其它方式来设置增量条件)
Java代码 复制代码
  1. source poi_name_incr : poi_name   
  2. {   
  3.     sql_query       = \   
  4.         SELECT id, poi_name, poi_name as name, branch_name, city_id, district_id, biz_area_id, type_id, level, latitude/1000000 latitude, longitude/1000000 longitude, complain_status, creator_id, create_time, check_status, modify_time, deleted, link_status \   
  5.         FROM poi where create_time > unix_timestamp() - 360  
  6.     ...   
  7. }  
source poi_name_incr : poi_name
{
    sql_query       = \
        SELECT id, poi_name, poi_name as name, branch_name, city_id, district_id, biz_area_id, type_id, level, latitude/1000000 latitude, longitude/1000000 longitude, complain_status, creator_id, create_time, check_status, modify_time, deleted, link_status \
        FROM poi where create_time > unix_timestamp() - 360
    ...
}

实时索引不需要设置数据源,直接在index里配置为rt即可

index
全量索引的index配置如下,这里没有配置采用外置的分词插件如mmseg等

Java代码
  1. index poi_name   
  2. {   
  3.     source          = poi_name     #### 声明索引数据源   
  4.     path            = /opt/***/mtpoi/indexfiles/poi_name   #######索引文件存放路径   
  5.     docinfo         = extern       #### 文档信息存储方式   
  6.     mlock           = 0            #### 缓存数据内存锁定   
  7.     morphology      = none         #### 形态学(对中文无效)   
  8.     min_word_len        = 1        #### 索引的词最小长度   
  9.     charset_type        = utf-8    #### 数据编码   
  10.     ngram_len       = 1            #### 对于非字母型数据的长度切割   
  11.     ngram_chars     = U+3000..U+2FA1F  #加上这个选项,则会对每个中文,英文字词进行分割   
  12.     charset_table   = 0..9, A..Z->a..z, _, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F    ##### 字符表,如使用这种方式,则Sphinx会对中文进行单字切分   
  13.     html_strip      = 0  
  14. }  
index poi_name
{
    source          = poi_name     #### 声明索引数据源
    path            = /opt/***/mtpoi/indexfiles/poi_name   #######索引文件存放路径
    docinfo         = extern       #### 文档信息存储方式
    mlock           = 0            #### 缓存数据内存锁定
    morphology      = none         #### 形态学(对中文无效)
    min_word_len        = 1        #### 索引的词最小长度
    charset_type        = utf-8    #### 数据编码
    ngram_len       = 1            #### 对于非字母型数据的长度切割
    ngram_chars     = U+3000..U+2FA1F  #加上这个选项,则会对每个中文,英文字词进行分割
    charset_table   = 0..9, A..Z->a..z, _, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F    ##### 字符表,如使用这种方式,则Sphinx会对中文进行单字切分
    html_strip      = 0
}

增量索引的index配置与之类似,只是将数据源及path设置为增量索引的即可

Java代码
  1. index poi_name_incr   
  2. {   
  3.     source          = poi_name_incr   
  4.     path            = /opt/***/mtpoi/indexfiles/poi_name_incr   
  5.   
  6.     ....   
  7. }  
index poi_name_incr
{
    source          = poi_name_incr
    path            = /opt/***/mtpoi/indexfiles/poi_name_incr

    ....
}

实时索引由于不需要设置数据源,配置有些不同
Java代码
  1. index poi_rt   
  2. {   
  3.     type               = rt       #### 声明为实时索引   
  4.     rt_mem_limit       = 512M   
  5.     path               = /opt/***/mtpoi/indexfiles/poi_rt   
  6.     charset_type       = utf-8  
  7.     charset_table        = 0..9, A..Z->a..z, _, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F   
  8.     ngram_chars        = U+3000..U+2FA1F   
  9.   
  10.     #### 实时索引的条件查询字段 ####   
  11.     rt_attr_uint       = city_id   
  12.     rt_attr_uint       = district_id   
  13.     rt_attr_uint       = biz_area_id   
  14.     rt_attr_uint       = type_id   
  15.     rt_attr_uint       = level   
  16.     rt_attr_uint       = complain_status   
  17.     rt_attr_uint       = creator_id   
  18.     rt_attr_uint       = create_time   
  19.     rt_attr_uint       = check_status   
  20.     rt_attr_uint       = deleted   
  21.     rt_attr_uint       = modify_time   
  22.     rt_attr_uint       = link_status   
  23.     rt_attr_float      = latitude   
  24.     rt_attr_float      = longitude   
  25.     rt_attr_string     = poi_name   
  26.     #### 参与全文检索的属性 ####   
  27.     rt_field           = poi_name   
  28.     rt_field           = branch_name   
  29. }  
index poi_rt
{
    type               = rt       #### 声明为实时索引
    rt_mem_limit       = 512M
    path               = /opt/***/mtpoi/indexfiles/poi_rt
    charset_type       = utf-8
    charset_table        = 0..9, A..Z->a..z, _, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F
    ngram_chars        = U+3000..U+2FA1F

    #### 实时索引的条件查询字段 ####
    rt_attr_uint       = city_id
    rt_attr_uint       = district_id
    rt_attr_uint       = biz_area_id
    rt_attr_uint       = type_id
    rt_attr_uint       = level
    rt_attr_uint       = complain_status
    rt_attr_uint       = creator_id
    rt_attr_uint       = create_time
    rt_attr_uint       = check_status
    rt_attr_uint       = deleted
    rt_attr_uint       = modify_time
    rt_attr_uint       = link_status
    rt_attr_float      = latitude
    rt_attr_float      = longitude
    rt_attr_string     = poi_name
    #### 参与全文检索的属性 ####
    rt_field           = poi_name
    rt_field           = branch_name
}


indexer
indexer的配置比较简单,一般来说不需要改动,配置完毕后执行indexer工具重建索引即可

Java代码
  1. # 重建配置里的全部索引,必须关闭searchd   
  2. /usr/local/sphinx-2.1.0/bin/indexer -c /opt/***mtpoi/conf/sphinx.conf.incr --all  
  3. # 重建部分索引(poi_name_incr),可指定多个  
  4. /usr/local/sphinx-2.1.0/bin/indexer -c /opt/***/mtpoi/conf/sphinx.conf.incr poi_name_incr   
  5. # searchd运行过程中更新索引,添加--ratate参数   
  6. /usr/local/sphinx-2.1.0/bin/indexer -c /opt/***/mtpoi/conf/sphinx.conf.incr --rotate poi_name  
# 重建配置里的全部索引,必须关闭searchd
/usr/local/sphinx-2.1.0/bin/indexer -c /opt/***mtpoi/conf/sphinx.conf.incr --all
# 重建部分索引(poi_name_incr),可指定多个
/usr/local/sphinx-2.1.0/bin/indexer -c /opt/***/mtpoi/conf/sphinx.conf.incr poi_name_incr
# searchd运行过程中更新索引,添加--ratate参数
/usr/local/sphinx-2.1.0/bin/indexer -c /opt/***/mtpoi/conf/sphinx.conf.incr --rotate poi_name


searchd
searchd的配置项里最主要的是监听端口
Java代码
  1. searchd   
  2. {   
  3.     listen          = 9346           # 监听端口,api访问端口   
  4.     listen          = 9340:mysql41   # SphinxQL访问端口   
  5.     log             = /var/sankuai/logs/sphinx_poi_incr/sphinx-searchd.log   
  6.     query_log       = /var/sankuai/logs/sphinx_poi_incr/sphinx-query.log   
  7.     max_matches     = 10000          # 最大匹配结果,在某些情况下该数值会导致查询不到结果,比如有设置分页项时想获取1w条之后的记录   
  8.     query_log_format   = sphinxql    # 日志查询格式化,plain为简单文本格式,这里采用sphinxql以获取更丰富的查询信息   
  9.     mysql_version_string   = 5.5.21  # 返回给通过SphinxQL访问的MySQL版本号,目前采用的mysql-connector-java-5.1.15需要设置该值,否则连接时会报错   
  10.     ....   
  11. }  
searchd
{
    listen          = 9346           # 监听端口,api访问端口
    listen          = 9340:mysql41   # SphinxQL访问端口
    log             = /var/sankuai/logs/sphinx_poi_incr/sphinx-searchd.log
    query_log       = /var/sankuai/logs/sphinx_poi_incr/sphinx-query.log
    max_matches     = 10000          # 最大匹配结果,在某些情况下该数值会导致查询不到结果,比如有设置分页项时想获取1w条之后的记录
    query_log_format   = sphinxql    # 日志查询格式化,plain为简单文本格式,这里采用sphinxql以获取更丰富的查询信息
    mysql_version_string   = 5.5.21  # 返回给通过SphinxQL访问的MySQL版本号,目前采用的mysql-connector-java-5.1.15需要设置该值,否则连接时会报错
    ....
}


执行indexer建好索引后,直接启动searchd即可启用Sphinx查询服务
/usr/local/sphinx-2.1.0/bin/searchd -c /opt/***/mtpoi/conf/sphinx.conf.incr
然后通过crontab等方式调用indexer来更新索引文件

SphinxQL
   Sphinx的searchd守护程序从版本0.9.9-rc2开始支持MySQL二进制网络协议,并且能够通过标准的MySQL API访问

Java代码
  1. $ mysql -P 9306  
  2. Welcome to the MySQL monitor.  Commands end with ; or \g.   
  3. Your MySQL connection id is 1  
  4. Server version: 0.9.9-dev (r1734)   
  5.   
  6. Type 'help;' or '\h' for help. Type '\c' to clear the buffer.   
  7.   
  8. mysql>  
$ mysql -P 9306
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 0.9.9-dev (r1734)

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql>


    新的访问方法是对原生API的一种补充,原生API仍然可用。事实上,两种访问方法可以同时使用。另外,原生API仍旧是默认的访问方法。MySQL协议支持需要经过额外的配置才能启用。当然这只需要更动一行配置文件,加入一个协议为mysql41的监听器(listener)就可以了:
listen = 9340:mysql41 # SphinxQL访问端口

分布式索引
除了实时索引之外,Sphinx还支持一种特殊的索引方式------分布式索引,分布式检索可以改善查询延迟问题(即缩短查询时间)和提高多服务器、多CPU或多核环境下的吞吐率(即每秒可以完成的查询数)。这对于大量数据(即十亿级的记录数和TB级的文本量)上的搜索应用来说是很关键的。其关键思想是对数据进行水平分区(HP,Horizontally partition),然后并行处理:

  • 1:在不同服务器上设置Sphinx程序集(indexer和searchd)的多个实例
    2:让这些实例对数据的不同部分做索引(并检索)
    3:在searchd的一些实例上配置一个特殊的分布式索引然后对这个索引进行查询


这个特殊索引只包括对其他本地或远程索引的引用,因此不能对它执行重新建立索引的操作,相反,如果要对这个特殊索引进行重建,要重建的是那些被这个索引被引用到的索引。
当searchd收到一个对分布式索引的查询时,它做如下操作:


  • 1:连接到远程代理
    2:执行查询 :#(在远程代理执行搜索的同时)对本地索引进行查询;
    3:接收来自远程代理的搜索结果
    4:将所有结果合并,删除重复项
    5:将合并后的结果返回给客户端


在应用程序看来,普通索引和分布式索引完全没有区别。也就是说,分布式索引对应用程序而言是完全透明的,实际上也无需知道查询使用的索引是分布式的还是本地的。<br/> 任一个searchd实例可以同时做为主控端(master,对搜索结果做聚合)和从属端(只做本地搜索)。这有如下几点好处:


  • 1: 集群中的每台机器都可以做为主控端来搜索整个集群,搜索请求可以在主控端之间获得负载平衡,相当于实现了一种HA(high availability,高可用性),可以应对某个节点失效的情况.
    2: 如果在单台多CPU或多核机器上使用,一个做为代理对本机进行搜索的searchd实例就可以利用到全部的CPU或者核

这里采用前述配置的几种索引做一个简单的分布式索引配置示例
Java代码
  1. index poi_dist   
  2. {   
  3.     type        =   distributed            #### 设置为分布式搜索   
  4.     local       =   poi_name               #### 设置查询本地全量索引   
  5.     local       =   poi_name_incr          #### 设置查询本地增量索引   
  6.     local       =   poi_rt                 #### 设置查询本地实时索引   
  7.     agent       =   srv24:9340:poi_name    #### 也可以通过agent来进行查询远程全量索引   
  8. }  
index poi_dist
{
    type        =   distributed            #### 设置为分布式搜索
    local       =   poi_name               #### 设置查询本地全量索引
    local       =   poi_name_incr          #### 设置查询本地增量索引
    local       =   poi_rt                 #### 设置查询本地实时索引
    agent       =   srv24:9340:poi_name    #### 也可以通过agent来进行查询远程全量索引
}

更详细的分布式搜索的相关配置参数比如超时等参考官方文档。


近实时索引实现
在商家数据中心的使用场景中,目前存在一些对实时性要求比较高的检索需求,比如在CRM系统里,对商家的审核状态进行审核(0-&gt;1)后,页面会自动刷新,此时会根据审核状态(1)进行查询,如果实时性不够的话此时会查询不到该数据,而且使用原状态(0)进行查询的时候,依然能查询到,这就要求目前的Sphinx查询能够尽可能的支持实时检索。

rt
如前面介绍,现有的Sphinx是有实时索引这种类型的,但据一些文章说其在大数据量的情况下性能不太好,另外,其初始时是没有数据的,而现有的数据库里已经有大约100w+的数据需要索引,全部采用rt索引看来不是一个好选择
参考:


全量+增量
结合目前MDC中商家数据的实际情况(新增,更新相对较少),可以采用对稳定数据采用全量索引,对发生更新的数据采用增量索引,然后利用distributed的特性来合并查询

Java代码
  1. index poi_dist   
  2. {   
  3.     type        =   distributed            #### 设置为分布式搜索   
  4.     local       =   poi_name               #### 设置查询本地全量索引   
  5.     local       =   poi_name_incr          #### 设置查询本地增量索引   
  6. }  
index poi_dist
{
    type        =   distributed            #### 设置为分布式搜索
    local       =   poi_name               #### 设置查询本地全量索引
    local       =   poi_name_incr          #### 设置查询本地增量索引
}

增量索引由于数据量少,每次重建索引时耗时不到1s,可以做到5-10s左右更新一次,然后与全量索引进行merge,把增量索引更新到进来。
Java代码
  1. indexer --merge DSTINDEX SRCINDEX --rotate  
indexer --merge DSTINDEX SRCINDEX --rotate

这种方案依然存在一些问题:

  •     1:由于索引合并的间隔问题,如果一条记录被修改了,在还没有执行增量索引合并前,全量索引里依然是修改前的值,而增量索引已更新为修改后的值,这样在通过distributed来进行查询时合并后的结果集可能并不符合预期(有可能查询到修改前的记录)。对这个问题,可以采用API里提供的updateAttributes方法来实时更新索引的值,但Java版本的API目前仅支持对int,long类型的属性进行实时更新;当然也可以采用SphinxQL来进行属性的更新,其依然存在不支持非int,long类型的属性即时更新问题,但由于增量索引可以重建的比较频繁,在索引重建时会将这些非int,long类型的属性修改进行更新,这样对这些属性的索引大约存在5-10s左右的延迟,对int,long属性的修改可以即时索引进来.
         2:由于其不支持即时新增索引项,只能等待增量索引重建时进行更新,所以其对新增记录也存在5-10s左右的延迟


全量+rt+SphinxQL
和全量+增量的方式类似,只不过将增量索引换成直接使用rt索引,然后类似进行merge合并,其好处是可以即时将新增或修改的记录反映到索引中(这里对新增索引必须采用SphinxQL,目前Java版本的API不支持新增索引记录),但对于非int,long类型的属性修改依然没有什么好办法,只能等待执行索引更新时进行更新,但全量索引的更新相对周期比较长,所以相对延迟会比较大。

与Lucene的简单对比

  • 对Lucene暂时接触不深,简单对比一下:
    1:   Sphinx建索引速度非常的快;Lucene建索引相比Sphinx要差很多,同样建1000w数据,Sphinx2分钟以内,Lucene10分钟多,不过搜索性能上相差不太大
    2:   Sphinx的索引结构必须提前预定义好;Lucene的索引结构是比较自由的
    3:   Sphinx查询中Attribute(属性)的概念,而且Sphinx在启动Searchd的时候会将所有属性加载到内存中;而Lucene则没有,虽然Lucene也有NumericField,但是底层仍然是作为String处理的。这点可能会导致Sphinx比Lucene查询性能上好一些
    4:   Lucene用Java,代码阅读上相对容易
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值