Redis是一个高性能的内存数据库,redis内部是一个key-value存储系统,以Key-value的方式储存数据,可以作为缓存使用。Redis以内存作为数据存储介质,所以读写数据的效率极高,远远超过数据库,Redis基于内存运行并支持持久化的NoSQL数据库,是当前最热门的NoSql数据库之一,也被人们称为数据结构服务器。
为什么要使用缓存?
1.高并发(原因是它可以直接访问内存,而以往我们用的是数据库(硬盘),他提高了访问效率,解决了数据库服务器压力。)
2.高性能(基于内存,内存IO效率远远高于磁盘)
官方数据表示Redis读的速度是110000次/s,写的速度是81000次/s 。redis尽量少写多读,符合缓存的适用要求。单机redis支撑万级,如果10万+就需要用redis replication模式,也就是集群模式;
MySQL宕机:
MySQL的连接数存在瓶颈,连接过大可能会导致MySQL宕机
解决办法:
1.部署多个MySQL服务,主从复制
2.部署缓存,承担一部分的并发
Redis的特点:
1.性能高
2.支持多重储存类型(包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类型,类似于Java中的map))
3.丰富的特性(发布订阅、事务、过期策略等)
4.支持持久化
5.单线程(避免了上下文的切换,线程同步问题等)
Redis持久化:
储存在Redis中的数据是持久化的,断电或重启后,数据也不会丢失。因为Redis的存储分为内存存储、磁盘存储和log文件三部分,重启后,Redis可以从磁盘重新将数据加载到内存中,这些可以通过配置文件对其进行配置,正因为这样,Redis才能实现持久化。
Redis怎么在项目中使用的?
1.导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2.配置文件
spring.redis.host=localhost spring.redis.port=6379 spring.redis.database=0 spring.redis.jedis.pool.max-active=100 spring.redis.jedis.pool.max-wait=100ms spring.redis.jedis.pool.max-idle=100 spring.redis.jedis.pool.min-idle=10
3.配置RedisTemplate
@Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<String, Object>(); template.setConnectionFactory(factory); 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); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式采用jackson template.setValueSerializer(jackson2JsonRedisSerializer); // hash的value序列化方式采用jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } }
4.使用RestTemplate 常用方法:
1.opsForValue 获得string类型的操作对象
2.opsForHash 获得hash类型的操作对象
5.缓存使用的流程
按id查询商品的过程
1. 以id为键查询Redis缓存,如果能查到就返回数据,结束
2.如果查不到,就查询数据库,数据库查到,缓存到Redis,返回数据
3.如果数据库查不到,返回null,结束
4.增删改数据库的同时,要修改缓存
@Autowired private RedisTemplate<String,Object> redisTemplate; public static final String KEY = "Employee-"; @Override public Product findById(Long id) { //获得key-value格式的操作对象 ValueOperations<String, Object> ops = redisTemplate.opsForValue(); //1 先查询redis中是否存在该id的数据 Product emp = (Product) ops.get(KEY + id); if (emp != null) { //2.如果有 就直接返回数据 System.out.println("redis中存在数据,返回" + emp); return emp; } // 3.如果没有 就查询数据 emp = productMapper.selectById(id); if(emp != null){ //4.如果数据库有 就保存到redis中,返回数据 System.out.println("查询数据库,返回" + emp); ops.set(KEY + id,emp); return emp; } System.out.println("数据库不存在"); //5.如果数据库没有 就返回null return null; }
Redis的数据类型(Redis的数据以key-value方式存储)
数据类型有:
1.string 字符串(适合保存单个数据)
2.hash 哈希(适合保存复杂类型数据,如:对象)
3.list 列表(适合保存有序的、可重复的数据)
4.set 无序集合(适合保存无序的,不可重复的数据)
5.zset 有序集合(适合保存有序的,不可重复的数据)
储存 :
存储字符串
set key value get key 可以设置失效时间 set key value EX 10 //10秒钟之后失效
存储Hash值(hash存储,一般可以用来存储Java中的一个完整的自定义对象)
//hmset是存储hash值的指令, //user是当前hash的key //name "zhangsan" age 23 sex "nan" 是 key对应的值 127.0.0.1:6379> hmset user name "zhangsan" age 23 sex "nan" OK //hmget获取hash中的某一个属性的值 127.0.0.1:6379> hmget user name 1) "zhangsan" 127.0.0.1:6379> hmget user age 1) "23" //hgetall是获取hash中的所有属性对应的值 127.0.0.1:6379> hgetall user 1) "name" 2) "zhangsan" 3) "age" 4) "23" 5) "sex" 6) "nan"
存储List列表(有序列表)
采用的链表结构进行数据存储
lpush 从右向左添加
rpush 从左向右添加
lrange key start stop
//lpush用来存储一个列表的命令。interesting是列表的名称,"basketball"列表中的值 127.0.0.1:6379> lpush interesting "basketball" (integer) 1 127.0.0.1:6379> lpush interesting "football" "ball" (integer) 3 //lrange输出列表中的数据的命令, interesting就是列表的名称 。 0 2是列表的开始输出索引和结束索引。 127.0.0.1:6379> lrange interesting 0 2 1) "ball" 2) "football" 3) "basketball"
存储Set集合(无序集合)
不能有重复的数据
sadd key member //存数据 smembers key //取数据 案例: 127.0.0.1:6379> sadd strset "a" "b" "c" (integer) 3 127.0.0.1:6379> smembers strset 1) "b" 2) "c" 3) "a"
存储zset集合(有序集合)
zadd key score member (score是一个数字,zset就是通过这个数字进行排序,可以重复) zrangebyscore key 0 1000 //通过分数排序输出 有序集合是按照score进行排序
Redis的常见问题
1.缓存击穿
高并发的情况下,短时间内缓存会被穿过,请求直接打到数据库上,可能导致数据库压力过大。
解决方案:对代码上锁(双重检查锁)
2.缓存穿透
高并发的情况下,如果查询不存在的数据,因为缓存和数据库都不存在,请求都会打到数据库上,可能导致系统崩溃。
解决方案:
1) 保存不存在的数据到缓存中,设置一定过期时间
2) 布隆过滤器(直接过滤掉不存在数据的请求) 不能准确判断是否存在数据,能准确判断数据不存在
3.缓存雪崩
高并发的情况下,缓存服务器重启或热点数据同时过期,全部访问数据库,导致数据库宕机
解决方案:
1)配置缓存集群
2)尽量给热点数据设置不一样的过期时间,相对均匀
解决代码:
public Goods getGoodsById(Long id){ ValueOperations<String, Object> ops = redisTemplate.opsForValue(); Object value = ops.get(TYPE + id); //外层先读缓存,缓存如果有,就不执行同步块 if(value == null) { synchronized (this) { //1) 以id为键查询Redis缓存,如果能查到就返回数据,结束 value = ops.get(TYPE + id); //2)如果查不到,就查询数据库 if (value == null) { System.out.println("缓存不存在,查询数据库"); // 数据库查到,缓存到Redis,返回 Goods goods = this.getById(id); if (goods != null) { System.out.println("数据库存在,保存到缓存"); ops.set(TYPE + id, goods); } else { System.out.println("数据库不存在,返回null"); //保存空数据到缓存中,设置过期时间 ops.set(TYPE + id,new Goods(),30, TimeUnit.SECONDS); } return goods; } else { System.out.println("缓存存在,返回" + value); //如果能查到就返回数据,结束 return (Goods) value; } } } System.out.println("缓存存在,返回" + value); return (Goods) value; }
声明式缓存
SpringBoot项目需要的依赖,配置文件同上
1.在启动类上添加注解 @EnableCaching
2.Redis的配置类
@Configuration public class RedisConfig { @Bean public RedisCacheConfiguration provideRedisCacheConfiguration(){ //加载默认配置 RedisCacheConfiguration conf = RedisCacheConfiguration.defaultCacheConfig(); //返回Jackson序列化器 return conf.serializeValuesWith( RedisSerializationContext.SerializationPair .fromSerializer(new GenericJackson2JsonRedisSerializer())); } }
3.缓存注解
@CacheConfig 使用在Service类上,如:@CacheConfig(cacheNames = "books")
@Cacheable 使用在查询方法上,让方法优先查询缓存
@CachePut 使用在更新和添加方法上,数据库更新和插入数据后同时保存到缓存里
@CacheEvict 使用在删除方法上,数据库删除后同时删除缓存
注意:实体类必须实现序列化接口:
@CacheConfig(cacheNames = "brand") @Service public class BrandServiceImpl extends ServiceImpl<BrandMapper, Brand> implements IBrandService { @Autowired private BrandMapper brandMapper; @Cacheable(cacheNames = "brand-category",key = "T(String).valueOf(#cid)") @Override public List<Brand> findBrandsByCategory(Integer cid) { return brandMapper.selectBrandsByCategory(cid); } @Cacheable(key = "T(String).valueOf(#id)") @Override public Brand findBrandById(Long id) { return this.getById(id); } @CachePut(key = "T(String).valueOf(#brand.id)") @Override public Brand saveBrand(Brand brand) { this.saveOrUpdate(brand); return brand; } @CacheEvict(key = "T(String).valueOf(#id)") @Override public void deleteBrand(Long id) { this.removeById(id); } @Cacheable(cacheNames = "brand-page",key = "T(String).valueOf(#page)") @Override public IPage<Brand> pageBrands(Long page) { return this.page(new Page<>(page,10)); } }