功能02-商鋪查詢快取
3.商鋪詳情快取查詢
3.1什么是快取?
快取就是資料交換的緩沖區(稱作Cache),是存盤資料的臨時地方,一般讀寫性能較高,
快取的作用:
- 降低后端負載
- 提高讀寫效率,降低回應時間
快取的成本:
- 資料一致性成本
- 代碼維護成本
- 運維成本
3.2需求說明
如下,當我們點擊商店詳情的時候,前端會向后端發出請求,后端需要把相關的商店資料回傳給客戶端顯示,
3.3思路分析(添加Redis快取)
使用Redis的快取模型如下:
當客戶端發送請求到服務端時,先去redis中查詢有沒有對應的資料:
- 如果命中,則直接給客戶端回傳資料,這樣直接訪問資料庫的請求就會大大減少
- 如果未命中,則到資料庫中查詢,同時將資料寫入redis,防止下一次查詢同樣的資料,然后將資料回傳給客戶端
3.4代碼實作
(1)Shop.java 物體類
package com.hmdp.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @author 李
* @version 1.0
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("tb_shop")
public class Shop implements Serializable {
private static final long serialVersionUID = 1L;
//主鍵
@TableId(value = "https://www.cnblogs.com/liyuelian/p/id", type = IdType.AUTO)
private Long id;
//商鋪名稱
private String name;
//商鋪型別id
private Long typeId;
//商鋪圖片,多個圖片以','隔開
private String images;
//商圈,例如陸家嘴
private String area;
//地址
private String address;
//經度
private Double x;
//緯度
private Double y;
//均價,取整數
private Long avgPrice;
//銷量
private Integer sold;
//評論數量
private Integer comments;
//評分,1~5分,乘10保存,避免小數
private Integer score;
//營業時間,例如 10:00-22:00
private String openHours;
//創建時間
private LocalDateTime createTime;
//更新時間
private LocalDateTime updateTime;
@TableField(exist = false)
private Double distance;
}
(2)對應的mapper介面
package com.hmdp.mapper;
import com.hmdp.entity.Shop;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* Mapper 介面
*
* @author 李
* @version 1.0
*/
public interface ShopMapper extends BaseMapper<Shop> {
}
(3)IShopService.java 介面
package com.hmdp.service;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* 服務類
*
* @author 李
* @version 1.0
*/
public interface IShopService extends IService<Shop> {
Result queryById(Long id);
}
(4)ShopServiceImpl 服務實作類
package com.hmdp.service.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import static com.hmdp.utils.RedisConstants.*;
/**
*
* 服務實作類
*
* @author 李
* @version 1.0
*/
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
@Resource
StringRedisTemplate stringRedisTemplate;
@Override
public Result queryById(Long id) {
String key = CACHE_SHOP_KEY + id;
//1.從redis中查詢商鋪快取
String shopJson = stringRedisTemplate.opsForValue().get(key);
//2.判斷快取是否命中
if (StrUtil.isNotBlank(shopJson)) {
//2.1若命中,直接回傳商鋪資訊
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return Result.ok(shop);
}
//2.2未命中,根據id查詢資料庫,判斷商鋪是否存在資料庫中
Shop shop = getById(id);
if (shop == null) {
//2.2.1不存在,則回傳404
return Result.fail("店鋪不存在!");
}
//2.2.2存在,則將商鋪資料寫入redis中
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));
return Result.ok(shop);
}
}
(5)ShopController 控制類
package com.hmdp.controller;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.service.IShopService;
import com.hmdp.utils.SystemConstants;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* 前端控制器
*
* @author 李
* @version 1.0
*/
@RestController
@RequestMapping("/shop")
public class ShopController {
@Resource
public IShopService shopService;
/**
* 根據id查詢商鋪資訊
* @param id 商鋪id
* @return 商鋪詳情資料
*/
@GetMapping("/{id}")
public Result queryShopById(@PathVariable("id") Long id) {
return shopService.queryById(id);
}
}
(6)測驗:首次查詢的時候因為資料為寫入reids,因此查詢較慢,第二次因為已寫入redis,查詢較快
4.商鋪型別快取查詢
4.1需求說明
店鋪型別在首頁和其他多個頁面都會用到,如下:
要求當我們點擊商鋪型別的時候,前端會向后端發出請求,后端需要把相關的商店型別資料回傳給客戶端顯示:
4.2思路分析
該功能的實作思路與上述的思路大體一致,
4.3代碼實作
(1)物體類 ShopType
package com.hmdp.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @author 李
* @version 1.0
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("tb_shop_type")
public class ShopType implements Serializable {
private static final long serialVersionUID = 1L;
//主鍵
@TableId(value = "https://www.cnblogs.com/liyuelian/p/id", type = IdType.AUTO)
private Long id;
//型別名稱
private String name;
//圖示
private String icon;
//順序
private Integer sort;
//創建時間
@JsonIgnore
private LocalDateTime createTime;
//更新時間
@JsonIgnore
private LocalDateTime updateTime;
}
(2)ShopTypeMapper介面
package com.hmdp.mapper;
import com.hmdp.entity.ShopType;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* Mapper 介面
*
* @author 李
* @version 1.0
*/
public interface ShopTypeMapper extends BaseMapper<ShopType> {
}
(3)服務類介面 IShopTypeService
package com.hmdp.service;
import com.hmdp.dto.Result;
import com.hmdp.entity.ShopType;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* 服務類介面
*
* @author 李
* @version 1.0
*/
public interface IShopTypeService extends IService<ShopType> {
Result queryShopList();
}
(4)服務實作類 ShopTypeServiceImpl
package com.hmdp.service.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.ShopType;
import com.hmdp.mapper.ShopTypeMapper;
import com.hmdp.service.IShopTypeService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import static com.hmdp.utils.RedisConstants.CACHE_SHOP_TYPE;
/**
* 服務實作類
*
* @author 李
* @version 1.0
*/
@Service
public class ShopTypeServiceImpl extends ServiceImpl<ShopTypeMapper, ShopType> implements IShopTypeService {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result queryShopList() {
//查詢redis中有沒有店鋪型別快取
String shopTypeJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_TYPE);
//如果有,則將其轉為物件型別,并回傳給客戶端
if (StrUtil.isBlank(shopTypeJson)) {
List<ShopType> shopTypeList = JSONUtil.toList(shopTypeJson, ShopType.class);
return Result.ok(shopTypeList);
}
//如果redis中沒有快取,到DB中查詢
//如果DB中沒有查到,回傳錯誤資訊
List<ShopType> list = query().orderByAsc("sort").list();
if (list == null) {
return Result.fail("查詢不到店鋪型別!");
}
//如果DB查到了資料
//將資料存入Redis中(轉為json型別存入)
stringRedisTemplate.opsForValue()
.set(CACHE_SHOP_TYPE, JSONUtil.toJsonStr(list));
//并回傳給客戶端
return Result.ok(list);
}
}
(5)控制類 ShopTypeController
package com.hmdp.controller;
import com.hmdp.dto.Result;
import com.hmdp.entity.ShopType;
import com.hmdp.service.IShopTypeService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
/**
* 前端控制器
*
* @author 李
* @version 1.0
*/
@RestController
@RequestMapping("/shop-type")
public class ShopTypeController {
@Resource
private IShopTypeService typeService;
@GetMapping("list")
public Result queryTypeList() {
return typeService.queryShopList();
}
}
(6)測驗,訪問客戶端首頁,
回傳的資料如下:
5.快取更新
5.1快取更新策略
5.1.1主動更新策略
- Cache Aside Pattern:由快取的呼叫者,在更新資料庫的同時更新快取(可控性最高,推薦使用)
- Read/Write Through Pattern:快取與資料庫整合為一個服務,由服務來維護一致性,呼叫者呼叫該服務,無需關心快取一致性問題
- Write Behind Caching Pattern:呼叫者只操作快取,由其他執行緒異步的將快取資料持久化到資料庫,保證最終一致
操作快取和資料庫時有三個問題需要考慮:
-
洗掉快取還是更新快取?
- 更新快取:每次更新資料庫都更新快取,無效寫操作較多
- 洗掉快取:更新資料庫時讓快取失效,查詢時再更新快取(推薦使用)
-
如何保證快取與資料庫的操作的同時成功或失敗?(原子性)
- 單體系統,將快取與資料庫操作放在一個事務
- 分布式系統,利用TCC等分布式事務方案
-
先操作快取還是先操作資料庫?(執行緒安全問題)
如上,雖然兩種方案都有可能造成快取和資料庫不一致,但更推薦先更新資料庫再洗掉快取,
先更新資料庫再洗掉快取出現資料不一致概率更低,因為操作快取一般比資料庫更快,所以發生右圖的情況很低(右圖),即使發生了,可以配合TTL定時清除快取,
5.1.2總結
快取更新策略的最佳實踐方案:
- 低一致性需求:使用Redis自帶的記憶體淘汰機制即可
- 高一致性需求:主動更新,并以超時剔除作為兜底方案
- 讀操作:
- 快取命中則直接回傳
- 快取未命中則查詢資料庫,并寫入快取,設定超時時間
- 寫操作:
- 先寫資料庫,然后再洗掉快取
- 要確保資料庫與快取操作的原子性
- 讀操作:
5.2需求說明
給查詢商鋪的快取添加超時剔除和主動更新策略:
- 根據id查詢店鋪時,如果快取未命中,則查詢資料庫,將資料庫結果寫入快取,并設定超時時間
- 根據id修改店鋪,先修改資料庫,再洗掉快取
5.3代碼實作
(1)修改ShopServiceImpl的queryById()方法,設定超時時間
并添加update()方法如下:
@Override
@Transactional
public Result update(Shop shop) {
Long id = shop.getId();
if (id == null) {
return Result.fail("店鋪id不能為空");
}
//1.更新資料庫
updateById(shop);
//2.洗掉redis快取
stringRedisTemplate.delete(CACHE_SHOP_KEY + id);
return Result.ok();
}
(2)修改IShopService,添加方法宣告
Result update(Shop shop);
(3)修改ShopController,添加方法
/**
* 更新商鋪資訊
* @param shop 商鋪資料
* @return 無
*/
@PutMapping
public Result updateShop(@RequestBody Shop shop) {
// 寫入資料庫
return shopService.update(shop);
}
(4)測驗
讀操作:首次訪問店鋪詳情,可以看到redis中存入資料,并且設定了TTL
寫操作:使用postman向服務端發送更新店鋪資訊請求,可以看到當更新資料時候,先更新資料庫,然后將redis的快取洗掉,之后如果再有查詢,將會重建redis的快取,實作資料的一致性,
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/550599.html
標籤:NoSQL
上一篇:day02-短信登錄