如何实现的

1、基于Redis消息中间件,实现了页面缓存和对象级缓存,提高热点商品并发量,减少数据库压力。

页面缓存就是当访问页面的时候,从缓存中取如果取到就返回这个html,(这里声明方法的返回格式为text/HTML,这样就是返回html的源代码),如果缓存中没有,利用ThymeleafViewResolver的getTemplateEngine().process和我们获取到的数据,渲染模板。如果页面一般不会变动时效性低,可以将过期时间设置长一些,反之如果时效性高,则设置的短一些。

对象缓存的粒度更小,并且涉及到缓存数据库一致性的问题,我们采用的是旁路缓存模式,针对写数据直接更新DB,然后删除缓存。对于读操作,先从缓存中读,读的到就返回,读不到就从数据库返回,再把数据放到缓存。

2、基于RabbitMQ支持消息事务这一特点,请求先入队缓冲,异步下单,以增强用户体验。请求出队,生成订单再减少库存,客户端定时轮询检查是否秒杀成功。

请求先入队缓冲,返回客户端正在排队的信息,以增强用户体验。请求出队,生成订单再减少库存,客户端浏览器定时轮询检查是否秒杀成功。

3、基于Redis集群实现分布式session存储,结合拦截器实现token用户验证和接口防刷。

将表单中的密码经过加盐和加密之后,与数据库中加密的密码对比,一致则验证通过,用UUID生成一个token,设置有效期加前缀放入Redis中,并且把token放入HTTPservletResponse返回给客户端放入cookie,下次客户端请求时带上cookie中的token,服务器通过拦截器查看request是否携带token,若携带了通过token从Redis中得出该用户的信息,从而实现用户验证。

而接口防刷是结合拦截器和一个自定义注解来实现,通过注解的的参数,设定了这个地址在几秒钟能被同一个用户够访问几次。当一个秒杀请求到达,拦截器读到这个请求所映射的方法上有防刷注解后,会读取到注解的参数,一开始第一次访问的时候,根据参数和用户id以及方法名会在Redis中set一个带时效限制的计数器,之后重复访问计数器会累加,当超过一定次数后,拦截器会拦截该请求。

项目超卖问题如何解决

1、对于并发量不高的情况:在数据库上利用排他锁解决超卖问题

MySQL InnoDB存储引擎支持事务和行锁。利用加排它锁解决超卖,比如说在SQL语句中库存减一时判断库存是否大于0

update product set stock = stock - 1 where product_id = 1 and stock > 0;

这种方式虽然能解决超卖的问题,但是会出现争锁情况,会出现先请求的用户抢不到锁不能购买产品而后请求的用户抢到锁能购买产品的情况。而且受限于MySQL软件本身,在高并发的场景下支持不是很理想。

2、并发量较高:利用Redis解决

使用Redis有几个方案比如:

1、分布式锁

多线程情况下,让线程竞争锁,没有得到则等待一段时间后重试,得到锁之后则查库存,和购买数量进行比较,充足就减库存然后释放锁。其中这里面可以进行一下细节优化,比如说给锁设置一个过期时间防止死锁的产生,还有可以将key的value设置为当前客户端的特征值比如说一个UUID,这样防止其他客户端误删操作。

2、Redis事务

先执行watch命令对库存进行监听,再开启一个事务对库存进行减一,如果在事务期间库存被其他线程更改,watch命令会撤销事务。如果watch没有检测到更改并且库存大于0,则执行事务。
缺点:为保证结果的正确,可能导致资源的消耗,可能因为事务失败后需要反复重试,另外大量的watch命令对服务器的负担也很重,主要是这两点对资源的消耗。