项目概述:设计一个外卖点菜系统,用户端采用微信小程序,管理采用前端页面
- 采用jwt令牌来进行登录校验,利用拦截器加redis缓存的进行通过对token(不用session的原因是因为session无法在多个服务器上共享),用户是否存在来判断是否刷新token失效的时间。(使用的方法是redis的expire方法重新设置过期时间)
- Redis采用Cache Aside缓存更新策略(更新,删除数据库数据时,删除缓存),确保MySQL和redis的数据一致,解决一致性问题的时候又遇到了缓存穿透,缓存雪崩和缓存击穿的问题。
- 缓存穿透是指在缓存和数据库中都没有,请求会直接打到数据库上此时会造成穿透,通过布隆过滤器和返回空值来解决)
- 布隆过滤器是位图的方法存储数据,特点是占用内存小但是实时性差会出现误判的情况,为了避免这种情况可以,在每次数据增加和删除的时候更新,空值就是实现简单但会造成短期的不一致,有额外的内存消耗
- 缓存雪崩是大量的缓存键在同一时间过期或失效或者是服务器发生宕机,此时的访问会全部打到数据库上从而造成雪崩现象,可以通过以及redis集群,设置逻辑过期时间,或者使用Sentinel进行请求限流,熔断。
- 缓存击穿是指一个热点key失效重建缓存过程繁杂,通过利用设置不同的过期时间(要数据预热),以及互斥锁解决缓存穿透(查询缓存未命中,加锁查询数据库重建,重建成功后释放锁)
- 优惠劵秒杀业务,正常通过查询库存的方法会导致超卖的现象,我们开始通过采用乐观锁的方法使用CAS法(将库存作为版本号,记录获取锁)解决超卖问题。
- 发现一人多单,之后改为synchronized锁解决一人多单的问题,发现在spring中使用@Transactional事务生效必须使用动态代理(自注入或者创建代理对象),但是在集群的情况下(负载均衡会把请求分配个多个端口)每个jvm都有一把synchronized锁不能跨JVM进行加锁,还是会出现一人多单的问题,
- 采用redis的setnx来作为分布式锁,但是在特殊情况下高并发多线程的情况下,会出现线程1在发生业务阻塞时超时释放锁,之后线程2进入,此时线程1完成业务就会把线程2的所给释放,线程3就会再次乘虚而入发生超卖问题
- 添加线程标识,为了确保判断锁标识和释放锁的原子性我们引入了lua脚本
- 最后一次优化采用了Reddision来解决再用Kafka来进行异步秒杀优化,通过使用订单号作为锁标识把用户id存入set集合中,同时在扣减库存和下单前也会进行二次校验(这一部分工作交给Kafka来异步实现)(把获取锁、检查库存、检查用户、生成订单ID、扣减 Redis 库存和单落库,数据库库存扣减进行了异步)
- Redission的看门狗机制
- 采用feed流时间排序来实现关注推送(feed流常见的两种方式时间排序和智能排序),时间排序也有三种(1.拉模式,读扩散,提供者将数据发送至数据源,用户通过主动请求来获取自己感兴趣的数据,内存消耗小延迟高如果数据多会对服务器造成很大压力2.推模式写扩散,在发布时把消息推给每一个关注的人,实时性好内存消耗大3.推拉结合,读写混合,普通人粉丝少直接发送给粉丝,大v就写到数据源一份同时发一份给活跃粉丝)
- 我的项目用户量小,我采用了推的模式,因为推送数据在不断更新,传统的分页不能满足项目的需求,会使得用户查看到重复的数据。
- 所以我采用了SortedSet实现滚动分页,记录当前数据最小的时间戳作为下一页的最大时间,同时根据最小时间来计算偏移量,下一页查询时直接跳过,此时查询数据库时也要按照redis录入的顺序查询博客,要自定义排序规则,用last将排序语句拼接放到最后。(先用redis筛选出符合的数据,然后根据数据找出对应的ids,计算最小时间戳,偏移量,然后利用特定的排序规则和ids查出数据返回给前端)
- spring task定时任务(利用@scheduled注解搭配cron表达式来实现定时任务,也可以使用rabbitmq的延迟队列)解决了订单长时间未支付自动改为已取消的操作和长期处于派送中的订单,每天一点准时检查(自营的店铺,12点打烊就合理,其他情况可以通过消息队列监听订单状态异步完成比如派送时间后2个小时)。
- Websocket实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接, 并进行双向数据传输。
- HTTP协议和WebSocket协议对比:HTTP是短连接WebSocket是长连接,HTTP通信是单向的基于请求响应模式WebSocket支持双向通信,HTTP和WebSocket底层都是TCP连接