巴别塔合约作战终端开发日记4——后端效率优化
注册优化
注册功能存在很严峻的短时间访问压力,并且这个功能会直接影响用户体验(我连账号都注册不了何谈后续呢?)。为了防止这个问题的发生,一共经历了三个阶段的修改:
第一版:写锁+数据库判重+直接数据库插入
虽然是初版代码,以实现功能为目的。但也只能说,这种操作写出来就是注定两个字:失败。 MySQL作为效率瓶颈,虽然代码逻辑正确,先加锁,再判重,再入库。但是强行将多线程变为单线程,然后再使用效率瓶颈判重,虽然最后一步逃不掉,但是每次数据库操作都需要连接,虽然有连接池的存在。然后再加上通信开销,这效率想想就爽啊。实测QPS大概在30左右。就这效率也想给用户体验?这不是折磨用户么?这不改天理难容。
第二版:分布式锁+Redis判重+直接数据库插入
根据账号的长度和首字母进行分类,每个种类的账号判重是互不影响的(首字母不同,长度也不同还能重?)。将单线程判重变为多线程判重。判重位置从数据库改成Redis,解决了MySQL慢的问题。最后插入数据库怎么解决,慢就慢呗,数据总不能不持久化了吧。
实测QPS在80左右,这个效率还是很不错的,但是转折点是在分布式锁这个位置,毕竟将单线程变为了多线程。然后就是又一个问题了,为啥还是这么慢,100都没过。后来想到了可能是Redis的问题,检查了一下Redis的执行时间,发现确实,Redis分布式锁在请求的时候大量请求压在Redis上,而且Redis使用的是单线程处理操作,这个效率就不见得很高了。然后就是最致命的,插入数据库还是浪费了很多时间。这效率也不过关,还能操作。
第三版:分段锁+Redis配合内存判重+直接数据库插入
思路与第二版一致,唯一的区别就是把一部分操作放到了内存上:将锁从分布式锁改成了Synchronized,然后在堆里开了一块儿内存来存放账号,内存判重配合Redis判重做操作。
这个思路牺牲了内存来换取效率,开56把锁内存是撑得住的,但是大量账号堆积在内存里怎么办?这个好办,为啥要用Redis配合,开一个定时任务每10秒执行一次,检查账号池里是否有数值,有的话就全存到缓存中去。这个位置只要能赌在内存中发现重复,就能有效减少一次Redis的连接开销。这样的话内存只要不在10秒内爆炸,就可以正确运行。但是实测下来QPS也就100左右没高多少,唯一还有问题的就是最后一步插入数据库了,打印了一下执行时间,发现确实,时间都浪费在最后一步了,但是这一步始终逃不掉,怎么优化呢?
第四版:分段锁+Redis配合内存判重+数据库批处理
思路与第三版一致,但是优化数据库操作:将判重结束的用户信息留在内存,通过自生成ID直接返回,设立一个定时任务,定时向数据库执行一个批处理插入,这样就可以极大幅度降低最后一步入库的时间,这样绝大部分操作都被放到了内存上,执行效率那能不快吗?
这个思路其实还可以优化,就是将分段锁的synchronized换成CAS,减少锁开销,但是在高并发量的情况下可能很消耗CPU,不过实际用户数量也不会特别多,那CAS其实是最优解。由于偏向锁的出现,在低并发情况下,锁开销实际也还好,然后再加上项目临近上线,也没太多时间测试,就没有继续优化了。
最后其实还有一个很关键的东西就是:每次执行插入操作也就是insert()方法时,由于使用的是远程连接,那么理论上都会产生一个与数据库通信的开销。实测了一下确实是这样的。然后MyBatisPlus自封装有insertBatch()批量插入,但是翻看了源码,底层使用的是迭代器循环调用insert()方法。那这样批处理就没有了意义啊,还是要吃通信开销的时间损耗。没有办法,使用HttpClient并额外编写了一个模块部署在数据库服务器上,每次批处理不调用MyBatisPlus的批量插入,直接使用HTTP请求发送数据到数据库服务器上,让模块执行本地批量存储,这样就相当于使用了一次通信损耗,插入了若干条数据。实测下来,最后这样做总耗时20-30毫秒,但是执行常规批处理是没有这种效率的。
第四版就非常舒服了,QPS实测在300左右,这优化还是杠杠的。
2022/3/1 补充:今天在维护项目的时候,发现对注册功能进行了限流。起初的意愿是:害怕并发过高打垮数据库,使用了令牌桶算法进行了限流。但是经过这么多轮优化,注册功能没有直接操作过数据库。那我这限流限了个啥啊????把限流的AOP实现去掉之后,本机注册一秒直接跑完了3000条请求。放服务器上肯定也会更快。(亏了。。。我投出去的简历QPS都写得很低,都亏了。。。。。)
分段锁思想
无论在哪个操作里,对A的操作永远与B无关,如果强行使用锁来保证线程安全,那执行效率是一个很大程度的损耗。于是,我们为什么不能把本身就相互无关的东西放行呢?
详细解释一下就是:所有与用户A相关的操作,那么在用户B执行操作的时候,两者只要没有使用共享资源,那么这些操作完全无关,不会产生并发问题。如果此时只用一个synchronized来操作,那就相当于白白浪费了执行时间。
回归到实现上,synchronized锁需要对象,lock同样也需要自己创建对象使用。那么假设现在有10000个用户,上哪去找10000个锁来呢?可能会想到,锁字符串吧,这个好搞。但是字符串由于存在常量池的问题,使用锁是不允许锁字符串的,容易发生意想不到的问题。
那么,分布式锁就可以很方便地使用,使用Redis分布式锁只需要使用key标记就可以,通过id来控制key生成就可以。本项目使用的分布式锁Redisson ,直接导入maven依赖,然后添加配置即可。
<!-- 分布式锁Redisson --><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.6.5</version></dependency>
@Configuration
public class RedisConfiguration {@Value(value = "${RedissonPassword}")private String password;@Value(value = "${RedissonHost}")private String redissonAddress;//向Bean容器中添加Redisson分布式锁@Beanpublic Redisson redisson(){Config config = new Config();//此时为单机模式//绑定Redis所在的IP和使用哪个DataBase,这里设置为第0个DatabaseSingleServerConfig singleServerConfig = config.useSingleServer().setAddress(redissonAddress).setPassword(password).setDatabase(0);return (Redisson)Redisson.create(config);}}
添加至容器之后 ,使用时只需要@Autowired注入,然后像Lock类一样使用即可。
@Autowired
Redisson redisson;String lockKey = "Challenger::"#获得锁,锁的是目标key
RLock lock = this.redisson.getLock(lockKey);#加锁
lock.lock();
#解锁 记得写在final里
lock.unlock();
数据库处理逻辑优化
数据库处理逻辑方面,由于使用了MySQL远程连接,远程连接通信也是需要消耗资源的。在获取连接的开销上额外增加消耗,也就是说,每一次执行sql语句,代价都是非常大的,对于部分并发要求较高的程序,这会成为严重的效率瓶颈。
对此,本项目中的注册功能,在活动开启当天肯定是热点功能,为了不影响效率,就需要额外优化。
注册业务的优化在注册优化部分提到过,其余的业务做到的是:能批处理就绝对不单独去执行一条sql语句。使用远程连接给我最大的一个感觉就是,即便使用了@Transcational注解使用事务处理,同一个连接处理两条sql语句的效率也是很感人的。这是真实的感受,实际打印一下执行效率就能看到了,真的效率感人。
Redis性能优化
Redis作为高速缓存,在存储热点key提高响应速度这是公认的,但是,在项目实际使用的时候,Redis往往响应速度并不是很理想,在查询审核记录方面,在1000并发的情况下,会出现Redis执行速度逐渐变慢的问题。竟然拖慢了我的访问效率?这可是读请求啊,这再反应慢,项目也太失败了吧。
看到这个问题的时候,第一反应是回归到硬件上,硬件太差了导致Redis反应不理想。然后发现其实并不是这样的。转念想一下,去打印了一个简单读取数字的Redis请求,发现秒并发量10000的情况下,返回一直都很快,没有出现拖沓的情况。这个时候就很不解了,这是啥啊?然后又对比了一下Set的存取和直接key的存取,发现效率其实没差。
最后终于定位到问题原因了,由于审核列表在后期会越来越多,测试用的账号有10+条审核记录,存储的都是Java类型,一个对象的数据有不少。这样就会出现一个情况:Redis反应是很快,但是时间都浪费在打包给你返回数据上了,要返回的数据太大了。
最后对大型数据做了一个分页,这个问题就完美解决了,每次只返回一部分数据,减少数据量。一次性返回那么多数据,用户实际想要查看的并不是很多。
异步处理
对于耗时业务,比如邮件发送(这个是真的很耗时),部分操作涉及的日志存储和不重要的数据处理(与代币消费有关的消费记录日志)。都可以使用多线程来做,提高响应速度。
在异步处理方面使用的策略是:线程池。
线程的创建和销毁也是需要消耗资源的,那既然这些操作都是频繁出现的,那就直接搞一两个线程帮我们后台处理就好了,需要直接拿来用,不要了就直接还就好了。
巴别塔合约作战终端开发日记4——后端效率优化相关推荐
- 巴别塔合约作战终端开发日记3——服务器负载优化
对于性能优化方面,首先服务器硬件配置还有2核2G内存5MBPS带宽.从硬件上来说其实并不支持太多的访问量,即便是加了Redis作为高速缓存,效率也堪忧.需求方面存在一条:活动会在定时开放,并且为了竞技 ...
- 巴别塔合约作战终端开发日记2——Linux系统部署项目
1. 安装XShell和Xftp 其中Xshell用于输入Linux命令,Xftp可以方便地进行文件传输. 2. 连接服务器 之后输入用户名密码保存指纹秘钥就可以正常使用了. 服务器就是一台Linux ...
- 巴别塔合约终端开发日记1-----技术选择
巴别塔合约终端开发日记1-----技术选择 需求 拿到开发任务之后,组里给出的开发时间差不多两个月左右.由于开发人员只有我自己一个人,开发时间还是比较紧张的.在开头的时候很重要,避免频繁修改代码才能尽 ...
- 萌新一手包App前后端开发日记(一)
从事Android移动端也有些日子了,还记得一开始选择这份工作,是憧憬着有朝一日能让亲朋好友用上自己开发的软件,但日子久了才发现,并不是所有的公司,所有的项目的适用群体都是"亲朋好友&quo ...
- linux桌面小程序开发日记3(pyqt5+yolov5)
linux桌面小程序开发日记3 vm虚拟机配置yolov5环境(Ubuntu 18.04) 最后一篇博客地址:https://blog.csdn.net/Liuchengzhizhi/article/ ...
- EOS智能合约与DApp开发入门
# EOS智能合约与DApp开发入门 EOS智能合约与DApp开发入门,并最终完成一个基于React和 EOS的便签DApp. ## 终端使用建议 - 1#终端用于nodeos运行 - 2#终端用于k ...
- YunTable开发日记(16)-教程(0.9版RC)
为了帮助广大初学者能尽快上手YunTable,我代表整个YunTable核心团队写一篇使用教程,原因很简单,那就是我写文章比较熟练,呵呵:) 本教程将首先给大家介绍YunTable的架构和命令接口,以 ...
- 鸿蒙版瑞幸咖啡开发日记(二)首页功能实现
鸿蒙版瑞幸咖啡开发日记之首页功能实现 1.需开发的功能归纳 2.首页功能开发 2.1 顶部TabList开发 2.2 一级分类数据渲染 2.3 右侧大分类模板渲染 2.4 右侧具体咖啡数据渲染 2.5 ...
- 鸿蒙版瑞幸咖啡开发日记(一)首页布局设计
鸿蒙版瑞幸咖啡开发日记之首页布局设计 1.整体布局设计思路 2.三大模块开发 2.1 头部信息栏的开发 2.2 中间菜单栏的开发 2.2.1 一级分类菜单 2.2.2 二级分类菜单思路整理 2.2.3 ...
最新文章
- 军队可以用oracle,使用Oracle JRockit 提高tomcat性能
- sync是同步还是非同步_MySQL半同步复制你可能没有注意的点
- vue中使用mockjs
- 小时级的进度监控工具
- 通过jquer连接数据库里面的数据、LINQ简介
- Python风格总结:函数
- SPSS统计功能与模块对照表
- MindNode 5 for Mac(思维导图)中文版
- DHCP、PNF、SXE、DNS等综合实验
- 打印机驱动兼容问题导致打印乱码
- 游戏美术师的火绝对不是捧出来的!不看不知道游戏模型师这么吃香
- 五、肿瘤全基因组学体细胞结构突变特征(Patterns of somatic structural variation in human cancer genomes)
- [P4V]Perforce(P4V)使用教程
- 值得学习的C/C++开源项目
- ANDROID中的VOLD分析
- Phoenix重磅 | Phoenix核心功能原理及应用场景介绍
- 批量修复自定义标题带来的word题注错误:错误,文档中没有指定样式的文字
- SpringBoot 项目上传文件异常【java.io.IOException: Stream closed】
- CISA考试通过了!!
- linux swap分区满了,Linux下如何释放内存、swap分区满了怎么办!
热门文章
- 支付宝VIE的罪与罚
- 「Activiti精品 悟纤出品」Activiti插件来助你一臂之力 - 第327篇
- 《MySQL必知必会》读书笔记
- 运行python的两种方式磁盘式_day03-python-学习笔记
- 正确的IE卸载与重装方法
- 飞龙:蒙语“牵手”人工智能的拓荒者
- WSL(ubuntu) 和Cisco Anyconnect存在的网络连接异常问题
- 股市做短线操作技巧 股市做短线操作技巧
- 【图像重建】基于布雷格曼迭代(bregman alteration)算法集合ART算法实现CT图像重建附matlab代码
- 2018php最新面试题之PHP核心技术