注册优化

注册功能存在很严峻的短时间访问压力,并且这个功能会直接影响用户体验(我连账号都注册不了何谈后续呢?)。为了防止这个问题的发生,一共经历了三个阶段的修改:

第一版:写锁+数据库判重+直接数据库插入

虽然是初版代码,以实现功能为目的。但也只能说,这种操作写出来就是注定两个字:失败。 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——后端效率优化相关推荐

  1. 巴别塔合约作战终端开发日记3——服务器负载优化

    对于性能优化方面,首先服务器硬件配置还有2核2G内存5MBPS带宽.从硬件上来说其实并不支持太多的访问量,即便是加了Redis作为高速缓存,效率也堪忧.需求方面存在一条:活动会在定时开放,并且为了竞技 ...

  2. 巴别塔合约作战终端开发日记2——Linux系统部署项目

    1. 安装XShell和Xftp 其中Xshell用于输入Linux命令,Xftp可以方便地进行文件传输. 2. 连接服务器 之后输入用户名密码保存指纹秘钥就可以正常使用了. 服务器就是一台Linux ...

  3. 巴别塔合约终端开发日记1-----技术选择

    巴别塔合约终端开发日记1-----技术选择 需求 拿到开发任务之后,组里给出的开发时间差不多两个月左右.由于开发人员只有我自己一个人,开发时间还是比较紧张的.在开头的时候很重要,避免频繁修改代码才能尽 ...

  4. 萌新一手包App前后端开发日记(一)

    从事Android移动端也有些日子了,还记得一开始选择这份工作,是憧憬着有朝一日能让亲朋好友用上自己开发的软件,但日子久了才发现,并不是所有的公司,所有的项目的适用群体都是"亲朋好友&quo ...

  5. linux桌面小程序开发日记3(pyqt5+yolov5)

    linux桌面小程序开发日记3 vm虚拟机配置yolov5环境(Ubuntu 18.04) 最后一篇博客地址:https://blog.csdn.net/Liuchengzhizhi/article/ ...

  6. EOS智能合约与DApp开发入门

    # EOS智能合约与DApp开发入门 EOS智能合约与DApp开发入门,并最终完成一个基于React和 EOS的便签DApp. ## 终端使用建议 - 1#终端用于nodeos运行 - 2#终端用于k ...

  7. YunTable开发日记(16)-教程(0.9版RC)

    为了帮助广大初学者能尽快上手YunTable,我代表整个YunTable核心团队写一篇使用教程,原因很简单,那就是我写文章比较熟练,呵呵:) 本教程将首先给大家介绍YunTable的架构和命令接口,以 ...

  8. 鸿蒙版瑞幸咖啡开发日记(二)首页功能实现

    鸿蒙版瑞幸咖啡开发日记之首页功能实现 1.需开发的功能归纳 2.首页功能开发 2.1 顶部TabList开发 2.2 一级分类数据渲染 2.3 右侧大分类模板渲染 2.4 右侧具体咖啡数据渲染 2.5 ...

  9. 鸿蒙版瑞幸咖啡开发日记(一)首页布局设计

    鸿蒙版瑞幸咖啡开发日记之首页布局设计 1.整体布局设计思路 2.三大模块开发 2.1 头部信息栏的开发 2.2 中间菜单栏的开发 2.2.1 一级分类菜单 2.2.2 二级分类菜单思路整理 2.2.3 ...

最新文章

  1. 军队可以用oracle,使用Oracle JRockit 提高tomcat性能
  2. sync是同步还是非同步_MySQL半同步复制你可能没有注意的点
  3. vue中使用mockjs
  4. 小时级的进度监控工具
  5. 通过jquer连接数据库里面的数据、LINQ简介
  6. Python风格总结:函数
  7. SPSS统计功能与模块对照表
  8. MindNode 5 for Mac(思维导图)中文版
  9. DHCP、PNF、SXE、DNS等综合实验
  10. 打印机驱动兼容问题导致打印乱码
  11. 游戏美术师的火绝对不是捧出来的!不看不知道游戏模型师这么吃香
  12. 五、肿瘤全基因组学体细胞结构突变特征(Patterns of somatic structural variation in human cancer genomes)
  13. [P4V]Perforce(P4V)使用教程
  14. 值得学习的C/C++开源项目
  15. ANDROID中的VOLD分析
  16. Phoenix重磅 | Phoenix核心功能原理及应用场景介绍
  17. 批量修复自定义标题带来的word题注错误:错误,文档中没有指定样式的文字
  18. SpringBoot 项目上传文件异常【java.io.IOException: Stream closed】
  19. CISA考试通过了!!
  20. linux swap分区满了,Linux下如何释放内存、swap分区满了怎么办!

热门文章

  1. 支付宝VIE的罪与罚
  2. 「Activiti精品 悟纤出品」Activiti插件来助你一臂之力 - 第327篇
  3. 《MySQL必知必会》读书笔记
  4. 运行python的两种方式磁盘式_day03-python-学习笔记
  5. 正确的IE卸载与重装方法
  6. 飞龙:蒙语“牵手”人工智能的拓荒者
  7. WSL(ubuntu) 和Cisco Anyconnect存在的网络连接异常问题
  8. 股市做短线操作技巧 股市做短线操作技巧
  9. 【图像重建】基于布雷格曼迭代(bregman alteration)算法集合ART算法实现CT图像重建附matlab代码
  10. 2018php最新面试题之PHP核心技术