redis

本条博客来源——狂神说

一、单机SQL的演进

NoSQL概述

NoSQL(non-relational,Not Only SQL),泛指非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在处理web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,出现了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,特别是大数据应用难题。

1.单机SQL时代

90年代,一个基本的网站访问量不会太大,单个数据库足够。

DAl(数据访问层)

SQL语言分类:数据查询语言DQL,数据操纵语言DML,数据定义语言DDL,数据控制语言DCL。

2.Memcache(缓存) + Mysql + 垂直拆分

缓存用来减轻数据库的压力,提高效率

二、Redis概述

 

三、Windows安装和redis-benchmark性能测试

 

四、五种数据类型

翻译如下:

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库缓存消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication)LUA脚本(Lua scripting), LRU驱动事件(LRU eviction)事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)

Redis-key

127.0.0.1:6379> keys *        # 查看所有的key
(empty list or set)
127.0.0.1:6379> set name syf   # 设置key
OK
127.0.0.1:6379> get name       # 获取key
"syf"
127.0.0.1:6379> exists name     # 判断当前库的key是否存在
(integer) 1
127.0.0.1:6379> exists name1
(integer) 0
127.0.0.1:6379> move name 1      # 移动key到别的数据库
(integer) 1
127.0.0.1:6379> select 1         # 切换数据库
OK
127.0.0.1:6379> expire name 5     # 设置key的过期时间,单位是秒
(integer) 1
127.0.0.1:6379> ttl name          # 查看key的剩余时间
(integer) 2
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> type name    # 查看对应key的value值类型
string

String(字符串)

127.0.0.1:6379> set key v1   # 设置key
OK
127.0.0.1:6379> get key   # 获取key
"v1"
127.0.0.1:6379> append key "syf"   # 向key中追加字符
(integer) 5
127.0.0.1:6379> get key
"v1syf"
127.0.0.1:6379> strlen key   # 获得key的字符串长度
(integer) 5
127.0.0.1:6379> append key1 "syf,wahaha"   # 如果key不存在,则相当于创建新的key来存储字符串
(integer) 10
127.0.0.1:6379> get key1
"syf,wahaha"
                    
127.0.0.1:6379> set views 1   # 模拟浏览量,初始为1
OK
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> incr views   # 自增,默认+1
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> incrby views 100  # 设置步长,自增100
(integer) 102
127.0.0.1:6379> get views
"102"
127.0.0.1:6379> decrby views 100  # 设置步长,自减100
(integer) 2
# range 获取范围

# GETRANGE
127.0.0.1:6379> set name syf
OK
127.0.0.1:6379> get name
"syf"
127.0.0.1:6379> getrange name 0 1   # 截取字符串[0,1]
"sy"
127.0.0.1:6379> getrange name 0 -1    # 截取字符串[0,-1],代表截取全部,和get name一样
"syf"

# SETRANGE
127.0.0.1:6379> setrange name 1 0   # 替换字符,从索引为1的位置,替换后面的字符
(integer) 3
127.0.0.1:6379> get name
"s0f"
127.0.0.1:6379> setrange name 1 XXX   # 替换字符,从索引为1的位置,替换后面的字符,自动增加
(integer) 4
127.0.0.1:6379> get name
"sXXX"
# setex (set with expire) 设置过期时间
# setnx (set if not exist) 不存在再设置 (分布式锁中使用)

127.0.0.1:6379> setex name2 10 "syf" # 设置name2值为syf,并在10秒后过期
OK
127.0.0.1:6379> ttl name2
(integer) 3
127.0.0.1:6379> ttl name2
(integer) -2

127.0.0.1:6379> set name syf
OK
127.0.0.1:6379> setnx name "123"    # name存在,设置失败返回0
(integer) 0
127.0.0.1:6379> setnx name1 "123"   # name1不存在,设置成功返回1
(integer) 1

# 批量操作
# mset
# mget
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"
# msetnx
127.0.0.1:6379> msetnx k1 v1 k4 v4   # k1存在,设置失败,(原子性,要么全成功,要么全失败)
(integer) 0
# 对象
127.0.0.1:6379> set user:1 {name:syf,age:3}   # 设置一个user:1对象,用json格式保存值
OK
127.0.0.1:6379> get user
(nil)
127.0.0.1:6379> get user:1
"{name:syf,age:3}"

# 另一种方式 : user:{id}:{filed}
127.0.0.1:6379> mset user:1:name syf user:1:age 23
OK
127.0.0.1:6379> get user:1:name
"syf"
# getset 先get再set
127.0.0.1:6379> set db redis
OK
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb  # 如果get的值存在,先get再set
"redis"
127.0.0.1:6379> get db
"mongodb"
127.0.0.1:6379> getset db1 redis  # 如果get的值不存在返回nil,并设置新的值
(nil)
127.0.0.1:6379> get db1
"redis"

String使用场景

设计:uid:123456:fans 

  • 计数器
  • 统计多单位的数量 
  • 粉丝数
  • 对象缓存存储

List(列表)

在redis中,可以把list作为,栈、队列、阻塞队列。

所有的list命令都是以l开头

# list插入值
127.0.0.1:6379> lpush name sss   # 将一个值或多个值,插入到列表头部
(integer) 1
127.0.0.1:6379> lpush name yyy
(integer) 2
127.0.0.1:6379> lpush name fff
(integer) 3
127.0.0.1:6379> lrange name 0 -1    # 获得列表所有值
1) "fff"
2) "yyy"
3) "sss"
127.0.0.1:6379> lrange name 0 1
1) "fff"
2) "yyy"
127.0.0.1:6379> rpush name rrr   # 将一个值或多个值,插入到列表尾部
(integer) 4
127.0.0.1:6379> lrange name 0 -1
1) "fff"
2) "yyy"
3) "sss"
4) "rrr"

# list移除值
127.0.0.1:6379> lpop name   #从头部移除
"fff"
127.0.0.1:6379> rpop name   #从尾部移除
"rrr"
127.0.0.1:6379> lrange name 0 -1
1) "yyy"
2) "sss"

# list移除指定的值
127.0.0.1:6379> lrange name 0 -1
1) "yyy"
2) "sss"
127.0.0.1:6379> lrem name 1 sss   # 语法:lrem key count value,count是指定移除的数量,因 
                                  #       list中可以存在重复的值
(integer) 1
127.0.0.1:6379> lrange name 0 -1
1) "yyy"
# 通过index获得list中的值
127.0.0.1:6379> lindex name 0
"yyy"
127.0.0.1:6379> lindex name 1
"sss"

# llen获取list长度
127.0.0.1:6379> lrange name 0 -1
1) "yyy"
2) "sss"
127.0.0.1:6379> llen name
(integer) 2
# trime 截取
127.0.0.1:6379> lpush name 1 2 3 4   # list中放入值
(integer) 4
127.0.0.1:6379> lrange name 0 -1   # 查看list中的值
1) "4"
2) "3"
3) "2"
4) "1"
127.0.0.1:6379> ltrim name 0 1   # 截取 0 1 位置的值
OK
127.0.0.1:6379> lrange name 0 -1   # 查看list中的值
1) "4"
2) "3"
# rpoplpush 弹出list列表中最后一个元素,并放入其他的list
127.0.0.1:6379> lpush name v1 v2 v3
(integer) 3
127.0.0.1:6379> lrange name 0 -1
1) "v3"
2) "v2"
3) "v1"
127.0.0.1:6379> rpoplpush name name1   # 从name的尾部弹出一个值,并放入name1中
"v1"
127.0.0.1:6379> lrange name 0 -1
1) "v3"
2) "v2"
127.0.0.1:6379> lrange name1 0 -1
1) "v1"
# lset 替换list中已经存在的值,更新操作
127.0.0.1:6379> lset name 0 v1   # 不存在key,失败
(error) ERR no such key
127.0.0.1:6379> lpush name v1 v2   # 创建key和value
(integer) 2
127.0.0.1:6379> lrange name 0 -1
1) "v2"
2) "v1"
127.0.0.1:6379> lset name 0 set1   # 替换索引为0的位置的值为 set1
OK
127.0.0.1:6379> lrange name 0 -1   # 成功
1) "set1"
2) "v1"
127.0.0.1:6379> lset name 2 set2   # 因为索引为2的位置的值不存在,替换失败
(error) ERR index out of range
# linsert 在某一个值前面或后面插入
127.0.0.1:6379> rpush name v1 v2 v1
(integer) 3
127.0.0.1:6379> linsert name before v1 insert
(integer) 4
127.0.0.1:6379> lrange name 0 -1
1) "insert"
2) "v1"
3) "v2"
4) "v1"
127.0.0.1:6379> linsert name after v1 insert1
(integer) 5
127.0.0.1:6379> lrange name 0 -1
1) "insert"
2) "v1"
3) "insert1"
4) "v2"
5) "v1"

小结

  • list实际上是一个链表,两边改动值效率最高
  • 作为消息队列(Lpush Rpop)、栈(Lpush Lpop)

set(集合)

set中的值是不能重复的且无序。

127.0.0.1:6379> sadd myset m1 m2 m3   # sadd 插入
(integer) 3
127.0.0.1:6379> smembers myset   # smembers 查看set成员
1) "m1"
2) "m3"
3) "m2"
127.0.0.1:6379> sismember myset m1   # sismember 判断是否存在成员
(integer) 1
127.0.0.1:6379> sismember myset m4   # sismember 判断是否存在成员
(integer) 0
127.0.0.1:6379> scard myset   #scard 查看set集合中元素个数
(integer) 3
127.0.0.1:6379> srem myset m1   #srem  移除指定元素
(integer) 1
127.0.0.1:6379> smembers myset
1) "m3"
2) "m2"
# srandmember 随机获取集合中的元素
127.0.0.1:6379> smembers myset
1) "m3"
2) "m2"
127.0.0.1:6379> srandmember myset
"m2"
127.0.0.1:6379> srandmember myset
"m2"
127.0.0.1:6379> srandmember myset
"m2"
127.0.0.1:6379> srandmember myset
"m2"
127.0.0.1:6379> srandmember myset
"m3"
127.0.0.1:6379> srandmember myset
"m2"
# spop 随机移除一个元素
127.0.0.1:6379> smembers myset
1) "v4"
2) "v1"
3) "m3"
4) "m2"
5) "v5"
127.0.0.1:6379> spop myset
"v4"
127.0.0.1:6379> spop myset
"m2"

# smove 集合中移动一个元素到另一个集合
127.0.0.1:6379> smove myset myset2 v1  # 另一个集合不存在,自动创建
(integer) 1
127.0.0.1:6379> smembers myset2
1) "v1"
# sdiff、sinter、sunion
127.0.0.1:6379> sadd set1 a b c
(integer) 3
127.0.0.1:6379> sadd set2 c d e
(integer) 3
127.0.0.1:6379> sdiff set1 set2  #差集
1) "b"
2) "a"
127.0.0.1:6379> sdiff set2 set1   #差集
1) "d"
2) "e"
127.0.0.1:6379> sinter set1 set2   #交集, 场景:两人的共同QQ好友
1) "c"
127.0.0.1:6379> sunion set1 set2   #并集
1) "b"
2) "c"
3) "a"
4) "d"
5) "e"

Hash(哈希)

key-map存储

127.0.0.1:6379>  hmset myhash field1 syf field2 syff   # 往集合中批量插入
OK
127.0.0.1:6379> hset myhash field1 syf1
(integer) 0
127.0.0.1:6379> hget myhash field1    # 获取集合某个字段的值
"syf1"
127.0.0.1:6379> hgetall myhash   # 获取集合所有的字段和值,按k-v排列
1) "field1"
2) "syf1"
3) "field2"
4) "syff"
127.0.0.1:6379> hdel myhash field1   #删除集合某个字段
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "syff"
127.0.0.1:6379> hlen myhash   # 获取字段长度
(integer) 1
127.0.0.1:6379> hmset myhash field1 v1 field2 v2   
OK
127.0.0.1:6379> hkeys myhash   # 获取字段
1) "field1"
2) "field2"
127.0.0.1:6379> hvals myhash   # 获取字段中的值
1) "v1"
2) "v2"
127.0.0.1:6379> hsetnx myhash field1 v11   # hsetnx 不存在再设置
(integer) 0
127.0.0.1:6379> hincrby myhash field 1   # hincrby 设置增长,可以是负数
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "v1"
3) "field2"
4) "v2"
5) "field"
6) "1"

hash更适合对象的存储,String更适合字符串的存储。

Zset(有序集合)

在set的基础上,增加了一个值,set k1 v1,zset k1 score v1

127.0.0.1:6379> zadd zset 1 v1 2 v2  # 插入
(integer) 2
127.0.0.1:6379> zrange zset 0 -1   # 查询
1) "v1"
2) "v2"
# 排序
127.0.0.1:6379> zadd salary 2500 ming 2600 clearlove 2300 uzi
(integer) 3
127.0.0.1:6379> zrangebyscore salary -inf +inf  # 升序 从-inf(无穷小)到 +inf(无穷大)
1) "uzi"
2) "ming"
3) "clearlove"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores   # withscores 带值的排序
1) "uzi"
2) "2300"
3) "ming"
4) "2500"
5) "clearlove"
6) "2600"
127.0.0.1:6379> zrevrangebyscore salary +inf -inf  # zrevrangebyscore 降序
1) "clearlove"
2) "ming"
3) "uzi"

案例思路:存储班级成绩,工资表排序,排行榜实现

五、三种特殊数据类型

geospatial(地理位置)

附近的人,打车距离计算?

城市经纬度查询网址

一共6个命令:

GEOADD

GEOPOS

GEODIST

GEORADIUS

附近的人

官网

GEORADIUSBYMEMBER

上边是通过经纬度作为中心查询,这个是可以用成员来作为中心查询

GEOHASH

Hyperloglog(基数统计)

基数:一个集合中,不重复的元素

127.0.0.1:6379> pfadd mykey1 1 2 3 4 5 6 7   # 创建mykey1
(integer) 1
127.0.0.1:6379> pfadd mykey2 6 7 8 9 10 11    # 创建mykey2
(integer) 1
127.0.0.1:6379> pfcount mykey1 mykey2    # 统计mykey1和mykey2的基数
(integer) 11
127.0.0.1:6379> pfcount mykey1   # 统计mykey1的基数
(integer) 7
127.0.0.1:6379> pfcount mykey2   # 统计mykey2的基数
(integer) 6
127.0.0.1:6379> pfmerge mykey3 mykey1 mykey2   #合并mykey1和mykey2为mykey3 并集
OK
127.0.0.1:6379> pfcount mykey3
(integer) 11

Bigmaps

位存储

六、事务

Redis事务本质:一组命令集合,一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行。

Redis中单条命令是保证原子性的,但是事务不保证原子性!

Redis事务没有隔离性的概念!

Redis事务:

  • (1)开启事务(multi)
  • (2)命令入队(...)
  • (3)执行事务(exec)/ 放弃事务(discard)
127.0.0.1:6379> multi
OK
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
QUEUED
127.0.0.1:6379> mget k1 k2 k3
QUEUED
127.0.0.1:6379> getrange k1 0 -1
QUEUED
127.0.0.1:6379> exec
1) OK
2) 1) "v1"
   2) "v2"
   3) "v3"
3) "v1"

编译时异常

运行时异常

Redis实现乐观锁

监视(watch)

七、Jedis操作Redis

1.导入依赖

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.3.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>

2.编码测试

  • 连接数据库
  • 操作命令
  • 断开连接
public class TestPing {
    public static void main(String[] args) {
        //1.创建jedis对象
        Jedis jedis = new Jedis("127.0.0.1",6379);
        //2.测试连接,连接成功输出PONG
        System.out.println(jedis.ping());
    }
}

方法就和前边在redis-cli中用的命令一样

 关闭连接

jedis.close();

jedis操作事务

public class TestTX {
    public static void main(String[] args) {

        Jedis jedis = new Jedis("127.0.0.1", 6379);

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello","world");
        jsonObject.put("name","syf");
        String jsonString = jsonObject.toJSONString();

        Transaction multi = jedis.multi();//开起事务
        try {
            multi.set("user1",jsonString);
            multi.set("user2",jsonString);
            multi.exec();//执行事务
        } catch (Exception e) {
            multi.discard();//失败,关闭事务
            e.printStackTrace();
        } finally {
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();//关闭连接
        }
    }
}

八、SpringBoot整合

 

3.测试

@SpringBootTest
class Redis02SpringbootApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    void contextLoads() {
        redisTemplate.opsForValue().set("name","sss");
        Object name = redisTemplate.opsForValue().get("name");
        System.out.println(name);
    }

}

自定义RedisTemplate类

@Configuration
public class RedisConfig {

    //自定义redisTemplate
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        //Json序列化配置
        Jackson2JsonRedisSerializer objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        objectJackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        //String序列化方式
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        //key采用string序列化方式
        template.setKeySerializer(stringRedisSerializer);
        //hash采用string序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        //value采用json序列化方式
        template.setValueSerializer(objectJackson2JsonRedisSerializer);
        //hash的value采用json序列化方式
        template.setHashValueSerializer(objectJackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

}

RedisUtils工具类

https://blog.csdn.net/weixin_44449838/article/details/109329709

 

九、Redis.config

单位 容量单位不区分大小写,G和GB有区别
在这里插入图片描述

1、配置文件 unit单位 对大小写不敏感!

包含 可以使用 include 组合多个配置问题
在这里插入图片描述

就是好比我们学习Spring、Import, include

网络

bind 127.0.0.1 # 绑定的ip
protected-mode yes # 保护模式
port 6379 # 端口设置

通用 GENERAL

daemonize yes # 以守护进程的方式运行(就是后台运行),默认是 no,我们需要自己开启为yes!
pidfile /var/run/redis_6379.pid # 如果以后台的方式运行,我们就需要指定一个 pid 文件!
# 日志
# Specify the server verbosity level.
# This can be one of:
redis 是内存数据库,如果没有持久化,那么数据断电及失!
REPLICATION 复制,我们后面讲解主从复制的,时候再进行讲解
SECURITY 安全
可以在这里设置redis的密码,默认是没有密码!
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) 生产环境
# warning (only very important / critical messages are logged)
loglevel notice
logfile "" # 日志的文件位置名
databases 16 # 数据库的数量,默认是 16 个数据库
always-show-logo yes # 是否总是显示LOGO

 

快照

持久化, 在规定的时间内,执行了多少次操作,则会持久化到文件 .rdb. aof
redis 是内存数据库,如果没有持久化,那么数据断电及失!

# 如果900s内,如果至少有一个1 key进行了修改,我们及进行持久化操作
save 900 1
# 如果300s内,如果至少10 key进行了修改,我们及进行持久化操作
save 300 10
# 如果60s内,如果至少10000 key进行了修改,我们及进行持久化操作
save 60 10000
# 我们之后学习持久化,会自己定义这个测试!
stop-writes-on-bgsave-error yes # 持久化如果出错,是否还需要继续工作!
rdbcompression yes # 是否压缩 rdb 文件,需要消耗一些cpu资源!
rdbchecksum yes # 保存rdb文件的时候,进行错误的检查校验!
dir ./ # rdb 文件保存的目录!

REPLICATION 复制,我们后面讲解主从复制的,时候再进行讲解

SECURITY 安全

可以在这里设置redis的密码,默认是没有密码!

127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get requirepass # 获取redis的密码
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "123456" # 设置redis的密码
OK
127.0.0.1:6379> config get requirepass # 发现所有的命令都没有权限了
(error) NOAUTH Authentication required.
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123456 # 使用密码进行登录!
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"

限制 CLIENTS

maxclients 10000 # 设置能连接上redis的最大客户端的数量
maxmemory <bytes> # redis 配置最大的内存容量
maxmemory-policy noeviction # 内存到达上限之后的处理策略
 # redis 中的默认的过期策略是 volatile-lru 。
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : 删除lru算法的key
3、volatile-random:随机删除即将过期key
4、allkeys-random:随机删除
5、volatile-ttl : 删除即将过期的
6、noeviction : 永不过期,返回错误
------------------------------------------------
# 设置方式
config set maxmemory-policy volatile-lru 

APPEND ONLY 模式 aof配置

appendonly no # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分所有的情况下,
rdb完全够用!
appendfilename "appendonly.aof" # 持久化的文件的名字
# appendfsync always # 每次修改都会 sync。消耗性能
appendfsync everysec # 每秒执行一次 sync,可能会丢失这1s的数据!
# appendfsync no # 不执行 sync,这个时候操作系统自己同步数据,速度最快!

bgsave

bgsave 是异步进行,进行持久化的时候,redis 还可以将继续响应客户端请求 ;

bgsave和save对比

命令savebgsave
IO类型同步异步
阻塞?是(阻塞发生在fock(),通常非常快)
复杂度O(n)O(n)
优点不会消耗额外的内存不阻塞客户端命令
缺点阻塞客户端命令需要fock子进程,消耗内存

具体的配置,我们在 Redis持久化 中去给大家详细详解

十、Redis持久化

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

RDB(Redis DataBase)

什么是RDB

在这里插入图片描述

在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效RDB的缺点是最后一次持久化后的数据可能丢失。我们默认的就是RDB,一般情况下不需要修改这个配置

有时候在生产环境我们会将这个文件进行备份!

rdb保存的文件是dump.rdb 都是在我们的配置文件中快照中进行配置的!

触发机制

  • 1、save的规则满足的情况下,会自动触发rdb规则
  • 2、执行 flushall 命令,也会触发我们的rdb规则!
  • 3、退出redis,也会产生 rdb 文件!

备份就自动生成一个 dump.rdb

如何恢复rdb文件!

1、只需要将rdb文件放在我们redis启动目录就可以,redis启动的时候会自动检查dump.rdb 恢复其中的数据!

2、查看需要存在的位置

127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin" # 如果在这个目录下存在 dump.rdb文件,启动就会自动恢复其中的数据

几乎就他自己默认的配置就够用了,但是我们还是需要去学习!

优点:

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

缺点:

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

AOF(Append Only File)

将我们的所有命令都记录下来,history,恢复的时候就把这个文件全部在执行一遍!

是什么

在这里插入图片描述

以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

Aof保存的是 appendonly.aof 文件

什么是AOF

快照功能(RDB)并不是非常耐久(durable): 如果 Redis因为某些原因而造成故障停机,那么服务器将丢失最近写入、以及未保存到快照中的那些数据。 从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化。

如果要使用AOF,需要修改配置文件
在这里插入图片描述

appendonly no yes则表示启用AOF

默认是不开启的,我们需要手动配置,然后重启redis,就可以生效了!

如果这个aof文件有错位,这时候redis是启动不起来的,我需要修改这个aof文件

redis给我们提供了一个工具redis-check-aof --fix

优点和缺点

appendonly yes  # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分的情况下,rdb完全够用
appendfilename "appendonly.aof"

# appendfsync always # 每次修改都会sync 消耗性能
appendfsync everysec # 每秒执行一次 sync 可能会丢失这一秒的数据
# appendfsync no # 不执行 sync ,这时候操作系统自己同步数据,速度最快

append

在这里插入图片描述

默认是不开启的,我们需要手动进行配置!我们只需要将 appendonly 改为yes就开启了 aof!
重启,redis 就可以生效了!
如果这个 aof 文件有错位,这时候 redis 是启动不起来的,我们需要修复这个aof文件
redis 给我们提供了一个工具 redis-check-aof --fix
在这里插入图片描述

如果文件正常,重启就可以直接恢复了!
在这里插入图片描述

重写规则说明

aof 默认就是文件的无限追加,文件会越来越大
在这里插入图片描述

如果 aof 文件大于 64m,太大了! fork一个新的进程来将我们的文件进行重写!

优点:

  • 1、每一次修改都同步,文件的完整会更加好!
  • 2、每秒同步一次,可能会丢失一秒的数据
  • 3、从不同步,效率最高的!

缺点:

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

RDB和AOF选择

有点RDBAOF
启动优先级
体积
恢复速度
数据安全性丢数据根据策略决定

如何选择使用哪种持久化方式?

一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。

如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。

有很多用户都只使用 AOF 持久化, 但并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。

十一、Redis发布订购

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。微信、微博、关注系统!
Redis 客户端可以订阅任意数量的频道。
订阅/发布消息图:
第一个:消息发送者, 第二个:频道 第三个:消息订阅者!
在这里插入图片描述

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

在这里插入图片描述

当有新消息通过 PUBLISH 命令发送给频道 channel1 时,这个消息就会被发送给订阅它的三个客户端:

在这里插入图片描述

命令

命令描述
PSUBSCRIBE pattern [pattern…]订阅一个或多个符合给定模式的频道。
PUNSUBSCRIBE pattern [pattern…]退订一个或多个符合给定模式的频道。
PUBSUB subcommand [argument[argument]]查看订阅与发布系统状态。
PUBLISH channel message向指定频道发布消息
SUBSCRIBE channel [channel…]订阅给定的一个或多个频道。
UNSUBSCRIBE channel [channel…]退订一个或多个频道
------------订阅端----------------------
127.0.0.1:6379> SUBSCRIBE sakura # 订阅sakura频道
Reading messages... (press Ctrl-C to quit) # 等待接收消息
1) "subscribe" # 订阅成功的消息
2) "sakura"
3) (integer) 1
1) "message" # 接收到来自sakura频道的消息 "hello world"
2) "sakura"
3) "hello world"
1) "message" # 接收到来自sakura频道的消息 "hello i am sakura"
2) "sakura"
3) "hello i am sakura"

--------------消息发布端-------------------
127.0.0.1:6379> PUBLISH sakura "hello world" # 发布消息到sakura频道
(integer) 1
127.0.0.1:6379> PUBLISH sakura "hello i am sakura" # 发布消息
(integer) 1

-----------------查看活跃的频道------------
127.0.0.1:6379> PUBSUB channels
1) "sakura"

原理

每个 Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer 结构, 结构的 pubsub_channels 属性是一个字典,这个字典就用于保存订阅频道的信息,其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。

在这里插入图片描述

客户端订阅,就被链接到对应频道的链表的尾部,退订则就是将客户端节点从链表中移除。

缺点

  • 如果一个客户端订阅了频道,但自己读取消息的速度却不够快的话,那么不断积压的消息会使redis输出缓冲区的体积变得越来越大,这可能使得redis本身的速度变慢,甚至直接崩溃。
  • 这和数据传输可靠性有关,如果在订阅方断线,那么他将会丢失所有在短线期间发布者发布的消息。

应用

  • 消息订阅:公众号订阅,微博关注等等(起始更多是使用消息队列来进行实现)
  • 多人在线聊天室。

这边消息队列的功能相比MQ之类的就差很多了,所以稍微复杂的场景,我们就会使用消息中间件MQ处理。

十二、Redis主从复制

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。

默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。

作用

  • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
  • 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
  • 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在读多写少的场景下,通过多个从节点分担负载,提高并发量。
  • 高可用基石:主从复制还是哨兵和集群能够实施的基础。

为什么使用集群

  • 单台服务器难以负载大量的请求
  • 单台服务器故障率高,系统崩坏概率大
  • 单台服务器内存容量有限。

环境配置

我们在讲解配置文件的时候,注意到有一个replication模块 (见Redis.conf中第8条)

查看当前库的信息:info replication

127.0.0.1:6379> info replication
# Replication
role:master # 角色
connected_slaves:0 # 从机数量
master_replid:3b54deef5b7b7b7f7dd8acefa23be48879b4fcff
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

既然需要启动多个服务,就需要多个配置文件。每个配置文件对应修改以下信息:

  • 端口号
  • pid文件名
  • 日志文件名
  • rdb文件名

启动单机多服务集群:

ps -ef|grep redis

在这里插入图片描述

一主二从配置

默认情况下,每台Redis服务器都是主节点 ;我们一般情况下只用配置从机就好了!

认老大!一主(79)二从(80,81)

使用SLAVEOF host port就可以为从机配置主机了。
在这里插入图片描述

在这里插入图片描述

然后主机上也能看到从机的状态:
在这里插入图片描述

我们这里是使用命令搭建,是暂时的,==真实开发中应该在从机的配置文件中进行配置,==这样的话是永久的
在这里插入图片描述

使用规则

1.从机只能读,不能写,主机可读可写但是多用于写。

 127.0.0.1:6381> set name sakura # 从机6381写入失败
(error) READONLY You can't write against a read only replica.

127.0.0.1:6380> set name sakura # 从机6380写入失败
(error) READONLY You can't write against a read only replica.

127.0.0.1:6379> set name sakura
OK
127.0.0.1:6379> get name
"sakura"

2.当主机断电宕机后,默认情况下从机的角色不会发生变化 ,集群中只是失去了写操作,当主机恢复以后,又会连接上从机恢复原状。

3.当从机断电宕机后,若不是使用配置文件配置的从机,再次启动后作为主机是无法获取之前主机的数据的,若此时重新配置称为从机,又可以获取到主机的所有数据。这里就要提到一个同步原理。

4.第二条中提到,默认情况下,主机故障后,不会出现新的主机,有两种方式可以产生新的主机:

- 从机手动执行命令slaveof no one,这样执行以后从机会独立出来成为一个主机
- 使用哨兵模式(自动选举)

如果没有老大了,这个时候能不能选择出来一个老大呢?手动!

如果主机断开了连接,我们可以使用SLAVEOF no one让自己变成主机!其他的节点就可以手动连接到最新的主节点(手动)!如果这个时候老大修复了,那么久重新连接!

十三、哨兵模式

更多信息参考博客:https://www.jianshu.com/p/06ab9daf921d

概述

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

在这里插入图片描述

这里的哨兵有两个作用

  • 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
  • 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。

然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。
各个哨兵之间还会进行监控,这样就形成了多哨兵模式。

在这里插入图片描述

假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。

测试

我们目前的状态是 一主二从!

1、配置哨兵配置文件 sentinel.conf

# sentinel monitor 被监控的名称 host port 1
sentinel monitor myredis 127.0.0.1 6379 1

后面的这个数字1,代表主机挂了,slave投票看让谁接替成为主机,票数最多的,就会成为主机!

2.启动哨兵

redis-sentinel xxx/sentinel.conf

# 成功启动哨兵模式

 

在这里插入图片描述

此时哨兵监视着我们的主机6379,当我们断开主机后:

在这里插入图片描述

哨兵模式优缺点

优点:

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

缺点:

  1. Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
  2. 实现哨兵模式的配置其实是很麻烦的,里面有很多配置项

哨兵模式的全部配置

完整的哨兵模式配置文件 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 1
 
# 当在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

 

十四、Redis缓存穿透和雪崩

服务的高可用问题

Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要害的问题,就是数据的一致性问题(事务在运行时不能保证原子性),从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。
另外的一些典型问题就是,缓存穿透、缓存雪崩和缓存击穿。目前,业界也都有比较流行的解决方案。

在这里插入图片描述

缓存穿透

概念

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

解决方案

1.布隆过滤器

对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力。

在这里插入图片描述

2.缓存空对象

一次请求若在缓存和数据库中都没找到,就在缓存中方一个空对象用于处理后续这个请求。

在这里插入图片描述

1、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;
2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响

缓存击穿(量太大 缓存过期)

概述

相较于缓存穿透,缓存击穿的目的性更强,一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。

比如热搜排行上,一个热点新闻被同时大量访问就可能导致缓存击穿。

解决方案

1.设置热点数据永不过期

这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。

2.加互斥锁(分布式锁)

在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。

缓存雪崩

缓存概念

大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。

在这里插入图片描述

解决方案

redis高可用

这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群

限流降级

这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

数据预热

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

参考网站:https://blog.csdn.net/DDDDeng_/article/details/108118544

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值