XX银行网银系统是一套全新的对公业务渠道类系统,经过两年的建设,将逐步对外提供服务。

该系统融合了原来多个对公渠道系统,并发量是以前多个系统之和,吞吐量要求将大幅上升。为了使广大对公客户使用系统时获得更快的响应时间体验,项目组对系统进行了持续的性能测试和优化。这一过程中,形成了一套针对新建系统进行性能测试和优化的方法论。

该方法论包括测试环境准备、测试功能优先级、性能优化原则、常用性能指标及工具、工具使用方法、常见性能问题原因和优化方法,以及典型案例和进一步优化方法的讨论。

由于系统已经开发完成,根据性能优化修改范围尽可能小且不引入更多问题的原则,本文将只讨论对系统进行局部优化的方法,而系统初始设计时为了提高性能而进行的设计不在讨论范围之内。

我们使用Linux操作系统,压测工具为loadrunner,中间件版本包括IHS8.5.5.9、WAS8.5、IBMJDK1.7(IBM J9VM)、DB2V10、Redis3.2.3。

一、应用系统性能评价指标

  • 响应时间:尽快的给用户返回响应,体现系统处理请求的速度;

  • 吞吐量TPS:每秒完成的事务数,体现系统处理能力;

  • 并发性:业务请求高并发时,系统能否稳定运行;

  • 扩展性:单机处理能力不足时,系统能否横向扩展。

TPS = 并发用户数 / 响应时间

二、常见性能监控指标及工具

1、操作系统监控指标及工具

主要监控指标:CPU、系统CPU、内存、磁盘IO、网络IO、请求耗时。

常用命令:

  • top –H –p pid:cpu负载监控,实时查看占用cpu高的线程;

  • vmstat:系统负载监控;

  • pidstat:cpu让步式上下文切换监控,监控锁竞争;

  • iostat:磁盘利用率监控;

  • nmon:监控cpu、内存、io使用率 ./nmon -f -t -s 2 -c 100 每2秒采集一次,共100次;

  • netstat -anp|grep端口或IP|grep ESTABLISHED|wc -l:服务器连接数监控。

2、JVM监控指标及工具

  • Jconsole,监控cpu、内存垃圾回收。

JVM启动参数中增加:

-Dcom.sun.management.jmxremote.port=1088

-Dcom.sun.management.jmxremote.authenticate=false

-Dcom.sun.management.jmxremote.ssl=false

  • jvisualvm,cpu采样、java方法耗时分析、jvm线程栈快照、监控cpu、内存垃圾回收。

CPU抽样:

线程快照:

  • jca457.jar,ibm javacore线程快照分析:

  • ga456.jar,ibmjvm垃圾回收gc分析:

  • ha456.jar,ibmjvm内存,heapdump分析,内存溢出时使用;

  • TProfiler,cpu采样、java方法cpu耗时分析,cpu高,响应慢时使用;

  • JProfiler,cpu采样、java方法cpu耗时分析,cpu高,响应慢时使用;

  • OracleDeveloperStudio12.6-linux-x86,cpu采样、java方法cpu耗时分析,cpu高,响应慢时使用。

三、性能测试前置条件

1、数据库表数据量准确

要和生产数据量保持一致,至少一个数量级。数据分布尽量均匀。

2、测试环境和生产一致

测试环境机器配置、参数、代码尽可能和生产保持一致(数据库服务器硬件除外)。

3、合理确定并发用户量

系统并发用户量有多种算法可以估算:

  • 平均并发用户数:

    C=nL/T(n是考察时间内用户登录数,L是用户平均在线时间长度,T是考察值时间长度)

    并发用户数峰值:C’=C + 3*根号C

  • 用户总量/统计时间*影响因子:

    网银用户量100万,根据2/8原则, 80%用户在上午9点到11点,下午2点到4点之间登录系统,每次登录耗时1到1.5秒。则并发用户为:

    1000000*0.8/4/3600*1.5=82.5,

    1000000*0.8/4/3600*1=55

  • 根据系统用户数计算:

    并发用户数=系统最大在线用户数的8%到12%

  • 根据TPS估计:

    C=(Think Time + 1)* TPS,

    网银用户思考时间10s,

    C=(10+1)* 3=33。

4、预估各功能交易量,确定压测功能优先级

根据交易量从大到小排名,排名靠前的优先压测。

5、设置性能问题认定标准

比如响应时间超过3s、TPS低于10、服务器cpu占用率超过70%、jvm堆内存使用100%、垃圾回收频繁、网络IO或磁盘IO达到瓶颈等……都可能是性能问题。

四、性能优化一般思路

1、找到性能瓶颈

性能瓶颈定义:导致系统TPS低、响应时间长、资源(CPU、内存、网络)占用高等问题的关键程序模块。提升该程序模块的性能,可以大幅度改善性能。

常见的性能瓶颈原因包括:数据库慢查询SQL、日志打印、xml大报文解析和格式转换、复杂业务逻辑、锁竞争等。

2、如何找到性能瓶颈

  • 使用LoadRunner给每个接口的增加事务,记录其响应时间和TPS,最慢的那个接口往往是瓶颈;

  • 分析慢交易的日志,查看是哪个操作耗时最长;

  • 分析数据库快照,看是否有执行较慢或者全表扫描的SQL;

  • 通过Javacore查看线程正在执行的代码,是大部分阻塞在IO上,还是大部分在进行计算。针对不同的问题,使用不同的分析工具。详细内容可以看下一章节。

3、针对性能瓶颈进行合理优化

性能优化原则:

  • 先优化瓶颈问题;

  • 方案简单,尽量不引入更多复杂性,尽量不降低业务体验;

  • 满足系统性能要求即可,不引入新的bug。

五、常见问题及优化方法

1、SQL执行时间长

问题现象:系统响应时间长、数据库cpu高。

问题原因:全表扫描、索引低效、排序溢出。

解决方法:

  • 通过DB2数据库快照查看执行时间长的SQL,查看该SQL执行计划,在cost比较高的SQL上增加合适索引。要求所有大表SQL执行计划的cost低于100,超过100的SQL要评审。对于排序溢出、可参考数据中心规范设置排序堆大小,规范中排序堆设置为AUTOMATIC。

  • 制定数据库表清理策略。根据数据的生命周期要求,对流水类的数据定期进行清理备份,不再长期保留。定期对全库的表结构进行reorg、runstats操作,以提高索引效率。

排查方法:

  • 获得数据库快照:db2 get snapshot for all on corpdb >gswyzfzzshpl1207.log

  • 从快照中提取慢SQL,Toad查看SQL执行计划

  • Db2命令方式查看SQL执行计划:db2expln -d corpdb -t -g -q "SQL语句"

  • 执行计划查看方法:自上而下查看cost最大的分支,找到未走索引或索引使用不当的表

2、数据库出现死锁

问题现象:数据库快照检测到存在数据库死锁,或通过db2evmon -db corpdb -evm DB2DETAILDEADLOCK>dlock.txt生成死锁监控文件,快照或监控文件中存在deadlock的情况。

问题原因:全表扫描、大事务、更新相同表记录的SQL执行顺序交叉等。

解决方法:缩短事务路径长度,避免全表扫描。如果必须存在大事务,则更新相同表的SQL执行顺序一致,并且坚决避免全表扫描。网银系统指令发送功能全表扫描+全局大事务,导致数据库死锁。

排查方法:根据死锁监控文件dlock.txt找到导致死锁的SQL,以及该SQL持有的锁,分析该SQL可能存在的问题。

3、线程阻塞在日志记录上

问题现象:系统响应时间长、通过javacore查看很多线程阻塞在打印日志上。

问题原因:log4j1.x版本较低,性能较差;大报文日志多次输出。

解决方法:

  • 减少无效日志、删除无用日志,减少大日志输出。

  • 升级log4j组件到log4j2,参考log4j2官方文档,配置合理的日志缓冲区,采用高效的Appenders,比如RollingRandomAccessFile。但log4j2仍然采用同步日志,不采用异步日志。因为网银系统日志量较大,异步日志队列很快就满了,如果单条日志存在大报文,还有可能导致内存溢出,因此不适合采用异步日志。如果日志量少(压测产生日志的速度,低于日志写入文件的速度),则可以使用异步日志,大幅提高性能。如果日志量较大,则不建议使用异步日志。

排查方法:

  • JVM启动参数中增加-XX:+HeapDumpOnCtrlBreak,压测进行时,kill -3 pid 杀几个javacore,使用jca457.jar工具打开并分析。推荐使用该工具,因为该工具可以对所有线程状态进行统计,并生成饼状图,方便查看。

  • 压测进行时,使用jvisualvm获取jvm快照,分析线程堆栈。

4、多线程并发问题

问题现象:采用合理的并发数压测,系统出现逻辑错误、交易失败或异常报错。经查是由于对象中变量被异常修改导致。

问题原因:系统中全局对象中的类变量或全局对象,被多个线程修改。

解决方法:排查系统中所有持有全局对象或类变量的代码,检查其全局变量是否可能被多个线程并行修改。

修改方法:

  • 将全局变量转成方法内的局部变量;

  • 对全局变量进行同步控制比如syncronized代码块,或者java.util.concurrent锁。

排查方法:并发问题很可能是由全局变量或者对象导致,准确识别全局变量,通过阅读代码找问题。建议应用梳理所有可能存放全局对象的代码,统一管控,或者把所有全局对象放到一个类中,方便管理。

5、打开了太多文件

问题现象:采用合理的并发数压测,交易失败,或后台日志报错:To many open files。

问题原因:

  • 读取配置文件或者业务数据文件后,未关闭文件流;

  • /etc/security/limits.conf中最大打开文件数配置过小。

解决方法:

  • 使用lsof –p pid 命令查看进程打开的文件,如果大部分文件都是同一类型的文件,说明可能未关闭文件流。找到打开文件的代码,关闭文件流即可。

  • 如果不存在未关闭文件流的问题,且业务本身就需要处理大量文件,则修改/etc/security/limits.conf文件如下内容:

* hard nproc 10240

* soft nproc 10240

6、内存泄漏

问题现象:JVM内存耗尽,后台日志抛出OutOfMemeryError异常 ;

问题原因:内存溢出问题可能的原因比较多,可能是全局的List、Map等对象不断被扩大,也可能是程序不慎将大量数据读到内存里;可能是循环操作导致,也可能后台线程定时触发加载数据导致。

解决方法:对于ibmjdk纯java应用,在jvm启动时设置-XX:+HeapDumpOnOutOfMemory Error参数,会在内存溢出时生成heapdump文件。使用ha456.jar工具打开heapdump文件,分析大对象是如何产生的。

当然,在heapdump中对象类型可能只是List这种结构,看不出具体哪个业务代码创建的对象。此时要分析所有的全局对象,列出可疑的List或Map对象,排查其溢出原因。

全局对象、引用的初始化、修改要慎重。建议应用梳理所有可能存放全局对象的代码,统一管控。

7、JVM垃圾回收频繁

问题现象:top –H –p pid命令查看,GC Slave线程CPU占用排名始终为前三名,同时Jconsole查看jvm内存占用较高,垃圾回收频繁。使用ga456.jar分析gc日志,查看gc频率、时长。

打印gc日志的方法:在jvm启动参数里增加-verbose:gc -Xverbosegclog:/usr/ebank/ logs/cobp/gcdetail.log

问题原因:高并发下,内存对象较多,jvm堆内存不够用 。

解决方法:扩大堆内存大小–Xmx2048m –Xms2048m。

8、CPU高

问题现象:50并发压测,监控工具显示bp、前置CPU占用90%以上。

问题原因:业务处理中存在大量CPU计算操作。

解决方法:采用更高效的算法、数据结构替换原来消耗CPU的代码,或者采用新的设计绕过瓶颈代码,比如查找数据的逻辑,可以把List改为Map,以空间换时间;比如用Json报文替换XML报文,提高传输、解析和打印日志的效率。

导致Cpu计算资源高消耗的代码:报文格式转换、加解密、正则表达式、低效的循环、低效的正则表达式。

排查方法:

  • 压测进行时,使用jvisualvm工具远程连接应用,点抽样器àCPU,点快照生成线程快照。采样一段时间后,抽样器会显示各个方法占用cpu时间,可以针对CPU时间占用高的方法进行优化。

  • 使用tprofiler,jprofiler,OracleDeveloperStudio12.6-linux-x86工具分别分析消耗CPU时间长的方法,以上工具分析结果可能有些差别。针对CPU计算耗时最长的方法进行优化。

9、批处理时间长、数据库逐笔插入缓慢

问题现象:大批量数据(10万条以上)更新或插入数据库,耗时较长。

问题原因:批量数据处理时,如果逐条更新数据库,则会存在大量网络io、磁盘io,耗时较长,而且对数据库资源消耗较大。

解决方法:

  • 采用java提供的batchUpdate方法批量更新数据库,每1000条commit一次,可大幅提高数据更新效率。

  • 单线程改成线程池,并行处理,充分利用多核CPU,通过数据库或者其他同步锁控制并行性;增加缓冲池,降低数据库或磁盘IO访问频次。

10、数据库CPU高

问题现象:后台指令发送满负荷工作时,数据库CPU高。

问题原因:后台指令发送线程每次对全量查询结果排序,结果集很大,然后取一条记录;索引区分度不高,满负荷执行时;查询频率很高;压测显示,并行发送指令的后台线程越多,数据库CPU越高,效率越低。

解决方法:

  • 去掉ORDER BY,增加索引后,效果不明显。因为结果集大和查询频繁两个问题没有解决,因此考虑使用设计新的方案。

  • 新方案:设计指令发送线程池,生产者线程每台任务服务器只有一个线程,负责查询待发送指令,每次查询50条指令。每条指令包装成一个Runnable对象,放进ThreadPoolExecutor线程池,线程池大小参数设置为100或200。每当线程池满时,生产者停止生产指令,休息15秒后继续。消费者线程即线程池里的线程,参数设置为4,8或12(和不同指令类型的指令数据量成正比)。

改进后的方案,数据库CPU降到10%一下,发送效率单机提升6倍,且可线性扩展任务服务器。

11、压测TPS曲线剧烈下降或抖动

问题现象:50并发压测,TPS曲线正常应该是平缓的,波动不大,如果突然出现剧烈下降,并且短时间内无法恢复,则可能存在问题。

问题原因:一般是由于前置或bp的jvm进行垃圾回收,或者日志记录磁盘满导致的。

解决方法:如果不是特别剧烈的波动或者TPS曲线下降后长时间不反弹,则可以忽略该问题。否则,需要分析曲线下降的时刻,系统当时正在发生的事情。可以通过top命令监控当时CPU占用比价高的线程,也可以kill -3 pid杀javacore来查看线程堆栈。

六、优化案例

1、网银系统基本情况

1) 压测环境系统架构

压测环境不包括F5,只有1台WEB、1台前置(应用服务器)、1台BP(业务处理服务器)、1台DB、1台Redis服务器。

2)客户请求链路:

客户端压力机-〉Web-〉PRE-〉BP-〉DB2 ;

客户端压力机-〉Web-〉PRE-〉BP-〉挡板服务器(模拟后端服务方)。

3)系统特点:

  • 用户、账号权限校验较多。

    对公业务典型场景:经办、审核、查询、下载、批量。用户权限通过业务链控制。

  • 接口调用较多,且由前端组合调用接口。

    前后端分离,前端通过调用一个个接口来完成业务。接口粒度较细。登陆、支付转账经办需要15~19个接口完成一次交易。

  • 强一致性、高可用性。

    资金交易系统,一致性需要通过数据库来保证。

4)网银登录压测曲线

2、数据库消息队列案例(指令发送队列)

需求是:对于需要异步处理的交易指令,需要设计一个基于数据库的消息队列,我们称之为指令发送队列。该队列既要满足服务器的性能约束,又要满足每日处理交易量的要求。

1)优化前处理过程

后台任务每次从数据库指令表中排序并取最早的一条指令,获取该指令的详细交易信息,组装成报文并调用接口发送后台核心系统。

在压测环境下,该方案如果配置一个后台任务,无法达到系统设计的指令处理速度;如果配置多个后台任务,则会导致数据库CPU占用较高,影响其他联机业务的开展。

2)优化后处理过程

每台后台任务服务器配置一个生产者任务,20个消费者任务。

生产者任务一次取100个(可配置)指令,依次分配给20个消费者任务中空闲的任务,消费者任务获取指令详细信息,并组装报文后发送后台核心系统;生产者任务如果发现无空闲消费者任务,则等待15s后重新判断。

此方案显著降低了数据库CPU负载,合理利用了应用服务器的并发能力,处理效率大为提高,以上线程数和每次获取的指令数可以配置。

指令发送线程池模型图:

3、数据库死锁案例

1)死锁问题现象

当多台任务服务器同时运行大额指令发送后台线程,即多个生产者线程并行更新数据库指令表时,数据库快照检测到存在数据库死锁,或通过db2evmon -db corpdb -evm DB2DETAILDEADLOCK>dlock.txt 生成死锁监控文件,快照或监控文件中存在deadlock的情况。

DB2数据库有自动解除死锁功能,死锁超时时间默认为10s,数据库会随机选择一个死锁事务kill掉。本案例由于是后台任务,所以用户感觉不到死锁;如果是联机交易,一个用户会发现交易失败,另一个用户交易成功,但是会感觉交易变慢。指令发送后台任务模型详见下一章节内容。

2)数据库死锁发生原理

两个不同的数据库事务使用排它锁锁住了同一张表的不同行记录,并且互相等待读取对方锁住的行记录。

3)导致死锁的可能原因

全表扫描、大事务、事务之间对死锁访问顺序交叉等。

4)死锁问题排查过程

Step1:分析数据库快照和死锁监控日志,查看导致死锁的SQL,定位问题SQL。

Step2:问题SQL不存在事务之间对死锁访问顺序交叉的情况,当时尚不清楚程序中的全表扫描、大事务可能会导致死锁,因此做了以下实验:

  • 模拟问题SQL在两个不同的数据库客户端执行SQL,查看数据是否更新成功。

  • 完全按照源程序的SQL逻辑执行验证:

UPDATE ( SELECT BP_SRVR_IP FROM ${tableName} WHERE TSK_STAT='TODO' AND ( BP_SRVR_IP IS OR BP_SRVR_IP='') AND PRTY =? AND eff_tm <= CURRENT TIMESTAMP FETCH FIRST 50 ROWS ONLY WITH RS) t SET BP_SRVR_IP=?

  • 不加索引,A事务在R上加了X锁,B事务无法在任何记录上加X锁,B事务会等待A事务提交后再加锁。

  • 增加索引,A事务在R记录加了X锁,B事务在S记录加X锁,互不冲突。

Step3:实验发现查询SQL增加with RS隔离级别,查询效率会更高。当A事务已对记录R加X锁,B事务扫描到R记录时,如果是CS隔离级别,B事务会自动退出,返回空结果集;如果是RS隔离级别,B事务会等待A事务完成后,跳过R记录,对符合条件的R+1记录加X锁。

Step4:PRTY字段增加索引,没有出现死锁问题;或者不加索引,维持全表扫描不变,大事务改成小事务后,也没有出现死锁问题。

5)两点经验

  • 需要提前熟悉DB2锁的种类和作用,隔离级别的种类和作用,表锁和行锁发生的条件。比如,全表扫描时,DB2会在整个表上加表级锁;如果是增删改操作,会加表级排他锁。

  • 在不清楚死锁原因时,或者不了解锁的机制和隔离级别机制时,不同隔离级别下SQL的影响范围,可以在数据库客户端工具上进行手工小实验,验证数据库机制以及猜想。

七、后续提升网银系统性能备选方法

1、增加缓存的使用

  • 对于读多写少的数据,可以加载到分布式缓存,降低数据库压力;

  • 目前已经将部分参数和错误码数据放到分布式缓存,后续谨慎提高缓存使用率,降低数据库压力。

2、精简BP日志。删除交易访问记录日志表的操作。

3、合并、精简接口数量,前端缓存数据。

转自

快被系统性能逼疯了?你需要这份性能优化策略 https://www.toutiao.com/a6642043265338049037/?tt_from=mobile_qq&utm_campaign=client_share&timestamp=1546482914&app=news_article&utm_source=mobile_qq&iid=26112390770&utm_medium=toutiao_ios&group_id=6642043265338049037

java应用的优化【转】的更多相关文章

  1. 35 个 Java 代码性能优化总结

    前言 代码优化,一个很重要的课题.可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没用, ...

  2. Java程序性能优化技巧

    Java程序性能优化技巧 多线程.集合.网络编程.内存优化.缓冲..spring.设计模式.软件工程.编程思想 1.生成对象时,合理分配空间和大小new ArrayList(100); 2.优化for ...

  3. Java的性能优化

    http://www.toutiao.com/i6368345864624144897/?tt_from=mobile_qq&utm_campaign=client_share&app ...

  4. Java 代码性能优化总结

    前言 代码优化,一个很重要的课题.可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没用, ...

  5. Java代码性能优化总结

    代码优化,一个很重要的课题.可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没用,但是, ...

  6. 《Java程序性能优化:让你的Java程序更快、更稳定》

    Java程序性能优化:让你的Java程序更快.更稳定, 卓越网更便宜,不错的书吧

  7. [JAVA] java程序性能优化

    一.避免在循环条件中使用复杂表达式 在不做编译优化的情况下,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快. 例子: import java.util ...

  8. MemCached Cache Java Client封装优化历程

    1.什么是memcached?(从官网翻译翻译) 免费和开源.高性能.分布式内存对象缓存系统,通用在自然界,但用于加速动态web应用程序,减轻数据库负载. Memcached是一个内存中的键值存储为小 ...

  9. java常用重构优化总结--自己亲身体验

    代码重构  6大原则:    单一职责原则(一个类最好最好只有一种行为动机,太多承担职责会导致耦合度太高).    开放封闭原则(功能可以扩展,但是不可以内部修改).    依赖倒转原则(应该依赖抽象 ...

  10. Java GC 专家系列5:Java应用性能优化的原则

    本文是GC专家系列中的第五篇.在第一篇理解Java垃圾回收中我们学习了几种不同的GC算法的处理过程,GC的工作方式,新生代与老年代的区别.所以,你应该已经了解了JDK 7中的5种GC类型,以及每种GC ...

随机推荐

  1. Linux内存管理 (25)内存sysfs节点解读

    1. General 1.1 /proc/meminfo /proc/meminfo是了解Linux系统内存使用状况主要接口,也是free等命令的数据来源. 下面是cat /proc/meminfo的 ...

  2. 【原创】架构师必备,带你弄清混乱的JAVA日志体系!

    引言 还在为弄不清commons-logging-xx.jar.log4j-xx.jar.sl4j-api-xx.jar等日志框架之间复杂的关系而感到烦恼吗? 还在为如何统一系统的日志输出而感到不知所 ...

  3. 【翻译】WhatsApp 加密概述(技术白皮书)

    目录      简介      术语      客户端注册      会话初始化设置      接收会话设置      交换信息      传输媒体和附件      群组消息      通话设置   ...

  4. Tomcat服务器下载、安装、配置环境变量教程(超详细)

    请先配置安装好Java的环境,若没有安装,请参照我以下的步骤进行安装! 请先配置安装好Java的环境,若没有安装,请参照我以下的步骤进行安装! 请先配置安装好Java的环境,若没有安装,请参照我以下上 ...

  5. Announcing Microsoft Research Open Data – Datasets by Microsoft Research now available in the cloud

    The Microsoft Research Outreach team has worked extensively with the external research community to ...

  6. codeforces451C

    Predict Outcome of the Game CodeForces - 451C There are n games in a football tournament. Three team ...

  7. fiddler软件测试——Fiddler抓取https设置详解(图文)(摘抄)

    随笔- 8  文章- 0  评论- 0 fiddler软件测试——Fiddler抓取https设置详解(图文)   强烈推荐(原创亲测)!!!Fiddler抓取https设置详解(图文)转 本文主要说 ...

  8. SQLSERVER 实现三元运算符

    三元运算符在很多种编程语言中都存在,那么在SQL Server中有没有呢? 很遗憾,SQL server中并没有这个功能,三元运算符是什么呢? 这是一段表达式:[条件 ? 满足返回值 : 不满足返回值 ...

  9. opencv 图片旋转

    import cv2 as cv import numpy as np # 图片旋转 img = cv.imread('../images/face.jpg', flags=1) # flags=1读 ...

  10. 一丢丢学习之webpack4 + Vue单文件组件的应用

    之前刚学了一些Vue的皮毛于是写了一个本地播放器https://github.com/liwenchi123000/Local-Music-Player,如果觉得ok的朋友可以给个star. 就是很简 ...