redis商户缓存
越野车,山地自行车,都拥有"避震器",防止车体加速后因惯性,在酷似"U"字母的地形上飞跃,硬着陆导致的损害,像个弹簧一样;
同样,实际开发中,系统也需要"避震器",防止过高的数据访问猛冲系统,导致其操作线程无法及时处理信息而瘫痪;
这在实际开发中对企业讲,对产品口碑,用户评价都是致命的;所以企业非常重视缓存技术;
例1:Static final ConcurrentHashMap<K,V> map = new ConcurrentHashMap<>(); 本地用于高并发
例2:static final Cache<K,V> USER_CACHE = CacheBuilder.newBuilder().build(); 用于redis等缓存
例3:Static final Map<K,V> map = new HashMap(); 本地缓存
由于其被Static修饰,所以随着类的加载而被加载到内存之中,作为本地缓存,由于其又被final修饰,所以其引用(例3:map)和对象(例3:new HashMap())之间的关系是固定的,不能改变,因此不用担心赋值(=)导致缓存失效;
一、为什么要使用缓存
一句话:因为速度快,好用
缓存数据存储于代码中,而代码运行在内存中,内存的读写性能远高于磁盘,缓存可以大大降低用户访问并发量带来的服务器读写压力
实际开发过程中,企业的数据量,少则几十万,多则几千万,这么大数据量,如果没有缓存来作为"避震器",系统是几乎撑不住的,所以企业会大量运用到缓存技术;
但是缓存也会增加代码复杂度和运营的成本。
二、如何使用缓存
实际开发中,会构筑多级缓存来使系统运行速度进一步提升,例如:本地缓存与redis中的缓存并发使用
浏览器缓存:主要是存在于浏览器端的缓存
应用层缓存: 可以分为tomcat本地缓存,比如之前提到的map,或者是使用redis作为缓存
数据库缓存: 在数据库中有一片空间是 buffer pool,增改查数据都会先加载到mysql的缓存中
CPU缓存: 当代计算机最大的问题是 cpu性能提升了,但内存读写速度没有跟上,所以为了适应当下的情况,增加了cpu的L1,L2,L3级的缓存
三、案例使用缓存
添加商户缓存
在我们查询商户信息时,我们是直接操作从数据库中去进行查询的,大致逻辑是这样,直接查询数据库那肯定慢咯,所以我们需要增加缓存
@GetMapping("/{id}")
public Result queryShopById(@PathVariable("id") Long id) {
//这里是直接查询数据库
return shopService.queryById(id);
}
1. 缓存模型和思路
标准的操作方式就是查询数据库之前先查询缓存,如果缓存数据存在,则直接从缓存中返回,如果缓存数据不存在,再查询数据库,然后将数据存入redis。
2. 查询店铺
根据id查询店铺时,如果缓存未命中,则查询数据库,将数据库结果写入缓存,并设置超时时间
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public Result getByIdShop(Long id) {
//查询redis中是否有值,有则直接返回
String key = CACHE_SHOP_KEY + id;
String cacheShop = stringRedisTemplate.opsForValue().get(key);
if (ObjectUtil.isNotEmpty(cacheShop)) {
Shop shopBean = JSONUtil.toBean(cacheShop, Shop.class);
return Result.ok(shopBean);
}
//没有,查询数据库
Shop shop = getById(id);
//判断查询的店铺是否为空
if (shop == null) {
return Result.fail("店铺不存在");
}
//将对象转为json字符串
String jsonShop = JSONUtil.toJsonStr(shop);
//放进redis
stringRedisTemplate.opsForValue().set(key,jsonShop);
//设置超时时间
stringRedisTemplate.expire(key,CACHE_SHOP_TTL, TimeUnit.MINUTES);
return Result.ok(shop);
}
}
3. 修改店铺信息
分析:修改店铺时,应先修改数据库,再删除redis缓存,这样能最小化出现缓存和数据库不一致的情况。
如果为先删除缓存再修改数据库的话,删除缓存比较快,几乎是毫秒级的(毕竟redis以速度快而闻名嘛),而修改数据库就相对比较慢了,这时如果有另外一个线程查询这条数据,而redis中的值刚被干掉,就会将查询出的结果又存入redis,所以就出现了缓存与数据库不一致的情况。
而先修改数据库的再删除缓存的话,就能降低上面的巧合,但不能完全避免。
我们确定了采用删除策略,来解决双写问题,当我们修改了数据之后,然后把缓存中的数据进行删除,查询时发现缓存中没有数据,则会从mysql中加载最新的数据,从而避免数据库和缓存不一致的问题
分析完之后,写代码时十分简单:
@Override
@Transactional
public Result update(Shop shop) {
Long id = shop.getId();
if (id == null) {
Result.fail("店铺ID不能为空");
}
// 更新数据库
boolean b = updateById(shop);
if (b){
//删除缓存
stringRedisTemplate.delete(CACHE_SHOP_KEY + id);
return Result.ok();
}
// 更新失败
return Result.fail("操作失败");
}
@Transactional
: 事务,标记的代码要么全成功,要么全失败。失败了会回滚已经操作过的