springboot整合redis以及使用场景
StringRedisTemplate是RedisTemplate的子类。StringRedisTemplate它的泛型key和value都是String类型。RedisTemplate它的key value的泛型是Object。
springboot整合redis
介绍
StringRedisTemplate和RedisTemplate是java操作redis数据库提供的两个类库
本文通过springboot整合redis
步骤:
引入操作redis数据的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
进行数据库连接,配置数据源 application.properties
spring.redis.host=192.168.31.31
spring.redis.port=6379
spring.redis.jedis.pool.max-active=20
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=0
spring.redis.jedis.pool.max-wait=20000
测试
@Autowired
private StringRedisTemplate redisTemplate;
@Test
public void contextLoads() {
//获取所有key
Set<String> keys = redisTemplate.keys("*");
//删除key
redisTemplate.delete("kkk");
//设置key的过期时间
redisTemplate.expire("k3", 60, TimeUnit.SECONDS);
//获取到设置的过期时间
redisTemplate.getExpire("k3");
//操作字符串
redisTemplate.opsForValue();
}
RedisTemplate
RedisTemplate的数据存放
RedisTemplate该类可以存放任意类型的数据,但是该类型的数据必须实现序列,获取redis中对应的数据时,会进行反序列化。 如果使用RedisTemplate建议大家指定key,value,以及hashkey的序列化方式。
RedisTemplate的配置类
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
//比如验证码
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
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);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@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;
}
}
springboot连接集群
两种集群模式:哨兵和去中心化
哨兵模式的配置
spring.redis.sentinel.master=master
spring.redis.sentinel.nodes=192.168.31.31:26379
去中心化集化的配置
spring.redis.cluster.nodes=192.168.31.31:8000 192.168.31.31:8001 192.168.31.31:8002 192.168.31.31:8003 192.168.31.31:8004 192.168.31.31:8005
Redis的用途
作为缓存
作用:减少数据库的访问频率。 提高数据的访问率。
热点数据 、修改频率比较低、安全系数低的可以放入缓存
测试:
代码实现
搭建项目,配置redis省略
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private RedisTemplate redisTemplate;
public User findById(Integer deptId){
//1.从缓存中查询该数据
Object o = redisTemplate.opsForValue().get("findById::" + deptId);
if(o!=null){//表示从缓存中获取该数据
return (Dept) o;
}
User user = userDao.selectById(userId);
redisTemplate.opsForValue().set("findById::"+userId,user);//把查询的结果放入缓存
return user;
}
//数据库和缓存同步问题!
public int delete(Integer userId){
redisTemplate.delete("findById::"+userId);//删除缓存
int i = userDao.deleteById(userId);
return i;
}
public int update(User user){
redisTemplate.delete("findById::"+user.getUserId());//删除缓存
int i = userDao.updateById(user);
redisTemplate.opsForValue().set("findById::"+user.getDeptId(),user);
return i;
}
}
使用注解实现
主启动类:
@SpringBootApplication
@MapperScan(basePackages = "com.redis.dao")
@EnableCaching //开启缓存的注解
public class SpringbootRedisApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootRedisApplication.class, args);
}
}
业务层:
@Service
public class UserService {
@Autowired
private UserDao userDao;
//该注解作用:会先查询缓存,如果缓存存在,则不会执行代码块。 如果缓存中不存在则执行该方法,并把该方法的返回值存放到redis中
@Cacheable(cacheNames = "findById",key = "#userId") //缓存的key值 为findById
public Dept findById(Integer userId){
User user = userDao.selectById(userId);
return user;
}
//数据库和缓存同步问题!
// beforeInvocation:是否在方法执行前就清空,缺省为 false,
// 如果指定为 true,则在方法还没有执行的时候就清空缓存。缺省情况下,如果方法执行抛出异常,则不会清空缓存。
@CacheEvict(cacheNames = "findById",key = "#userId")
public int delete(Integer userId){
int i = userDao.deleteById(userId);
return i;
}
//这个注解是必须执行方法体,而且会把方法体执行的结果放入到缓存中。 如果发生异常则不操作缓存。
@CachePut(cacheNames = "findById",key = "#user.userId")
public User update(Dept dept){
int i = userDao.updateById(dept);
return user;
}
}
分布式锁
概念:当多个进程不在同一个系统中,用分布式锁控制多个进程对资源访问。
注意:不可使用synchronized,它只可锁同一系统中的不同线程
关键:
锁信息要设置超时,不能让一个线程长期占用锁导致思索
同一时刻只能有一个线程获得锁
代码实现
@Service
public class StockService {
@Resource
private StockDao stockDao;
@Autowired
private StringRedisTemplate redisTemplate;
public String decrStock(Integer productId) {//synchronized () 同步方法 同步代码块
Boolean flag = redisTemplate.opsForValue().setIfAbsent("product::" + productId, "ykq",30, TimeUnit.SECONDS);
//查询对应的id的库存
if(flag) {//获取锁了
try {
Stock stock = stockDao.selectById(productId);
if (stock.getNum() > 0) {
//根据id修改库存
sleep
stock.setNum(stock.getNum() - 1);
stockDao.updateById(stock); //异常发生
// int c=1/0;
System.out.println("库存剩余:" + (stock.getNum()));
return "库存减少成功";
} else {
return "库存不足";
}
}catch (Exception e){
throw new RuntimeException(e.getMessage());
}
finally {
redisTemplate.delete("product::" + productId);//释放锁资源 一定再finally
}
}else{
System.out.println("服务器正忙请稍后再试..........");
return "服务器正忙请稍后再试..........";//可重复调用
}
}
}
使用Redisson实现分布式锁
步骤:
- 导入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.15.0</version>
</dependency>
- 配置类
@Bean
public RedissonClient getRedisson(){
Config config=new Config();
//config.useSingleServer().setAddress("redis://192.168.31.199:6379");
config.useClusterServers().addNodeAddress(
"redis://192.168.31.31:8000",
"redis://192.168.31.31:8001",
"redis://192.168.31.31:8002",
"redis://192.168.31.31:8003",
"redis://192.168.31.31:8004",
"redis://192.168.31.31:8005"
);
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
- 测试
使用trylock()方法解决
@Service
public class ProductService {
@Autowired
private ProductDao productDao;
@Autowired
private RedissonClient redissonClient;
public String reduceStock(Integer productId) {
RLock lock = redissonClient.getLock("lock::" + productId);
if (lock.tryLock()) {
try {
Product product = productDao.selectById(productId);
if (product != null && product.getCount() > 0) {
product.setCount(product.getCount() - 1);
productDao.updateById(product);
System.out.println("剩余票数为:" + product.getCount());
return "成功";
} else {
return "没了";
}
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
} finally {
if (lock.isLocked()) {
lock.unlock(); // 释放锁
}
}
}
return reduceStock(productId);
}
}
使用lock()方法:
@Service
public class ProductService {
@Autowired
private ProductDao productDao;
@Autowired
private RedissonClient redissonClient;
public String reduceStock(Integer productId) {
RLock lock = redissonClient.getLock("lock::" + productId);
lock.lock();
try {
Product product = productDao.selectById(productId);
if (product != null && product.getCount() > 0) {
product.setCount(product.getCount() - 1);
productDao.updateById(product);
System.out.println("剩余票数为:" + product.getCount());
return "成功";
} else {
return "没了";
}
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
} finally {
if (lock.isLocked()) {
lock.unlock(); // 释放锁
}
}
}
}