redis学习笔记

本文是关于Redis的学习笔记,涵盖了Redis的基础知识,包括Redis与NoSql的关系、Redis的五大数据类型、事务处理、Jedis的使用以及Spring Boot整合Redis。文章还讨论了Redis的持久化策略,如RDB和AOF,并介绍了主从复制和哨兵模式,最后提到了缓存穿透和雪崩问题及其解决方案。
摘要由CSDN通过智能技术生成

NoSql

RDBMS和NoSql

传统的RDBMS
- 结构化组织
- SQL
- 数据和关系存在单独的表
- 操作远,数据定义语言
- 严格的一致性
- 基础的事务
Nosql
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库
- 最终一致性
- CAP定理和BASE
- 高性能,高可用,高扩展
大数据时代的3V:1.海量Volume,2.多样Variety,3.实时Velocity
大数据时代的3高:高并发、高可扩、高性能

Redis

Re mote Dictionary Server

干什么?

内存存储,持久化,内存时断电即失,所以持久化很重要(rdb、aof)
效率高可以用高速缓存
发布订阅系统
地图信息分析
计时器、计数器
特性:
1.多样的数据类型
2.持久化
3.集群
4.事务

Redis命令

保证有redis-benchmark

redis-server (配置文件位置)
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

常见命令

# 查看数据库
DEBSIZE
# 设置值,得到值
set name qingjiu
get name
# 得到所有的key
keys *
# 删除当前的数据库所有的值
flushdb
# redis有16数据库,默认为0,可以切换数据库,切换数据库
select 0 
# 清除所有的数据库内容
FlushAll
# 是否存在某个key,存在返回1(Integer类型):EXISTS key
EXISTS name
# 移动move key databaseIndex
move name 1
# 设置过期时间EXPIRE key seconds
EXPIRE name 50000
# 获得redis的key的过期时间ttl key
ttl name 
# 获得类型type name
type key
# SETEX (SET WITH EXPIRE) 	设置过期时间
# SETNX (SET IF NOT EXPIRE) 不存在设置(在分布式锁中常用),存在则失败
SETEX key 30 "xxx"
SETNX key value
# 批量设置获取MSET,MGET
# MSET KEY1 VALUE1 KEY2 VALUE2... 
# MGET KEY1 KEY2
MSET KEY1 VALUE1 KEY2 VALUE2
MGET KEY1 KEY2
# 对象设置东西,第一个JSON数据表示,第二的用key
MSET user:1 {name:zhangshan,age:3}
MSET user:1:name:zhangshan user:1:name:age:3
# GETSET得到在设置,GETSET KEY VALUE。
GETSET KEY VALUE
# 易错点
#MSETNX KEY1 VALUE1....如果中间有任何一个存在,所有都失败(原子操作)

Redis是单线程(6.0之后版本可以开启多线程)

主要瓶颈在于网络带宽和机器内存,所以使用单线程

核心:

redis所有数据在内存使用单线程是效率最高的,没有上下文切换

五大数据类型

key,value是区分大小写的

String(字符串)

#添加字符串,如果key不存在,相当于set key
append key "xxx" 
#得到字符串长度
STRLEN key
# 自增一
incr key
# 自减一
decr key
# 步长命令,类似于i += 50:INCRBY/DECRBY key number
INCRBY key 50
DECRBY key 50
# 字符串范围 GETRANGE KEY START END,[START,END]。如果end是-1则是全部查出来
getrange key 1 5
# 替换 SETRANGE KEY OFFSET VALUE,OFFSET指的是偏移了多少。下标是OFFSET开始修改
SETRANGE key 1 xxx

List

基本上redis命令是所有list都会加L,这里下标是从0开始的

# LPUSH\RPUSH KEY VALUE1 VALUE2...(左边插入\右边插入)
lpush list value
# LRANGE KEY START STOP(STOP==-1那么是全部输出,左边下标是0.START是负数,语句就是LRANGE KEY 0 END)
lrange list 0 -1
# LPOP\RPOP KEY(左边移除、右边移除)
lpop list
# LINDEX KEY INDEX(通过下标获得值)
lindx list 0
# LLEN KEY(获得长度)
Llen list
# LREM KEY COUNT VALUE(移除COUNT数量的VALUE值,LIST中VALUE值是可以多个)
# LTRIM KEY START STOP(截取[START,STOP]的所有的值)
# RPOPLPUSH KEY OTHERKEY(RIGHT POP LEFT PUSH,右边弹出并且加入新的OTHERKEY的里面,如果OTHERKEY不在会创建)
# LSET KEY INDEX VALUE(KEY必须存在,INDEX是负数则从右边开始,左边从0开始。但是负数最大是长度负数,正数时最大为长度-1)
# LINSERT KEY BEFORE|AFTER PIVOT VALUE(在PIVOT前面或者后面加入VALUE值)
  • 这个实际时链表,key不存在,创建。如果存在则添加。
  • 如果移除所有值,空列表。也代表没了

  • 可以变为消息队列和栈

SET

  • 不可以重复

  • 常见的命令

    # SADD KEY MEMBER
    # SMEMBERS KEY (查看所有的的值)
    # SISMEMBER KEY MEMBER(判断MEMBER是不是在这里面,是则返回一,否则为0)
    # SCARD KEY(得到所有的元素个数)
    # SREM KEY MEMBER MEMBER1...(删除东西)
    # SRANDMEMBER KEY [COUNT](随机取出count个成员,count可以省略,则默认为1)
    # SPOP KEY [COUNT](随机弹出去一个值)
    # SMOVE SOURCE DESTINATION MEMBER(从源移动到目的地)
    # SDIFF\SINTER\SUNINON KEY KEY1 KEY...(KEY的差集、交集、并集)
    

hash

Map集合,key-map时候这个值是一个map集合!本质和String类型没有太大区别,一个简单的key-value值

 #hset key field value [field value...] 设置值
 #hmset key field value [field value...] 设置多个东西
 #hget key field value [field value...] 得到值
 #hmget key field value [field value...] 得到多个值
 #hgetall key //得到所有field和value
 #hdel key field [field...] 删除field
 #hlen 长度
 #hexists key field 判断是否存在
 #hkeys key 得到所有key
 #hvals key 得到所有value
 #HINCRBY KEY FIELD INCREMENT
 HINCRBY KEY FIELD -1
 #HSETNX KEY FIELD VALUE 不存在可以设置,否则失败返回0
 HSET user:1 name "张三";

Zset(有序集合)

# 添加
# ZADD KEY SCORE FIELD [SCORE FIELD...]
ZADD KEY 1 ONE 2 TWO 3 THREE
# 查看范围的值
# ZRANGE KEY START STOP
ZRANGE KEY 0 -1
# 排序
# ZRANGEBYSCORE KEY MIN MAX [WITHSCORES] [LIMIT OFFSET COUNT]
# ZREVRANGE KEY START STOP [WITHSCORES] 从大到小
# -INF +INF最小最大,[WITHSCORES] 是不是带上分数
ZRANGEBYSCORE KEY -INF +INF WITHSCORES 50
# 移除
# ZREM KEY MEMBER [MEMBER...]
# ZCARD KEY 获取个数
# ZCOUNT KEY MIN MAX 获得key的分数在min和max之间的

三种特殊的数据类型

geosptial

底层是zset设计的,zet命令可以使用

# geoadd key longitude latitude member [longitude latitude member...]添加
# 南北两极无法直接添加,有效的经度:-180度到180度,有效的维度在-85.05到85.05度,建议java程序一次加入
# GEOPOS KEY MEMBER [MEMBER....]
# GEODIST KEY MEMBER1 MEMBER2 [m/km/ft/mi]
# 没有指定单位默认为m,四个单位:m->米,km->千米,mi->英里,ft->英尺
# 找出指定坐标的周围元素
# GEORADIUS KEY LONGITUDE LATITUDE RADIUS m|km|ft|mi [WITHCOORD] [WITHDIST] [COUNT NUMBER]
# 查看距离,返回11个字符串。二维的字符串变为一维的字符串
# GEOHASH KEY MEMBER1 MEMBER2

Hyperloglog

基数: 不重复的元素,可以接受误差

统计访问次数:一个人可以访问很多次,但是只能算作一个人

传统方式是用set保存ID:但是高并发,有点误差。而且很多ID导致内存消耗太多了

# PFADD KEY ELEMENT [ELEMENT...] 创建
# PFCOUNT KEY 统计
# PFMERGE DESTKEY SOURCEKEY [SOURCEKEY...] 合并

Bitmaps

位存储

适用于0和1状态

# SETBIT KEY OFFSET VALUE 
# GETBIT KEY OFFSET VALUE
# BITCOUNT KEY [START END] 统计打卡

事务

ACID A:原子性 C:一致性 I:隔离性 D:持久性

一组命令的集合,命令被序列化,在事务执行过程中,会按照顺序执行

一次性、顺序性、排他性!执行命令

Redis事务没有隔离级别概念

故所有命令,在事务中没有被执行!只有发起执行命令才开始执行

redis事务:

  • 开启事务:MULTI
  • 命令
  • 执行事务(EXEC)
MULTI
[SET KEY VALUE.....]
EXEC
# DISCARD 放弃事务,事务的 

编译异常:代码有错,所有命令不会执行

运行异常:其他命令继续执行

监控

悲观锁

  • 认为什么时候都会出问题,都要加锁

乐观锁

  • 认为什么时候都不会有问题,所以不会上锁!更新数据时回去判断是不是有人在这之间修改了数据
 # watch
 # 注意:这里遇到事务的时候会自动解锁
 # When EXEC is called, all keys are UNWATCHed, regardless of whether the transaction was aborted or not. Also when a client connection is closed, everything gets UNWATCHed.

Jedis

Redis时官方推荐的JAVA的连接开发工具!

如何使用Jedis

导入依赖

		<dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.3.0</version>
        </dependency>

创建对象

public class example {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.56.10",6379);
        //Jedis 所有的命令都是我们之前学习所有的命令!
        System.out.println(jedis.ping());
    }
}

整合Spring Boot

Jedis在springBoot 2.x被lettuce替换了

jedis:采用直连,多个线程操作是不安全的。如果需要避免不安全,就要jedis pool连接池。更像BIO模型

lettuce:采用netty,实例可以在多个线程池共享,不存在线程安全。更像NIO模型(推荐使用lettuce,因为jedis很多包没有导入)

  • 导包

  • 配置

  • private RedisTemplate redis;
    
  • 上述命令就是函数,有点变化。

@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;
  }

写的Util

@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;
        }

    }

}

Redis配置

启动时候用配置文件来启动

单位

不区分大小写

include

包含文件路径

网络

bind 127.0.0.1		#绑定端口
protect-mode yes	#保护模式
port 6379			#端口

通用配置

daemonize yes	#以守护进程开启运行
pidfile /var/run/redis_6379.pid 	#如果以后台运行那么需要指定一个pid文件
loglevel notice			#日志级别,还有debug,verbose(和bug级别很像),notice(生产环境),warning
logfile ""				#日志文件名
database 16
always-show-logo	yes		#就是启动的时候的logo要不要

快照

持久化,在规定的时间内,执行了多少的操作,那么就会持久化到文件 .rdb .aof

redis是内存数据库,没有持久化,那么断电就会消失

#如果900s内,至少一个key修改就会持久化
save 900 1			
#持久化如果出错,是否继续工作
stop-writes-on-bgsave-error yes
# 是否压缩rdb文件,消耗CPU资源
rdbchecksum yes
# rdb文件检测校验
rdbchecksum yes
# 持久化目录
dir ./

复制

安全

可以设置密码

config get requirepass
config get requirepass "xxxx"
auth 123456				#登入

客户端

maxclients 10000	#最大客户端
maxmemory <bytes>   #redis设置最大内存
maxmemory-policy noeviction	#内存达到处理策略
###
# noeviction:不删除策略,达到最大内存限制时,如果需要更多内存,直接返回错误信息。
# allkeys-lru:所有key通用,优先删除最近最少使用的(less recently used,LRU)key.
# volatile-lru: lru的算法删除过期的key
# allkeys-random:所有key通用,随机删除一部分key.
# volatile-random:只限于设置了expire的部分,删除一部分超时的key.
# volatile-ttl:只限于设置了expire的部分,优先删除剩余时间(time to live,TTL)短的key.
###

aof配置

appendonly no #默认不开启aof模式的使用rdb方式持久化,rdb是够用的
appendfilename "" #持久化文件名字

appendfsync everysec	#每秒执行一次,宕机就会少了这一秒数据
# appendfsync always(no)

持久化

RDB(Redis DataBase)

父进程fork子进程,父进程继续做原来的事情但是子进程开始把数据写入临时文件,然后替代原来的临时文件。但是最后一次宕机了,就没有。

文件是:dump.rdb

触发机制

  1. save规则成立
  2. 执行flushall命令
  3. 退出redis,shutdown

恢复RDB文件

# 查看文件位置,如果过在目录中有就会自动扫描加入
config get dir

优点

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

缺点

  1. redis意外宕机了,之后的修改就没了
  2. fork进程,占有一部分内存

AOF(Append Only File)

将我们所有写命令记录下来,保存文件appendOnly.aof

appendonly no  							#是否开启
no-appendfsync-on-rewrite no			#AOF重写期间是否禁止fsync;如果开启该选项,可以减轻文件重写时CPU和硬盘的负载(尤其是硬盘),但是可能会丢失AOF重写期间的数据;需要在负载和安全性之间进行平衡
auto-aof-rewrite-percentage 100 		#重写的规则时:一百重写,过期的数据丢了
auto-aof-rewrite-min-size 64mb			#文件重写触发提交之一
appendfsync everysec				#同步时间,上面参数一致

aof文件出错了可以修复

redis-check-aof --fix appendonly.aof

优点

  1. 每次修改都同步那么就是文件完整性最好的
  2. 每一秒同步,可能丢失一秒的数据
  3. 不同步那么就是最快的

缺点

  1. aof文件远远大于dump文件,修复慢
  2. 效率慢

主从复制

将一台主节点数据复制到其他节点上。单向的

读写分离,读的方法交给从节点,写交给主节点。最低标准一主二从

info replication # 查看信息

伪集群

修改配置

修改配置信息

  • 端口
  • pid名字
  • dump名字
  • 日志名字
redis-server kconfig/文件名

启动,默认是都是主节点

认老大

  • 命令

    SLAVEOF HOST PORT
    SLAVEOF no one #变回主节点
    
  • 配置文件

    replicaof <masterip> <masterport>
    masterauth <masterpassword>
    

细节

  • 从节点只负责读,主节点只负责写
  • 主节点宕机了,没有配哨兵模式的话,所有结点只有读操作。主节点回来了就可以有写操作了
  • 从节点如果是命令行设置从机,那么重启不会连接回去,但是连接回去了就可以拿到主节点数据

复制细节

全量复制:从节点会发送请求sync请求,主节点就会把所有数据发过去

增量复制:之后主节点修改命令,从节点接受一部分数据就好了

二种连接

  1. 所有的连一个主节点
  2. 像链表一样 ()

哨兵模式

由于手动费时费力,故有了哨兵模式

有一堆哨兵监视所有的redis,都会给这些redis发消息,判断是不是下线。如果一个不回话,其他哨兵就会都去发这个消息,达到一定数量哨兵认为这个宕机了,就会选出一个主机

  • 配置文件sentinel.conf

    # sentinel monitor 名称 IP地址 端口 最后一个代表多少个哨兵认为这个下线了就是宕机了
    sentinel monitor myredis 127.0.0.1 6379 1
    
  • 启动哨兵

    redis-sentinel kconfig/sentinel.conf
    

细节

主机回来了只能当从机器

  • 优点
    • 高可用性
    • 主从模式升级,手动变为自动
    • 主从复制,优点都有
  • 缺点
    • 集群达到上限,扩容十分麻烦
    • 配置

配置项

# Example sentinel.conf
 
# 哨兵sentinel实例运行的端口 默认26379;如果有哨兵集群,我们还需要配置每个哨兵的端口
port 26379
 
# 哨兵sentinel的工作目录
dir /tmp
 
# 哨兵sentinel监控的redis主节点的 ip port 
# master-name  可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
 
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
 
 
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
 
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
#这个数字越小,完成failover所需的时间就越长,
# 但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
# 可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
 
 
 
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面: 
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。  
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
 
# SCRIPTS EXECUTION
 
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
 
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
#这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
#一个是事件的类型,
#一个是事件的描述。
#如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <script-path>
  sentinel notification-script mymaster /var/redis/notify.sh
 
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。 
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
 sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

缓存穿透和雪崩

缓存穿透

缓存没有这个数据,就会访问数据库发现也没有,返回失败。但是访问很多出现这个问题

方法一

  • 布隆过滤器(hash查找值,过滤掉一些东西)
  • 缓存空对象,将一个不存在的值放在缓存中
    • 但是可能会导致一段时间窗口不一致
    • 浪费内存

缓存穿透

比如消失为60s,但是恢复在60.01s恢复。但是访问流量过大凿开了一个洞。导致宕机

  • 设置永不过期,浪费空间
  • 加互斥锁,加锁保证一个过去查找数据库

雪崩

缓存集体失效

  • redis多个
  • 限制流量
  • 数据预热,将可能数据加上到缓存。设置过期均匀一点
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值