redis课程总结

前言

关系型数据库:ACID(atomicity,consistancy,isolation,durability)

非关系型数据库:分布式架构,CAP(consistency强一致性,availability高可用性,partition tolerance分区容忍性),三者最多只能满足两个,其中,P必需,一般A/C中选1。 AP是大多数网站架构的选择(强一致性不重要),CP的典型例子是 redis/mongodb。

特点:AP + BASE(basically available基本可用,soft state软状态,eventually consistent最终一致。)

分类:

1.kv键值型:redis

2.文档型:mongodb

3.列存储:HBase

4.图关系型:


redis(remote dictionary server):高性能K/V分布式内存数据库。

    特点:持久化;支持多种数据类型;master-slave的数据备份;异步保存数据到硬盘;设置过期时间等

提供的服务种类:

    redis-server    启动redis服务器

    redis-cli       作为客户端调用redis

    redis-benchmark    测试

    redis-sentinel    作为哨兵对主从复制进行自动监控

特点

1.单进程   :epoll包装请求

2.默认16个库  select 

常用命令

exists key1:key1是否存在

move key1 1:将key1移动到db1

expire key1 10: key1的过期时间为10s

ttl key1:key1还有多久过期,-1永不过期,-2已过期

type key1:查看key1的类型

DBSIZE:当前db的key数量

FLUSHDB:清空单个db的key

FLUSHALL:清空所有db的key


数据类型:

String:一个key对应一个value,二进制安全,可以包含任何数据(如图片/序列化对象)

    set/get/append/strlen

    incr/decr/incrby/decrby(对string的数字才有效)

    getrange/setrange

    setex(set with expire)/setnx(set if not exist)

    mset/mget:同时操作多个


List:底层是链表,前后都可添加数据,一个key对应多个value

    lpush/rpush/lrange(查看range内的值)

    lpop/rpop

    lindex(按下标获取元素)

    lrem:lrem key1 2 3(删除key1中的2个3)

    ltrim:ltrim  key1 2 4(从key1中获取2 4 两个index范围内的值并重新赋给key1)

    rpoplpush key1 key2(rpop key1的值,lpush到key2)

    lset key index value

    insert key before/after value

    值若全移除,则键消失

    两端操作快,中间操作慢


Set:无序集合,一个key对应多个value

    sadd/smembers(查看所有成员)/sismember(是否是成员)

    scard:获取集合的元素个数

    srem :删除某个元素

    srandmember:随机获取一个值(不出栈)

    spop:随机出栈

    sdiff key1 key2:差集,在key1中,但不在key2中

    sinter key01 key02:交集

    sunion key01 key02:并集


Hash:键值对集合,key/value模式不变,但value是键值对

    hset/hget/hmset/hmget/hgetall/hdel

    hlen

    hexists key keykey:key对应的hash中是否有keykey对应的值

    hkeys/hvals

    hincrby/hincrbyfloat

    hsetnx(set if not exist)


ZSet:每个元素关联一个double类型的分数,通过分数排序;成员唯一但分数(score)可以重复。

    在set基础上增加一个score,之前set是 sadd k1 v1  v2 v3,现在是zadd k1 score1 v1 score2 v2 score3 v3    

    zadd zset01 50 v1 60 v2 70 v3 80 v4(50:分数    v1:值)

    zrange zset01 0 -1 withscores

    zrangebyscore    zset01 60 90:获取zset01中分数在60 - 90之间的值

    zrangebyscore    zset01 60 (90:获取zset01中分数在60 - 90之间的值,不包含90

    zrangebyscore    zset01 60 90 limit 1 2:获取zset01中分数在60 - 90之间的值,从结果中索引为1 处获取2个值返回

    zrem zset01 v6:删除

    zcard zset01 :获取总个数

    zcount zset01 60 80:统计个数

    zrevrange:与zrange反序


redis.conf配置文件结构:

一,介绍等;

二,general通用

1.redis默认不以守护进程的方式进行,改为yes可启用守护进程

daemonize no

2.redis以守护进程运行时,pid会被写入到/var/run/redis.pid中,可通过pidfile修改

pidfile /var/run/redis.pid

3.监听端口

port 6379

4.绑定主机地址

bind 127.0.0.1

5.客户端闲置多长时间后关闭连接,指定为0则关闭该功能

timeout 300

6.指定redis日志级别,支持debug/verbose/notice/warning四个级别,默认verbose

loglevel verbose

7.日志记录方式,默认标准输出。redis为守护进程且输出方式为标准输出时,日志将发送到/dev/null

logfile stdout

8.设置数据库数量,默认16个,使用数据库0

database 16

9.指定本地数据库存放目录,不同位置启动redis,存放位置不同

dir /.

10.同一时间最大客户端连接数

maxclients 128


redis持久化:

存在rdb和aof两种存储模式

RDB主线程不负责IO(所以性能极高),会fork()一个线程单独进行快照文件的备份,每到时间间隔形成一个快照文件(dump.rdb格式),并用这个文件替换之前的快照文件。如果需要进行大规模的数据恢复,且恢复的完整性相对不敏感的情况下,性能优于AOF,但最后一次持久化的数据可能丢失。

RDB

rdb(redis database):指定时间间隔内将内存中的数据集快照(snapshot快照)写入磁盘,恢复时将快照文件读回磁盘。

redis.conf文件中 SNAPSHOT板块对存储策略进行了设置,如save 300 10,300s内存在10次以上key的修改,则进行一次存储。

此处修改为每120秒超过十次更改,则生成一次dump.rdb文件。dump.rdb文件存储位置可在redis.conf中修改,新地址为图示第二列。

执行命令:

生成文件如下:

将该文件拷贝,删除源文件与redis中的数据,重新启动redis-server,可以看到没有数据:

将dump.rdb复制到原位置,重启redis-server,并执行keys *,数据已恢复:

快照生成:1.按照.conf的默认配置进行(定期冷拷贝到其他机器,可对数据做备份);2.save(同步阻塞保存)或bgsave(异步保存)。

快照恢复:备份文件移动到redis安装目录(即.conf中dir 所配置的位置)并启动服务。

优劣:适合大规模的数据恢复,数据完整性和一致性要求不高,但由于定期做备份的策略,机器意外停止时,最后一次的信息会丢失部分,另外fork会导致数据膨胀一倍。


aof(append only file)

记录redis执行过的所有写指令(不记录读指令),只追加不改写,redis启动时读取该文件(appendonly.aof格式),根据文件内容执行所有指令以恢复数据。

.conf中的append only mode中

手动编辑.aof文件,重新运行server,数据恢复:

.rdb和.aof同时存在时,优先通过.aof恢复数据。

因为意外原因导致的.aof乱码可通过redis-check-aof --fix appendonly.aof修复。



.conf中append only mode可对aof进行设置:

    appendonly yes为配置开启;

    appendfsync配置持久化策略(always:同步持久化,记录每次的数据变更,性能较差但完整;

                                                 everysec:出场默认设置,异步,每秒记录)

    auto-aof-rewrite-percentage :重写基准值设置

    auto-aof-rewrite-min-size :重写基准值设置

aof的rewrite机制:aof采取文件追加机制,文件越来越大,当大小超过阈值时,redis启动aof文件的内容压缩,通过fork新进程对文件进行重写,保留可恢复数据的最小指令集。默认文件是上一次的一倍或者超过64M时,进行触发。

优劣:aof文件有序保存指令,易读易分析,但数据太大,效率慢

官网建议:

        RDB定期存储数据快照,AOF记录操作指令。如果只希望在服务运行时存在数据, 持久化也可省略。

        建议同时开启这两种持久化方式:redis重启时优先获取AOF文件,AOF文件比RDB更完整;但也不要只使用AOF,因为RDB更适合作为备份数据库,AOF变化快,且有潜在bug,RDB可作为万一的手段。

性能建议:

        RDB作为后备,建议只在slave上持久化RDB文件,15分钟一次即可(只保留save 900 1这条);

        enable AOF的话,最坏情况也不会丢失超过两秒的数据,启动脚本简单(只load AOF文件即可),但存在持续IO,且rewrite时新数据写到新文件必然存在阻塞,所以应该减小IO频率,改大rewrite的大小阈值(如5G);

        不enable AOF的话,仅靠主从架构也可实现高可用,节省IO和rewrite带来的阻塞和系统波动,但master/slave同时倒下时会丢失十几分钟的数据,启动脚本的时候也需要比较主从中的两个RDB文件,载入较新的那个。


redis的事务:

一次处理多条redis命令(序列化),串行,不被打扰。不支持原子性(部分支持,下面场景4即不支持)

事务命令:discard:取消事务,放弃所有命令

                   exec:执行事务中的所有命令

                   multi:标记一个事务块的开始

                   unwatch:取消watch命令对所有key的监控

                   watch key[key...]:监控一个/多个key,key被修改则事务被打断

场景:

1.执行事务

2.放弃事务

3.全体连坐:一条命令出错,所有命令不被执行。类似于受查一场,指令出错,入栈时已经失败

4.冤头债主:类似于运行时异常,运行时才发现错误,但指令入栈没问题。因此redis对事务是部分支持。

5.watch监控:

悲观锁:很悲观,认为每次拿数据时别人都会修改,因此先加锁再更改,如行锁,表锁

乐观锁:很乐观认为每次拿数据时别人都不会修改,因此先更改,每版信息都有一个version信息,提交之前进行对比,大于存储的版本信息才会进行存储,典型算法如CAS

watch对特定key进行监控,类似于乐观锁,执行multi时如果其他人对这个key做了更改,执行时会返回nil。

开启:muti

入队:命令入队,先不执行,放到执行队列中

执行:exec命令触发(执行exec后,之前添加的锁全部失效)


发布订阅

不常用

支持通配符:


主从复制(master/slave)

主机数据更新后,备机根据策略进行备份,master以写为主,slave以读为主。

作用:读写分离,容灾恢复。

用法:1.配从(库)不配主(库):salveof 主库IP 主库端口,每次与master断开后,需要重新进行连接,除非写入到.conf中。

左 6379 主                       中 6380  从                     右 6381 从

常用方法:

1.一主二仆。先set k1   k2   k3,后执行salveof,再set k4。


1.1.成为从机后,主机所有数据都会被备份

1.2.从机无法写入,只能读取

1.3.主机shutdown后,从机依然时slave

1.4.主机重新连接时,从机依然为slave

1.5.一个从机死亡时,其他从机正常工作,死亡的从机重连后变为master,不再保存有之前主机的数据(除非从机对应配置了.conf文件)

1.6.从机第一次连接时,全量复制主机数据,后面存量复制。

2.薪火相传:去中心化,上一个slave是下一个slave的master(类似链表),中途变更转向会清除之前的数据,以保存master的数据。以一串server中只有一个master,其他都是slave。

3.反客为主

主机挂掉后,从机可以作为新的其他主机的master,master重新连接后也不再拥有从机(slaveof no one :使当前数据库停止与其他数据库的同步)


#复制原理:第一次全量复制,之后增量复制(只要重新连接到master,都会触发一次全量复制)。

slave连接到master后会发送一次sync命令,master接收到命令后启动后台存盘进行,并收集修改数据集的命令,执行完毕后将命令文件传送到slave,以完成完全同步。slave接收到文件后将存盘加载到内存,完成第一次连接时的全量复制;之后master执行的命令,slave都会进行增量复制。


哨兵模式:自动模式的反客为主。

.conf所在文件夹下新建sentinel.conf文件,【sentinel monitor ip port 1】 指令进行配置,最后的1表示主机挂掉后,按照投票,票数多的从机成为新主机。(注:可同时监控多个master)

通过redis-sentinel *.conf 启动哨兵模式。

主机挂掉后,从机经过投票选择出一个新的master

主机79重新连接后,哨兵监控到这一信息,将使79成为新master的一台从机

缺点:写操作在master,读操作在slave,存在读写延迟


Jedis:

windows下用idea连接,报错connection refused:connect

解决:redis.conf中注释掉bind 127.0.0.1这一行,重新启动后又报错:

redis.clients.jedis.exceptions.JedisDataException: DENIED Redis is running in protected mode because protected mode is enabled

解决:redis.conf中修改protected-mode 为yes

问题解决

jedis调用api演示:

```

@Test

void contextLoads() {

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

    jedis.flushAll();

    System.out.println("String");

    jedis.set("k1", "v1");

    jedis.set("k2", "v2");

    jedis.set("k3", "v3");

    System.out.println(jedis.keys("*"));

    // list

    System.out.println("List");

    jedis.lpush("mylist", "v1", "v2", "v3");

    System.out.println(jedis.rpop("mylist"));

    jedis.lpush("mylist", "v4", "v5", "v6");

    jedis.ltrim("mylist", 1, 4);

    System.out.println(jedis.lrange("mylist", 0, -1));

    //set

    System.out.println("Set");

    jedis.sadd("myset","1","2","3","4");

    System.out.println(jedis.scard("myset"));

    System.out.println("myset members" + jedis.smembers("myset"));

    System.out.println(jedis.sismember("myset", "`10"));

    jedis.sadd("myset1","5","6","3","4");

    System.out.println(jedis.sinter("myset", "myset1"));

    // hash

    System.out.println("Hash");

    jedis.hset("myhash","k1", "v1");

    Map map =new HashMap<>();

    map.put("k2","v2");

    map.put("k3","v3");

    map.put("k4","v4");

    jedis.hmset("myhash", map);

    System.out.println(jedis.hmget("myhash","k2","k3"));

    // zset

    System.out.println("Zset");

    jedis.zadd("myzset",60, "v1");

    jedis.zadd("myzset",80, "v2");

    jedis.zadd("myzset",90, "v3");

    jedis.zadd("myzset",70, "v4");

    System.out.println(jedis.zrange("myzset", 0, 2));

    System.out.println(jedis.zrangeByScore("myzset", 70, 90));

    System.out.println(jedis.zcount("myzset", 70, 90));

}

```

结果:

jedis事务演示:

```java

@Test

void contextLoads()throws InterruptedException {

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

    //jedis.flushAll();

    System.out.println( transactionMethod(jedis));

}


private boolean transactionMethod(Jedis jedis)throws InterruptedException {

int cost =10;

    int balance = Integer.parseInt( jedis.get("balance"));

    int debt = Integer.parseInt( jedis.get("debt"));;

    System.out.println(balance);

    System.out.println(debt);

    jedis.watch("balance");

    Thread.sleep(20000);

    if(cost > balance) {

jedis.unwatch();

        System.out.println("modified");

return false;

    }else {

System.out.println("transaction");

        Transaction transaction = jedis.multi();

        transaction.decrBy("balance", 10);

        transaction.incrBy("debt", 10);

        transaction.exec();

        balance = Integer.parseInt( jedis.get("balance"));

        debt = Integer.parseInt( jedis.get("debt"));

        System.out.println(balance);

        System.out.println(debt);

return true;

    }

```

balance设定为100,debt设定为0, 先对balance进行watch,程序sleep()时对balance进行更改,改为40,事务失败,返回null,控制台如下:


主从复制演示:

```java

@Test

void contextLoads()throws InterruptedException {

Jedis jedis_M =new Jedis("192.168.64.128", 6379);

    Jedis jedis_S =new Jedis("192.168.64.128", 6380);

    jedis_S.slaveof("192.168.64.128", 6379);

    jedis_M.set("k1", "v1");

  System.out.println(jedis_S.get("k1"));

}

```

输出可能为v1,可能为空(内存读写比较快,在程序中sleep一段时间就能正常获取到v1)



jedis连接池演示:

```java

public class JedisPoolUtil {

private JedisPoolUtil() {

}

private static volatile JedisPooljedisPool;

    public static  JedisPoolgetInstance() {

if(jedisPool ==null) {

synchronized (JedisPool.class) {

if(jedisPool ==null) {

JedisPoolConfig poolConfig =new JedisPoolConfig();

                    poolConfig.setMaxTotal(1000);

                    poolConfig.setMaxIdle(500);

                    poolConfig.setMaxWaitMillis(100 *1000);

                    // 获得新连接时是否检测可用性(ping)

                    poolConfig.setTestOnBorrow(true);

                    jedisPool =new JedisPool(poolConfig, "192.168.64.128", 6379);

                }

}

}

return jedisPool;

    }

public static void release(JedisPool jedisPool, Jedis jedis) {

if(jedis !=null) {

jedisPool.returnResourceObject(jedis);

        }

}

}

```

测试代码:

```java

@Test

void contextLoads()throws InterruptedException {

JedisPool jedisPool1 = JedisPoolUtil.getInstance();

    JedisPool jedisPool2 = JedisPoolUtil.getInstance();

    assert jedisPool1 == jedisPool2;

    Jedis jedis =null;

    try {

jedis = jedisPool1.getResource();

        jedis.set("aa", "bb");

    }catch (Exception e){

}finally {

JedisPoolUtil.release(jedisPool1,jedis);

    }

}

```

结果:

注:本文为redis学习课程的总结笔记

课程链接:https://www.bilibili.com/video/BV1oW411u75R?p=28

你可能感兴趣的:(redis课程总结)