秒杀场景在电商平台是十分常见的,这种营销活动往往具有时间短,并发量大的特点。
TPS:数据库每秒执行的事务数。
QPS:数据库每秒执行的SQL数。
对于msql数据库,8核CPU16G内存通常TPS:1000 QPS:20000
用户界面点击请求 ---->服务器收到http请求 ------>修改数据库库存
对于秒杀系统这种短时间的海量请求往往是通过两种思路解决
CPU
处理,例如多线程,负载均衡,集群用户操作维度:前端页面限制用户请求频率,防止页面重复点击
服务器架设:机器集群,获得更高的的处理能力
负载均衡器nginx
用户请求先到nginx
,再由nginx
转发请求到tomcat
,nginx
监听80
端口随机转发到tomcat
集群下的机器。配置nginx.conf
文件如下
#user nobody;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
#并发连接数
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
#tomcat集群
upstream localhost{
#这里指定多个源服务器,ip:端口,80端口的话可写可不写
server 127.0.0.1:8080;
server 127.0.0.1:8081;
}
server {
#监听端口
listen 80;
server_name localhost;
location / {
#启动代理
proxy_pass http://localhost;
}
}
}
Java程序编码:对于秒杀系统很大的请求量很可能是由脚本发起的频繁请求,所以可以在后台编码中对请求频率进行限制。
1.使用redis存储请求记录,并设置过期时间,当用户请求时,如果redis已经存在则代表短时间请求过,不再让用户请求,直接返回,如果没有则同意用户进行请求。这里setnx+setex要使用组合命令相当于一条命令,用两条命令redis就效率减半了。
/**
* 如果redis内没有这个则插入,否则不插入,这是一个命令
* 如果是setnx + setex 设置值再设置时间是两条命令。
* @param key
* @param value
* @param expire 有效时间 单位 秒
* @return
*/
public Boolean setIfAbsent(String key,String value,long expire){
Boolean isSuccess = redisTemplate.execute(new RedisCallback() {
@Override
public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
Boolean result = redisConnection.set(key.getBytes(),value.getBytes(),Expiration.seconds(expire),RedisStringCommands.SetOption.SET_IF_ABSENT);
return result;
}
});
return isSuccess;
}
controller限制请求
//redis 实现请求频率限制
boolean result = redisUtil.setIfAbsent(userId,goodsId,5L);
//没有插入成功,请求频率太快
if(!result){
System.out.println("您的频率太快了,请稍后再试");
return "error";
}
2.通过上面的频率限制后,真实请求量还是十分庞大,下面使用令牌桶算法进一步限流。
令牌桶:先买票再上车!通过预先(异步)初始化一个和商品数相当数量的令牌池放在内存中,用户先那令牌再去数据库操作。
/**
* 初始化一个令牌桶
* @param num
*/
public void loadTokens(int num){
//相当一个Queue 从左边压入 右边取出
for(int i=0;i
在servlet初始化时加载令牌桶
@PostConstruct
public void init(){
//实际应通过任务调度 异步加载令牌桶,
redisUtil.loadTokens(100);
}
@ResponseBody
@RequestMapping(value = "/miaosha",method = RequestMethod.POST)
public String miaosha(String goodsId,String userId){
//redis 实现请求频率限制
boolean result = redisUtil.setIfAbsent(userId,goodsId,5L);
//没有插入成功,请求频率太快
if(!result){
System.out.println("您的频率太快了,请稍后再试");
return "error";
}
//令牌桶算法
//先取得令牌
String token = redisUtil.getToken();
if(token == null || "".equals(token)){
//没有抢到令牌,秒杀失败
System.out.println(userId+"没有抢到令牌,秒杀失败");
return "error";
}
//秒杀逻辑 消耗资源
if(orderService.miaoSha(goodsId,userId)){
return "ok";
}
return "error";
}
根据分流限流的思想对海量请求进行处理
1.前端禁止重复请求 限流
2.负载均衡服务器集群 分流
3.后端禁止快频率请求 限流
4.令牌桶算法 限流
详情代码见秒杀系统