目录
1. 热点数据缓存
1.1 什么是缓存?
为了把一些经常访问的数据,放入缓存中以减少对数据库的访问频率。从而减少数据库的压力,提高程序的性能。(内存中存储)
1.2 缓存的原理
1.3 什么样的数据适合放入缓存中
- 查询频率高且修改频率低
- 数据安全性低
1.4 哪个组件可以作为缓存
- redis组件
- memory组件
- ehcache组件
1.5 java使用redis如何实现缓存功能
1.5.1 需要的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
1.5.2 配置文件
server.port=8080
#数据源
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/1suo?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=12345678
#mybatisplus的sql日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#mapper映射文件
mybatis-plus.mapper-locations=classpath*:mapper/*.xml
#redis配置
spring.redis.host=172.16.7.192
spring.redis.port=6379
spring.redis.database=1
1.5.3 代码
控制层
package com.ls.controller;
import com.ls.entity.Clazz;
import com.ls.service.ClazzService;
import com.ls.vo.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* @program: springboot-redis-cache
* @description:
* @author: 1suo
* @create: 2024-07-24 19:56
**/
@RestController
@RequestMapping("clazz")
public class ClazzController {
@Autowired
private ClazzService clazzService;
@DeleteMapping("delete")
public R delete(Integer id) {
return clazzService.delete(id);
}
@GetMapping("get")
public R get(Integer id) {
return clazzService.get(id);
}
@GetMapping("getAll")
public R getAll() {
return clazzService.getAll();
}
@PostMapping("save")
public R save(@RequestBody Clazz clazz) {
return clazzService.save(clazz);
}
@PutMapping("update")
public R update(Clazz clazz) {
return clazzService.update(clazz);
}
}
业务层
package com.ls.service.impl;
import com.ls.dao.ClazzDao;
import com.ls.entity.Clazz;
import com.ls.service.ClazzService;
import com.ls.vo.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import sun.dc.pr.PRError;
import java.util.ArrayList;
import java.util.List;
/**
* @program: springboot-redis-cache
* @description:
* @author: 1suo
* @create: 2024-07-24 19:57
**/
@Service
public class ClazzServiceImpl implements ClazzService {
@Autowired
private ClazzDao clazzDao;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public R save(Clazz clazz) {
int insert = clazzDao.insert(clazz);
return new R(200,"保存成功",clazz);
}
@Override
public R update(Clazz clazz) {
int update = clazzDao.updateById(clazz);
if (update>0){
redisTemplate.opsForValue().set("clazz::" + clazz.getCid(),clazz);
}
return new R(200,"更新成功",clazz);
}
@Override
public R delete(Integer id) {
int delete = clazzDao.deleteById(id);
if (delete>0){
redisTemplate.delete("clazz::" + id);
}
return new R(200,"删除成功",id);
}
@Override
public R get(Integer id) {
ValueOperations<String, Object> forValue = redisTemplate.opsForValue();
Object o = forValue.get("clazz::" + id);
if(o!=null){
return new R(200,"查询成功",(Clazz)o);
}
Clazz clazz = clazzDao.selectById(id);
if (clazz!=null){
forValue.set("clazz::" + id,clazz);
}
return new R(500,"查询成功",clazz);
}
@Override
public R getAll() {
List<Clazz> list = clazzDao.selectList(null);
return new R(200,"查询成功",list);
}
}
dao层
package com.ls.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ls.entity.Clazz;
/**
* @program: springboot-redis-cache
* @description:
* @author: 1suo
* @create: 2024-07-25 08:41
**/
public interface ClazzDao extends BaseMapper<Clazz> {
}
实体类
package com.ls.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @program: springboot-redis-cache
* @description:
* @author: 1suo
* @create: 2024-07-24 19:56
**/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tbl_clazz")
public class Clazz {
@TableId(type = IdType.AUTO)
private Integer cid;
private String cname;
}
配置类
package com.ls.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String, Object> template = new RedisTemplate<>();//创建redisTemplate对象
StringRedisSerializer serializer = new StringRedisSerializer();//字符串序列化对象
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);//Jackson的序列化对象
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
// key序列化方式
template.setKeySerializer(serializer);
// value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
// value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.setKeySerializer(serializer);
return template;
}
}
@MapperScan("com.ls.dao") 注解,将dao层自动代理为Mapper代理对象。
1.5.4 发现
业务层代码除了要维护核心业务功能外,额外还要维护缓存的代码。
解决: 使用AOP面向切面编程。(spring框架用aop切面实现)
1.6 使用缓存注解完成缓存功能
必须spring缓存使用的组件。
config:为解决序列化的问题
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600)) //缓存过期10分钟 ---- 业务需求。
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))//设置key的序列化方式
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) //设置value的序列化
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
开启缓存注解
使用
package com.ls.service.impl;
import com.ls.dao.ClazzDao;
import com.ls.entity.Clazz;
import com.ls.service.ClazzService;
import com.ls.vo.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import sun.dc.pr.PRError;
import java.util.ArrayList;
import java.util.List;
/**
* @program: springboot-redis-cache
* @description:
* @author: 1suo
* @create: 2024-07-24 19:57
**/
@Service
public class ClazzServiceImpl implements ClazzService {
@Autowired
private ClazzDao clazzDao;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public R save(Clazz clazz) {
int insert = clazzDao.insert(clazz);
return new R(200,"保存成功",clazz);
}
@CachePut(cacheNames = "clazz", key = "#clazz.cid")
@Override
public Clazz update(Clazz clazz) {
int update = clazzDao.updateById(clazz);
return clazz;
}
@CacheEvict(cacheNames = "clazz", key = "#id")
@Override
public R delete(Integer id) {
int delete = clazzDao.deleteById(id);
return new R(200,"删除成功",id);
}
@Cacheable(cacheNames = "clazz", key = "#id")
@Override
public Clazz get(Integer id) {
Clazz clazz = clazzDao.selectById(id);
return clazz;
}
@Override
public R getAll() {
List<Clazz> list = clazzDao.selectList(null);
return new R(200,"查询成功",list);
}
}
2. 分布式锁
2.1模拟高并发
使用jmeter压测工具:
第一步:
第二步:
第三步:
通过压测发现库存超卖和重卖了,解决办法使用锁。
2.2 使用syn和lock锁
public String decrement(Integer productid) {
//根据id查询商品的库存
int num = stockDao.findById(productid);
synchronized (this) {
if (num > 0) {
//修改库存
stockDao.update(productid);
System.out.println("商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个");
return "商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个";
} else {
System.out.println("商品编号为:" + productid + "的商品库存不足。");
return "商品编号为:" + productid + "的商品库存不足。";
}
}
}
上面使用syn和lock虽然解决了并发问题,但是我们未来项目部署时可能要部署集群模式。
2.3 模拟多服务器
点击增加一个服务器,同时启动后,实现模拟多服务器。 下面是服务器配置。
启动俩个服务器:
2.4 nginx代理集群
下载window版本的nginx实现代理集群。
nginx.conf配置文件:
通过压测发现本地锁 无效了,使用redis解决分布式锁文件。
2.5 使用redis解决分布式锁文件
核心代码:
@Service
public class StockService {
@Autowired
private StockDao stockDao;
@Autowired
private RedissonClient redisson;
//
public String decrement(Integer productid) {
RLock lock = redisson.getLock("product::" + productid);
lock.lock();
try {
//根据id查询商品的库存: 提前预热到redis缓存中
int num = stockDao.findById(productid);
if (num > 0) {
//修改库存---incr---定时器[redis 数据库同步]
stockDao.update(productid);
System.out.println("商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个");
return "商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个";
} else {
System.out.println("商品编号为:" + productid + "的商品库存不足。");
return "商品编号为:" + productid + "的商品库存不足。";
}
}finally {
lock.unlock();
}
}
}