Oracle的SCN

一、检查点概述
    大多数关系型数据库都采用 在提交时并不强迫针对数据块的修改完成 而是 提交时保证修改记录(以重做日志的形式)写入日志文件 的机制,来获得性能的优势。这句话的另外一种描述是:当用户提交事务,写 数据文件 异步 的,写日志文件是 同步 的。这就可能导致数据库实例崩溃时,内存中的 DB_Buffer 中的修改过的数据,可能没有写入到数据块中。数据库在重新打开时,需要进行恢复,来恢复 DB Buffer 中的数据状态,并确保已经提交的数据被写入到数据块中。检查点是这个过程中的重要机制,通过它来确定,恢复时哪些重做日志应该被扫描并应用于恢复。


要了解这个检查点,首先要知道checkpoint queu概念,检查点发生后,触发dbwnCKPT获取发生检查点时对应的SCN,通知DBWn要写到这个SCN为止,dbwrdirty buffer是根据buffer在被首次modify的时候的时间的顺序写出,也就是buffermodify的时候会进入一个queuecheckpoint queue),dbwr就根据queue从其中批量地写到数据文件。由于这里有一个顺序的关系,所以dbwr的写的进度就是可衡量的,写到哪个buffer的时候该buffer的首次变化时候的scn就是当前所有数据文件block的最新scn,但是由于无法适时的将dbwr的进度记录下来,所以oracle选择了一些策略。其中就包括ckpt进程的检查点和心跳。


oracle考虑到检查点scn的间隔还是太大了,因为检查点的触发条件有限,周期可能比较长,有些情况下比如检查点需要5分钟才触发,那这个时候系统crash再重新启动就意味着很可能系统需要5分钟才能启动。


于是oracle采用了一个心跳的概念,以3秒的频率将dbwr写的进度反应到控制文件中,这样系统crash重新启动的时候将从更近的一个时间点开始恢复。


再这里同样需要说明的一点是dbwr并不是只有当检查点发生的时候才写,它大约有10几种条件触发写操作


所以这个问题,我们需要理解的是oracle为什么要这么做?

oracle的目的就是缩短崩溃恢复时间!


oracle
如何缩短恢复时间?

1:检查点机制

2:心跳机制

oracle为什么不适时的将dbwr写进度反应到文件中?

适时反应成本太高!3秒种是一个合适的值,可以接受,代价不高又能大大缩短崩溃后恢复时间。


检查点发生以后,CKPT进程检查checkpoint queue(也就是脏块链表)是否过长,如果是,则触发DBWn,将一部分脏块写入数据文件,从而缩短checkpoint queue


checkpoint
发生时,一方面通知dbwr进行下一批写操作,(dbwr写入的时候,一次写的块数是有一个批量写的隐藏参数控制的。)另一方面,oracle采用了一个心跳的概念,以3秒的频率将dbwr写的进度反应到控制文件中,也就是把dbwr当前刚写完的dirty buffer对应的scn写入数据文件头和控制文件,这就是检查点scn


这个3秒和增量检查点不是一个概念,3秒只是在控制文件中,ckpt进程去更新当前dbwr写到哪里了,这个对于ckpt进程来说叫heartbeatheartbeat3秒一次,3秒可以看作不停的检查并记录检查点执行情况(DBWR的写进度)。


检查点发生之后数据库的数据文件、控制文件处于一致状态的含义是不需要进行介质恢复,只表示数据文件头一致,但是并不表示数据文件内容一致,因为数据文件内容可能在没有发生检查点的其他情况下的dbwr写数据文件,这样数据文件内容就不一致,若掉电需要进行崩溃恢复。

二、触发的条件

这里需要明白两个概念完全检查点和增量检查点的区别。

1、增量检查点(incremental checkpoint

oracle8之前,那时候没有chekpoint queue,也没有增量的概念,dirty buffer的写出是无顺的,就是冻结所有dml等候所有dirty buffer被写出。后来随着数据库规模的扩展和buffer cache的不断增大,oracle意识到这个机制已经满足不了需要,所以提出增量检查点的概念,建立了checkpoint queue,让dirty buffer header根据首次变化时候的顺序排列在queue里面。这样dbwr只要顺着queue的顺序写,而其他进程不必等候dbwr的写操作完成就可以继续。


自从有了checkpoint queue之后,检查点就成为一个短暂的动作,就是通知dbwr你要继续写dirty buffer到当前检查点发生时候的scn,然后将当前dbwr刚写完的dirty buffer对应的scn,写进数据文件和控制文件【增量检查点时不写数据文件头】(比如日志切换这种动作引起的检查点动作等)。然后检查点动作就结束了。剩下的工作就交给DBWn了,检查点进程也不必等候dbwr的完成。


ckpt
进程通知dbwr之后并不需要等待dbwr写到当前这个检查点对应的时间点。所以ckpt可以将已经完成的最后一个检查点scn写到控制文件和数据文件(可能是上一个,也可能是上上个,总之dbwr完成了哪个算哪个)。这样本次需要写进数据文件的dirty buffer可能在下一次检查点发生的时候已经写完了,这样下一次检查点发生的时候就把本次的检查点scn更新到控制文件和数据文件。


oracle 8之前,没有ckpt queue,只有LRU list,而LRU list里面的Dirty Buffer是不按时间顺序排列的,所以checkpoint时都会做一个full thread checkpoint,将LRU list中的所有buffer写到数据文件。


oracle8i以后,当发生FULL CHECKPOINT,oracle只是获取系统当前的SCN,然后将这个SCN之前的脏数据块写入磁盘,后续的DML脏数据块继续入队,他并不是保证整个缓冲区没有脏块,只是保证此检查点发生之前这段距离间没有脏块,而在checkpoint点之后的DML可以正常操作。


oracle8
以后推出了incremental checkpoint的机制,在以前的版本里每checkpoint时都会做一个full thread checkpoint,这样的话所有脏数据会被写到磁盘,巨大的i/o对系统性能带来很大影响。为了解决这个问题,oracle引入了checkpoint queue机制,每一个脏块会被移到检查点队列里面去,按照low rdb(第一次对此块修改对应的redo block address)来排列,靠近检查点队列尾端的数据块的low rba值是最小的,而且如果这些赃块被再次修改后它在检查点队列里的顺序也不会改变,这样就保证了越早修改的块越早写入磁盘。每隔3秒钟ckpt会去更新控制文件和数据文件,记录checkpoint执行的情况。


在运行的Oracle数据中,有很多事件、条件或者参数来触发检查点。比如

1)当已通过正常事务处理或者立即选项关闭例程时;(shutdown immediate或者Shutdown normal;

2)当通过设置初始化参数LOG_CHECKPOINT_INTERVALLOG_CHECKPOINT_TIMEOUTFAST_START_IO_TARGET强制时;

3)当数据库管理员手动请求时;(ALter system checkpoint

4alter tablespace ... offline;

5)每次日志切换时;alter system switch logfile

需要说明的是,

alter system switch logfile也将触发完全检查点的发生。

alter database datafile ... offline不会触发检查点进程。


如果是单纯的offline datafile,那么将不会触发文件检查点,只有针对offline tablespace的时候才会触发文件检查点,这也是为什么online datafile需要media recoveryonline tablespace不需要。


对于表空间的offline后再online这种情况,最好做个强制的checkpoint比较好。

上面几种情况,将触发完全检查点,促使DBWR将检查点时刻前所有的脏数据写入数据文件。


另外,一般正常运行期间的数据库不会产生完全检查点,下面很多事件将导致增量检查点,比如:

在联机热备份数据文件前,要求该数据文件中被修改的块从DB_Buffer写入数据文件中。所以,发出这样的命令:

ALTER TABLESPACE tablespace_name BIGEN BACKUP & end backup;也将触发和该表空间的数据文件有关的局部检查点;另外,

ALTER TABLESPACE tablespace_name READ ONLY;

ALTER TABLESPACE tablespace_name OFFLINE NORMAL;

等命令都会触发增量检查点。


注意:

每隔三秒也会触发检查点,但是并没有被oracle正式作为一种检查点的触发方式列入文档,并且这个3秒是记录dbwr进度而不是通知dbwr写。

这是三秒触发的检查点与其它条件触发检查点不同的地方。


三、关于low cache rbaon disk rba的理解:

简单说:

low cache rba就是CKPT记录的DBWR写的进度。

on disk rba就是LGWR的写进度。


如果数据库carshlow cache rba是恢复的起点,on disk rba是恢复的终点。


阐述一下:dbwr成功写完后并不把此刻scn信息写到控制文件中,只有CKPT才更新控制文件和数据文件头,dbwr只要成功将dirty data写入数据文件就是成功,CKPT只要能将最新DBWR写完的SCN更新到控制文件和数据文件头就算成功。但是由于CKPT进程不是实时更新dbwr写完的scn到控制文件中,而是采用每3妙更新一次的策略,因此最后有ckpt进程写进控制文件的scn信息有可能不是当前dbwr刚刚写完的scn值。这点应该注意,也就是说dbwr写的进度与ckpt进程更新控制文件的进度是不同的。


关于检查点的一点具体应用讨论:

1Commit成功后,数据还会丢失吗?


对于Oracle来说,用户所做的DML操作一旦被提交,则先是在database buffer cache中进行修改,同时在修改之前会将数据的前镜像保存在回滚段中,然后将修改之前和修改之后的数据都写入到redo log buffer中,当接收到commit命令之后,则redo log buffer开始写redo log file,并且记录此时的scn,当redo log file写完了之后,表示这次事务提交操作已经确认被数据库记录了,只有当redo log file写成功了,才会给用户Commit completed的成功字样。而对于Database buffer cache中的dirty buffer则会等待触发DBWn才写入,但是如果此时断电,则数据已经被记录到了redo log file中,系统在重新启动的时候,会自动进行嵌滚和回滚来保证数据的一致。所以,只要是commit成功的了,数据不会丢失!


2
、数据库发生一次DBWn,是否将所有buffer cache中的dirty buffer都写入,还是先将脏队列中的数据写入?

这话看起来有道理,但实际上,dbwr在写的时候又不断地在产生dirty buffer ,所以说检查点发生的时候是期望把该时间点之前的所有脏缓冲区写入数据文件。

所有的buffer,不在LRU list上就在dirty list, dbwr写入的时候,一次写的块数是有一个批量写的隐藏参数控制的。

所以说要是dbwrdirty list也好,lru list上的也好,要实现全部写入,都是一个现实系统中很难存在的现象。dirty总是在不断的产生,dbwr总是在不断地写,增量检查点发生的时候也并不意味着一定要更新数据文件头,检查点开始的时候只表示该次检查点结束的时候要更新数据文件头的话数据文件头具有该时间点的一致性。


3
data block里面不是也有SCN吗?和文件头里面的SCN有什么关系?什么时候被更新?代表的是是什么含义?

data block里面的SCN是当block被更改的时候的SCN

而数据文件有那么多block,自然不同的block有不同的SCN

block中存在block SCNITL中的commit SCN

block SCN又在块头和块位都有,若不一致意味着block损坏(热碑可能出现这个情况,需要从redo log中拷贝回来,若是正在修改的过程中由于进程死掉则pmon负责清理。若由于一些以外发生这样的不一致的情况,则查询的时候出现1578错误,当然该错误号也可能是物理磁盘损坏,这里表示逻辑的损坏!)


这个头和尾的SCN的检查时机跟这两个参数有关:

db_block_checking boolean FALSE

db_block_checksum boolean FALSE

该两参数信息请查阅http://tahiti.oracle.com

ITL中的commit SCN则跟consistent gets and delay block cleanout有关

数据文件头的SCN是检查点发生时更新的,代表着当恢复的时候从这个SCN点开始在log file中寻找redo开始做恢复。

SQL> select max(ktuxescnw*power(2,32)+ktuxescnb) from x$ktuxe;

MAX(KTUXESCNW*POWER(2,32)+KTUXESCNB)
------------------------------------
                             1493658

SQL> alter system checkpoint;

System altered.

SQL> select checkpoint_change# from v$database;

CHECKPOINT_CHANGE#
------------------
           1493715

SQL> select max(ktuxescnw*power(2,32)+ktuxescnb) from x$ktuxe;

MAX(KTUXESCNW*POWER(2,32)+KTUXESCNB)
------------------------------------
                             1493727

解答:x$ktuxe计算出来的是已经结束的最新的事务的commit scn,所以可小于当前系统scn。检查点scn自然也小于当前系统scn。但是检查点scnx$ktuxe计算出来的大小却倚赖于系统状况了。

current scn是系统当前所产生的最大scn,可能是当前未结束事务所产生的scn。在9idbms_flashback.get_system_number可以得到这个值,这个值应该是大于等于x$ktuxe SCN (这个view记录的是当前数据库结束事务的最大scn)

四、SCN的管理方式

OracleSCN的管理,分为单节点和RAC两种方式:


1.
单节点的instance

单节点的instance中,SCN值存在SGA区,由system commit number latch保护。任何进程要得到当前的SCN值,都要先得到这个latch


2.RAC/OPS
环境中

Oracle通过排队机制(Enqueue)实现SCN在各并行节点之间的顺序增长。

具体有两种方法:

Lamport算法:

又称面包房算法,先来先服务算法。跟很多银行采用的排队机制一样。客户到了银行,先领取一个服务号。一旦某个窗口出现空闲,拥有最小服务号的客户就可以去空闲窗口办理业务。


Commit
广播算法:

一有commit完成,最新的SCN就广播到所有节点中。

上述两种算法可以通过调整初始化参数max_commit_propagation_delay来切换。在多数系统 (除了Compaq Tur64 Unix)中,该参数的默认值都是700厘秒(centisecond),采用Lamport算法。如果该值小于100厘秒,Oracle就采用广播算法,并且记录在alert.log文件中。

 


五、SCN的种类

1Commit SCN

当用户提交commit命令后,系统将当前scn赋给该transaction。这些信息都反映在redo buffer中,并马上更新到redo log 文件里。


2Offline SCN

除了System tablespace以外的任何表空间,当我们执行SQL>alter tablespace … offline normal命令时,就会触发一个checkpoint,将内存中的dirty buffer写入磁盘文件中。Checkpoint完成后,数据文件头会更新checkpoint scnoffline normal scn值。其中数据库文件头的checkpoint scn值可通过查询列x$kccfe.fecps得到。

如果执行SQL>alter tablespace …offline命令时采用temporary immediate选项,而不用normal选项时,offline normal scn会被设成0。这样当数据库重启后通过resetlog方式打开时,该表空间就无法再改回在线状态。


3Checkpoint SCN

当数据库内存的脏数据块(dirty blocks)写到各数据文件中时,就发生一次checkpoint。数据库的当前checkpoint scn值存在x$kccdi.discn中。Checkpoint scn在数据库恢复中起着至关重要的作用。无论你用何种办法恢复数据库,只有当各个数据库文件的checkpoint scn都相同时,数据库才能打开。

虽然参数“_allow_resetlogs_corruption”可以在checkpoint scn不一致时强制打开数据库,但是这样的数据库在open后必须马上作全库的export,然后重建数据库并import数据。


4Resetlog SCN

数据库不完全恢复时,在指定时间点后的scn都无法再应用到数据库中。Resetlog时的scn就被设成当前数据库scnredo log也会被重新设置。


5Stop SCN

Stop scn记录在数据文件头上。当数据库处在打开状态时,stop scn被设成最大值0xffff.ffffffff。在数据库正常关闭过程中,stop scn被设置成当前系统的最大scn值。在数据库打开过程中,Oracle会比较各文件的stop scncheckpoint scn,如果值不一致,表明数据库先前没有正常关闭,需要做恢复。


6High and Low SCN

OracleRedo log会顺序纪录数据库的各个变化。一组redo log文件写满后,会自动切换到下一组redo log文件。则上一组redo loghigh scn就是下一组redo loglow scn

在视图v$log_history中,sequence#代表redo log序列号,first_change#表示当前redo loglow scn,列next_change#表示当前redo loghigh scn

SQL> col recid format 9999
SQL> col requence# format 9999
SQL> col first_change# format 9,999,999,999,999
SQL> col next_change# format 9,999,999,999,999
SQL> select recid,sequence#,first_change#,next_change# from v$log_history where rownum<6;

RECID  SEQUENCE#      FIRST_CHANGE#       NEXT_CHANGE#
----- ---------- ------------------ ------------------
    1          1            754,488            789,239
    2          2            789,239            793,012
    3          3            793,012            801,957
    4          4            801,957            813,253
    5          5            813,253            828,939

六、SCN号与oracle数据库恢复的关系

SCN号与oracle数据库恢复过程有着密切的关系,只有很好地理解了这层关系,才能深刻地理解恢复的原理,从而才能很好地解决这方面的问题。

SCNCHECKPOINT CKPT进程在checkpoint发生时,将当时的SCN号写入数据文件头和控制文件,同时通知DBWR进程将数据块写到数据文件。

CKPT进程也会在控制文件中记录RBA(redo block address),以标志Recovery需要从日志中哪个地方开始。与checkpoint相关的SCN号有四个,其中三个存在控制文件中,一个存放在数据文件头中。


这四个分别是:


1System Checkpoint SCN

checkpoint完成后,ORACLESystem Checkpoint SCN号存放在控制文件中。

我们可以通过下面SQL语句查询:

select checkpoint_change# from v$database;


2Datafile Checkpoint SCN

checkpoint完成后,ORACLEDatafile Checkpoint SCN号存放在控制文件中。

我们可以通过下面SQL语句查询所有数据文件的Datafile Checkpoinnt SCN号。

select name,checkpoint_change# from v$datafile;


3Start SCN

ORACLEStart SCN号存放在数据文件头中。

这个SCN用于检查数据库启动过程是否需要做media recovery.

我们可以通过以下SQL语句查询:

select name,checkpoint_change# from v$datafile_header;


4End SCN

ORACLEEnd SCN号存放在控制文件中。

这个SCN号用于检查数据库启动过程是否需要做instance recovery.

我们可以通过以下SQL语句查询:

select name,last_change# from v$datafile;

在数据库正常运行的情况下,对可读写的,online的数据文件,该SCN号为NULL.


试验内容如下:

在执行检查点进程之前SCN号如下:

1System Checkpoint SCN

SQL> select checkpoint_change# from v$database;

CHECKPOINT_CHANGE#
------------------
           1493715

2Datafile Checkpoint SCN

SQL> select name,checkpoint_change# from v$datafile;

NAME                                     CHECKPOINT_CHANGE#
---------------------------------------- ------------------
/u01/oradata/wilson/system01.dbf                    1493715
/u01/oradata/wilson/sysaux01.dbf                    1493715
/u01/oradata/wilson/undotbs01.dbf                   1493715
/u01/oradata/wilson/users01.dbf                     1493715
/u01/oradata/wilson/example01.dbf                   1493715
/u01/oradata/wilson/tbs.dbf                         1493715

6 rows selected.

3Start SCN

SQL> select name,checkpoint_change# from v$datafile_header;

NAME                                     CHECKPOINT_CHANGE#
---------------------------------------- ------------------
/u01/oradata/wilson/system01.dbf                    1493715
/u01/oradata/wilson/sysaux01.dbf                    1493715
/u01/oradata/wilson/undotbs01.dbf                   1493715
/u01/oradata/wilson/users01.dbf                     1493715
/u01/oradata/wilson/example01.dbf                   1493715
/u01/oradata/wilson/tbs.dbf                         1493715

6 rows selected.

4End SCN

SQL> select name,last_change# from v$datafile;

NAME                                     LAST_CHANGE#
---------------------------------------- ------------
/u01/oradata/wilson/system01.dbf
/u01/oradata/wilson/sysaux01.dbf
/u01/oradata/wilson/undotbs01.dbf
/u01/oradata/wilson/users01.dbf
/u01/oradata/wilson/example01.dbf
/u01/oradata/wilson/tbs.dbf

6 rows selected.

执行alter system checkpoint后的SCN号如下:

1System Checkpoint SCN

SQL> select checkpoint_change# from v$database;

CHECKPOINT_CHANGE#
------------------
           1494366

2Datafile Checkpoint SCN

NAME                                     CHECKPOINT_CHANGE#
---------------------------------------- ------------------
/u01/oradata/wilson/system01.dbf                    1494366
/u01/oradata/wilson/sysaux01.dbf                    1494366
/u01/oradata/wilson/undotbs01.dbf                   1494366
/u01/oradata/wilson/users01.dbf                     1494366
/u01/oradata/wilson/example01.dbf                   1494366
/u01/oradata/wilson/tbs.dbf                         1494366

6 rows selected.

(3)Start SCN

SQL> select name,checkpoint_change# from v$datafile_header;

NAME                                     CHECKPOINT_CHANGE#
---------------------------------------- ------------------
/u01/oradata/wilson/system01.dbf                    1494366
/u01/oradata/wilson/sysaux01.dbf                    1494366
/u01/oradata/wilson/undotbs01.dbf                   1494366
/u01/oradata/wilson/users01.dbf                     1494366
/u01/oradata/wilson/example01.dbf                   1494366
/u01/oradata/wilson/tbs.dbf                         1494366

6 rows selected.

(4)End SCN

SQL> select name,last_change# from v$datafile;

NAME                                     LAST_CHANGE#
---------------------------------------- ------------
/u01/oradata/wilson/system01.dbf
/u01/oradata/wilson/sysaux01.dbf
/u01/oradata/wilson/undotbs01.dbf
/u01/oradata/wilson/users01.dbf
/u01/oradata/wilson/example01.dbf
/u01/oradata/wilson/tbs.dbf

6 rows selected.

SCN不连续原因可能如下:

1.当发生日志组切换的时候

2.当符合LOG_CHECKPOINT_TIMEOUTLOG_CHECKPOINT_INTERVALfast_start_io_target,fast_start_mttr_target参数设置的时候

3.当运行ALTER SYSTEM SWITCH LOGFILE的时候

4.当运行ALTER SYSTEM CHECKPOINT的时候

5.当运行alter tablespace XXX begin backupend backup的时候

6.当运行alter tablespace ,datafile offline的时候;

 

 

 

 

 

 

七、SCN号与数据库启动

在数据库启动过程中,当System Checkpoint SCNDatafile Checkpoint SCNStart SCN号都相同时,数据库可以正常启动,不需要做media recovery.三者当中有一个不同时,则需要做media recovery

如果在启动的过程中,End SCN号为NULL,则需要做instance recoveryORACLE在启动过程中首先检查是否需要media recovery,然后再检查是否需要instance recovery

八、SCN号与数据库关闭

如果数据库的正常关闭的话,将会触发一个checkpoint,同时将数据文件END SCN号设置为相应数据文件的Start SCN号。

当数据库启动时,发现它们是一致的,则不需要做instance recovery。在数据库正常启动后,ORACLE会将END SCN号设置为NULL

如果数据库异常关闭的话,则END SCN号将为NULL.

九、为什么需要System checkpoint SCN号与Datafile Checkpoint SCN号?

为什么ORACLE会在控制文件中记录System checkpoint SCN号的同时,还需要为每个数据文件记录Datafile Checkpoint SCN号?


原因有二:

1.对只读表空间,其数据文件的Datafile Checkpoint SCNStart SCNEND SCN号均相同。这三个SCN在表空间处于只读期间都将被冻结。

2.如果控制文件不是当前的控制文件,则System checkpoint会小于Start SCNEND SCN号。记录这些SCN号,可以区分控制文件是否是当前的控制文件。

十、Recovery database using backup controlfile

以下条件需要使用using backup controlfile

1)使用备份控制文件

2)重建resetlogs控制文件,如果重建立noresetlogs不必要使用using backup controlfile

当有一个Start SCN号超过了System Checkpoit SCN号时,则说明控制文件不是当前的控制文件,因此在做recovery时需要采用using backup controlfile。这是为什么需要记录System Checkpoint SCN的原因之一。

这里需要一提的是,当重建控制文件的时候,System Checkpoint SCN0Datafile Checkpoint SCN的数据来自于Start SCN

根据上述的描述,此时需要采用using backup controlfilerecovery

十一、alter database open resetlog

指定RESETLOGS将重设当前LOG sequence number1,抛弃所有日志信息。

以下条件需要使用resetlog

1)在不完全恢复(介质恢复)

2)使用备份控制文件

使用resetlogs打开数据库后无必完整地备份一次数据库。


十二、create controlfile resetlogs/noresetlogs

1)用noresetlogs重建控制文件时,控制文件中 datafile checkpoint来自online logs中的current log

2)用resetlogs重建控制文件时,控制文件中datafile checkpoint来自各数据文件头。

上文摘自CUUG,请勿转载。

 

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/20523441/viewspace-760667/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/20523441/viewspace-760667/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值