项目简介:为深入理解和应用Redis,设计并实现了一个名为"达人推荐"的平台,允许用户在平台上对商户进行点评和讨论。此外,平台还提供了秒杀优惠券的功能,使用户能够在抢购中获得实惠。 •技术栈:采用了SpringBoot、MyBatisPlus、Redis和RabbitMQ作为主要技术栈。 • 缓存问题解决:使用空值法解决了Redis缓存穿透问题,同时使用锁机制解决了缓存击穿和缓存雪崩问题。 • 异步下单:通过RabbitMQ消息队列的多线程并发方法,将业务流程分化,实现了异步下单功能,有效地完成了流量削峰,并实现了功能解耦。 • 登录验证:使用redis+token序列号进行登录验证,并通过ThreadLocal拦截预存登录信息,方便用户使用。 • 秒杀功能:使用Redisson分布式锁实现了秒杀功能,利用Lua脚本保证了操作的原子性,避免了超卖和多单问题。同时,使用redis生成全局唯一ID。 • 关注列表消息发送:采用Feed流推模式,实现了关注列表消息的发送,并实现了对收件消息的时间排序分段加载,提高了消息加载的效率。
一开始设计秒杀抢购业务,一人一单时,对多个相同用户的请求,我想当然的对于用户名的String进行加锁,毕竟防止一人多次下单,但是在代码中,通过Threadlocal获取到用户信息后,直接toString得到用户名,然后进行加锁,之后进行多次请求时,结果发现加的锁根本没用,还是出现了一人多单的问题,后来我用过打断点的方式,对多个线程都进行调试,发现虽然同一个用户的多次请求获得用户名的值虽然是一样的,但他们的引用地址并不是一样,这是因为synchronized关键字是对对象的实例进行加锁而不是对值,所以通过toString获得string虽然值是一样的,但是他们的引用不一样,导致锁失效,后来我查资料发现,可以在返回的string调用intern()方法这样就柠檬返回同一个引用,不会出现加锁失败的情况,这个bug我想了很久
异步下单:通过RabbitMQ消息队列的多线程并发方法,将业务流程分化,实现了异步下单功能,有效地完成了流量削峰,并实现了功能解耦。
秒杀功能:使用Redisson分布式锁实现了秒杀功能,利用Lua脚本保证了操作的原子性,避免了超卖和多单问题。同时,使用redis生成全局唯一ID。
Redis是一种NoSQL数据库,它的特点是数据存储在内存中,因此它的运行速度很快,并且它是键值型数据结构,所有的数据都是以键值对的形式存储 redis的应用场景主要是用来做缓存,保存会话信息,分布式锁,统计数量,用作消息队列,生成全局id
缓存穿透即redis和数据库中都不存在数据,请求消息反复在缓存中失效,并请求到了数据库,给数据库带来了压力, 解决方案有两种,
一种是当缓存和数据库中都不存在数据时,将空对象作为值写入对应的键存放在redis中并设置一个较短的过期时间, 当请求再次到缓存中时, 根据键查询到是空对象的话,则直接返回一个错误,这样请求就不会到达数据库中了
另一种方式是使用布隆过滤器,直接在将redis中的数据放入布隆过滤器当中,布隆过滤器使用bit数组存放数据,所以对于海量数据是可以存放的下的, 布隆过滤器使用hash算法将我们的数据映射到这个bit数组当中,能够快速的查询,当我们请求的数据不存在时就会被布隆过滤器拦截住不会走数据库, 虽然使用布隆过滤器可以解决缓存穿透问题但是也会带除了增加系统的一些复杂度外的两个问题,一个就是存在误判的可能性,会将不存在判断为存在(挡不住不正常请求的访问), 另外一个就是数据同步问题,它和mysql是不同的数据源,当出现网络问题时mysql新增的的数据没有同步到布隆过滤器中,那么会出现部分正常的请求没法访问(挡住正常请求的访问),
缓存击穿即热点key的缓存突然失效,导致大量的请求到达数据库,使得数据库受到了极大的压力,针对这个问题,通常有两种解决方案
1个是设置逻辑过期时间,即把过期时间作为value的一部分,这样就不会出现缓存失效的问题了,然后当我们从缓存中取出数据时,判断这个数据的时间是否过期, 是的话,则直接返回旧的过期数据,然后新开一个线程加锁从数据库读取数据并更新缓存再释放锁,其它线程取不到锁也直接返回旧的数据
第二种解决方案是使用互斥锁解决,即当第一个请求查询到缓存失效时,则由这个请求或新开一个线程去数据库中查询并更新到缓存中,对这个过程进行加锁, 并且在锁中还要判断缓存是否更新即双检锁,其它请求或线程也是同样的流程,它们碰到缓存失效的同时也会尝试阻塞地获取锁, 由于我们在锁的代码块再次做了一次判断,后面获得锁的线程会取消执行,然后从缓存中读取数据,这种加锁的做法使得只有一个线程同时访问数据库,可以减少数据库的压力, 它的缺点是缓存失效时,查询的效率会变低,从并行到串行执行 本项目采用的是利用互斥锁解决
缓存雪崩即大量热点数据在缓存中同时失效过期,导致请求到达数据库,给数据造成了极大的压力, 在本项目中采取的措施是当大量添加数据到redis中时,给每一个数据设置随机,不同的过期时间, 这样将来就不会大量同时失效了
我们首先在RabbitMqConfig配置了交换机,队列和路由键,并使用了注解来监听消息队列,我们的交换机类型是topicExchange,可以根据路由键的模式进行消息的路由。 至于多线程并发方法我们定义了多个监听时间来共同消费消息,防止消息堆积,而消息发送是在,用户抢购优惠卷时调用发布者发送消息
采用这种验证方式是考虑到了,项目以后如果要做水平扩展的话,比后端服务器要做集群,那么原先的cookie+session的验证方式将会出现问题,一个请求的session可能会被存在其它服务器,并且负载均衡后会路由到不同的机器上, 导致session失效,如果session也做共享和集群的话,还会增加存储压力,session的拷贝也会存在延迟,而如果将登录品证存在redis就可以避免session共享问题
对于本项目的秒杀功能,我们采用先是将优惠卷的库存信息存入redis,然后当请求过来时使用redis执行lue脚本执行判断购买资格并减少库存的数量,并提前在redis中创建一个消息队列用于保存下单的信息,其中有含有用户id和优惠卷id,而后端程序在开启运行时, 就开启一个线程循环去redis的stream消息队列中消费信息,然后根据取得到的信息再写入数据库中保存,而保存订单信息到数据库中代码使用redisson的分布式锁加锁,避免多单问题,而超卖问题也使用lue脚本操作redis的库存保证了操作原子性
feed流我们采用推模式实现,即当某一个用户发表推文博客时,会将新的内容推送到关注他的人的收件箱,而用户上线时到关注列表页面时,会收到这些新的推文并按时间排序,因为我们使用redis的sortedset进行存储
在本项目中,我们需要使得id为自增长并且不能时简单自增1,为了实现这个需求我们封装了个全局Redisid生成器, 他能生成全局唯一且自增的id,具体实现是采用时间戳+redis的自增命令实现的,id为64位, 将当前时间戳左移32位再与同一秒内redis自增的数字做与运算就得到了我们的全局id
使用过rabbitmq,没比较他们之间的区别
没有
没有
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。