java系统性能调优

系统性能调优(1)----步骤与思路

概念(来自度娘)

系统优化原来是系统科学(系统论)的术语,现在常用作计算机方面的术语。它尽可能减少计算机执行的进程,更改工作模式,删除不必要的中断让机器运行更有效,优化文件位置使数据读写更快,空出更多的系统资源供用户支配,以及减少不必要的系统加载项及自动启动项。当然优化到一定程度可能略微影响系统稳定性,但基本对硬件无害。

系统优化的一般步骤分为五步,不同的公司可能会省略其中的部分文档,但是大体的思路是一致的。

1、发现问题

一般性能的问题无非是系统工作效率低下,最常见的就是对数据库的CRUD操作缓慢、导出数据无响应等。有了问题我们应该精准的定位问题,尽可能的将问题的描述量化。在这里可以借助相应的工具进行一些性能测试,得到了详细的测试报告就可以为下一步的分析优化做好铺垫。

工欲善其事必先利其器,下面介绍几个比较有用的工具,篇幅有限具体的用法读者可以自己去度娘。

Httpwatch:IE插件,用来查看在浏览器中操作的详细情况。下图为Httpwatch某个查询的耗时记录:

JavaVisualVM:Java自带资源分析工具。下图为Java VisualVm记录某段循环的截图

LoadRunner:强大的压力测试工具,通过以模拟上千万用户实施并发负载及实时性能监测的方式来确认和查找问题。

根据得到性能测试结果要写一份性能测试报告为下一步工作提供依据,写性能测试报告一般有以下几点需要注意:

a) 测试使用数据是否为现场数据

b) 测试使用的客户端是什么配置

c) 服务器数据库端的配置

d) 选择的测试条件

e) 每个功能测试次数,平均耗时为多少

f) 每次查询返回的数据量

g) 数据库里对应表里的数据总量

h) 将测试记录写成文档,对于不达标的记录高亮显示

2、分析问题

根据上面得到的测试报告找到系统性能瓶颈的位置,然后从外向里添加日志,打印出各方法的执行时间,这样便很快就能找到问题的具体位置。将分析结果写入文档,为下一步提供依据。

切记:优化一定要对系统进行深入分析,找到性能问题根源切入点,而不被表象迷糊,对症下药。例如我们发现内存高了,首先想到不应该是扩大内存,而是为什么如此消耗内存,用工具(Java VisualVM)看看内存消耗在什么地方,想办法解决。

3、提出方案

根据分析结果提出合理的解决方案,然后写成文档提交审批。这里特别要注意的是解决方案的合理性,换句话说就是一定要考虑优化的成本。

从成本上面考虑大致的顺序如下。

数据库 ----> 应用层 ----> Web层 ----> 硬件

当然具体问题还需要具体分析(在后面的文章中将详细介绍更层次的优化方法)。一般来说在数据库上建立索引、分区等操作要比在应用层重构算法来的省时省力;应用层和Web层基本上是一致的,但和硬件比起来在软件层次的优化还是第一位的。就像上面说的,内存不够用了不应该去考虑换机器、加内存,而是找到内存开销大的地方,解决之。

4、解决问题

最后根据审批通过的文档进行修改,然后记录测试修改后的结果,符合性能指标后就可以提交到测试部门等待最后的总测试。

在这里要强调的是性能瓶颈都是相对的,也就是说的一处瓶颈消失了,系统一旦压力增大后,在其他地方又发现新的性能瓶颈。所以说性能优化是一个迭代的过程,需要逐步的去解决问题,直至满足系统需要。

5、结果报告

优化完成测试通过后一个优化就算结束了,为了后期有什么问题可以查看文档进行回溯,也为以后的优化提供现成的解决方案,要将优化过程中的文档上传保存。



系统性能调优(2)----数据库设计优化

1、逻辑设计的规范化

所谓逻辑设计的规范化就是使得数据库的逻辑更加合理。说白了就是我们平时所说的三范式。具体内容可以参考笔者之前的文章

现在总结如下:

第一范式:原子不可再分

第二范式:只能依赖主键

第三范式:不能依赖其他

其实三范式说到底就是方便查找、防止冗余,比如第一范式中原子性,就是把信息单元化,方便用户的查询,试想如果数据不容易查询还要数据库干什么?第二范式和第三范式是从不同的方面来描述其他非主键的字段,第二和第三范式保证了数据库的不冗余。这使得数据库在新增和更新的时候效率大大提高。

数据库范式中一共有五个,这里就只介绍三个。因为在实际项目中会发现真正能遵守三范式的项目很少,有很多项目为了提高查找效率不得不让数据库存在冗余,下面将详细介绍。

2、合理的冗余

任何事物都有两面性,三范式也不例外,达到一个平衡就好。就像前面文章中提到的系统性能的瓶颈都是相对的,原则就一条:谁影响整个系统的性能就解决谁。上面已经提到了三范式在一定程度上提高了数据库的查询效率,同时减少不必要的冗余,但是如果数据量大需要大量联合查询的时候那么影响查询效率的就是三范式了。

例如User表中的name字段,如果按照三范式的要求name字段就应该放在User表中其他地方只存User的ID。但是如果在另一张表订单表(Order)中需要User表的name字段这时就不得不将两个表进行关联,在数据量小的时候采用联合查询的方式是没有问题的。一旦数据量超过百万千万甚至更大的时候联合查询对性能的影响就显现出来了,这时完全就可以将name字段冗余的放在Order表中。需要注意的时候再在户修改姓名(一般很少有人修改)的时候需要修改两个地方Order表和User表,适当的冗余提高了效率。

“放弃”三范式导致一定的数据冗余,减少了联合查询,但最终目的还是提高效率。


3、索引的设计

在设计阶段,可以根据功能和性能的需求进行初步的索引设计,这里需要根据预计的数据量和查询来设计索引,可能与将来实际使用的时候会有所区别。

关于索引的选择,应改主意:

A、根据数据量决定哪些表需要增加索引,数据量小的可以只有主键。

B、根据使用频率决定哪些字段需要建立索引,选择经常作为连接条件、筛选条件、聚合查询、排序的字段作为索引的候选字段。

C、把经常一起出现的字段组合在一起,组成组合索引,组合索引的字段顺序与主键一样,也需要把最常用的字段放在前面,把重复率低的字段放在前面。

D、一个表不要加太多索引,因为索引影响插入和更新的速度

索引不单单是需要在设计数据库时候需要考虑,在系统维护解决性能瓶颈的时候同样是一把屡试不爽的绝招。关于索引在系统后期维护中的优化后面文章将详细叙述。

总结:

系统性能的提高是为了提高系统的执行速度和准确的处理数据。往往系统性能的调优是等到遇到系统瓶颈的时候再去做,这无可厚非谁也不是先知,但是我们可以根据自己或者他人已有的经验把调优的的过程放在开始设计的阶段。在得到需求的时候要考虑系统将会遇到哪些瓶颈,(当然不要过分设计,过分设计的后果就是增大系统的开销)然后根据预估的瓶颈进行有效的预防(比如合理的索引、高效的主键设计等等)。总之性能的优化绝不是遇到瓶颈才开始做的,从设计阶段就考虑性能问题才是明智之举。



 

系统性能调优(3)----数据库索引优化

   索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息。其实道理很简单,比如我们要从字典中查找一个字,那么这个字典就是我们所要面对的数据库,索引就好比是字典前面的拼音或者部首索引表,当需要查询一个字的时候我们首先去检索拼音或者部首索引表,然后再去字典中查找具体的位置,这样我们就加快数据库的查询速度。

索引分为聚簇索引和非聚簇索引两种,聚簇索引是按照数据存放的物理位置为顺序的,而非聚簇索引就不一样了(下一篇文章将介绍常见的索引)。显而易见的字典本身的内容以及前面的拼音检索表就是聚簇索引,因为他们都是按照26个字母的顺序排列的。而后面的部首检索表就是一个非聚簇索引,他并不是按照每个字所在的具体的位置排列的,而是通过特定的排列规则进行排序的。

使用索引的好处就是查询起来效率高了,但任何事物都有正反两方面,使用索引也会降低数据库的效率,这里的效率是指在增删改的时候会比不讲索引效率慢。道理很简单如果一本字典随时在变化那么需要变的不仅仅是这个字典的内容还有就是这个字典前面的拼音或者部首检字表。所以有索引的情况下增删改的效率肯定会降低的。

而且需要注意的是有索引不一定效率就会提升,试想一本字典可以把所有的字全部放在检索表中(实际上这是没有意义的)这样的话如果要查询一个字那么就相当于查询了多次,首先在庞大的检索表中查询一遍,然后再去数据库中查询这个字的详细内容,这要比直接从字典查询效率要低。

建立索引不一定就提高查询的效率,这取决于索引是否合理,以及查询是否用到索引。并非是在任何字段上简单地建立索引就能提高查询速度。索引的建立,会带来更多的系统开销,因为系统要耗费资源去维护它,如果建立了没有用到的索引,不适当的索引,过多的索引,反而会导致查询性能下降。总之索引的建立,要看表的结构,数据的分布,还有你要用到哪些数据,如果把索引建立在你根本不需要的数据列上,是根本不会发挥作用的举例如下:

(1)全表扫描

只在主键上建立聚集索引:

Selectid,name,dept,emdate from person    用时:20546毫秒(即:21秒)

不在主键上建立聚集索引,只建普通索引

Selectid,name,dept,emdate from person     用时:17923毫秒(即:18秒)

以上查询执行的实际上索引不会发挥作用,因为提取的是全部数据。聚集索引在这里会耗费更多的资源,所以会看到,不建立聚集索引比建立聚集索引还要快

(2):按日期进行过滤(用到索引)

在主键上建立聚集索引,在emdate上建立非聚集索引:

Select id,name,dept,emdatefrom person where     emdate>dateadd(day,+1,getdate()) 用时:12376毫秒(12秒)

在主键上建立聚集索引,在emdate上没有索引:

selectid,name,dept,emdate from person where emdate>dateadd(day,+1,getdate()) 用时:21296毫秒(21秒)

在主键上建立非聚集索引,在emdate上建立非聚集索引:

selectid,name,dept,emdate from person where emdate>dateadd(day,+1,getdate()) 用时:11590毫秒(12秒)

在主键上建立非聚集索引,在emdate上建立聚集索引:

selectid,name,dept,emdate from person where emdate>dateadd(day,+1,getdate()) andemdate<dateadd(day,+3,getdate())  用时:5233毫秒(5秒)

虽然每条语句提取出来的都是30万条数据,各种情况的差异却是比较大的,特别是将聚集索引建立在日期列时的差异。事实上,如果您的数据库真的有几千万条记录的话,差距会更明显。

关于索引方面的系统优化和其他优化一样,都需要根据实际的情况而定,了解了索引的原理那么就能对症下药,合理的建立索引进而提高系统的查询效率。


系统性能调优(4)----数据库常见索引

1、B树索引

这是最常见的索引,几乎所有的关系型数据库系统都支持B树结构的索引,也是被最多使用的,其树结构与二叉树比较类似,根据行id快速定位到行。大部分数据库默认建立的索引就是这种索引。B树索引在检索高基数数据列(高基数列是指该列有很多不同的值,该列所有不同值的个数之和与该列所有值的个数之和的比成为列基数)时提供了比较好的性能,B树索引是基于二叉树的,由分支块和叶块组成。在树结构中,位于最底层的块成为叶块,包含每个被索引列的值和行所对应的rowid。在叶节点的上面是分支块,用来导航结构,包含了索引列(关键字)范围和另一索引快的地址。

2、聚集索引

没错,这是sqlserver里很重要的一个索引,也叫群集索引。聚集索引是相对于常规索引而言的,oracle也有类似的索引,不过叫聚簇索引,注意,虽然聚簇和聚集仅有一字之差,但是oracle的聚簇索引和sqlserver的聚集索引还是有很多的不同的,oracle的聚簇索引可以针对多表,根据多个表相同列的不同值,将相关数据聚集在周围。

sqlserver聚集索引也有类似的意思,但是只能针对单表。在oracle里,聚簇”是oralce内部的一个对象,就像基本表、视图、触发器这些概念一样。聚簇索引就是对聚簇进行的索引,由于比较复杂。在此不详细讨论,但在sqlserver里,聚集索引直接作用在表上,因此不可以将二者混淆。反正不能等同来看就是了。

3、非聚集索引

非聚集索引是一种典型的B树索引,每个叶块只包含两种数据,一种是索引项,一种是该索引项所在行的行指针,当查询的数据匹配该索引项数据的时候,将会取出对应的行指针,取得该行的数据.如果要根据键值从大型SQLServer表提取具有良好选择性的少数几行,非聚集索引最有用。B树的底部或叶级包含组成该索引的列中的所有数据。当用非聚集索引检索表中与键值匹配的信息时,将搜索整个索引B树,直到在索引叶级找到一个与键值匹配的值。在非聚集索引中,叶级节点仅包含参与索引的数据以及快速找到相关数据页上其它行数据的指针。最糟糕的情况是,从非聚集索引中获得的每一行都要求一个额外的不连续磁盘I/O才能检索行数据。最好的情况是,所需要的行有许多都位于相同的数据页,因此在提取每个数据页时可检索多行。如果是聚集索引,索引的叶级节点是表的实际数据行。因此,检索表数据时不需要指针跳动。基于聚集索引的范围扫描执行情况很好,因为聚集索引的叶级(即表的所有行)在物理上按照组成聚集索引的列顺序排列在磁盘上。

4、覆盖索引

覆盖索引是非聚集索引的一个特例。覆盖索引的定义是在选择条件和WHERE谓词上均满足SQL查询的所有列的基础上建立的非聚集索引。覆盖索引可以节省大量的I/O,因此可极大地改善查询的性能。但是有必要在新建索引(以及与它相关的B树索引结构维护)所需要的代价和覆盖索引所带来的I/O性能增益之间进行权衡。如果覆盖索引对于SQLServer上经常运行的查询或查询组极其有利,那么创建覆盖索引是值得的。

5、位图索引

这个不是sqlserver的索引,它是oracle的,所以请不要混淆。之所以提出来,是因为它不是B树结构的索引。位图索引相对于Btree索引来说,它的存储结构是不一样的,通常在Btree索引中,索引条目和行之间有一对一的关系。对于位图索引,一个索引条目使用一个位图同时指向许多行。这对于基本上只读的低基数(数据只有很少的几个截然不同的值)数据是合适的。比如说,一个person表,有个性别字段sex,Y代表男,N代表女,对于有几百万行数据的表来说,位图索引是一个非常好的选择。它可以迅速的扫描出来,而不用象对B树索引那样的查找。



 

系统性能调优(5)----Java循环与字符串代码优化

在系统性能优化的时候循环和字符串处理一直是非常值得注意的地方。从心态上我们一定不能把自己的眼界放在十次或者是百次循环的层次上,也不能把自己要处理的字符串当做是有十个二十个字符。每次遇到循环都要假定这个循环是上万次的,每次要处理的字符串的时候一定要告诉自己这个字符串将来有可能是很大的。不要等到数据量真的达到十万、百万的级别之后再采取处理,那样的话成本的消耗就太大了。本文将介绍关于Java代码中循环和字符串的优化方法,希望对读者有用。

关于循环

嵌套for循环中次数多的放在内侧,次数少的放在外侧。众所周知for循环需要定义一个循环变量来遍历每一个需要循环的对象,那么如果循环次数多的循环放在外侧那么无疑将会使得总体的变量增多,效率自然会降低。下面进行代码测试

  1. public class Test{  
  2.     public static void main (String [] args)  
  3.     {  
  4.         Long time2Before=System.nanoTime();  
  5.         for (int i=0; i<10;i++ ){   
  6.             for (int j=0; j<1000000;j++ ){   
  7.   
  8.             }  
  9.         }  
  10.         Long time2After=System.nanoTime();  
  11.         System.out.println("faster--->"+(time2After-time2Before));  
  12.   
  13.         Long time1Before=System.nanoTime();  
  14.         for (int i=0; i<1000000;i++ ){   
  15.             for (int j=0; j<10;j++ ){   
  16.   
  17.             }  
  18.         }  
  19.         Long time1After=System.nanoTime();  
  20.         System.out.println("slower--->"+(time1After-time1Before));  
  21.     }  
  22. }  

在循环中只做与循环相关的事情,一些不必要的循环不要放到循环当中去做。比如在遍历集合的时候没有必要将取得集合大小放在循环中去做,完全可以放在集合的外边。效果上没区别,性能上差距巨大。

  1. import java.util.*;  
  2. public class Test1{  
  3.     public static void main (String [] args)  
  4.     {  
  5.         List<String> list=new ArrayList<String>();  
  6.         for(int i=0;i<1000000;i++){  
  7.             list.add("luck"+i);  
  8.         }  
  9.   
  10.         Long time1Before=System.nanoTime();  
  11.         for(int i=0;i<list.size();i++){  
  12.         //  System.out.println(list.get(i));  
  13.         }  
  14.         Long time1After=System.nanoTime();  
  15.         System.out.println("use .size-->"+(time1After-time1Before));  
  16.   
  17.         Long time2Before=System.nanoTime();  
  18.         int n=list.size();  
  19.         for(int i=0;i<n;i++){  
  20.         //  System.out.println(list.get(i));  
  21.         }  
  22.         Long time2After=System.nanoTime();  
  23.         System.out.println("do not use .size-->"+(time2After-time2Before));  
  24.   
  25.     }  
  26. }  

关于字符串

消除字符串连接,在程序中优先考虑使用StringBuffer或者StringBuilder代替String。一个字符串相当于一个匿名的String对象,如果在程序中拼接两个字符串那么会在内存中定义三个字符串空间。而StringBuffer或者StringBuilder就不会这么做,而是在原来已有的StringBuffer或者StringBuilder对象中进行修改。测试代码如下

  1. public class Test3{  
  2.     public static void main (String [] args)  
  3.     {  
  4.         long time1Before=System.nanoTime();  
  5.         String str="";  
  6.         for(int i=0;i<10000;i++){  
  7.             str+=i;  
  8.         }  
  9.         long time1After=System.nanoTime();  
  10.         System.out.println("use String --->  "+(time1After-time1Before));  
  11.   
  12.         long time2Before=System.nanoTime();  
  13.         StringBuilder sbuilder=new StringBuilder();  
  14.         for(int i=0;i<10000;i++){  
  15.             sbuilder.append(i);  
  16.         }  
  17.         long time2After=System.nanoTime();  
  18.         System.out.println("use StringBuilder--->  "+(time2After-time2Before));  
  19.   
  20.         long time3Before=System.nanoTime();  
  21.         StringBuffer stringBuffer=new StringBuffer();  
  22.         for(int i=0;i<10000;i++){  
  23.             stringBuffer.append(i);  
  24.         }  
  25.         long time3After=System.nanoTime();  
  26.         System.out.println("use StringBuffer--->  "+(time3After-time3Before));  
  27.     }  
  28. }  

需要说明的是在StringBuffer和StringBuilder之间如果需要考虑选其一的话原则很简单,前者是线程安全的后者是线程不安全的,换句话说后者比前者更快。综上所述如果单单从性能上考虑的话从高到低依次是:StringBuilder --> StringBuffer --> String。

 

循环和字符串是程序中最容易提升代码效率的地方,因为很多人在写程序的时候为了图一时方便将效率抛在脑后,当要处理的数据不大的时候无所谓,一旦程序要处理的数据增大那么性能的瓶颈也就出现了。所以就像文章开头所说的,要在写程序的时候就要考虑十万百万的数量级,不要等到性能瓶颈出现再去解决,因为代码重构或者说后期的优化成本要远远高于前期的开发成本,相信看过别人无注释而又冗长代码的童鞋深有体会(窃笑~~)。



系统性能调优(6)----Java异常处理性能优化

执行一个catch代码块和抛出一个异常花费是很高的,这个过程中的性能损耗主要是由于当创建一个异常时要获得线程栈的一个快照。抛出异常首先要创建一个新的对象Throwable类的构造函数调用名为fillInStackTrace的方法,fillInStackTrace方法检查堆栈,收集调用跟踪信息。由于在处理过程中创建了一个新的对象,所以说只要有异常被抛出,JVM就必须调整调用堆栈,系统资源开销也就增大了。

1、使编译器和运行时最优化,将几个方法调用放在一个try/catch块中,而不是为每个方法调用各自使用try/catch块

  1. try{   
  2.     Some.method1();    
  3. }catch(method1Exception e){   
  4.     handle exception 1   
  5. }  
  6. try{   
  7.     Some.method2();    
  8. }catch(method2Exception e){   
  9.     handle exception 2    
  10. }  
  11. try{   
  12.     Some.method3();    
  13. }catch(method3Exception e){   
  14.     handle exception 3    
  15. }  

应当优化为如下代码

  1. try{   
  2.     Some.method1();   
  3.     Some.method2();   
  4.     Some.method3();   
  5. }catch(method1Exception e){   
  6.     handle exception 1   
  7. }catch(method2Exception e){   
  8.     handle exception 2   
  9. }catch(method3Exception e){   
  10.     handle exception 3   
  11. }  


2、异常只能用于错误处理,不应该用来控制程序流程。

  1. public class Test {  
  2.     int value;  
  3.   
  4.   
  5.     public int getValue() {  
  6.         return value;  
  7.     }  
  8.   
  9.     public void reset() {  
  10.         value = 0;  
  11.     }  
  12.   
  13.     // Calculates without exception  
  14.     public void method1(int i) {  
  15.         value = ((value + i) / i) << 1;  
  16.         // Will never be true  
  17.         if ((i & 0xFFFFFFF) == 1000000000) {  
  18.             System.out.println("You'll never see this!");  
  19.         }  
  20.     }  
  21.   
  22.     // Could in theory throw one, but never will  
  23.     public void method2(int i) throws Exception {  
  24.         value = ((value + i) / i) << 1;  
  25.         // Will never be true  
  26.         if ((i & 0xFFFFFFF) == 1000000000) {  
  27.             throw new Exception();  
  28.         }  
  29.     }  
  30.   
  31.     // This one will regularly throw one  
  32.     public void method3(int i) throws Exception {  
  33.         value = ((value + i) / i) << 1;  
  34.         // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both  
  35.         // an AND operation between two integers. The size of the number plays  
  36.         // no role. AND on 32 BIT always ANDs all 32 bits  
  37.         if ((i & 0x1) == 1) {  
  38.             throw new Exception();  
  39.         }  
  40.     }  
  41.   
  42.     public static void main(String[] args) {  
  43.         int i;  
  44.         long l;  
  45.         Test t = new Test();  
  46.   
  47.         l = System.currentTimeMillis();  
  48.         t.reset();  
  49.         for (i = 1; i < 100000000; i++) {  
  50.             t.method1(i);  
  51.         }  
  52.         l = System.currentTimeMillis() - l;  
  53.         System.out.println(  
  54.             "method1 took " + l + " ms, result was " + t.getValue()  
  55.         );  
  56.   
  57.         l = System.currentTimeMillis();  
  58.         t.reset();  
  59.         for (i = 1; i < 100000000; i++) {  
  60.             try {  
  61.                 t.method2(i);  
  62.             } catch (Exception e) {  
  63.                 System.out.println("You'll never see this!");  
  64.             }  
  65.         }  
  66.         l = System.currentTimeMillis() - l;  
  67.         System.out.println(  
  68.             "method2 took " + l + " ms, result was " + t.getValue()  
  69.         );  
  70.   
  71.         l = System.currentTimeMillis();  
  72.         t.reset();  
  73.         for (i = 1; i < 100000000; i++) {  
  74.             try {  
  75.                 t.method3(i);  
  76.             } catch (Exception e) {  
  77.                 // Do nothing here, as we will get here  
  78.             }  
  79.         }  
  80.         l = System.currentTimeMillis() - l;  
  81.         System.out.println(  
  82.             "method3 took " + l + " ms, result was " + t.getValue()  
  83.         );  
  84.     }  
  85. }  

上段代码首先创建了三个方法,在这三个方法当中第一个没有抛出异常,第二个当符合条件的时候抛出异常,第三个和第二个一样。(实际上三个方法中value的存在就是为了延长程序的运行时间,没有实际意义。)从运行结果可以看出来抛出异常是相当消耗资源的,尽管有时不出现异常时性能尚可,但是依然不建议用异常控制程序流程。

不做多余的的事情就是调优,保证功能实现的情况下最低程度的减少开销就是优化。


转自大神:http://blog.csdn.net/beijiguangyong/article/details/8983472

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值