1. Springboot集成Redis

(1)创建springboot工程,勾选Nosql

(2)配置连接参数

spring:

  datasource:

    url: jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai

    username: root

    password: ok

    driver-class-name: com.mysql.jdbc.Driver

  redis:

    port: 6379

    host: 127.0.0.1

 

(3)  创建测试方法Demo2ApplicationTests-测试字符串

@SpringBootTest

class Demo2ApplicationTests {

    @Autowired

    private RedisTemplate redisTemplate;

 

    @Test

    void contextLoads() {

        redisTemplate.opsForValue().set(name66,zs);

        System.out.println(redisTemplate.opsForValue().get(name66));

    }

}

 

(4)创建实体类

@Component

public class User implements Serializable {

    private String name;

    private int age;

    public User() {

    }

    public User(String name, int age) {

        this.name = name;

        this.age = age;

    }

    public String getName() {

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }

    public int getAge() {

        return age;

    }

    public void setAge(int age) {

        this.age = age;

    }

}

 

(5)创建测试方法Demo2ApplicationTests-测试对象

 @Test

    public void test()throws Exception{

        User user = new User(中博,777);

        redisTemplate.opsForValue().set(user,user);

        System.out.println(redisTemplate.opsForValue().get(user));

    }

(6)RedisConfig

@Configuration

public class RedisConfig {

    @Bean

    @SuppressWarnings(all)

    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;

    }

}

 

(7)创建测试方法Demo2ApplicationTests-测试序列化

@Autowired

@Qualifier(redisTemplate)

private RedisTemplate redisTemplate;

 

@Test

public void test()throws Exception{

        User user = new User(中博,777);

        redisTemplate.opsForValue().set(user,user);

        System.out.println(redisTemplate.opsForValue().get(user));

}

 

(8)  企业级使用RedisUtil工具

package cn.kgc.util;

 

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.stereotype.Component;

import org.springframework.util.CollectionUtils;

 

import java.util.List;

import java.util.Map;

import java.util.Set;

import java.util.concurrent.TimeUnit;

 

@Component

public final class RedisUtil {

 

    @Autowired

    private RedisTemplate<String, Object> redisTemplate;

 

    // =============================common============================

    /**

     * 指定缓存失效时间

     * @param key  键

     * @param time 时间(秒)

     */

    public boolean expire(String key, long time) {

        try {

            if (time > 0) {

                redisTemplate.expire(key, time, TimeUnit.SECONDS);

            }

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

    /**

     * 根据key 获取过期时间

     * @param key 键 不能为null

     * @return 时间(秒) 返回0代表为永久有效

     */

    public long getExpire(String key) {

        return redisTemplate.getExpire(key, TimeUnit.SECONDS);

    }

 

 

    /**

     * 判断key是否存在

     * @param key 键

     * @return true 存在 false不存在

     */

    public boolean hasKey(String key) {

        try {

            return redisTemplate.hasKey(key);

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

 

    /**

     * 删除缓存

     * @param key 可以传一个值 或多个

     */

    @SuppressWarnings(unchecked)

    public void del(String... key) {

        if (key != null && key.length > 0) {

            if (key.length == 1) {

                redisTemplate.delete(key[0]);

            } else {

                redisTemplate.delete(CollectionUtils.arrayToList(key));

            }

        }

    }

 

 

    // ============================String=============================

 

    /**

     * 普通缓存获取

     * @param key 键

     * @return 值

     */

    public Object get(String key) {

        return key == null ? null : redisTemplate.opsForValue().get(key);

    }

 

    /**

     * 普通缓存放入

     * @param key   键

     * @param value 值

     * @return true成功 false失败

     */

 

    public boolean set(String key, Object value) {

        try {

            redisTemplate.opsForValue().set(key, value);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

 

    /**

     * 普通缓存放入并设置时间

     * @param key   键

     * @param value 值

     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期

     * @return true成功 false 失败

     */

 

    public boolean set(String key, Object value, long time) {

        try {

            if (time > 0) {

                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);

            } else {

                set(key, value);

            }

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

 

    /**

     * 递增

     * @param key   键

     * @param delta 要增加几(大于0)

     */

    public long incr(String key, long delta) {

        if (delta < 0) {

            throw new RuntimeException(递增因子必须大于0);

        }

        return redisTemplate.opsForValue().increment(key, delta);

    }

 

 

    /**

     * 递减

     * @param key   键

     * @param delta 要减少几(小于0)

     */

    public long decr(String key, long delta) {

        if (delta < 0) {

            throw new RuntimeException(递减因子必须大于0);

        }

        return redisTemplate.opsForValue().increment(key, -delta);

    }

 

 

    // ================================Map=================================

 

    /**

     * HashGet

     * @param key  键 不能为null

     * @param item 项 不能为null

     */

    public Object hget(String key, String item) {

        return redisTemplate.opsForHash().get(key, item);

    }

 

    /**

     * 获取hashKey对应的所有键值

     * @param key 键

     * @return 对应的多个键值

     */

    public Map<Object, Object> hmget(String key) {

        return redisTemplate.opsForHash().entries(key);

    }

 

    /**

     * HashSet

     * @param key 键

     * @param map 对应多个键值

     */

    public boolean hmset(String key, Map<String, Object> map) {

        try {

            redisTemplate.opsForHash().putAll(key, map);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

 

    /**

     * HashSet 并设置时间

     * @param key  键

     * @param map  对应多个键值

     * @param time 时间(秒)

     * @return true成功 false失败

     */

    public boolean hmset(String key, Map<String, Object> map, long time) {

        try {

            redisTemplate.opsForHash().putAll(key, map);

            if (time > 0) {

                expire(key, time);

            }

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

 

    /**

     * 向一张hash表中放入数据,如果不存在将创建

     *

     * @param key   键

     * @param item  项

     * @param value 值

     * @return true 成功 false失败

     */

    public boolean hset(String key, String item, Object value) {

        try {

            redisTemplate.opsForHash().put(key, item, value);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

    /**

     * 向一张hash表中放入数据,如果不存在将创建

     *

     * @param key   键

     * @param item  项

     * @param value 值

     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间

     * @return true 成功 false失败

     */

    public boolean hset(String key, String item, Object value, long time) {

        try {

            redisTemplate.opsForHash().put(key, item, value);

            if (time > 0) {

                expire(key, time);

            }

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

 

    /**

     * 删除hash表中的值

     *

     * @param key  键 不能为null

     * @param item 项 可以使多个 不能为null

     */

    public void hdel(String key, Object... item) {

        redisTemplate.opsForHash().delete(key, item);

    }

 

 

    /**

     * 判断hash表中是否有该项的值

     *

     * @param key  键 不能为null

     * @param item 项 不能为null

     * @return true 存在 false不存在

     */

    public boolean hHasKey(String key, String item) {

        return redisTemplate.opsForHash().hasKey(key, item);

    }

 

 

    /**

     * hash递增 如果不存在,就会创建一个 并把新增后的值返回

     *

     * @param key  键

     * @param item 项

     * @param by   要增加几(大于0)

     */

    public double hincr(String key, String item, double by) {

        return redisTemplate.opsForHash().increment(key, item, by);

    }

 

 

    /**

     * hash递减

     *

     * @param key  键

     * @param item 项

     * @param by   要减少记(小于0)

     */

    public double hdecr(String key, String item, double by) {

        return redisTemplate.opsForHash().increment(key, item, -by);

    }

 

 

    // ============================set=============================

 

    /**

     * 根据key获取Set中的所有值

     * @param key 键

     */

    public Set<Object> sGet(String key) {

        try {

            return redisTemplate.opsForSet().members(key);

        } catch (Exception e) {

            e.printStackTrace();

            return null;

        }

    }

 

 

    /**

     * 根据value从一个set中查询,是否存在

     *

     * @param key   键

     * @param value 值

     * @return true 存在 false不存在

     */

    public boolean sHasKey(String key, Object value) {

        try {

            return redisTemplate.opsForSet().isMember(key, value);

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

 

    /**

     * 将数据放入set缓存

     *

     * @param key    键

     * @param values 值 可以是多个

     * @return 成功个数

     */

    public long sSet(String key, Object... values) {

        try {

            return redisTemplate.opsForSet().add(key, values);

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }

 

 

    /**

     * 将set数据放入缓存

     *

     * @param key    键

     * @param time   时间(秒)

     * @param values 值 可以是多个

     * @return 成功个数

     */

    public long sSetAndTime(String key, long time, Object... values) {

        try {

            Long count = redisTemplate.opsForSet().add(key, values);

            if (time > 0)

                expire(key, time);

            return count;

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }

 

 

    /**

     * 获取set缓存的长度

     *

     * @param key 键

     */

    public long sGetSetSize(String key) {

        try {

            return redisTemplate.opsForSet().size(key);

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }

 

 

    /**

     * 移除值为value的

     *

     * @param key    键

     * @param values 值 可以是多个

     * @return 移除的个数

     */

 

    public long setRemove(String key, Object... values) {

        try {

            Long count = redisTemplate.opsForSet().remove(key, values);

            return count;

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }

 

    // ===============================list=================================

 

    /**

     * 获取list缓存的内容

     *

     * @param key   键

     * @param start 开始

     * @param end   结束 0 到 -1代表所有值

     */

    public List<Object> lGet(String key, long start, long end) {

        try {

            return redisTemplate.opsForList().range(key, start, end);

        } catch (Exception e) {

            e.printStackTrace();

            return null;

        }

    }

 

 

    /**

     * 获取list缓存的长度

     *

     * @param key 键

     */

    public long lGetListSize(String key) {

        try {

            return redisTemplate.opsForList().size(key);

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }

 

 

    /**

     * 通过索引 获取list中的值

     *

     * @param key   键

     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推

     */

    public Object lGetIndex(String key, long index) {

        try {

            return redisTemplate.opsForList().index(key, index);

        } catch (Exception e) {

            e.printStackTrace();

            return null;

        }

    }

 

 

    /**

     * 将list放入缓存

     *

     * @param key   键

     * @param value 值

     */

    public boolean lSet(String key, Object value) {

        try {

            redisTemplate.opsForList().rightPush(key, value);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

 

    /**

     * 将list放入缓存

     * @param key   键

     * @param value 值

     * @param time  时间(秒)

     */

    public boolean lSet(String key, Object value, long time) {

        try {

            redisTemplate.opsForList().rightPush(key, value);

            if (time > 0)

                expire(key, time);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

 

    }

 

 

    /**

     * 将list放入缓存

     *

     * @param key   键

     * @param value 值

     * @return

     */

    public boolean lSet(String key, List<Object> value) {

        try {

            redisTemplate.opsForList().rightPushAll(key, value);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

 

    }

 

 

    /**

     * 将list放入缓存

     *

     * @param key   键

     * @param value 值

     * @param time  时间(秒)

     * @return

     */

    public boolean lSet(String key, List<Object> value, long time) {

        try {

            redisTemplate.opsForList().rightPushAll(key, value);

            if (time > 0)

                expire(key, time);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

 

    /**

     * 根据索引修改list中的某条数据

     *

     * @param key   键

     * @param index 索引

     * @param value 值

     * @return

     */

 

    public boolean lUpdateIndex(String key, long index, Object value) {

        try {

            redisTemplate.opsForList().set(key, index, value);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

 

 

    /**

     * 移除N个值为value

     *

     * @param key   键

     * @param count 移除多少个

     * @param value 值

     * @return 移除的个数

     */

 

    public long lRemove(String key, long count, Object value) {

        try {

            Long remove = redisTemplate.opsForList().remove(key, count, value);

            return remove;

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

 

    }

 

}

 

(9)创建测试方法塞值取值

    @Test

    public void test2()throws Exception{

        redisUtil.set(name,sh);

        System.out.println(redisUtil.get(name));

    }

 

(10)测试增删改查

@RestController

public class UserController {

    @Autowired

    private UserMapper userMapper;

 

    @Resource

    private RedisUtil redisUtil;

 

    @PostMapping(/set)

    public void set(@RequestBody User user){

        redisUtil.set(user,user);

    }

 

    @PostMapping(/get/{key})

    public User get(@PathVariable(key)String key){

        return (User) redisUtil.get(key);

    }

 

    @PostMapping(/delete/{key})

    public void delete(@PathVariable(key)String key){

        redisUtil.del(user);

    }

 

    @PostMapping(/findAll)

    public List<User> findAll(){

        List<User> list = (List<User>)redisUtil.get(users);

        if(list ==null){

            list = userMapper.findAll();

            redisUtil.set(users,list);

        }

        return list;

    }

 

}

 

(11)           附源码

 

 

1. Redis持久化机制

(1)背景:

Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失,所以Redis提供了持久化功能.

 

(2)RDB持久化(Redis DataBase)

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了。再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能。

 

1.定义:

 

2.优点:

  1. 适合大规模的数据恢复
  2. 对数据完整性要求不高

 

 

3.缺点:

  1. 需要一定的时间间隔进程操作!如果redis意外宕机了,这个最后一次修改数据就没有的了!
  2. fork进程的时候,会占用一定的内容空间!

 

(3)AOF持久化(Append only File)

1.定义:

AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

 

 

 

 

2.优点:

(1)AOF可以更好的保护数据不丢失,一般AOF会以每隔1秒,通过后台的一个线程去执行一次fsync操作,如果redis进程挂掉,最多丢失1秒的数据。

(2)AOF以appen-only的模式写入,所以没有任何磁盘寻址的开销,写入性能非常高。

(3)AOF日志文件的命令通过非常可读的方式进行记录,这个非常适合做灾难性的误删除紧急恢复,如果某人不小心用flushall命令清空了所有数据,只要这个时候还没有执行rewrite,那么就可以将日志文件中的flushall删除,进行恢复。

 

3.缺点:

  1. 相对于数据文件来说,aof远远大于rdb,修复的速度也比rdb慢!
  2. aof运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化!

 

1. 哨兵模式

主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,非常费时间费力气,还会造成一段时间内服务不可用,所以我们推荐优先考虑哨兵模式。

哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。

 

 

 

(1)  优点:

  1. 哨兵集群,基于主从复制模式
  2. 主从可以切换,故障可以转移,系统的可用性比较好
  3. 哨兵模式就是主从模式的升级,手动到自动,更加健壮

 

(2)  缺点:

  1. Redis不好在线扩容,集群容量一旦达到上限,在线扩容比较麻烦
  2. 实现哨兵机制的配置非常麻烦

 

1. Redis缓存穿透和雪崩(它们都是服务器的高可用)

Redis缓存的使用,极大的提高了效率,但是要有一些问题,例如数据的一致性问题,这个问题没有办法解决。如果对数据的一致性要求很高,那么就不能使用缓存。

 

(1)  缓存穿透(查不到导致)

1.定义

缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库,这会给持久层数据库造成很大的压力,这时候相当于出现了缓存穿透。

 

 

 

2.现象

系统平稳运行过程中

  1. 应用服务器流量随时间增量较大
  2. Redis服务器命中率随时间逐步降低
  3. Redis内存平稳,内存无压力
  4. Redis服务器CPU占用激增
  5. 数据库服务器压力激增
  6. 数据库崩溃

 

3解决方案

  1. 布隆过滤器
  2. 缓存空对象

 

 

(1)  缓存击穿(某个Key过期,并且该key访问量巨大!)

1.定义

缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,会导致数据库瞬间压力过大。

 

2.现象

系统平稳运行过程中

  1. 数据库连接量突然激增
  2. Redis服务器无大量key过期
  3. Redis内存平稳,无波动
  4. Redis服务器CPU正常
  5. 数据库崩溃

 

3.解决方案

(1)设置热点数据永不过期:

从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题。

(2)加互斥锁:

分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。

 

 

 

 

 

(3)缓存雪崩(短时间,缓存中较多的key集中过期)

1定义

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

 

2.现象

系统平稳运行过程中,忽然数据库连接量激增

  1. 应用服务器无法及时处理请求
  2. 大量408,500错误页面出现
  3. 用户反复刷新页面获取数据
  4. 数据库崩溃
  5. 应用服务器崩溃
  6. 重启应用服务器无效
  7. Redis服务器崩溃
  8. Redis集群崩溃
  9. 重启数据库后再次被瞬间流量击崩

 

3.解决方案

  1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
  3. 设置热点数据永远不过期。

 

(4)常见企业级解决方案

1 Redis集群

如果一台挂掉之后还可以继续工作。

 

2限流降级

在缓存失效后,通过枷锁或队列来读取数据库写缓存的线程数量。例如,对某个key只允许一个线程查询数据和写缓存,其他线程等待。

 

3缓存预热

缓存预热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中,在即将发生大规模并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。