目录
1. springboot连接redis
2. springboot连接redis集群。
3. springboot使用场景
(1)作为缓存
(2)作为分布式
(3) 作为点赞量videaId,0 incr(videaId),排行榜,转发量。
4. redis的常见面试题。
1. springboot连接redis
使用redisTemplate该类可以存放任意类型的数据,但是该类型的数据必须实现序列,获取redis中对应的数据时,会进行反序列化。 如果使用RedisTemplate建议大家指定key,value,以及hashkey的序列化方式。
package com.kh.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
import java.time.Duration;
@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;
}
}
2. springboot连接redis集群。
(1)哨兵
spring.redis.sentinel.master=mymaster
spring.redis.sentinel.nodes=192.168.31.98:26379
(2) 去中心化集群
spring.redis.cluster.nodes=192.168.31.98:8001,192.168.31.98:8002,192.168.31.98:8003,192.168.31.98:8004,192.168.31.98:8005,192.168.31.98:8006
3. springboot使用场景
(1)作为缓存。
1.为什么使用缓存?
减少数据库的访问频率。 提高数据的访问率。
2.什么样的数据适合放入缓存?
1.热点数据。 2. 修改频率比较低。3.安全系数低的。
缓存的原理:
如何使用缓存
(1)搭建一个springboot+mp的工程
(2)引入redis相关的依赖
(3)配置redis
(4)service代码
package com.kh.service;
import com.kh.dao.DeptDao;
import com.kh.entity.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class DeptService {
@Resource
private DeptDao deptDao;
@Autowired
private RedisTemplate redisTemplate;
public Dept findById(Integer deptId){
//1.从缓存中查询该数据
Object o = redisTemplate.opsForValue().get("findById::" + deptId);
if(o!=null){//表示从缓存中获取该数据
return (Dept) o;
}
Dept dept = deptDao.selectById(deptId);
redisTemplate.opsForValue().set("findById::"+deptId,dept);//把查询的结果放入缓存
return dept;
}
//数据库和缓存同步问题!
public int delete(Integer deptId){
redisTemplate.delete("findById::"+deptId);//删除缓存
int i = deptDao.deleteById(deptId);
return i;
}
public int update(Dept dept){
redisTemplate.delete("findById::"+dept.getDeptId());//删除缓存
int i = deptDao.updateById(dept);
redisTemplate.opsForValue().set("findById::"+dept.getDeptId(),dept);
return i;
}
}
上面这些代码每次都要写很多与业务无关的一些非业务代码!
(1)使用aop来解决---->动态代理(动态代理的实现模式基于JDK动态代理)
https://blog.csdn.net/qq_39470733/article/details/77315010
(2)基于spring的缓存注解。
package com.kh;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@MapperScan(basePackages = "com.kh.dao")
@EnableCaching //开启缓存的注解
public class SpringbootRedis02Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootRedis02Application.class, args);
}
}
package com.kh.service;
import com.kh.dao.DeptDao;
import com.kh.entity.Dept;
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.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class DeptService {
@Resource
private DeptDao deptDao;
//该注解作用:会先查询缓存,如果缓存存在,则不会执行代码块。 如果缓存中不存在则执行该方法,并把该方法的返回值存放到redis中
@Cacheable(cacheNames = "findById",key = "#deptId") //缓存的key值 为findById
public Dept findById(Integer deptId){
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
Dept dept = deptDao.selectById(deptId);
return dept;
}
//数据库和缓存同步问题!
// beforeInvocation:是否在方法执行前就清空,缺省为 false,
// 如果指定为 true,则在方法还没有执行的时候就清空缓存。缺省情况下,如果方法执行抛出异常,则不会清空缓存。
@CacheEvict(cacheNames = "findById",key = "#deptId")
public int delete(Integer deptId){
int i = deptDao.deleteById(deptId);
return i;
}
//这个注解是必须执行方法体,而且会把方法体执行的结果放入到缓存中。 如果发生异常则不操作缓存。
@CachePut(cacheNames = "findById",key = "#dept.deptId")
public Dept update(Dept dept){
int i = deptDao.updateById(dept);
return dept;
}
}
(2)作为分布式锁
锁场景:
package com.kh.distributedlock.service;
import com.kh.distributedlock.dao.StockDao;
import com.kh.distributedlock.entity.Stock;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class StockService02 {
@Resource
private StockDao stockDao;
public String decrStock(Integer productId) {//synchronized () 同步方法 同步代码块
//查询对应的id的库存
synchronized (this) {
Stock stock = stockDao.selectById(productId);
if (stock.getNum() > 0) {
//根据id修改库存
stock.setNum(stock.getNum() - 1);
stockDao.updateById(stock);
System.out.println("库存剩余:" + (stock.getNum()));
return "库存减少成功";
} else {
return "库存不足";
}
}
}
}
这中锁不适合分布式项目。
使用redis解决分布式锁的问题
package com.kh.distributedlock.service;
import com.kh.distributedlock.dao.StockDao;
import com.kh.distributedlock.entity.Stock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@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修改库存
stock.setNum(stock.getNum() - 1);
stockDao.updateById(stock); //异常发生
// int c=10/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</artifactId>
<version>3.13.4</version>
</dependency>
@Bean
public RedissonClient getRedisson(){
Config config=new Config();
config.useSingleServer().setAddress("redis://192.168.213.188:6379");
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
package com.kh.distributedlock.service;
import com.kh.distributedlock.dao.StockDao;
import com.kh.distributedlock.entity.Stock;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Service
public class StockService {
@Resource
private StockDao stockDao;
@Autowired
private RedissonClient redisson;
public String decrStock(Integer productId) {//synchronized () 同步方法 同步代码块
RLock lock = redisson.getLock("product::" + productId);//获取锁对象
try {
lock.tryLock(60,20,TimeUnit.SECONDS); //自己别设置时间。
Stock stock = stockDao.selectById(productId);
if (stock.getNum() > 0) {
//根据id修改库存
stock.setNum(stock.getNum() - 1);
stockDao.updateById(stock); //异常发生
// int c=10/0;
// Thread.sleep(35000);
System.out.println("库存剩余:" + (stock.getNum()));
return "库存减少成功";
} else {
return "库存不足";
}
}catch (Exception e){
throw new RuntimeException(e.getMessage());
}
finally {
lock.unlock();
}
}
}
(3)作为点赞量videaId,0 incr(videaId),排行榜,转发量。
什么是计数器,如电商网站商品的浏览量、视频网站视频的播放数等。为了保证数据实时效,每次浏览都得给+1,并发量高时如果每次都请求数据库操作无疑是种挑战和压力。Redis提供的incr命令来实现计数器功能,内存操作,性能非常好,非常适用于这些计数场景 。
关系型数据库在排行榜方面查询速度普遍偏慢,所以可以借助redis的SortedSet进行热点数据的排序。 在奶茶活动中,我们需要展示各个部门的点赞排行榜, 所以我针对每个部门做了一个SortedSet,然后以用户的openid作为上面的username,以用户的点赞数作为上面的score, 然后针对每个用户做一个hash, 通过zrangebyscore就可以按照点赞数获取排行榜,然后再根据username获取用户的hash信息,这个当时在实际运用中性能体验也蛮不错的。
(4) 限时业务的运用
redis中可以使用expire命令设置一个键的生存时间,到时间后redis会删除它。利用这一特性可以运用在限时的优惠活动信息、手机验证码等业务场景。
4. redis的面试题
1. redis缓存穿透? 如何避免缓存穿透。
(1)数据库中没有该记录,而缓存中没有该记录。而这时有人恶意访问这种数据。 直接访问数据库。
解决方案:
(1) 如果数据库中不存在该对象,则往缓存中放入一个空对象,并且设置很短的过期时间。
2.缓存的雪崩?如何避免?
(1)再某一时刻,缓存中没有该记录,而这时会有大量的请求。这些请求打压到数据库。
比如: 项目刚刚上线。------预访问热点数据。
某一个缓存的时间到了。----> 失效时间是一个散列值。
使用队列: ----->每次有限的请求访问数据库。