mongodb基本操作

mongodb是一种非关系型数据库。所谓 NoSQL ,并不是指没有 SQL ,而是指“Not Only SQL ”,即非传统关系型数据库。这类数库的主要特点包括非关系型、水平可扩展、分布式与开源。另外它还具有模式自由、最终一致性。

mongodb特点

1、数据文件存储格式为 BSON (JSON 的扩展)。{“name”:“joe”}这是 BSON 的例子,其中"name"是键,"joe"是值。键值对组成了 BSON 格式。
2、面向集合存储,易于存储对象类型和 JSON 形式的数据。所谓集合(collection)有点类似一张表格,区别在于集合没有固定的表头。
3、模式自由。一个集合中可以存储一个键值对的文档,也可以存储多个键值对的文档,还可以存储键不一样的文档,而且在生产环境下可以轻松增减字段而不影响现有程序的运行。
4、支持动态查询。mongodb 支持丰富的查询表达式,查询语句使用 JSON 形式作为参数,可以很方便地查询内嵌文档和对象数组。
5、完整的索引支持。文档内嵌对象和数组都可以创建索引。
6、支持复制和故障恢复。mongodb数据库从节点可以复制主节点的数据,主节点所有对数据的操作都会同步到从节点,从节点的数据和主节点的数据是完全一样的,以作备份。当主节点发生故障之后,从节点可以升级为主节点,也可以通过从节点对故障的主节点进行数据恢复。
7、二进制数据存储。mongodb使用传统高效的二进制数据存储方式,可以将图片文件甚至视频转换成二进制的数据存储到数据库中。
8、自动分片。自动分片功能支持水平的数据库集群,可动态添加机器。分片的功能实现海量数据的分布式存
9、支持多种语言。支持 C++、 C#、 Erlang、Haskell、JavaScript、Java、Perl、PHP、Python、Ruby、Scala 等开发语言。
10、mongodb使用的是内存映射存储引擎。mongodb会把磁盘 IO 操作转换成内存操作,如果是读操作,内存中的数据起到缓存的作用;如果是写操作,内存还可以把随机的写操作转换成顺序的写操作,总之可以大幅度提升性能。这也是mongodb快的原因。坏处是没有办法很方便地控制 mongodb占多大内存,mongodb会占用所有能用的内存,所以最好不要把别的服务和mongodb放在同一台服务器部署。
这里再提示一下,批量数据处理中,数据库的读写吞吐量与实时性大概是负相关的。
这里注意一下与redis的区别。redis存储的是键值对,本身没有办法分析数据;mongodb相当于一个大数组,本身提供了一些处理功能,如后面提到的聚合。很多复杂的查询只能通过聚合实现,这样的话redis就做不了了。

应用

mongodb适用于以下场景,只要有一项需求满足就可以考虑。匹配越多,就越可以考虑使用mongodb。
1、网站数据
mongodb非常适合实时地插入、更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。如果你正考虑搭建一个网站,可以考虑使用 mongodb,你会发现它非常适用于迭代更新快、需求变更多、以对象数据为主的网站应用。
2、缓存
由于 mongodb 是内存+硬盘型数据库,性能很高,mongodb 也适合作为信息基础设施的缓存层。在系统重启后,由 mongodb 搭建的持久化缓存可以避免下层的数据过载。
3、大尺寸、低价值的数据
使用传统的关系型数据库存储一些数据会超级麻烦,首先要创建表,再设计数据表结构,进行数据清理,得到有用的数据,按格式存入表中,而 mongodb 可以随意构建一个 json 格式的文档就能把它先保存起来,留着以后处理。另外,对于较大的数据,比如要存储 TB 甚至 PB 级别的数据。mongodb扩展起来也非常方便。
4、高伸缩性的场景
如果网站的数据量非常大,很快就会超过一台服务器能够承受的范围,那么 mongodb 可以胜任网站对数据库的需求,mongodb 可以轻松地自动分片到数十甚至数百台服务器。
5、用于对象及json数据的存储
mongodb 的 BSON 数据格式非常适合文档格式化的存储及查询。
6、不需要事务及复杂 join 支持。
7、新应用,需求会变,数据模型不确定,想快速迭代开发。
8、要应对 2000~3000 以上的的读写 QPS(或更高)。
当然,mongodb也有不使用的场景,主要有:
1、高度事务性的系统
传统的关系型数据库还是更适用于大量原子性复杂事务的应用程序,例如银行或会计系统。支持事务的传统关系型数据库,当原子性操作失败时数据能够回滚,以保证数据在操作过程中的准确性,而且目前mongodb 不支持此事务。所以,mongodb对钱对数字非常敏感的业务是不适合的。
2、传统的商业智能应用
3、数据结构相对固定等使用 SQL 方便时。
mongodb的查询方式是json类型的查询方式,虽然查询也比较灵活,但如果使用 SQL 进行统计会比较方便时,这种情况就不适合使用 mongodb。

使用方式

1、单机部署
在这里插入图片描述
主要用于开发测试,或者只是存一些日志。
2、复制集集群
在这里插入图片描述
这是线上使用主要的方式。官方统计百分之七八十的线上项目采用复制集的方式进行部署。
3、分片集群
在这里插入图片描述
10%左右采用分片集群的方式部署。主要是在用户量大,对数据库安全性、稳定性要求高的场景。

工作原理

mongodb 存取读写速度快,甚至可以用来当作缓存数据库。但是在使用过程中会发现 mongodb 服务非常占内存,几乎是服务器有多少内存就会占用多少内存。为什么会出现这种情况呢?我们要从 mongodb 的读写工作流程和对内存的使用方式说起 。
mongodb 在存取工作流程上有一个非常酷的设计决策,mongodb 的所有数据实际上是存放在硬盘的,然后把部分或者全部要操作的数据通过内存映射存储引擎映射到内存中。 即是:虚拟内存+持久化的存储方式。
在这里插入图片描述
如果是读操作,直接从内存中取数据,如果是写操作,就会修改内存中对应的数据,然后就不需要管了,会有相应的写盘策略。
mongodb 的存取工作流程区别于一般硬盘数据库在于两点:
读:一般硬盘数据库在需要数据时才去硬盘中读取请求数据, mongodb 则是尽可能地放入内存中。 这也是mongodb占内存的原因。
写:一般硬盘数据库在有数据需要修改时会马上写入刷新到硬盘,但是mongodb 只是修改内存中的数据就不管了,写入的数据会排队等待操作系统的定时刷新保存到硬盘。
mongodb 的设计思路有两个好处:
1、将什么时候调用 IO 操作写入硬盘这样的内存管理工作交给操作系统的虚拟内存管理器来完成,大大简化了 mongodb 的工作。
2、把随机的写操作转换成顺序的写操作,顺其自然地写入,而不是有数据修改就调 IO 操作去写入,这样减少了 IO 操作,避免了零碎的硬盘操作,大幅度提升性能。
但是这样的设计思路也有坏处:如果 mongodb 在内存中修改了数据,在数据刷新到硬盘之前,停电了或者系统宕机了,就会丢失数据了。
针对这样的问题, mongodb 设计了 Journal 模式, Journal 是服务器意外宕机的情况下,将数据库操作进行重演的日志。如果打开 Journal ,默认情况下100 毫秒(这是在数据文件和 Journal 文件处于同磁盘卷上的情况,而如果数据文件和 Journal 文件不在同磁盘卷上时,默认刷新输出时间是 30 毫秒)往 Journal 文件中 flush 一次数据,那么即使断电也只会丢失 100ms 的数据,这对大多数应用来说都可以容忍了。从版 1.9.2+, mongodb 默认打开 Journal 功能,以确保数据安全。而且 Journal 的刷新时间是可以改变的,使用–journalCommitInterval 命令修改,范围是2~300ms 值越低,刷新输出频率越高,数据安全度也就越高,但磁盘性能上的开销也更高。mongodb 存取工作流程的实现关键在于通过内存映射存储引擎把数据映射到内存中。

存储引擎

mongodb 目前支持的有 MMAP,MMAPV1,WiredTiger 以及 In-Memory 存储引擎,

MMAPV1

在3.2 之前默认开启,使用B树数据结构。
1、采用 Linux 操作系统内存映射技术,所以不支持压缩
2、会尽可能多的使用内存,默认会使用所有的空闲内存,但当别的进程需要使用的时候会释放部分内存
3、采用集合级锁,导致并发吞吐量较低
4、善于处理大量插入,更新,删除的操作

WiredTiger

在3.2 之后默认开启,使用B+树数据结构。
1、采用文档型锁,粒度较低,吞吐量相对高许多
2、支持数据压缩。相比 MMAPV1 存储索引时 WiredTiger 使用前缀压缩,更节省对内存空间的损耗。提供压缩算法,可以大大降低对硬盘资源的消耗,节省约 60%以上的硬盘资源。支持 snappy(默认)和 zlib 两种压缩模式
3、默认每分钟一次 checkpoint,及数据持久化
4、内存使用。从 mongodb 3.4 开始,默认的 WiredTiger 内部缓存大小是以下两者中的较大者:50%*(RAM-1 GB)或 256 MB。

写入流程

1、wiredTiger 写操作先到 cache,并持久化 WAL
2、每 60s 或 log 文件达到 2G,则执行一次 Checkpoint 持久化,产生一个新快照。
3、wiredTiger 连接初始化时,先将数据恢复到最新的快照,再根据 WAL 恢复数据。

Checkpoint

每次 Checkpoint 的过程:
1、对所有的 table 进行一次 Checkpoint,将每个 table 的 Checkpoint 的元数据更新到 WiredTiger.wt
2、再对 WiredTiger.wt 进行 Checkpoint,将 Checkpoint 的 meta 数据更新到 WiredTiger.turtle.set
3、将 WiredTiger.turtle.set 重命名为 WiredTiger.turtle

WiredTiger文件

WiredTiger.basecfg :存储基本配置信息
WiredTiger.lock 用于防止多个进程连接同一个 WiredTiger 数据库table*.wt 存储各个 table 的数据
WiredTiger.wt:是特殊的 table,用于存储所有其他 table 的 meta 数据信息
WiredTiger.turtle:存储 WireTiger.wt 的 meta 数据信息

安装

准备

这里只讲linux系统下的安装。
1、在官网https://www.mongodb.com/try/download/community中选择mongodb版本和自己对应的系统的安装包,这里选择tgz包。
2、将对应包解压并放到/usr/local目录下。
3、添加环境变量,方便执行
vim /etc/profile
在/etc/profile 最后一行,添加export PATH=/usr/local/mongodb/bin:$PATH
最后,在命令行执行source /etc/profile使文件生效。

启动

执行命令

mongod --dbpath=/home/ss/data/db --logpath /home/ss/data/db/mongod.log --bind_ip=0.0.0.0 --fork

这里解释一下fork。默认情况下,mongod是前台启动,加上fork之后,相当于后台启动,也可以在日志路径后面添加–logappend,防止日志被删除。此外,还可以不在命令行设置参数,使用配置文件

dbpath=/home/ss/data/db
logpath=/home/ss/data/db/mongod.log
logAppend=true
port=27017
bindIpAll=true
fork=true

执行命令

mongod --config=/home/ss/data/mogod.conf

使用mongo 命令,连接到mongodb 库,相当于mysql命令。mongo是 mongodb 自带的交互式 Javascript shell,用来对 mongodb 进行操作和管理的交互式环境。

mongodb层级

在这里插入图片描述
实例:系统上运行的进程及节点集,一个实例可以有多个库,默认端口 27017。如果要在一台机器上启动多个实例,需要设置不同端口和不同的dbpath。
库:多个集合组成数据库,每个数据库都是独立的,有自己的用户、权限信息,独立的存储文件集合。
集合:即是一组文档的集合,集合内的文档结构可以不同,相当于mysql中的表。
文档:mongodb 的最小数据单元,其基本概念为:多个键值对有序组合在一起的数据单元。相当于mysql中的行。

数据库

一个 mongodb 中可以建立多个数据库。mongodb 的默认数据库为"db",该数据库存储在 data 目录中。mongodb 的单个实例可以容纳多个独立的数据库,每一个都有自己的集合和权限,不同的数据库也放置在不同的文件中。
“show dbs” 命令可以显示所有数据的列表。注意如果某个数据库没有数据,将不会显示。
数据库也通过名字来标识。数据库名可以是满足以下条件的任意 UTF-8 字符串。

  • 不能是空字符串("")。
  • 不得含有’ '(空格)、.、$、/、\和\0 (空字符)。
  • 应全部小写。
  • 最多 64 字节。

有一些数据库名是保留的,可以直接访问这些有特殊作用的数据库。
admin: 从权限的角度来看,这是"root"数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。
local: 这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合
config: 当 mongodb 用于分片设置时,config 数据库在内部使用,用于保存分片的相关信息。

集合

集合就是 mongodb 文档组,类似于关系数据库中的表格。集合存在于数据库中,集合没有固定的结构,这意味着可以在对集合可以插入不同格式和类型的数据,但通常情况下我们插入集合的数据都会有一定的关联性。
当第一个文档插入时,集合就会被创建。合法的集合名

  • 集合名不能是空字符串""。
  • 集合名不能含有\0 字符(空字符),这个字符表示集合名的结尾。
  • 集合名不能以"system."开头,这是为系统集合保留的前缀。
  • 用户创建的集合名字不能含有保留字符。

固定集合

固定集合Capped collections 就是固定大小的 collection。它有很高的性能以及队列过期的特性(过期按照插入的顺序),有点和 “RRD” 概念类似。写入速度提升。
与普通集合相比,固定集合有两个优点:一是固定集合中的数据被顺序写入磁盘上的固定空间,所以,不会因为其他集合的一些随机性的写操作而“中断”,其写入速度非常快(不建立索引,性能更好);二是固定集合会自动覆盖掉最老的文档,因此不需要再配置额外的工作来进行旧文档删除。
创建时必须要显式的创建一个 capped collection,指定一个 collection 的大小,单位是字节。collection 的数据存储空间值提前分配的。Capped collections 可以按照文档的插入顺序保存到集合中,而且这些文档在磁盘上存放位置也是按照插入顺序来保存的,所以当我们更新 Capped collections 中文档的时候,更新后的文档不可以超过之前文档的大小,这样话就可以确保所有文档在磁盘上的位置一直保持不变。

db.createCollection("mycoll", {capped:true, size:100000,max:1000})

固定集合有几个特点:
1、能进行更新,然而,对象不会增加存储空间。如果增加,更新就会失败 。
2、使用 Capped Collection 不能删除一个文档,可以使用 drop() 方法删除 collection 所有的行。
3、删除之后,你必须显式的重新创建这个 collection。
4、在 32bit 机器中,capped collection 最大存储为 1 ∗ 1 0 9 1*10^{9} 1109个字节。
基于以上特点,固定集合可以用到比如日志文件,聊天记录,通话信息记录等只需保留最近某段时间内的应用场景。

文档

文档是一组键值(key-value)对(即 BSON)。mongodb 的文档不需要设置相同的字段,并且相同的字段不需要相同的数据类型,这与关系型数据库有很大的区别,也是 mongodb 非常突出的特点。
文档键命名规范:

  • 键不能含有\0 (空字符)。这个字符用来表示键的结尾。
  • .和$有特别的意义,只有在特定环境下才能使用。
  • 以下划线"_"开头的键是保留的(不是严格要求的)。

需要注意的几点:
1、文档中的键/值对是有序的。
2、文档中的值不仅可以是在双引号里面的字符串,还可以是其他几种数据类型(甚至可以是整个嵌入的文档)。
3、mongodb 区分类型和大小写。
4、mongodb 的文档不能有重复的键。
5、文档的键是字符串。除了少数例外情况,键可以使用任意 UTF-8 字符。
6、文档对应于许多编程语言中的本机数据类型

数据结构

下面介绍mongodb中部分数据结构。

ObjectId

ObjectId 类似唯一主键,可以很快的去生成和排序,包含 12 bytes,含义是:

  • 前 4 个字节表示创建 unix 时间戳,格林尼治时间 UTC 时间,比北京时间晚了 8 个小时
  • 接下来的 3 个字节是机器标识码
  • 紧接的两个字节由进程 id 组成 PID
  • 最后三个字节是随机数

在这里插入图片描述
mongodb 中存储的文档必须有一个 _id 键。这个键的值可以是任何类型的,默认是个 ObjectId 对象,也可以自己设置。由于 ObjectId 中保存了创建的时间戳,所以你不需要为你的文档保存时间戳字段,你可以通过getTimestamp 函数来获取文档的创建时间。

> var newObject = ObjectId()
> newObject.getTimestamp()
ISODate("2019-09-17T04:42:12Z")

ObjectId 转为字符串

> newObject.str
5d80642415db2f66a53467a4

字符串

BSON 字符串都是 UTF-8 编码。

日期

表示当前距离 Unix 新纪元(1970 年 1 月 1 日)的毫秒数。日期类型是有符号的, 负数表示 1970 年之前的日期。

> var date1 = new Date() //格林尼治时间
> date1
ISODate("2019-09-17T04:47:39.919Z")
> typeof mydate1
object
# 这样创建的时间是日期类型,可以使用 JS 中的 Date 类型的方法。返回一个时间类型的字符串
> var date1str = date1.toString()
> date1str
Tue Sep 17 2019 12:47:39 GMT+0800 (CST)
> typeof date1str
string

mongodb基本操作

mongodb数据库操作

创建数据库:如果数据库不存在,则创建数据库,否则切换到指定数据库。

use DATABASE_NAME

mongodb 中默认的数据库为 test,如果没有创建新的数据库,集合将存放在 test 数据库中。
查看所有数据库,可以使用 show dbs 命令。
删除数据库:

db.dropDatabase()

删除当前数据库,默认test,注意要先切换到待删除数据库。

mongodb集合操作

创建集合:

db.createCollection(name, options)

参数options,基本是用于创建固定集合,具体格式前面已说明。
如果要查看已有集合,可以使用 show collectionsshow tables 命令。
当插入一些文档时,如果指定集合不存在,mongodb 会自动创建集合。

> db.col2.insert({"name": "ss"})
WriteResult({ "nInserted" : 1 })
> show tables
col  # 之前已创建
col2

删除集合:同样需要先切换到该集合

db.collection.drop()

重命名集合:

db.collection1.renameCollection( " collection2" )

mongodb文档操作

插入文档

db.collection.insertOne()   # 将单个文档插入到集合中
db.collection.insertMany()  #将多个文件插入集合中
db.collection.insert()       #将单个文档或多个文档插入到集合中

我们也可以将数据定义为一个变量,如下所示:

> document=({title:'mongodb', description: 'DB', by: 'ss', tags: ['mongodb', 'database', 'NoSQL'], likes: 100})
{
    "title" : "mongodb",
    "description" : "DB",
    "by" : "ss",
"tags" : [
        "mongodb",
    "database",
        "NoSQL"
    ],
    "likes" : 100
}
> db.col.insert(document)
WriteResult({ "nInserted" : 1 })

查看已插入文档

db.col.find()

更新文档

db.collection.updateOne(<filter>, <update>, <options>)
# 更新数量小于 50 的所有文档
db.collection.updateMany(<filter>, <update>, <options>)
# 替换_id 字段以外的文档的全部内容
db.collection.replaceOne(<filter>, <update>, <options>)

查询文档

db.collection.find(query, projection)

参数query :可选,使用查询操作符指定查询条件
参数projection :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。
如果需要以易读的方式来读取数据,可以使用 pretty() 方法。

db.col.find().pretty()

pretty() 方法以格式化的方法输出,就是加了缩进和换行。
我们还可以使用 limit()方法来读取指定数量的数据,使用 skip()方法来跳过指定数量的数
据,分别接受一个数字参数作为跳过的记录条数。

db.COLLECTION_NAME.find().limit(NUMBER).skip(NUMBER)

此外,我们还可以使用 sort() 方法对数据进行排序,sort() 方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而 -1 是用于降序排列。

db.COLLECTION_NAME.find().sort({KEY:1})

除了 find() 方法之外,还有一个 findOne() 方法,它只返回一个文档。这与insert和insertone类似。

删除文档

在执行 remove()函数前先执行 find()命令来判断执行的条件是否正确,这是一个比较好的习惯。

db.collection.remove(
    <query>,
    {
        justOne: <boolean>,
        writeConcern: <document>
    }
)

参数query :(可选)删除的文档的条件。
justOne : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值 false,则删除所有匹配条件的文档。
writeConcern :(可选)抛出异常的级别。
此外,也有删除一条与多条数据的命令。

db.collection.deleteMany()
db.collection.deleteOne()

关于删除操作,还有两点需要注意:
1、即使从集合中删除所有文档,删除操作也不会删除索引。
2、mongodb 中的所有写操作都是单个文档级别的原子操作。

mongodb索引

索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构。

创建索引

db.collection.createIndex(keys, options)

key 值为你要创建的索引字段,1 为指定按升序创建索引,如果你想按降序来创建索引指定为 -1 即可。mongodb也可以设置使用多个字段创建索引(关系型数据库中称作复合索引)。

db.col.createIndex({"title":1,"description":-1})

查询索引

db.collection.getIndexes()

删除索引

db.values.dropIndex()

dropIndexes 方法将删除除_id 索引以外的所有索引。如果要删除某个指定索引

db.values.dropIndexes({open:1, close:1})  #删除组合索引

mongodb聚合

mongodb 聚合框架(Aggregation Framework)是一个计算框架,它可以:

  • 作用在一个或几个集合上;
  • 对集合中的数据进行的一系列运算;
  • 将这些数据转化为期望的形式;

整个聚合运算过程称为管道(Pipeline),它是由多个步骤(Stage)组成的,每个管道接受一系列文档(原始数据),每个步骤对这些文档进行一系列运算,再将结果文档输出给下一个步骤。
在这里插入图片描述

aggregate() 方法

mongodb 中聚合的方法使用 aggregate()。aggregate() 方法的基本语法格式如下所示:

db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)

aggregate() 方法与find()主要区别在于,find()不能完成复杂运算。
关于管道、运算符等,不再介绍,这里给出官方给出的例子,如果需要进行其他操作,自行查询。
在这里插入图片描述
这里提醒一下,$后面如果接的是自己定义的变量,需要加双引号或单引号;如果接的是内置操作,不需要加引号。

mapreduce方法

除aggregate外,mongodb还有一个实现聚合的方法。
mapreduce将大量的数据处理工作拆分成多个线程进行并行处理,然后将结果合并在一起,对于大规模数据处理非常实用。
mapreduce具有两个阶段:
1、将具有相同key的文档聚合在一起的map阶段
2、组合map操作结果进行统计输出的reduce阶段。

db.collection.mapReduce(
   function() {emit(key,value);},  //map 函数
   function(key,values) {return reduceFunction},   //reduce 函数
   {
      out: collection,
      query: document,
      sort: document,
      limit: number
   }
)

各个参数:
map :映射函数 (生成键值对序列,作为 reduce 函数参数)。
reduce :统计函数,reduce函数的任务就是将key-values变成key-value,也就是把values数组变成一个单一的值value。
out: 统计结果存放集合 (不指定则使用临时集合,在客户端断开后自动删除)。
query :一个筛选条件,只有满足条件的文档才会调用map函数。(query。limit,sort可以随意组合)
sort :和limit结合的sort排序参数(也是在发往map函数前给文档排序),可以优化分组机制
limit :发往map函数的文档数量的上限(要是没有limit,单独使用sort的用处不大)
还可以有其他参数,这里不仔细介绍了。
还是那个官方例子
在这里插入图片描述

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值