优惠券项目话术
自我介绍
我从18年在郑州科技学院毕业后就一直从事Java开发这个岗位,之前是在致维网络有限公司,主要做Java工程后端开发的工作,平时主要工作就是和(前端对接接口,后台接口实现,文档编写。)最近做的是一个“袋鼠盒子”的一个商城项目,是一个孕妇奶粉,纸尿布,进口奶粉,玩具等母婴用品,主要使用的技术是,SpringCloudAlibaba,Nacos,Sentinel,Feign等技术实现
我在项目中负责的是商品模块中的优惠券模块,
首先从大的方面说分为三部分实现,优惠券模板模块、优惠券分发模块、优惠券结算模块。
设计的表主要有优惠券模板表 、优惠券表、用户表,我们这个项目大概有几十张表当时我们在设计这个表的时候,讨论了很长时间,大概用了三天左右时间就把这个优惠券模板的表给定了下来。我们这个优惠券模板中有优惠券名称、优惠券类型、优惠券的发放数量、优惠券发放方式、有效期、优惠券截止日期、使用产品等。优惠券模板确认之后就开始设计数据库,
设计表时遇到的难点
优惠券模板的难点在于我们优惠券的类型,类型比较多有满减卷、折扣卷、立减卷,优惠券的发放方式有用户领取、系统发放,使用产品有单品、商品线、全品类,截止日期有到什么时候过期、自领取之日到什么时候过期。当用户来访问我们网站的时候我们,我们会自动发放一些优惠券来进行引流,优惠券模板就是通过优惠券模板创建一些相对应的优惠券供用户使用。
由于我们使用的规则比较复杂,需要使用到Redis来存储优惠券,其实我们不是领取的优惠券模板,我们是领取的优惠券码,这个时候我们再去设计一下优惠对象,优惠券表中有,优惠券ID,优惠券模板id,优惠券码,优惠券的使用时间,优惠券的使用状态,优惠券的状态我们分为可用优惠券、已用优惠券、已过期优惠券,,
创建模板模块(mq,Redis,)
创建优惠券模板模块之前,我们需要对我们系统中的商品类别进行获取,查出来商品的类别之后往Redis中存一份,之后再添加新的商品的时候往数据库存的同时,捎带往Redis中存一份。
构建优惠券模板的时候我们要保证ID不能重复,分布式ID问题在这儿我用的是美团leaf算法(有很多这种算法都是基于雪花算法,会出现时针回拨问题)。
我们刚开始创建的优惠券都是不可用的状态,需要部门的审核,审核通过以后才能变成可用状态,我们的后台管理权限也设置了只有负责当前优惠券模块的人才能对优惠券进行操作。
然后就是优惠券码,通过优惠券模板创建优惠券码,存到Redis中,用户领取的时候对Redis中的优惠券码进行领取,这时候我还做了判断因为在循环使用优惠券的时候可能会有遗漏,使用set集合不能重复,生成完之后再进行判断,确定优惠券数量一致 ,一致的话就放到Redis里边。我们优惠券模板这块儿还基于mybatis做了二级缓存,之前是我们每次调用方法的时候都走数据库,做了缓存之后就不走SQL语句直接走缓存了,提高性能。
如何生成的优惠券码
通过异步生成优惠券码可以节省时间,这个地方我们用到了线程池,用线程池的好处就是提高效率降低消耗、提高响应时间,
优惠卷分发模块(mq,)
优惠券分发模块,我们在优惠券模板进行分类,让用户看到哪些是可用的、过期的、已用的优惠券。
主要就是通过优惠券状态查询优惠券信息,我们在查询的时候都是可用优惠券查询完优惠券之后我们往Redis里边存一分,
在查询的时候Redis里边没有去数据库里边查,数据库里边也没有就返回一个 空对象, 防止击穿问题。刚开始优惠券为可用,从Redis里边取,用完之后从Redis里边移除,改变优惠券的状态码,还要判断优惠券的过期时间,优惠券过期肯定是没用过的优惠券,改变状态码为过期优惠券。
系统自动分发和用户手动领取,系统分发模块要先把优惠劵模板信息拿过来 ,我需要去过滤已过期的优惠卷模板,剩下的就是没过期的优惠券,通过优惠券模板拿到对应的优惠券,再对优惠券进行判断看是否过期,拿到优惠券之后往集合里边存一分,这个集合就是用户所有的可用优惠券,然后批量插入到数据库。 系统领取是把可用优惠卷ID传过去,自动领取是指定领-+.取优惠券ID。rabbitMQ主要是获取接收优惠券中的消息,处理过期优惠券,把过期优惠券删除。
(这个rabbitMQ除了在订单中使用外,还可以在订单中使用,一定的时间内没有提交订单,就可以放到死信 队列里边做销毁,这个时候回调我们的库存,订单处于未支付状态)。手动领取优惠券发送异步消息, 然后对当前的商品进行查询,查对应的商品和种类,然后领取优惠券码。
优惠券的结算模块
结算与核销,主要是计算金额,优惠券的叠加使用,首先获取结算信息,对商品进行分类、对优惠券进行分类,最终要达到一个map里边key值对应一个map(![img](file:///C:/Users/14477/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg))某一类商品里边某一个商品的ID和商品信息,拿到这些结算信息之后,在拿到优惠券的信息。找到某一个商品,拿到对应的优惠券,获取它的到价格,判断优惠券是否符合规范,符合规范之后就开始计算。
如果要问项目里面有多少张表?
就回答几十张
什么是雪花算法
雪花算法产生的背景当然是twitter高并发环境下对唯一ID生成的需求,得益于twitter内部牛逼的技术,雪花算法流传至今并被广泛使用。它至少有如下几个特点:
- 能满足高并发分布式系统环境下ID不重复
- 基于时间戳,可以保证基本有序递增(有些业务场景对这个又要求)
- 不依赖第三方的库或者中间件
- 生成效率极高
时间回拨问题
在获取时间的时候,可能会出现
时间回拨
的问题,什么是时间回拨问题呢?就是服务器上的时间突然倒退到之前的时间。
人为原因,把系统环境的时间改了。
有时候不同的机器上需要同步时间,可能不同机器之间存在误差,那么可能会出现时间回拨问题。
解决方式
1. 回拨时间小的时候,不生成 ID,循环等待到时间点到达。 2. 上面的方案只适合时钟回拨较小的,如果间隔过大,阻塞等待,肯定是不可取的,因此要么超过一定大小的回拨直接报错,拒绝服务,或者有一种方案是利用拓展位,回拨之后在拓展位上加1就可以了,这样ID依然可以保持唯一。但是这个要求我们提前预留出位数,要么从机器id中,要么从序列号中,腾出一定的位,在时间回拨的时候,这个位置 `+1`。
项目中mq的使用场景
rabbitMQ除了在订单中使用外,还可以在订单中使用,一定的时间内没有提交订单,就可以放到死信队列里边做销毁,这个时候回调我们的库存,订单处于未支付状态
项目中用到事务的地方是哪里
当用户领取优惠卷的时候,库存减一保证数据的一致性还有当生成优惠卷的时候数据保证一
项目里面为什么使用rides?
Redis是纯内存数据库,一般都是简单的存取操作,所以读取速度快。
再说一下IO,Redis使用的是非阻塞IO,IO多路复用,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下文的切换和竞争。
Redis采用了单线程的模型,保证了每个操作的原子性,也减少了线程的上下文切换和竞争。
另外,数据结构也帮了不少忙,Redis全程使用hash结构,读取速度快,还有一些特殊的数据结构,对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如,跳表,使用有序的数据结构加快读取的速度。
还有一点,Redis采用自己实现的事件分离器,效率比较高,内部采用非阻塞的执行方式,吞吐能力比较大。
项目中存储rides的数据类型是什么?
String类型 List类型 set类型
项目中涉及SQL优化
1.我们的select查询语句指定了字段名称,没有查询所有数据
2.当我们在合并一个结果集的时候,当我们查询订单和对应的客户的时候使用的是union来代替union all
3.涉及到分页,不会直接使用limit分页查询。二十取前一页最大行数的id,来根据这个最大的ID来限制下一页的起点
4.我们没有使用%前缀进行模糊查询,因为会导致索引失效会进行全表扫描
我们使用的是LIKE "name%".
项目中涉及到的调优有什么?
1.我们会对JVM 的参数进行调优。针对JVM堆的设置,一般可以通过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,我们通常把最大、最小设置为相同的值 。
2.数据库进行调优:
(1)连接数调优,MYSQL默认最大连接数是151,上限为100000
修改mysql的主配置文件/etc/my.cnf
[mysqld]部分添加
“max_connections=1000(这个根据实际的需要来进行设置即可)”
重启MySQL服务
(2)索引
为查询条件中的字段建立索引,通常这些字段应该为状态值等字典字段或日期字段
索引并不是越多越好,索引会降低插入和更新的速度,因为当insert或Update的时候要更新索引
3.代码逻辑调优
1:避免for 多层嵌套
2:避免对大数组的迭代对比,考虑使用Arrays.binarySearch()
4.静态资源调优
1:不要引用重复的、没必要的js、css文件
2:对于js、css文件较大的,需要压缩
3:尽量不要使用>100K的图片
项目中的分布式事务有什么?
没用到
优惠券项目中你用到的redis是什么类型的?
Spring map list 都有
为什么要把这些数据存在redis中?
缓存效率高,单线程不用太考虑并发问题,数据带有失效性,一段时期之后,大量的数据会过期弃用
生成的优惠券码为什么没有存在数据库中?
优惠券码具有时效性并且数据量比较大,考虑到优惠券抢购导致高并发使用Redis效率更好
如何保持数据库和rides缓存的一致性
1、对删除缓存进行重试,数据的一致性要求越高,我越是重试得快。
2、定期全量更新,简单地说,就是我定期把缓存全部清掉,然后再全量加载。
3、给所有的缓存一个失效期。(绝招,任何不一致,都可以靠失效期解决,失效期越短,数据一致性越高。但是失效期越短,查数据库就会越频繁。因此失效期应该根据业务来定)
如果redis的主节点挂掉了,而且没用挂掉的时候还没用把锁同步到从节点该怎么办?
项目中的分布式锁
1.有时候我们会校验优惠券码是否存在,每次都会去查询数据库导致数据库压力大
让同一时间的请求不每次都走数据库,在不同的机器上如果是同一个请求是不是可以直接拦截掉不走后面下单逻辑
1.利用第三方的订单号和商户的id作为key
2.锁定的时间可以设置为几秒,这样几秒内如果同一个订单号就会被拒绝保护后面查询数据库的资源每个不同的服务之间数据库表都不相同,但发生异常的时候,会进行事务回滚,本地锁不适用不同服务之间,这时候就用到了分布式事务