Redis

Redis 学习

安装

1,Redis(端口 6379) Centos7中安装

# 安装
yum install redis 
# 启动
service redis start
# 重启
service redis restart
# 查看状态
service redis status
# 设置开机自启
chkconfig redis on
# 进入Redis服务
redis-cli 或者 ./redis-cli
# 指定ip -p 指定端口 -a 指定密码
redis-cli -h 指定ip -p 指定端口 -a 指定密码
# redis的配置文件
vi /etc/redis.conf
# 绑定的ip  要注释 
bind 127.0.0.1 
# 保护模式  改为  protected-mode no
protected-mode no
# 以守护进程的方式运行 改为 yes
daemonize yes 
# 设置redis密码 进入redis中
config set requirepass '123456'
# 登录
auth 123456
# 关闭redis
shutdown
# 清空当前数据库中的所有 key  
flushdb  
# 查看所有key
keys *

配置完成后重启redis服务

服务器中安全组的端口开启6379

**2,SpringBoot 集成 Redis **

使用Redis作为缓存需要shiro重写cache、cacheManager、SessionDAO

  • 添加依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>
  • 编写配置文件
#springboot所有的配置类 都有一个自动配置类  RedisAutoConfiguration
#自动配置类会绑定一个 properties 配置文件   RedisProperties
# redis 配置
spring.redis.jedis.pool.min-idle=5
spring.redis.jedis.pool.max-active=10
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.max-wait=2000

spring.redis.port=6379
spring.redis.host=localhost
spring.redis.password=123456
spring.redis.timeout=1000
  • 编写 RedisTemplate

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
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.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {
/*
*    编写我们自己的  RedisTemplate
*    固定的模板
* */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        // Json序列化配置
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);
        // String 的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        //  设置具体的序列号方式~
           //------------- key 采用string的序列化方式
        template.setKeySerializer(stringRedisSerializer);
          // -------------hash 的key 采用 string的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
          //------------- value 采用 json的序列化方式
        template.setValueSerializer(serializer);
          // -------------hash 的 value 采用json的序列化方式
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();
        return template;
    }
}
  • 编写pojo实体类
//  必须-要实现--序列化
public class User  implements Serializable {
    private String name;
    private Integer age;
}
  • 测试
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate redisTemplate;
//  ----测试
@Test
void contextLoads() {
    User user = new User("弋凡",21);
    redisTemplate.opsForValue().set("user",user);
    System.err.println(redisTemplate.opsForValue().get("us"));
}
  • 封装自己的RedisUtils
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;

// 自己封装---RedisUtil--以后可以直接使用工具类
// ---- 来自 狂神说
@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;
        }
    }
}
  • 测试
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate redisTemplate;
@Autowired
private RedisUtil redisUtil;

@Test
void Test1(){
    redisUtil.set("name","yifan");
    System.err.println(redisUtil.get("name"));
}

end –

基础

1,redis的数据类型

(一)String
最常规的set/get操作,value可以是String也可以是数字。一般做一些复杂的计数功能的缓存。
(二)hash
这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。博主在做单点登录的时候,就是用这种数据结构存储用户信息,以cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果。
(三)list
使用List的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用lrange命令,做基于redis的分页功能,性能极佳,用户体验好。本人还用一个场景,很合适—取行情信息。就也是个生产者和消费者的场景。LIST可以很好的完成排队,先进先出的原则。
(四)set
因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用JVM自带的Set进行去重?因为我们的系统一般都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了。
另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
(五)sorted set
sorted set多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用,取TOP N操作。


2,获取 Redis 数据类型操作接口

// 获取地理位置操作接口
redisTemplate.opsForGeo();
// 获取散列操作接口
redisTemplate.opsForHash();
// 获取基数接口操作
redisTemplate.opsForHyperLogLog();
// 获取列表操作接口
redisTemplate.opsForList();
// 获取集合操作接口
redisTemplate.opsForSet();
// 获取字符串操作接口
redisTemplate.opsForValue();
// 获取有序集合操作接口
redisTemplate.opsForZSet();

3,获取绑定键的操作类

// 获取地理位置绑定键操作接口
redisTemplate.boundGeoOps("geo");
// 获取散列绑定键操作接口
redisTemplate.boundHashOps("hash");
//获取列表(链表)绑定键的操作接口
redisTemplate.boundSetOps("set");
// 获取字符串绑定键操作接口
redisTemplate.boundValueOps("string");
// 获取有序集合绑定键操作接口
redisTemplate.boundZSetOps("zset");

Redis监听过期Key

  • 配置文件

@Configuration
@EnableCaching
public class RedisConfig {
    .....
 
    /**
     * 监听redis的过期事件
     */
    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }
}
  • 创建监听

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;
 

@Component
@Slf4j
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
 
    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }
 
    public void onMessage(Message message, byte[] pattern) {
        String expiredKey = message.toString(); // 获取过期的key
        if (expiredKey.contains("mechanic:order")) { // 判断是否是想要监听的过期key
            log.e("redis key过期:{}", expiredKey);
            // TODO 业务逻辑
        }
    }
 
}

MybatisPlus基于Redis实现缓存

数据的增,删,改,都会调用RedisCache的clear方法,清空缓存

放redis缓存中实体类必须实现Serializable序列化

表关联查询,共享缓存,可以使用,cache-ref 引用

例如:关联查询老师和学生表,在学生中引用老师的缓存,查询学生会把老师的数据缓存起来,查询老师的也会把学生的数据缓存起来,缓存的key为老师的namespace

配置文件

# 开启缓存
mybatis-plus.configuration.cache-enabled=true

自定义集成Redis缓存

/**
 * 使用redis实现mybatis二级缓存
 */
public class RedisCache implements Cache {

    Log log = LogFactory.getLog(RedisCache.class);

    private static final String COMMON_CACHE_KEY = "mybatis";

    //缓存对象唯一标识
    private final String nameSpace; //orm的框架都是按对象的方式缓存,而每个对象都需要一个唯一标识.
    //用于事务性缓存操作的读写锁
    private static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    private RedisTemplate redisTemplate; //Redis的模板负责将缓存对象写到redis服务器里面去
    //缓存对象的是失效时间,7天
    private static final long EXPRIRE_TIME_IN_DATS = 7;

    //构造方法---把对象唯一标识传进来
    public RedisCache(String nameSpace) {
        if (nameSpace == null) {
            throw new IllegalArgumentException("缓存对象id是不能为空的");
        }
        this.nameSpace = nameSpace;
    }

    private String getKey(Object key) {
        return COMMON_CACHE_KEY + ":" + nameSpace + ":" + DigestUtil.md5Hex(String.valueOf(key));
    }
    private String getKeys() {
        return COMMON_CACHE_KEY + ":" + nameSpace + ":*";
    }


    @Override
    public String getId() {
        return this.nameSpace;
    }

    //给模板对象RedisTemplate赋值,并传出去
    private RedisTemplate getRedisTemplate() {
        if (redisTemplate == null) { //每个连接池的连接都要获得RedisTemplate
            redisTemplate = ApplicationContextUtil.getBean("redisTemplate");
        }
        return redisTemplate;
    }

    /*
    保存缓存对象的方法
    */
    @Override
    public void putObject(Object key, Object value) {
        try {
            RedisTemplate redisTemplate = getRedisTemplate();
            //使用redisTemplate得到值操作对象
            ValueOperations operation = redisTemplate.opsForValue();

            //使用值操作对象operation设置缓存对象
            operation.set(getKey(key), value, EXPRIRE_TIME_IN_DATS, TimeUnit.DAYS);
            //TimeUnit.MINUTES系统当前时间的分钟数
            log.info("缓存对象保存成功 过期时间 " + EXPRIRE_TIME_IN_DATS + " 天");
        } catch (Throwable t) {
            log.error("缓存对象保存失败{}", t);
        }
    }

    /*
    获取缓存对象的方法
    */
    @Override
    public Object getObject(Object key) {
        try {
            RedisTemplate redisTemplate = getRedisTemplate();
            ValueOperations operations = redisTemplate.opsForValue();
            Object result = operations.get(getKey(key));
            log.info("获取缓存对象key: " + key);
            return result;
        } catch (Throwable t) {
            log.error("缓存对象获取失败: " + t);
            return null;
        }
    }

    @Override
    public Object removeObject(Object key) {
        try {
            RedisTemplate redisTemplate = getRedisTemplate();
            redisTemplate.delete(getKey(key));
            log.info("删除缓存对象成功k: " + key);
        } catch (Throwable t) {
            log.error("删除缓存对象失败: " + t);
        }
        return null;
    }


    /*
    清空缓存对象
    当缓存的对象更新了的化,就执行此方法
    */
    @Override
    public void clear() {
        Set<String> keys = redisTemplate.keys(getKeys());
        if (CollectionUtil.isNotEmpty(keys)) {
            assert keys != null;
            redisTemplate.delete(keys);
        }
        log.info("清空缓存对象成功");
    }

    //可选实现的方法
    @Override
    public int getSize() {
        Set<String> keys = redisTemplate.keys(getKeys());
        if (CollectionUtil.isNotEmpty(keys)) {
            assert keys != null;
            return keys.size();
        }
        return 0;
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }
}
@Component
public class ApplicationContextUtil implements ApplicationContextAware {
    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }

    public static <T> T getBean(Class<T> tClass) {
        return context.getBean(tClass);
    }

    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) {
        return (T) context.getBean(name);
    }
}

Mapper接口上添加注解

@CacheNamespace(implementation= RedisCache.class,eviction=RedisCache.class)

如上使用调用缓存就会基于redis

userMapper.xml中添加

<!--    mapper 使用缓存的方式是使用”<cache-ref>“标签引用dao的命名空间,因为dao中已经开启缓存,-->
<!--    这样与dao中的缓存保持一直,如果mapper文件不引用缓存,-->
<!--    执行update方法时,不会清除dao的缓存,导致数据库数据与缓存数据不一致。-->
<!--    如果直接使用”<cache>“标签开启缓存,会与dao中的冲突,服务启动失败。-->

<!--    将多个具有关联关系查询的数据放在一起处理-->    
<cache-ref namespace="com.v0710.mybatis_redis.mapper.UserMapper"/>

image-20210811183350627

  • 优化策略以及面试题

Redis主从复制

84d265b0f6b120972a740d509357014a

从节点只读主节点数据

搭建

# 准备三台主机,三个目录下复制redis.config
- master
    port:6379
    bind:0.0.0.0 #开启远程链接

- slave1
    port:6380
    bind:0.0.0.0 #开启远程链接
    slaveof:<masterip> <masterport>

- slave2 
    port:6381
    bind:0.0.0.0 #开启远程链接
    slaveof:<masterip> <masterport>
# 启动
redis-server /etc/redis.conf                      6379
redis-server /redis-test/slave1/redis.conf        6380
redis-server /redis-test/slave2/redis.conf        6381

# 进行redis
redis-cli -p 6380
redis-cli -p 6381

# 缺点
master 主节点出现故障后故障不能转移从系节点不能升级为主节点
# 解决
哨兵机制

image-20210812093913051

Redis哨兵机制

简单来说哨兵就是带有自动排除故障的主从架构

# 主节点创建哨兵配置
master对应的redis.conf同目录下创建sentinel.conf文件,名字绝对不能改
默认yum安装的redis文件存放在/etc下

# 配置哨兵,在sentinel.conf文件中填入内容
bind 0.0.0.0 #开启远程链接
sentinel.monitor -起个名称(随意) -redis的ip -port(监听的redis的端口) 1(几个哨兵)
eg:sentinel.monitor test 127.0.0.1 6379 1

Redis分布式锁

分布式锁应该具备哪些条件:

  • 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行
  • 高可用的,高性能的获取锁与释放锁
  • 具备可重入特性(可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁)
  • 具备锁失效机制,即自动解锁,防止死锁

1)基于set命令的分布式锁

1、加锁:使用setnx进行加锁,当该指令返回1时,说明成功获得锁

2、解锁:当得到锁的线程执行完任务之后,使用del命令释放锁,以便其他线程可以继续执行setnx命令来获得锁

3、设置锁超时时间:setnx 的 key 必须设置一个超时时间 命令:set <lock.key> <lock.value> nx ex <expireTime>

2)基于Redisson的分布式锁

img

线程去获取锁,获取成功:执行lua脚本,保存数据到redis数据库。

线程去获取锁,获取失败:一直通过while循环尝试获取锁,获取成功后,执行lua脚本,保存数据到redis数据库。

Redis集群模式

1:主从模式:一个主节点,多个从节点,主节点读写,从节点备份,不方便扩容,主节点宕机需手动修改IP

2:哨兵模式:主从的基础上增加哨兵节点,主节点宕机后,哨兵会到从节点中选择一个从节点作为主库,保证高可用,不方便扩容

(数据量小可以使用)

3:Cluster模式:支持多主多从,按照key的hash值进行分配使不同的key分布到不同的主节点上,每个主节点可以有多个从节点,如果主节点宕机,会到从节点选出一个新的主节点(用的较多)

Geo地理位置

@Autowired
private StringRedisTemplate redisTemplate;
// 添加经纬度保存redis    
// redis 命令:geoadd key 116.405285 39.904989 "北京"
private Long insertRedis(double lng, double lat, String id){
    Map<String, Point> points = new HashMap<>();
    points.put(id, new Point(lng, lat));
    // 添加 Geo
    return redisTemplate.boundGeoOps("key").geoAdd(points);
    // redisTemplate.opsForGeo().add(key, points, member);
}

// 将一个点位信息删除
private void deleteRedis(String id) {
    redisTemplate.boundZSetOps(Constans.REDIS_CAMERA_LNGLAT).remove(id);
}
// 根据页面上绘制的路径得到的点位经纬度查询半径范围内的所有数据

private List<String> selectRidesPoint(float lng, float lat, Integer radius) {
    String redisLnglatName = "常量key";
    //中心点设置
    Point point = new Point(lng, lat);
    //单位米
    Metric metric = RedisGeoCommands.DistanceUnit.METERS;
    //距离
    Distance distance = new Distance(radius, metric);
    //圆
    Circle circle = new Circle(point, distance);

    //redis查询参数配置:GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]
    // includeDistance 包含距离
    // includeCoordinates 包含经纬度
    // sortAscending 正序排序
    // limit 限定返回的记录数
    RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs
        .newGeoRadiusArgs()
        .includeDistance()
        .includeCoordinates()
        .sortAscending()
        .limit(limit);
    GeoResults<RedisGeoCommands.GeoLocation<String>> data = stringRedisTemplate.opsForGeo().geoRadius(redisLnglatName, circle, args);
    
    //数据封装
    Map<String, Map<String, Double>> cameraMap = new HashMap<>();
    if (data != null) {
        data.forEach(geoLocationGeoResult -> {
            RedisGeoCommands.GeoLocation<String> content = geoLocationGeoResult.getContent();
            Map<String, Double> pointMap = new HashMap<>();
            //member 名称  如  tianjin 
            String name = content.getName();
             // 对应的经纬度坐标
            Point pos = content.getPoint();
            // 距离中心点的距离
            Distance dis = geoLocationGeoResult.getDistance();
            pointMap.put("lng", pos.getX());
            pointMap.put("lat", pos.getY());
            cameraMap.put(name, pointMap);
        });
    }
    return cameraMap;
}
// 关于地球经纬度之间间距计算
/**
 * 根据经纬度和半径计算经纬度范围
 *
 * @param raidus 单位米
 * @return minLat, minLng, maxLat, maxLng
 */
private Map<String, Double> getAround(double lat, double lon, int raidus) {

    Double latitude = lat;
    Double longitude = lon;

    Double degree = (24901 * 1609) / 360.0;
    double raidusMile = raidus;

    Double dpmLat = 1 / degree;
    Double radiusLat = dpmLat * raidusMile;
    Double minLat = latitude - radiusLat;
    Double maxLat = latitude + radiusLat;

    Double mpdLng = degree * Math.cos(latitude * (Math.PI / 180));
    Double dpmLng = 1 / mpdLng;
    Double radiusLng = dpmLng * raidusMile;
    Double minLng = longitude - radiusLng;
    Double maxLng = longitude + radiusLng;
    // return new double[]{minLat, minLng, maxLat, maxLng};
    
    Map<String, Double> aroundMap = new HashMap<>();
    aroundMap.put("minLng", minLng);
    aroundMap.put("minLat", minLat);
    aroundMap.put("maxLng", maxLng);
    aroundMap.put("maxLat", maxLat);
    return aroundMap;
}
/**
 * 计算地球上任意两点(经纬度)距离
 *
 * @param long1 第一点经度
 * @param lat1  第一点纬度
 * @param long2 第二点经度
 * @param lat2  第二点纬度
 * @return 返回距离 单位:米
 */
private double distanceByLongNLat(double long1, double lat1, double long2, double lat2) {
    double a, b, R;
    R = 6378137;//地球半径
    lat1 = lat1 * Math.PI / 180.0;
    lat2 = lat2 * Math.PI / 180.0;
    a = lat1 - lat2;
    b = (long1 - long2) * Math.PI / 180.0;
    double d;
    double sa2, sb2;
    sa2 = Math.sin(a / 2.0);
    sb2 = Math.sin(b / 2.0);
    d = 2 * R * Math.asin(Math.sqrt(sa2 * sa2 + Math.cos(lat1) * Math.cos(lat2) * sb2 * sb2));
    return d;
}

参考链接

https://www.cnblogs.com/xuwenjin/p/12715339.html

https://blog.csdn.net/juejiangjianchi/article/details/109205198

https://www.freesion.com/article/2617608220/


日夜颠倒头发少 ,单纯好骗恋爱脑 ,会背九九乘法表 ,下雨只会往家跑 ,搭讪只会说你好 ---- 2050781802@qq.com

×

喜欢就点赞,疼爱就打赏

相册 说点什么