学习路线:
这个方向初期比较容易入门一些,掌握一些基本技术,拿起各种现成的工具就可以开黑了。不过,要想从脚本小子变成黑客大神,这个方向越往后,需要学习和掌握的东西就会越来越多以下是网络渗透需要学习的内容:
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
![](https://img-blog.csdnimg.cn/20190623180802101.png) ![](https://img-blog.csdnimg.cn/20190623180814293.png)
**3. 基于库存令牌桶 + MQ 实现异步修改库存并提交订单**
在高并发情况下,如果有1万个用户同时秒杀某一商品,对数据库频繁的IO操作,可能会产生数据库崩溃问题(分表分库,读写分离治标不治本),解决方法:基于MQ+库存令牌桶实现。
同时有1万个请求实现秒杀,商品库存只有100个, 实现只需要修改库存100次就可以了:
方案实现流程:商品库存是多少,就提前在redis生成多少对应的库存令牌(这里即为100),在1万个请求中,只要谁能够获取到令牌谁就能够秒杀成功, 获取到秒杀令牌后,在使用mq异步实现修改减去库存。
代码实现:
**① 编写生成100个令牌桶接口**
![](https://img-blog.csdnimg.cn/20190623204556611.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FraXJhTmlja3k=,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20200211140113922.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FraXJhTmlja3k=,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20190623220449776.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FraXJhTmlja3k=,size_16,color_FFFFFF,t_70) ![](https://img-blog.csdnimg.cn/20190623222025666.png)
**② 编写获取token代码,并用mq异步发送**
![](https://img-blog.csdnimg.cn/20190623210555849.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FraXJhTmlja3k=,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20190623210617587.png)![](https://img-blog.csdnimg.cn/20190623210648961.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FraXJhTmlja3k=,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20190623211002578.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FraXJhTmlja3k=,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20190623211123792.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FraXJhTmlja3k=,size_16,color_FFFFFF,t_70)
**RabbitMQ消费者**
@Component
Slf4j
public class StockConsumer {
@Autowired
private SeckillMapper seckillMapper;
@Autowired
private OrderMapper orderMapper;
@RabbitListener(queues = “modify_inventory_queue”)
@Transactional
public void process(Message message, @Headers Map<String, Object> headers, Channel channel) throws IOException {
String messageId = message.getMessageProperties().getMessageId();
String msg = new String(message.getBody(), “UTF-8”);
JSONObject jsonObject = JSONObject.parseObject(msg);
// 1.获取秒杀id
Long goodsId = jsonObject.getLong(“seckillId”);
SeckillEntity seckillEntity = seckillMapper.findBySeckillId(goodsId);
if (seckillEntity == null) {
log.warn(“goodsId:{},商品信息不存在!”, goodsId);
return;
}
Long version = seckillEntity.getVersion();
// 2.减库存
int inventoryDeduction = seckillMapper.inventoryDeduction(goodsId, version);
if (!toDaoResult(inventoryDeduction)) {
log.info(“>>>seckillId:{}修改库存失败”, goodsId);
return;
}
// 3.添加订单
OrderEntity orderEntity = new OrderEntity();
String phone = jsonObject.getString(“phone”);
orderEntity.setUserPhone(phone);
orderEntity.setSeckillId(goodsId);
orderEntity.setState(1l);
int insertOrder = orderMapper.insertOrder(orderEntity);
if (!toDaoResult(insertOrder)) {
return;
}
log.info(“>>>成功消费seckillId:{},秒杀成功!”, goodsId);
}
// 调用数据库层判断
public Boolean toDaoResult(int result) {
return result > 0 ? true : false;
}
}
**③ 提供一个根据用户信息查询秒杀结果接口(实际开发中,也可以根据userId)**
@GetMapping(“/checkSpike”)
public BaseResponse getOrder(String phone, Long goodsId) {
if (StringUtils.isEmpty(phone)) {
return setResultError(“手机号码不能为空!”);
}
if (goodsId== null) {
return setResultError(“商品库存id不能为空!”);
}
OrderEntity orderEntity = orderMapper.findByOrder(phone, goodsId);
if (orderEntity == null) {
return setResultError(“正在排队中…”);// 要么还没被mq消费,要么没抢到token令牌
}
return setResultSuccess(“恭喜你秒杀成功!”);
}
public interface OrderMapper {
@Select(“SELECT goods_id,user_phone,stateFROM order_table WHERE USER_PHONE=#{phone} and goods_id=#{goodsId} AND STATE=1”)
OrderEntity findByOrder(@Param(“phone”)String phone, @Param(“goodsId”)Long goodsId);
}
**前端需要写一个定时器,用于查询秒杀成功状态:**
前端调用秒杀接口spike,如果秒杀成功的话,返回正在排队中。。。
前端写一个定时器调用checkSpike接口,使用手机号/userId + 商品id查询是否秒杀成功。
(如果调用spike返回售罄,则前端不用写定时器)
![](https://img-blog.csdnimg.cn/20190623220934172.png) ![](https://img-blog.csdnimg.cn/20190623220954855.png)
#### 三. 网关优化
**1. 基于Google的guava实现限流(基于令牌桶)**
令牌桶实现原理: 以规定的速率往令牌桶中存入Token,用户请求必须获取到令牌中的Token才可以处理 请求,如果没有从令牌桶中获取到令牌则丢失该请求。 例如:令牌桶中最多只能存放50个Token,以规定速率存入Token实现在高并发情况下限流 。
![](https://img-blog.csdnimg.cn/168076c0655048a3965b9137d6486910.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQWtpcmFOaWNreQ==,size_10,color_FFFFFF,t_70,g_se,x_16)
Google的Guava工具包中就提供了一个限流工具类——RateLimiter,本文也是通过使用该工具类来实现限流功能。RateLimiter是基于“令牌通算法”来实现限流的,**具体步骤如下:**
com.google.guava guava ```
在zuul网关过滤器的run方法里,加入以下代码:
@Component
@Slf4j
public class GatewayFilter extends ZuulFilter {
// 每秒存入令牌中token数为1
private static final RateLimiter rateLimiter = RateLimiter.create(1);
public Object run() throws ZuulException {
/** 这里省略一系列的验证token,黑名单白名单等... */
/*
默认超时时间是0,意思是拿不到就立即返回false,如果想修改超时时间,采用该代码:
boolean tryAcquire = rateLimiter.tryAcquire(0, TimeUnit.SECONDS);
*/
boolean tryrateLimiter.tryAcquire(); //阻塞等待超时时间,这里设为0,如果没有拿到令牌,直接拒绝访问,无需等待
if (!tryAcquire) {// 返回false,表示没有获取到令牌,直接return
resultError(500, ctx, "现在抢购的人数过多,请稍等一下下哦!");
return;
}
// 否则,获取到令牌,放行,继续执行后续逻辑,待所有过滤都通过,则直接访问秒杀接口...
}
}
2. 使用Hystrix实现服务线程池隔离
默认情况下,上面的秒杀接口spike和查询秒杀结果接口checkSpike,都在一个线程池;在高并发场景,秒杀接口的压力会非常大,当一秒内用户全部请求秒杀接口,线程池都去处理秒杀接口,没有空闲线程去处理查询秒杀结果接口,这时候会产生延迟等待问题(默认tomcat只有一个线程池去处理所有请求,一旦线程池满了,导致其他线程无法访问)。
同时请求spike和checkSpike接口,打印日志如下:
>>>>>秒杀接口线程池名称:http-nio-9800-exec-1
>>>>>查询秒杀结果线程名称:http-nio-9800-exec-2
可以看到,两个接口处于同一个线程池,下面引入Hystrix实现服务降级,隔离:
<!-- 引入hystrix依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
启动类加入该注解:@EnableHystrix
秒杀接口,加入hystrix实现服务降级,线程池隔离
@HystrixCommand(fallbackMethod = "spikeFallback")
public BaseResponse<JSONObject> spike(String phone, Long seckillId) {
// 上面spike里面的逻辑...
}
private BaseResponse<JSONObject> spikeFallback(String phone, Long seckillId) {
return setResultError("服务器忙,请稍后重试!");
}
再次请求spike和checkSpike接口,打印日志如下:
>>>>>秒杀接口线程池名称:hystrix-SpikeApiImpl-1
>>>>>查询秒杀结果线程名称:http-nio-9800-exec-3
可以看到,两个接口的线程池是不同的;网络延迟的情况下,防止用户一直等待,会走服务降级方法spikeFallback,返回一个友好提示!
秒杀思路总结
**前端:①.**动静分离 **②.**防止表单重复提交 **③.**秒杀详情页面,使用定时器根据用户信息查询(对应后台第4点)
**网关:①.**限流 **②.**用户黑名单和白名单拦截
写在最后
在结束之际,我想重申的是,学习并非如攀登险峻高峰,而是如滴水穿石般的持久累积。尤其当我们步入工作岗位之后,持之以恒的学习变得愈发不易,如同在茫茫大海中独自划舟,稍有松懈便可能被巨浪吞噬。然而,对于我们程序员而言,学习是生存之本,是我们在激烈市场竞争中立于不败之地的关键。一旦停止学习,我们便如同逆水行舟,不进则退,终将被时代的洪流所淘汰。因此,不断汲取新知识,不仅是对自己的提升,更是对自己的一份珍贵投资。让我们不断磨砺自己,与时代共同进步,书写属于我们的辉煌篇章。
需要完整版PDF学习资源私我
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!