Redis系列

redis相关介绍

redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/popadd/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了JavaC/C++C#PHPJavaScriptPerlObject-CPythonRubyErlang等客户端,使用很方便.


今天我们来利用Redis来实现一个大家在工作中都可能遇到的需求,热卖排行榜

话不多说开干!!!!!

  • 需求说明

1、本次我们实现一个每日热卖商品排行榜的需求
2、简单的设计俩张表:goods(商品表)、sell(销售记录表)
3、主要是将当日热卖的商品查询出来,利用Redis的有序集合进行大到小排序显示在前端页面

  • 技术列表

Springboot 2.1.2.RELEASE
Redis
freemarker
mybatis-plus 3.2.0
hutool-all


搭建项目基础环境
  • 首先我们来创建一个springboot项目,并且引入需要的依赖,后续需要的依赖后面用到在引入

  • 所有的依赖如下:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--mp-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency> <!--代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.2.0</version>
</dependency> <!-- sql分析器 -->
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.8.6</version>
</dependency> <!-- commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<!--工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.1.17</version>
</dependency>
  • 由于我们项目用到的模本引擎是freemarker下面简单的介绍一下这个模版引擎

FreeMarker是一款模板引擎:即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页、配置文件、源代码等)的通用工具。它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件.
能够很方便的对后端的数据进行渲染
敲黑板重点!!FreeMarker的宏:宏是在模板中使用macro指令定义
宏是和某个变量关联的模板片断,以便在模板中通过用户定义指令使用该变量。
大白话:就是提高前端的代码的重用

  • 如下是本项目中用到的FreeMarker的宏

<#macro layout title>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>${title}</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="keywords" content="公众号:北漂码农有话说">
<meta name="description" content="公众号:北漂码农有话说致力于输出优质技术文章">
<link rel="stylesheet" href="/res/layui/css/layui.css">
<link rel="stylesheet" href="/res/css/global.css">
</head>
<body> <#include "/include/header.ftl" /> <#nested > <#include "/include/footer.ftl" /> <script src="/res/layui/layui.js"></script>
<script>
layui.cache.page = '';
layui.cache.user = {
username: '游客'
,uid: -1
,avatar: '../res/images/avatar/00.jpg'
,experience: 83
,sex: '男'
};
layui.config({
version: "3.0.0"
,base: '../res/mods/' //这里实际使用时,建议改成绝对路径
}).extend({
fly: 'index'
}).use('fly');
</script> <script type="text/javascript">var cnzz_protocol = (("https:" == document.location.protocol) ? " https://" : " http://");document.write(unescape("%3Cspan id='cnzz_stat_icon_30088308'%3E%3C/span%3E%3Cscript src='" + cnzz_protocol + "w.cnzz.com/c.php%3Fid%3D30088308' type='text/javascript'%3E%3C/script%3E"));</script> </body>
</html>
</#macro>
  • 由于选择的是mybatis-plus开发所以我们将代码进行生成

参考官网:mybatis-plus官网

找到代码生成器将代码拷贝到你的项目下进行必要的配置执行main方法,生成代码。如下:

代码如下:

public class CodeGenerator {

    /**
* <p>
* 读取控制台内容
* </p>
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotEmpty(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
} public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator(); // 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
// gc.setOutputDir("D:\\test");
gc.setAuthor("公众号:北漂码农有话说");
gc.setOpen(false);
// gc.setSwagger2(true); 实体属性 Swagger2 注解
gc.setServiceName("%sService");
mpg.setGlobalConfig(gc); // 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/triumphxxtop?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=UTC");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
mpg.setDataSource(dsc); // 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(null);
pc.setParent("com.triumphxx");
mpg.setPackageInfo(pc); // 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
}; // 如果模板引擎是 freemarker
String templatePath = "/templates/mapper.xml.ftl";
// 如果模板引擎是 velocity
// String templatePath = "/templates/mapper.xml.vm"; // 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + "/src/main/resources/mapper/"
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
}); cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg); // 配置模板
TemplateConfig templateConfig = new TemplateConfig(); // 配置自定义输出模板
//指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
// templateConfig.setEntity("templates/entity2.java");
// templateConfig.setService();
// templateConfig.setController(); templateConfig.setXml(null);
mpg.setTemplate(templateConfig); // 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setSuperEntityClass("com.triumphxx.entity.BaseEntity");
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
strategy.setSuperControllerClass("com.triumphxx.controller.BaseController");
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setSuperEntityColumns("id", "created", "modified", "status");
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}
  • 执行main方法,如下,输入你的表名,生成你的代码

  • 生成代码

  • 由于我们使用的是mp所以需要配置sql分析器,将sql打印出来,便于分析,当然配置sql分析器是有一定的性能损耗,所以不建议在产线上配置,配置如下:

首先添加依赖

<!-- sql分析器 -->
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.8.6</version>
</dependency>

修改数据库配置信息,driver-class-name:com.p6spy.engine.spy.P6SpyDriver,jdbc后边加上p6spy

spring:
datasource:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://localhost:3306/triumphxxtop?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=UTC
username: root
password: root

引入配置文件

  • 项目设计思路,在项目启动的时候,将今日的热卖商品数据缓存到redis中,代码如下:

/**
* @author:triumphxx
* @Date:2020/5/16
* @Time:7:40 上午
* @微信公众号:北漂码农有话说
* @desc:系统启动加载类
**/
@Component
public class ContextStartUp implements ApplicationRunner { @Autowired
SellService sellService; /**
* 服务启动是加载执行的的方法
* 将每天热卖的商品进行展示
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) throws Exception {
sellService.initGoodsSellTop();
} }
  • 核心代码如下,每一步都有详细的说明。

 @Autowired
GoodsService goodsService;
@Autowired
RedisUtil redisUtil;
@Autowired
com.triumphxx.util.DateUtil dateUtil;
@Override
public void initGoodsSellTop() {
//获取当天内销售量大于80的商品id和销售记录id
List<Sell> sells = this.list(new QueryWrapper<Sell>()
.ge("sell_num",80)
.ge("sell_date", DateUtil.format(new Date(),"yyyy-MM-dd"))
.select("goods_id","sell_num")
);
List<GoodsVo> goodsVos = new ArrayList<>();
for (Sell sell : sells) {
//1、根据销售量大于80的销售记录查询出货物的信息
GoodsVo goods = goodsService.selectSellTop(new QueryWrapper<Goods>().eq("g.goods_id",sell.getGoodsId())
.eq("s.sell_date",DateUtil.format(new Date(),"yyyy-MM-dd")));
//2、将销售货物一天的销售的数量进行数据缓存
redisUtil.zSet(Constants.REDIS_KEY.GOODS_TOP_KEY+goods.getGoodsId(),goods.getGoodsId(),goods.getSellNum());
//设置过期时间 当天有效
//0点的时间减去现在的时间换算层毫秒数
Long expireTime = dateUtil.initDateByDay()-DateUtil.currentSeconds();
redisUtil.expire(Constants.REDIS_KEY.GOODS_TOP_KEY+goods.getGoodsId(),expireTime);
//同时缓存一下货物的基本信息 货物id 货物名称 销售货物数量
this.hasCacheGoods(goods,expireTime);
goodsVos.add(goods);
}
//做并集
this.zuionOneDayTop(goodsVos);
} /**
*合并当天热卖榜
*/
private void zuionOneDayTop(List<GoodsVo> goodsVos) {
List<String> otherKeys = new ArrayList<>();
for (GoodsVo goodsVo : goodsVos) {
String temp = Constants.REDIS_KEY.GOODS_TOP_KEY +goodsVo.getGoodsId();
otherKeys.add(temp);
}
redisUtil.zUnionAndStore(Constants.REDIS_KEY.GOODS_TOP_KEY,
otherKeys,Constants.REDIS_KEY.GOODS_ONE_DAY_RANK);
} /**
* 缓存货物的基本信息
* @param goods
* @param expireTime
*/
private void hasCacheGoods(GoodsVo goods, Long expireTime) {
//构造货物基本信息key
String goodsKey =Constants.REDIS_KEY.GOODS_KEY+goods.getGoodsId();
boolean isKey = redisUtil.hasKey(goodsKey);
if (!isKey){
redisUtil.hset(goodsKey,Constants.REDIS_KEY.GOODS_KEY_ID,goods.getGoodsId(),expireTime);
redisUtil.hset(goodsKey,Constants.REDIS_KEY.GOODS_KEY_NAME,goods.getGoodsName(),expireTime);
redisUtil.hset(goodsKey,Constants.REDIS_KEY.GOODS_KEY_SELL_NUM,goods.getSellNum(),expireTime); }
}
  • 代码中设计的常量类如下

/**
* @author:triumphxx
* @Date:2020/5/16
* @Time:9:43 上午
* @微信公众号:北漂码农有话说
* @desc:常量类
**/
public class Constants { public static class REDIS_KEY {
/**
* 货物销量key 每天某一个货物的销售量
*/
public static final String GOODS_TOP_KEY= "goods:rank:";
/**
* 货物的基本信息key
*/
public static final String GOODS_KEY= "goods:";
/**
* 货物id
*/
public static final String GOODS_KEY_ID= "goods:id:";
/**
* 货物名称
*/
public static final String GOODS_KEY_NAME= "goods:name:";
/**
* 货物销售数量
*/
public static final String GOODS_KEY_SELL_NUM= "goods:sellnum:";
/**
* 每日销售并集后的key
*/
public static final String GOODS_ONE_DAY_RANK= "oneday:rank";
}
}

项目中涉及的其他技术点比如:自定义标签等有机会专题讨论
项目中的具体细节请移步GitHub分析相关源码

项目最终效果如图

本文的案例,本被人已经上传到 GitHub上了,地址:https://github.com/triumphxx/triumphxxtop

如果觉得笔者的内容对你有用,请关注微信公众号,欢迎点赞评论。

Redis实现商品热卖榜的更多相关文章

  1. 想知道谁是你的最佳用户?基于Redis实现排行榜周期榜与最近N期榜

    本文由云+社区发表 前言 业务已基于Redis实现了一个高可用的排行榜服务,长期以来相安无事.有一天,产品说:我要一个按周排名的排行榜,以反映本周内用户的活跃情况.于是周榜(按周重置更新的榜单)诞生了 ...

  2. 使用redis防止商品超发

    redis不仅仅是单纯的缓存,它还有一些特殊的功能,在一些特殊场景上很好用.redis中key的原子自增incrby和判断key不存在再写入的setnx方法,可以有效的防止超发. 下面使用两个不同的方 ...

  3. redis解决商品秒杀问题

    博主最近在项目中遇到了抢购问题!现在分享下.抢购.秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个:1 高并发对数据库产生的压力2 竞争状态下如何解决库存的正确减少("超卖" ...

  4. Redis案例——商品秒杀,购物车

    秒杀案例: <?php header("content-type:text/html;charset=utf-8"); $redis = new redis(); $resu ...

  5. .NetCore+Jexus代理+Redis模拟秒杀商品活动

    开篇叙 本篇将和大家分享一下秒杀商品活动架构,采用的架构方案正如标题名称.NetCore+Jexus代理+Redis,由于精力有限所以这里只设计到商品添加,抢购,订单查询,处理队列抢购订单的功能:有不 ...

  6. Redis常见的应用场景解析

    Redis是一个key-value存储系统,现在在各种系统中的使用越来越多,大部分情况下是因为其高性能的特性,被当做缓存使用,这里介绍下Redis经常遇到的使用场景. Redis特性 一个产品的使用场 ...

  7. redis介绍及常见问题总结

    1.redis c语言编写的一个开源软件,使用字典结构存储数据,支持多种类型数据类型 数据类型:字符串,字典,列表,集合,有序集合 2.redis特点 速度快:c语言实现的,所有数据都存储在计算机内存 ...

  8. ThinkPHP5+Redis单例型购物车

    <?php /** * Redis + 单例型购物车 * param $basket 存储商品信息 * param $ins 存储实例化对象 */ namespace lib; use redi ...

  9. 细谈Redis五大数据类型

    文章原创于公众号:程序猿周先森.本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号. 上一篇文章有提到,Redis中使用最频繁的有5种数据类型:String.List.Hash.Set.SortS ...

随机推荐

  1. Python——数据库like模糊查询

    在Python中%是一个格式化字符,所以如果需要使用%则需要写成%%.将在Python中执行的sql语句改为:sql = "SELECT * FROM table_test WHERE va ...

  2. 【Windows10】如何使用Segoe MDL2 Assets图标

    众所周知,在Windows 10中,微软引入了汉堡菜单,方便Android和ios的开发者移植程序,而不需要单独为Windows设计一套UI.但有人可能发现在symbol icon里根本找不到所谓的汉 ...

  3. day58 bootstrap效果无法显示

    在学习bootstrap时直接复制官网的组件的时候,如果效果无法想官网一样显示,最大的可能是类库导入的顺序问题. 打开页面>检查>Console 我们会发现一条报错,导入的js需要jQue ...

  4. 汉王JAVA笔试题

    汉王JAVA笔试题 1,jsp中动态include与静态include的区别? (1)动态包含总是会检查文件中的变化,适合用于包含动态页面,并且可以带参数. (2)静态包含不会检查所含文件的变化,适用 ...

  5. 数据可视化之DAX篇(十四)DAX函数:RELATED和RELATEDTABLE

    https://zhuanlan.zhihu.com/p/64421378 Excel中知名度最高的函数当属VLOOKUP,它的确很有用,可以在两个表之间进行匹配数据,使工作效率大大提升,虽然它也有很 ...

  6. 各种jar包下载地址

    standard.jar和jstl.jar的下载地址 http://repo2.maven.org/maven2/javax/servlet/jstl/ http://repo2.maven.org/ ...

  7. 事件循环 event loop 究竟是什么

    事件循环 event loop 究竟是什么 一些概念 浏览器运行时是多进程,从任务管理器或者活动监视器上可以验证. 打开新标签页和增加一个插件都会增加一个进程,如下图:  浏览器渲染进程是多线程,包 ...

  8. P3406 海底高铁 (洛谷)

    题目背景 大东亚海底隧道连接着厦门.新北.博艾.那霸.鹿儿岛等城市,横穿东海,耗资1000亿博艾元,历时15年,于公元2058年建成.凭借该隧道,从厦门可以乘坐火车直达台湾.博艾和日本,全程只需要4个 ...

  9. CppUnit使用和源码解析

    前言 CppUnit是一个开源的单元测试框架,支持Linux和Windows操作系统,在linux上可以直接进行源码编译,得到动态库和静态库,直接链接就可以正常使用,在Windows上可以使用VC直接 ...

  10. C++语法小记---类模板

    类模板 类模板和函数模板类似,主要用于定义容器类 类模板可以偏特化,也可以全特化,使用的优先级和函数模板相同 类模板不能隐式推倒,只能显式调用 工程建议: 模板的声明和实现都在头文件中 成员函数的实现 ...