实战享元模式「基于Redis秒杀,提供活动与库存信息查询场景」
mhr18 2024-11-08 12:14 18 浏览 0 评论
作者:小傅哥博客:https://bugstack.cn
沉淀、分享、成长,让自己和他人都能有所收获!
一、前言
程序员??的上下文是什么?
很多时候一大部分编程开发的人员都只是关注于功能的实现,只要自己把这部分需求写完就可以了,有点像被动的交作业。这样的问题一方面是由于很多新人还不了解程序员的职业发展,还有一部分是对于编程开发只是工作并非兴趣。但在程序员的发展来看,如果不能很好的处理上文(产品),下文(测试),在这样不能很好的了解业务和产品发展,也不能编写出很有体系结构的代码,日久天长,1到3年、3到5年,就很难跨越一个个技术成长的分水岭。
拥有接受和学习新知识的能力
你是否有感受过小时候在什么都还不会的时候接受知识的能力很强,但随着我们开始长大后,慢慢学习能力、处事方式、性格品行,往往会固定。一方面是形成了各自的性格特征,一方面是圈子已经固定。但也正因为这样的故步,而很少愿意听取别人的意见,就像即使看到了一整片内容,在视觉盲区下也会过掉到80%,就在眼前也看不见,也因此导致了能力不再有较大的提升。
编程能力怎样会成长的最快
工作内容往往有些像在工厂拧螺丝,大部分内容是重复的,也可以想象过去的一年你有过多少创新和学习了新的技能。那么这时候一般为了多学些内容会买一些技术书籍,但!技术类书籍和其他书籍不同,只要不去用看了也就只是轻描淡写,很难接纳和理解。就像设计模式,虽然可能看了几遍,但是在实际编码中仍然很少会用,大部分原因还是没有认认真真的跟着实操。事必躬亲才是学习编程的最好是方式。
二、开发环境
- JDK 1.8
- Idea + Maven
- 涉及工程三个,可以通过关注公众号:bugstack虫洞栈,回复源码下载获取(打开获取的链接,找到序号18)
工程描述itstack-demo-design-11-01使用一坨代码实现业务需求itstack-demo-design-11-02通过设计模式优化代码结构,减少内存使用和查询耗时
三、享元模式介绍
享元模式,主要在于共享通用对象,减少内存的使用,提升系统的访问效率。而这部分共享对象通常比较耗费内存或者需要查询大量接口或者使用数据库资源,因此统一抽离作为共享对象使用。
另外享元模式可以分为在服务端和客户端,一般互联网H5和Web场景下大部分数据都需要服务端进行处理,比如数据库连接池的使用、多线程线程池的使用,除了这些功能外,还有些需要服务端进行包装后的处理下发给客户端,因为服务端需要做享元处理。但在一些游戏场景下,很多都是客户端需要进行渲染地图效果,比如;树木、花草、鱼虫,通过设置不同元素描述使用享元公用对象,减少内存的占用,让客户端的游戏更加流畅。
在享元模型的实现中需要使用到享元工厂来进行管理这部分独立的对象和共享的对象,避免出现线程安全的问题。
四、案例场景模拟
在这个案例中我们模拟在商品秒杀场景下使用享元模式查询优化
你是否经历过一个商品下单的项目从最初的日均十几单到一个月后每个时段秒杀量破十万的项目。一般在最初如果没有经验的情况下可能会使用数据库行级锁的方式下保证商品库存的扣减操作,但是随着业务的快速发展秒杀的用户越来越多,这个时候数据库已经扛不住了,一般都会使用redis的分布式锁来控制商品库存。
同时在查询的时候也不需要每一次对不同的活动查询都从库中获取,因为这里除了库存以外其他的活动商品信息都是固定不变的,以此这里一般大家会缓存到内存中。
这里我们模拟使用享元模式工厂结构,提供活动商品的查询。活动商品相当于不变的信息,而库存部分属于变化的信息。
五、用一坨坨代码实现
逻辑很简单,就怕你写乱。一片片的固定内容和变化内容的查询组合,CV的哪里都是!
其实这部分逻辑的查询在一般情况很多程序员都是先查询固定信息,在使用过滤的或者添加if判断的方式补充变化的信息,也就是库存。这样写最开始并不会看出来有什么问题,但随着方法逻辑的增加,后面就越来越多重复的代码。
1. 工程结构
itstack-demo-design-11-01
└── src
└── main
└── java
└── org.itstack.demo.design
└── ActivityController.java
- 以上工程结构比较简单,之后一个控制类用于查询活动信息。
2. 代码实现
/**
* 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获!
* 公众号:bugstack虫洞栈
* Create by 小傅哥(fustack) @2020
*/
public class ActivityController {
public Activity queryActivityInfo(Long id) {
// 模拟从实际业务应用从接口中获取活动信息
Activity activity = new Activity();
activity.setId(10001L);
activity.setName("图书嗨乐");
activity.setDesc("图书优惠券分享激励分享活动第二期");
activity.setStartTime(new Date());
activity.setStopTime(new Date());
activity.setStock(new Stock(1000,1));
return activity;
}
}
- 这里模拟的是从接口中查询活动信息,基本也就是从数据库中获取所有的商品信息和库存。有点像最开始写的商品销售系统,数据库就可以抗住购物量。
- 当后续因为业务的发展需要扩展代码将库存部分交给redis处理,那么久需要从redis中获取活动的库存,而不是从库中,否则将造成数据不统一的问题。
六、享元模式重构代码
接下来使用享元模式来进行代码优化,也算是一次很小的重构。
享元模式一般情况下使用此结构在平时的开发中并不太多,除了一些线程池、数据库连接池外,再就是游戏场景下的场景渲染。另外这个设计的模式思想是减少内存的使用提升效率,与我们之前使用的原型模式通过克隆对象的方式生成复杂对象,减少rpc的调用,都是此类思想。
1. 工程结构
itstack-demo-design-11-02
└── src
├── main
│ └── java
│ └── org.itstack.demo.design
│ ├── util
│ │ └── RedisUtils.java
│ ├── Activity.java
│ ├── ActivityController.java
│ ├── ActivityFactory.java
│ └── Stock.java
└── test
└── java
└── org.itstack.demo.test
└── ApiTest.java
享元模式模型结构
- 以上是我们模拟查询活动场景的类图结构,左侧构建的是享元工厂,提供固定活动数据的查询,右侧是Redis存放的库存数据。
- 最终交给活动控制类来处理查询操作,并提供活动的所有信息和库存。因为库存是变化的,所以我们模拟的RedisUtils中设置了定时任务使用库存。
2. 代码实现
2.1 活动信息
public class Activity {
private Long id; // 活动ID
private String name; // 活动名称
private String desc; // 活动描述
private Date startTime; // 开始时间
private Date stopTime; // 结束时间
private Stock stock; // 活动库存
// ...get/set
}
- 这里的对象类比较简单,只是一个活动的基础信息;id、名称、描述、时间和库存。
2.2 库存信息
public class Stock {
private int total; // 库存总量
private int used; // 库存已用
// ...get/set
}
- 这里是库存数据我们单独提供了一个类进行保存数据。
2.3 享元工厂
public class ActivityFactory {
static Map<Long, Activity> activityMap = new HashMap<Long, Activity>();
public static Activity getActivity(Long id) {
Activity activity = activityMap.get(id);
if (null == activity) {
// 模拟从实际业务应用从接口中获取活动信息
activity = new Activity();
activity.setId(10001L);
activity.setName("图书嗨乐");
activity.setDesc("图书优惠券分享激励分享活动第二期");
activity.setStartTime(new Date());
activity.setStopTime(new Date());
activityMap.put(id, activity);
}
return activity;
}
}
- 这里提供的是一个享元工厂,通过map结构存放已经从库表或者接口中查询到的数据,存放到内存中,用于下次可以直接获取。
- 这样的结构一般在我们的编程开发中还是比较常见的,当然也有些时候为了分布式的获取,会把数据存放到redis中,可以按需选择。
2.4 模拟Redis类
public class RedisUtils {
private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
private AtomicInteger stock = new AtomicInteger(0);
public RedisUtils() {
scheduledExecutorService.scheduleAtFixedRate(() -> {
// 模拟库存消耗
stock.addAndGet(1);
}, 0, 100000, TimeUnit.MICROSECONDS);
}
public int getStockUsed() {
return stock.get();
}
}
- 这里处理模拟redis的操作工具类外,还提供了一个定时任务用于模拟库存的使用,这样方面我们在测试的时候可以观察到库存的变化。
2.4 活动控制类
public class ActivityController {
private RedisUtils redisUtils = new RedisUtils();
public Activity queryActivityInfo(Long id) {
Activity activity = ActivityFactory.getActivity(id);
// 模拟从Redis中获取库存变化信息
Stock stock = new Stock(1000, redisUtils.getStockUsed());
activity.setStock(stock);
return activity;
}
}
- 在活动控制类中使用了享元工厂获取活动信息,查询后将库存信息在补充上。因为库存信息是变化的,而活动信息是固定不变的。
- 最终通过统一的控制类就可以把完整包装后的活动信息返回给调用方。
3. 测试验证
3.1 编写测试类
public class ApiTest {
private Logger logger = LoggerFactory.getLogger(ApiTest.class);
private ActivityController activityController = new ActivityController();
@Test
public void test_queryActivityInfo() throws InterruptedException {
for (int idx = 0; idx < 10; idx++) {
Long req = 10001L;
Activity activity = activityController.queryActivityInfo(req);
logger.info("测试结果:{} {}", req, JSON.toJSONString(activity));
Thread.sleep(1200);
}
}
}
- 这里我们通过活动查询控制类,在for循环的操作下查询了十次活动信息,同时为了保证库存定时任务的变化,加了睡眠操作,实际的开发中不会有这样的睡眠。
3.2 测试结果
22:35:20.285 [main] INFO org.i..t.ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1592130919931,"stock":{"total":1000,"used":1},"stopTime":1592130919931}
22:35:21.634 [main] INFO org.i..t.ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1592130919931,"stock":{"total":1000,"used":18},"stopTime":1592130919931}
22:35:22.838 [main] INFO org.i..t.ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1592130919931,"stock":{"total":1000,"used":30},"stopTime":1592130919931}
22:35:24.042 [main] INFO org.i..t.ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1592130919931,"stock":{"total":1000,"used":42},"stopTime":1592130919931}
22:35:25.246 [main] INFO org.i..t.ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1592130919931,"stock":{"total":1000,"used":54},"stopTime":1592130919931}
22:35:26.452 [main] INFO org.i..t.ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1592130919931,"stock":{"total":1000,"used":66},"stopTime":1592130919931}
22:35:27.655 [main] INFO org.i..t.ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1592130919931,"stock":{"total":1000,"used":78},"stopTime":1592130919931}
22:35:28.859 [main] INFO org.i..t.ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1592130919931,"stock":{"total":1000,"used":90},"stopTime":1592130919931}
22:35:30.063 [main] INFO org.i..t.ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1592130919931,"stock":{"total":1000,"used":102},"stopTime":1592130919931}
22:35:31.268 [main] INFO org.i..t.ApiTest - 测试结果:10001 {"desc":"图书优惠券分享激励分享活动第二期","id":10001,"name":"图书嗨乐","startTime":1592130919931,"stock":{"total":1000,"used":114},"stopTime":1592130919931}
Process finished with exit code 0
- 可以仔细看下stock部分的库存是一直在变化的,其他部分是活动信息,是固定的,所以我们使用享元模式来将这样的结构进行拆分。
七、总结
- 关于享元模式的设计可以着重学习享元工厂的设计,在一些有大量重复对象可复用的场景下,使用此场景在服务端减少接口的调用,在客户端减少内存的占用。是这个设计模式的主要应用方式。
- 另外通过map结构的使用方式也可以看到,使用一个固定id来存放和获取对象,是非常关键的点。而且不只是在享元模式中使用,一些其他工厂模式、适配器模式、组合模式中都可以通过map结构存放服务供外部获取,减少ifelse的判断使用。
- 当然除了这种设计的减少内存的使用优点外,也有它带来的缺点,在一些复杂的业务处理场景,很不容易区分出内部和外部状态,就像我们活动信息部分与库存变化部分。如果不能很好的拆分,就会把享元工厂设计的非常混乱,难以维护。
相关推荐
- redis 7.4.3更新!安全修复+性能优化全解析
-
一、Redis是什么?为什么选择它?Redis(RemoteDictionaryServer)是一款开源的高性能内存键值数据库,支持持久化、多数据结构(如字符串、哈希、列表等),广泛应用于缓存、消...
- C# 读写Redis数据库的简单例子
-
CSRedis是一个基于C#的Redis客户端库,它提供了与Redis服务器进行交互的功能。它是一个轻量级、高性能的库,易于使用和集成到C#应用程序中。您可以使用NuGet包管理器或使用以下命令行命令...
- 十年之重修Redis原理
-
弱小和无知并不是生存的障碍,傲慢才是。--------面试者总结Redis可能都用过,但是从来没有理解过,就像一个熟悉的陌生人,本文主要讲述了Redis基本类型的使用、数据结构、持久化、单线程模型...
- 高频L2行情数据Redis存储架构设计(含C++实现代码)
-
一、Redis核心设计原则内存高效:优化数据结构,减少内存占用低延迟访问:单次操作≤0.1ms响应时间数据完整性:完整存储所有L2字段实时订阅:支持多客户端实时数据推送持久化策略:RDB+AOF保障数...
- Magic-Boot开源引擎:零代码玩转企业级开发,效率暴涨!
-
一、项目介绍基于magic-api搭建的快速开发平台,前端采用Vue3+naive-ui最新版本搭建,依赖较少,运行速度快。对常用组件进行封装。利用Vue3的@vue/compiler-sfc单文...
- 项目不行简历拉胯?3招教你从面试陪跑逆袭大厂offer!
-
项目不行简历拉胯?3招教你从面试陪跑逆袭大厂offer!老铁们!是不是每次面试完都感觉自己像被大厂面试官婉拒的渣男?明明刷了三个月题库,背熟八股文,结果一被问项目就支支吾吾,简历写得像大学生课程设计?...
- 谷歌云平台:开发者部署超120个开源包
-
从国外相关报道了解,Google与Bitnami合作为Google云平台增加了一个新的功能,为了方便开发人员快捷部署程序,提供了120余款开源应用程序云平台的支持。这些应用程序其中包括了WordPre...
- 知名互联网公司和程序员都看好的数据库是什么?
-
2017年数据库领域的最大趋势是什么?什么是最热的数据处理技术?学什么数据库最有前途?程序员们普遍不喜欢的数据库是什么?本文都会一一揭秘。大数据时代,数据库的选择备受关注,此前本号就曾揭秘国内知名互联...
- 腾讯云发布云存储MongoDB服务
-
近日,著名安全专家兼Shodan搜索引擎的创建者JohnMatherly发现,目前至少有35000个受影响的MongoDB数据库暴露在互联网上,它们所包含的数据暴露在网络攻击风险之中。据估计,将近6...
- 已跪,Java全能笔记爆火,分布式/开源框架/微服务/性能调优全有
-
前言程序员,立之根本还是技术,一个程序员的好坏,虽然不能完全用技术强弱来判断,但是技术水平一定是基础,技术差的程序员只能CRUD,技术不深的程序员也成不了架构师。程序员对于技术的掌握,除了从了解-熟悉...
- 面试官:举个你解决冲突的例子?别怂!用这个套路……
-
面试官:举个你解决冲突的例子?别怂!用这个套路……最近收到粉丝私信,说被问到:团队技术方案有分歧怎么办?当场大脑宕机……兄弟!这不是送命题,是展示你情商+技术判断力的王炸题!今天教你们3招,用真实案例...
- 面试碰到MongoDB?莫慌,跟面试官这样吹MongoDB 复制集
-
推荐阅读:吊打MySQL:21性能优化实践+学习导图+55面试+笔记+20高频知识点阿里一线架构师分享的技术图谱,进阶加薪全靠它十面字节跳动,依旧空手而归,我该放弃吗?文末会分享一些MongoDB的学...
- SpringBoot集成扩展-访问NoSQL数据库之Redis和MongoDB!
-
与关系型数据库一样,SpringBoot也提供了对NoSQL数据库的集成扩展,如对Redis和MongoDB等数据库的操作。通过默认配置即可使用RedisTemplate和MongoTemplate...
- Java程序员找工作总卡项目关?
-
Java程序员找工作总卡项目关?3招教你用真实经历写出HR抢着要的简历!各位Java老哥,最近刷招聘软件是不是手都划酸了?简历投出去石沉大海,面试邀请却总在飞别人的简历?上周有个兄弟,13年经验投了5...
- Java多租户SaaS系统实现方案
-
嗯,用户问的是Java通过租户id实现的SaaS方案。首先,我需要理解用户的需求。SaaS,也就是软件即服务,通常是指多租户的架构,每个租户的数据需要隔离。用户可能想知道如何在Java中利用租户ID来...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)