Worker线程在执行的过程中,有一部计算时间需要占用CPU,另一部分等待时间不需要占用CPU,通过量化分析,例如打日志进行统计,可以统计出整个Worker线程执行过程中这两部分时间的比例,例如:线程计算和等待的时间是1:1,即有50%的时间在计算(占用CPU),50%的时间在等待(不占用CPU):
1)假设此时是单核,则设置为2个工作线程就可以把CPU充分利用起来,让CPU跑到100%
2)假设此时是N核,则设置为2N个工作现场就可以把CPU充分利用起来,让CPU跑到N*100%
结论:
N核服务器,通过执行业务的单线程分析出本地计算时间为x,等待时间为y,则工作线程数(线程池线程数)设置为 N*(x+y)/x 也就是: N * (1+y/x),能让CPU的利用率最大化, 等待时间越长,线程数就可以越多。
技术方案
具体的业务逻辑
创建订单完成以后,向RabbitMQ的延迟交换机发送延迟30分钟的消息,消息中存放了订单的id。
30分钟以后,消息到期会从MQ出队,消费者收到这个消息以后,根据消息中的订单id查询数据
库,检查订单的状态,如果是已经支付什么事情也不需要做,如果是未支付,则把订单状态
修改成已取消。
如何来使用?
实现原理
@EnableAspectJAutoProxy(exposeProxy = true) + AopContext.currentProxy()
@Lazy + 注入bean本身
@Service
public class AopService {
@Autowired
@Lazy
private AopService selfService;
public void f1(){
// f2()的事务会失效
this.f2();
// 第一种方式
AopService aopService = (AopService)AopContext.currentProxy();
aopService.f2();
// 第二种方式
selfService.f2();
}
@Transactional(rollbackFor = Exception.class)
public void f2(){
log.info("some business logic in f2()");
}
}
防止xss主要就是对一些特殊字符做替换。
我们是写了一个Filter,在Filter中会把原始的HttpServletRequest替换成我们自定义的一个Request,我们在这个自定义的Requst中去做了特殊字符的替换,主要是替换掉比如
防止sql注入主要就是使用SQL的预编译。
1)导出JVM内存映像
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./
jmap -dump:format=b,file=heap.hprof [pid]
2)使用MAT之类的工具分析内存泄漏的原因
客户端点击导出按钮以后,服务端记录一个日志,状态是待处理,给客户端返回日志的id,然后服务端异步做excel的导出,完成以后,把excel上传到oss,把下载的url地址记录到日志,并把日志的状态改成已完成。
客户端拿到这个id以后可以到服务端的一个单独的页面上做查询,如果是已完成,则可以点击下载按钮去下载excel。
MQ削峰
Redis预减
//请求url:/miaosha/product/12234
//服务端controller:
@PostMapping(“/product/miaosha/{productId}”)
public boolean miaosha(@PathVariable(“productId”) long productId){
// 秒杀活动开始之前,把商品id和商品的库存数量加载到redis中,key:商品id,value:库存数量
// redis预减库存,让1万个人去抢数据库中的10个商品是没有意义的,只让10个人去抢就可以了
int count = redisTemplate.decr(“”+productId);
if(count <= 0){
// 秒杀失败
return false;
}
//预减成功以后, 再放入MQ,返回给前端排队中
kafkaTemplate.send(productId);
// 从MQ收消息,下单,SQL中要加上stock>0的判断,防止把库存扣成负数
select * from product where id = #{productId} //version
int ret = update product set stock=stock-1 where id = #{productId} and stock>0
if(ret > 0){
// 秒杀成功,生成订单
return true;
}else{
// 秒杀失败
return false;
}
}
验证码,非常复杂,防止机器人刷接口,减少瞬间的并发
活动开始之前换接口,换页面,防机器人
回仓
卖不完是允许的,卖超是不允许的!!
参考秒杀功能
主要就是设置了每一个节点的名称、集群的名称、启动的端口、数据存储路径以及其他节点的IP和端口,集群名称一样的话,他们就能组成一个集群。
services:
es01:
image: elasticsearch:7.12.1
container_name: es01
environment:
- node.name=es01
- cluster.name=es-docker-cluster
- discovery.seed_hosts=es02,es03
- cluster.initial_master_nodes=es01,es02,es03
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
volumes:
- data01:/usr/share/elasticsearch/data
ports:
- 9200:9200
networks:
- elastic
红黑树是一种自平衡二叉搜索树,因此查找和插入操作的时间复杂度为 O(log n),而链表的时间复杂度为 O(n)。在哈希冲突比较严重的情况下,使用红黑树能够更快地进行搜索和插入操作。
ES与MySQL的数据同步分成了3部分:
我们的系统中是使用的基于MQ的异步双写来实现数据同步的,具体来说,当mysql数据发生变化的时候,会向MQ中发一个消息,然后我们的搜索服务会接收这个消息,根据消息中的数据Id构造出完整的数据,然后同步到ES中。
为了提高MQ的性能,我们没有对消息的可靠性做特殊处理,因此理论上会存在消息丢失导致数据不一致的风险,所以,我们有一个定时任务,每周会把mysql中的数据全量的再导入一次ES,如果这中间发现不一致,人工手动处理。
// 启动两个容器
docker run --name d1 --network=host -e port=8081 -d demo
docker run --name d2 --network=host -e port=8082 -d demo
// 进入d1容器,执行telnet命令
docker exec -it d1 sh
telnet 192.168.137.138 8082
GET /demo 正常输出
docker network create mynet
// 启动两个容器
docker run --name d3 --network=mynet -e port=8083 -d demo
docker run --name d4 --network=mynet -e port=8084 -d demo
// 进入d3容器,执行telnet命令
docker exec -it d3 sh
telnet d4 8084
GET /demo 正常输出
//Dockerfile
FROM java:8-alpine
COPY ./demo.jar /tmp/app.jar
ENV port 8080
EXPOSE $port
ENTRYPOINT java -Dserver.port=$port -jar /tmp/app.jar
// 接口
@RestController
public class DemoController {
@GetMapping("/demo")
public String demo() {
return "demo";
}
}
//docker-compose.yml
version: '3.9'
services:
d1:
image: demo
environment:
port: '8081'
ports:
- '8081:8081'
container_name: d1
hostname: d1
d2:
image: demo
environment:
port: '8082'
ports:
- '8082:8082'
container_name: d2
hostname: d2