项目场景问题

本文探讨了高并发场景下如何避免重复下单,利用Http幂等性和token机制确保操作唯一性。同时,讲解了大文件去重和排序的解决方案,以及Spring事务失效的常见场景。还深入介绍了Redis的lua命令和分布式锁的实现,以及如何防止Redis的缓存穿透、击穿和雪崩问题。此外,文章还涵盖了Java性能优化和内存泄漏的预防措施,以及如何优化高并发系统的性能。
摘要由CSDN通过智能技术生成

1. 避免重复下单

问题描述:用户快速点击了两次提交订单按钮,浏览器会向服务端发送两条创建订单的请求,最终创建一模一样的订单。
解决方案:

1.使用Http协议幂等性,http方法一次和多次请求一个资源应该具有相同的作用。多次请求和一次请求的效果是一样的。
2.提交订单添加token参数信息校验,用户提交订单时,后端业务逻辑会校验token。

2.大文件去重、排序

如何对43亿的qq号去重,相同的号码只保留一个,内存1G。

1.创建一个自己的集合BitMap来实现,java中已经有一个类BitSet,用bit来标识数据存不存在,43亿/8/1024/1024使用的内存不
到512MB。
2.使用布隆过滤器,布隆过滤器可能会出现判断错误的情况,其中如果布隆过滤器判断这个数据不存在,那么就一定不存在,如果判断这个数据存
在,可能不存在。

怎么对43亿数据排序

可以通过循环2^32-1的数据,判断这个这个数是否在bitmap(存放的是40亿数据)中存在,则输出这个数,输出的顺序就是升序排序。

3.Spring事务失效的场景

  1. 注解@Transactional配置的方法为非public权限修饰。
  2. @Transactional所在类不是Spring容器管理的bean。
  3. 注解@Transactional所在类注解修饰的方法被类的内部方法之间调用(this调用)。
  4. 业务代码存在异常,使用try catch捕获异常,而catch捕获之后没有抛出RuntimeException异常。
  5. @Transactional事务传播属性设置为NOTSUPPORTED。
  6. 数据库不支持事务。

4.幂等性解决方法

  1. Token机制
用户进入这个页面,后台生成token并且存入Redis,返回至客户端,发送请求时,携带token,通过判断token是否能被删除来确定
是否进行业务操作还是提示重复操作(这里为什么不使用判断token是否存在呢,因为第一个请求过来,token还没有删除,但是第二
个请求就过来了,所以就会发生判断错误)。

可以通过发送请求的页面携带的信息生成token携带至后台,使用Redis分布式锁(setnx加锁,expire自动释放锁),判断这个token是否存在,不存在才设置这个值,
设置成功,执行业务操作,业务完成之后,设置失败提示重复操作。

5.Redis的lua命令

eval命令

eval script numkeys key [key ...] arg [arg ...] # script 脚本程序,numkeys 后面key的数量
eval "if redis.call('set',KEYS[1],ARGV[1]) == 1 then redis.call('expire',KEYS[1],ARGV[2]) return 1 else 
return 0 end;" 1 k1 v1 60 #这里不会将过期时间设置成功,return的结果为0
eval "if redis.call('set',KEYS[1],ARGV[1]) then redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end;" 1 k1 v1 60 # 设置过期时间成功,return为1
#上面过程中使用到了redis的call函数,还有函数pcall可以使用
使用call函数,将后面expire命令修改错过,结果会提示错误,但是会添加key,pcall函数会返回1

eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 k1 k2 v1 v2 #会返回一列数据

evalsha和script load命令

script load ”if redis.call('set',KEYS[1],ARGV[1]) then redis.call('expire',KEYS[1],ARGV[2]) return 1 else 
return 0 end;#script load就是将eval执行的script添加到Redis服务器的脚本缓存中,并不立即执行脚本,而是得到一个脚
#本的结果,在脚本被加到缓存中之后,可以通过evalsha script load的结果 numkeys key [key ...] arg [arg ...]执行脚
#本,脚本会保存至SCRIPT FLUSH。
evalsha "496407384aa2585c7cf039961fe3e1e8c42eea22" 1 k1 v1 60 #执行效果相同

在这里插入图片描述

script exists

script exists [sha1 ...] #根据顺序返回结果,1标识存在,0表示不存在
script exists "496407384aa2585c7cf039961fe3e1e8c42eea22"

script flush

script flush #清空Redis服务端所有的lua脚本缓存

script kill

script kill #杀死当前运行的lua脚本,并且仅当这个脚本没有执行过任何写操作时才有效。这个命令可以终止运行时间长的脚本。
#如果当前运行的脚本已经执行过写操作,那么执行script kill无法杀死这个脚本,因为这个违法lua原子性原则。可行的办法是:
shutdown nosave# 命令停止redis服务。

6. Redis分布式锁实现方式

  1. setnx(加锁) + expire(自动释放锁)。不是原子操作,设置自动释放锁的过程中,如果服务宕机了,这个锁就永远获取不到了。
  2. setnx+value(设置过期时间)。这里解决了上面的问题,但是还有一个问题是,单锁过期时,多个客户端请求并发,修改value过期时间,导致都无法加锁成功,业务执行失败。(这个问题应该不是很严重,因为加锁就是为了只让一个线程调用)。
  3. Lua脚本。解决了上述两个问题,因为lua脚本命令具有原子性。
# lua脚本
if redis.call('setnx',KEYS[1],ARGV[1]) then redis.call('expire',KEYS[1],ARGV[2])
	return 1
else
	return 0
end;
# ********************************

  1. set key value [EX seconds|PX millisenconds] [NX|XX] [KEEPTTL]。NX表示不存在值设置,XX表示存在值才设置。(当前线程执行过程中,到期了锁被释放,但是把key删除了,导致其他线程的锁被释放了。)在这里可以采用设置一个标识value来确定是不是自己的锁,但是这里如果使用了get+del并不是原子操作,也可能导致别人的锁被释放。所有还是采用lua脚本更加安全。
  2. Redisson解决锁过期释放问题。
  3. RedLock+Redisson实现。

7.高并发系统性能优化

  1. 尽量使用缓存,用户缓存、信息缓存等,花内存来做缓存,大量减少与数据库的交互,提高性能。
  2. 使用jprofiler工具找出性能瓶颈,减少额外的开销。
  3. 优化数据库查询语句,减少之间使用hibernate等工具直接生成的工具。
  4. 优化数据库结构,通过索引提高查询效率。
  5. 统计的功能尽量做缓存,或按每天一统计或者定时统计相关报表,避免需要时进行统计的功能。
  6. 能使用静态页面的地方尽量使用静态页面,减少容器的解析。
  7. 搭建集群解决单台服务器的性能的瓶颈问题。

8.Java性能优化

  1. 复用优化。代码复用提取工具类、缓存、池化。
  2. 计算优化。并行、多进程、多线程。异步。惰性加载。
  3. 结果集优化。去掉不需要的数据。缓存。
  4. 资源冲突优化。
  5. 算法优化。
  6. JVM优化。

9.内存泄漏

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

  1. 静态集合类:一些静态容器ArrayList、HashMap,因为这些容器时静态的,那么它的生命周期是与程序一致的,则容器中的对象在程序结束之前并不能把它们释放,从而造成内存的泄漏。就是长生命周期的容器对象持有短周期对象的引用,尽管短生命周期对象不再使用,但是因为长生周期对象持有它而导致它不能被回收。
  2. 各种连接:如数据库连接、网络连接、IO连接等。在对数据库进行操作的过程中,首先需要建立于数据库的连接,当连接不在使用,需要调用close方法来释放连接。如果大量的连接不显性的关闭,将造成大量对象无法被回收,造成内存泄漏。
  3. 变量不合理的作用域:规范代码。
  4. 内部类持有外部类:
  5. 改变集合中元素的hash值:

10.Redis缓存穿透、缓存击穿及缓存雪崩

缓存穿透:客户端发起请求,需要查询某个数据,可是这个数据在Redis数据库中不存在,又去关系型数据库查,发起大量的这样查询请求,导致数据库宕机。
解决方案:1.使用加锁的方式去查询数据库,查到结果返回至Redis保存,如果没有,放一个空的结果。2.加过滤器,需要查询的数据在里面才放行,否则直接不让通过。使用布隆过滤器(问题:key可能不存在可能放行)。

缓存击穿:一个key设置了有效时间,可是在有效时间的附近,发生了大量的请求,因为key失效了,所有的请求都到了关系型数据库。
解决方案:多过期的key加Redis锁查询,限制并发,并通过第一个请求,将结果放入缓存中。

缓存雪崩:当某一时刻缓存集体失效,这个时候有大量的查询请求,所有请求都到了数据库层,引起数据库的宕机。
解决方案:1.针对数据库层面做二级缓存。2.做数据预热,在正式部署之前,将数据添加至缓存中。3.均匀设置缓存失效时间。4.限流,如果Redis宕机,限流,避免大量请求查询使数据库宕机。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值