9-分布式缓存List-Hash最佳案例实战

本章主要讲解Redis数据结构里的List与Hash在实际使用场景做介绍与解答,分别对List-热销榜单实战演练,Hash-购物车的实战演练。通过这两个实战演练可以清晰了解到在实际应用开发的思路与应用。

在线教育-List-天热销榜单

需求分析

在淘宝京东的首页会有一个热销榜单,榜单又分为实时的与非实时的。而这个List结构适合做非实时的榜单。这种榜单的更新除了可以让程序自动更新外,有一些还需要人工运营替换榜单位置的。企业中的流程,定时任务计算昨天最多人访问的商品,晚上12点到1点更新到榜单上,预留一个接口支持人工运营。

为啥不是实时计算呢?其实在真正的高并发项目,都是预先计算好结果,然后直接返回数据,这样存储结构最简单。下面来简单讲解下,首先用户访问这个应用程序,应用程序再从Redis榜单里获取数据。但这个Redis榜单又是如何获取的呢?会有一个定时调度的任务+大数据处理的方法,这个方法先从数据库获取数据源再进行统计,统计完成后再给Redis中存储榜单。运营后台也可以人工的调动榜单。

在线教育-热销视频榜单实战

编写接口代码:在controller层中添加如下代码

@RequestMapping("rank")

public JsonData videoRank(){

List<VideoDO> list = redisTemplate.opsForList().range(RANK_KEY,0,-1);

return JsonData.buildSuccess(list);

}

接下来我们在测试环境里编写该接口的测试数据

@Test

void saveRank(){

String RANK_KEY = "rank:video";

VideoDO video1 = new VideoDO(3,"PaaS⼯业级微服务⼤课","xdclass.net",1099);

VideoDO video2 = new VideoDO(5,"AlibabaCloud全家桶实战","xdclass.net",59);

VideoDO video3 = new VideoDO(53,"SpringBoot2.X+Vue3综合实战","xdclass.net",49);

VideoDO video4 = new VideoDO(15,"玩转23种设计模式+最近实战","xdclass.net",49);

VideoDO video5 = new VideoDO(45,"Nginx⽹关+LVS+KeepAlive","xdclass.net",89);

redisTemplate.opsForList().leftPushAll(RANK_KEY,video5,video4,video3,video2,video1);

}

把代码写完后我们可以在浏览器发请求看下是否实现了榜单效果。

上面就实现了程序实现的榜单排行了,下面我们在继续模拟人工运营操作榜单的实现,再重新发送请求后,你会发现上面榜单第二名的"Alibaba全家桶实战"已经被替换掉,这实战例子就实现了人工替换榜单的教程。

/**

* 替换榜单第⼆名

*/

@Test

void replaceRank(){

String RANK_KEY = "rank:video";VideoDO video = new VideoDO(42,"⼩滴课堂⾯试专题第⼀季⾼级⼯程师","xdclass.net",89);

//在集合的指定位置插⼊元素,如果指定位置已有元素,则覆盖,没有则新增

redisTemplate.opsForList().set(RANK_KEY,1,video);

}

电商平台-Hash-购物车实战

前面我们讲了String和List数据类型对应的实战案例。能够充分明白在什么应用场景下用什么样的数据结构实现什么样的功能。接下来我们对Hash这个数据结构来讲解它一般用在什么样的功能上,如何实现的。

需求分析

在我们常用的电商购物平台,都具备有购物车这个功能。在购物车里,可以看到商品的图片、名字、价格、数量等等一些信息,并且可以购买不一样的商品。那如何去设计这个购物车呢?购物车常见的实现方式有:

(1)数据库存储:用户添加一条商品到购物车时就往对应的数据库表添加一条信息,后续可以根据商品的id来查询相对应的商品,这种方式存在着一定的性能瓶颈。

(2)前端本地存储(localstorage-sessionstorage ):localstorage在浏览器中存储是以key-value进行存储的,没有过期时间。sessionstorage 在浏览器中存储key-value,在关闭会话窗口后会删除这些数据。

(3)后端存储到缓存中,比如Redis:可以开启Redis的数据持久化AOP防止重启数据丢失。

购物车数据结构介绍

一个购物车里面会存在着多个购物项,所以购物车的结构是双层的Map结构:Map<String,Map<String,String>>,第一层Map的key是用来存储用户id,第二层Map的key是用来存储购物车中商品的id,Value是购物车中的数据。那么在Redis里就可以使用Hash结构用来实现。

电商购物车实战

VideoDO类前面第七章的内容已经编写过了,这里就不在给代码了

CartItemVO新建一个Vo层

public class CartItemVO {

/**

* 商品id

*/

private Integer productId;

/**

* 购买数量

*/

private Integer buyNum;

/**

* 商品标题

*/

private String productTitle;

/**

* 图⽚

*/

private String productImg;

/**

* 商品单价

*/

private int price ;

/**

* 总价格,单价+数量

*/

private int totalPrice;

public int getProductId() {

return productId;

}

public void setProductId(int productId) {

this.productId = productId;

}

public Integer getBuyNum() {

return buyNum;

}

public void setBuyNum(Integer buyNum) {

this.buyNum = buyNum;

}

public String getProductTitle() {

return productTitle;

}

public void setProductTitle(String

productTitle) {

this.productTitle = productTitle;

}

public String getProductImg() {

return productImg;

}

public void setProductImg(String

productImg) {

this.productImg = productImg;

}

/**

* 商品单价 * 购买数量

* @return

*/

public int getTotalPrice() {

return this.price*this.buyNum;

}

public int getPrice() {

return price;

}

public void setPrice(int price) {

this.price = price;

}

public void setTotalPrice(int totalPrice) {

this.totalPrice = totalPrice;

}

}

CartVO在VO层里

public class CartVO {

/**

* 购物项

*/

private List<CartItemVO> cartItems;

/**

* 购物⻋总价格

*/

private Integer totalAmount;

/**

* 总价格

* @return

*/

public int getTotalAmount() {

return

cartItems.stream().mapToInt(CartItemVO::getTotalPrice).sum();

}

public List<CartItemVO> getCartItems() {

return cartItems;

}

public void setCartItems(List<CartItemVO>cartItems) {

this.cartItems = cartItems;

}

}

数据源层在Dao层创建,这里是数据库里的数据。

@Repository

public class VideoDao {

private static Map<Integer,VideoDO> map = new HashMap<>();

static {

map.put(1,new VideoDO(1,"⼯业级PaaS云平台+SpringCloudAlibaba 综合项⽬实战(完 结)","https://xdclass.net",1099));

map.put(2,new VideoDO(2,"玩转新版⾼性能RabbitMQ容器化分布式集群实战","https://xdclass.net",79));

map.put(3,new VideoDO(3,"新版后端提效神器MybatisPlus+SwaggerUI3.X+Lombok","https://xdclass.net",49));

map.put(4,new VideoDO(4,"玩转Nginx分布式架构实战教程 零基础到⾼级","https://xdclass.net",49));

map.put(5,new VideoDO(5,"ssm新版SpringBoot2.3/spring5/mybatis3","https://xdclass.net",49));

map.put(6,new VideoDO(6,"新⼀代微服务全家桶AlibabaCloud+SpringCloud实 战","https://xdclass.net",59));

}

/**

* 模拟从数据库找

* @param videoId

* @return

*/

public VideoDO findDetailById(int videoId)

{

return map.get(videoId);

}

}

编写一个Json工具类,用于存到Redis里做个Json的序列化方便操作

public class JsonUtil {

// 定义jackson对象

private static final ObjectMapper MAPPER = new ObjectMapper();

/**

* 将对象转换成json字符串。

* @return

*/

public static String objectToJson(Object data) {

try {

String string = MAPPER.writeValueAsString(data);

return string;

} catch (Exception e) {

e.printStackTrace();

}

return null;

}

/**

* 将json结果集转化为对象

*

* @param jsonData json数据

* @param clazz 对象中的object类型

* @return

*/

public static <T> T jsonToPojo(String jsonData, Class<T> beanType) {

try {

T t = MAPPER.readValue(jsonData,beanType);

return t;

} catch (Exception e) {

e.printStackTrace();

}

return null;

}

}

购物车接口开发CartController,在controller层创建。

@RestController

@RequestMapping("api/v1/cart")

public class CartController{

@RequestMapping("addCart")

public JsonData addCart(int videoId,intbuyNum){

//获取购物⻋

BoundHashOperations<String, Object,Object> myCart = getMyCartOps();

Object cacheObj =myCart.get(videoId+"");

String result = "";

if (cacheObj != null) {

result = (String) cacheObj;

}

if (cacheObj == null) {

//不存在则新建⼀个购物项

CartItemVO cartItem = new CartItemVO();

//从数据库查询详情,我们这边直接随机写个

VideoDO videoDO = videoDao.findDetailById(videoId);

videoDO.setId(videoId);

cartItem.setPrice(videoDO.getPrice());

cartItem.setBuyNum(buyNum);

cartItem.setProductId(videoId);

cartItem.setProductImg(videoDO.getImg());

cartItem.setProductTitle(videoDO.getTitle());

myCart.put(videoId+"",JsonUtil.objectToJson(cartItem));

} else {

//存在则新增数量

CartItemVO cartItem = JsonUtil.jsonToPojo(result, CartItemVO.class);

cartItem.setBuyNum(cartItem.getBuyNum() +buyNum);

myCart.put(videoId+"",JsonUtil.objectToJson(cartItem));

}

return JsonData.buildSuccess();

}

}

编写购物车通用方法

/**

* 抽取我的购物⻋通⽤⽅法

*

* @return

*/

private BoundHashOperations<String, Object,Object> getMyCartOps() {

String cartKey = getCartKey();

return redisTemplate.boundHashOps(cartKey);

}

/**

* 获取购物⻋的key

*

* @return

*/

private String getCartKey() {

//从拦截器获取 ,这⾥写死即可,每个⽤户不⼀样

int userId = 88;

String cartKey =

String.format("product:cart:%s", userId);

return cartKey;

}

在模拟完购物车功能实现后,可以接着来实现查看和清空购物车的功能。在CartController里继续编写

@GetMapping("/mycart")

public JsonData findMyCart(){

BoundHashOperations<String,Object,Object>

myCart = getMyCartOps();

List<Object> itemList =

myCart.values();

List<CartItemVO> cartItemVOList = new

ArrayList<>();

for(Object item: itemList){

CartItemVO cartItemVO =

JsonUtil.jsonToPojo((String)item,CartItemVO.cla

ss);

cartItemVOList.add(cartItemVO);

}

//封装成cartvo

CartVO cartVO = new CartVO();

cartVO.setCartItems(cartItemVOList);

return JsonData.buildSuccess(cartVO);

}

//清空购物车

@GetMapping("/clear")

public JsonData clear() {

String cartKey = getCartKey();

redisTemplate.delete(cartKey);

return JsonData.buildSuccess();

}

到这里本章已经对List-Hash这两个数据结构的实战环节已经结束了,总结一下List可以实现榜单的功能,Hash可以实现购物车的功能。

本章小结:通过本章的两个实战演练,可以充分了解到List与Hash在Redis中的使用方法,并且实现在当前热门的功能上,当然这些数据结构还有很多的应用场景,需要小伙伴们自行学习了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容