Redis笔记

Redis

什么是NoSQL?

NoSQL是 Not Only SQL, 即不仅仅是SQL,泛指非关系型数据库。

NoSQL不依赖业务逻辑方式存储,以K-V模式存储.

不遵循SQL标准

不支持ACID,即事务

性能高于SQL

NoSQL的用途:

解决服务器CPU的内存压力: eg: Cookie的存放问题

解决IO压力:eg: 水平分切,垂直分切,读写分离,通过破坏一定的业务逻辑来换取性能;

缓存数据库:

Memcached:数据在内存,一般作为缓存数据库辅助持久化的数据库;

Redis: 数据存在内存,支持K-V,一般作为缓存数据库辅助持久化的数据库;

文档数据库

MongoDB:文档性数据库,数据存储在内存,支持K-V;

列式数据库

Hbase:以一列为单位存储数据;

Redis简介

开源K-V存储系统,数据缓存在内存中。通常用于配合关系型数据库做高速缓存。

Redis安装

下载地址:https://redis.io/download

编译:在redis目录中,执行make命令,进行编译,需要有gcc

安装: 使用 make install PREFIX=/指定命令的路径 进行安装即可

执行: 进入指定命令的bin目录,执行相应的命令,启动server。

Redis配置

Redis的配置文件为Redis.conf,如果在启动Redis_server时,不指定配置文件,则使用默认的配置。

默认Redis_server为用户服务,将Redis.conf中的daemonize改为Yes,这样就可以让Redis_server在后台运行。

问题:Redis为什么这么快?

Redis是基于内存的,其次是单线程多路IO复用;

多路复用即一个线程检查多个文件描述符的就绪状态,调用select,poll,epoll,当传入多个文件描述符,如果有一个文件的描述符就绪,则返回,否则阻塞直到超时。

Redis默认16个数据库,下标从0开始,初始默认使用0号库,

Redis的基本数据类型

解决原子性问题: 同步,CAS, 使用原子类(autoInteger) , 注意volivate不能保证原子性,它只能保证工作内存和主内存的同步;

注意:i++不是原子操作,赋值运算是原子操作,但double ,long 的赋值操作不是原子性的。因为long.double是64位,但在计算机在执行的过程中,不是一次性分配64位,而是32位。

redis的所有指令都是原子的。

当设置某个k-v的过期值,然后再过期之前修改该值,则该k-v就会为永久的。

有关K的操作

key * : 列出所有的K;

exist<key> : 判断K是否存在

type<key> :判断K的类型

del <key> : 删除某个K

expire<key> <seconds> : 为某个K设置过期时间

ttl <key> : 查看某个K的过期时间,-1为永不过期,-2表示已经过期

dbsize: 查看当前数据库的K的数量

Flushdb:清除当前库

FlushAll: 清除所有的库;

String

二进制安全,可以包含任何数据,一个Redis字符串最多有512M;

set<k><v> ;get<k> ;incr<key> ; decr<key> ; incrby /decrby <key> <step> ;mest<k1> <v1> <k2><v2> ;mget<k1><k2><k3>;msetnx<k1><vl><k2><v2>; gettrange<k><start><end> ; setrange<key> <start><end> ;setex<k><expire><value> ; getset<k><v>;

list

单键多值,是简单的字符串列表,按照插入顺序,即左边插入是采用头插法,右边插入是尾插法;低层是一个双向链表。

lpush/rpush <k1><v1><k2><v2>;lpop/rpop <k> ,注意值在列表在,值不在K不在;rpoplpush <k1><k2> 从k列表的右边吐出一个值,查到k2列表的左边;lrange<k> ,start><stop> ;lindex<k><index> ;llen<key> ;linsert<k>before|after<v><newv> ; lrem<k><n><v> n取正数,从左边删除n个v,n取负数,从右边删除n个v,取值为0,从列表中符合v的值都删除;

set

String的类型的无序集合,低层是一个Value为null的hash表,添加,查找,删除的复杂度都是O(1);

sadd<k> <v1><v2> ;smembers<k> ; sismember<k><v> ;scard<k> ;srem<k> <v1><v2> ;stop<k> ;srandmember<k><n>;sinter<k1><k2>;sunion<k1><k2>;sdiff<k1><k2> ;

hash

键值对集合,String类型的field和value的映射表;

hset<k> <field><value>; hget<k> <field> ; hmset<k1> <field><v1> <field><v2> ;hexists key < field> ; hkeys <key> ;hvals<key> ;hincrby<k><field><increment>; hsetnx<key> <field><value>,

zset

有序集合,没有重复元素的字符串集合。

zadd <k><score><v> <k2><score><v>;zrange<k><start><stop><withscope>;zrangebyscore key min max [withscores] [limit offset count];zrevrangescore key min max min

Java连接Jedis

前提设置:

禁用Linux防火墙,

systemctl stop firewalld.service

redis.conf中注释掉bind 127.0.0.1 ,将protect-mode 设置为no

连接redis

需要导入Jar包: jedis-2.9.0.jar Commons-pool-1.6.jar

@Test
    public void testc() {
        //使用Jedis创建实例
        Jedis jedis = new Jedis("127.0.0.1",6379);
        System.out.println("testing ...." + jedis.ping());
    }

Key

    @Test
    public void tests() {
        Set<String> keys = jedis.keys("*");
        for(Iterator iterator = keys.iterator();iterator.hasNext();) {
            String key = (String) iterator.next();
            System.out.println(key);
        }
        System.out.println(jedis.exists("k2"));
        System.out.println(jedis.ttl("k1"));
    }

String

    @Test
    public void testt() {
        jedis.set("k4","k4_redis");
        System.out.println("-----------------------");
        jedis.mset("str1","v1","str2","v2","str3","v3");
        System.out.println(jedis.mget("str1","str2","str3"));
    }

List

    @Test
    public void testf() {
        List<String> list = jedis.lrange("list", 0, -1);
        for(String str: list)
            System.out.println(str);
    }

set

    @Test
    public void testfi() {
        jedis.sadd("score", "89");
        jedis.sadd("score", "99");
        jedis.sadd("score", "100");
        Set<String> s1 = jedis.smembers("score");
        for(Iterator iterator = s1.iterator();iterator.hasNext();) {
            String str = (String) iterator.next();
            System.out.println(str);
        }
        jedis.srem("score", "89");
    }

hash

    @Test
    public void testsi() {
        jedis.hset("h1", "name", "land");
        System.out.println(jedis.hget("h1","name"));
        Map<String,String> map = new HashMap<String,String>();
        map.put("age", "27");
        map.put("idcard", "123");
        jedis.hmset("h2",map);
        
        List<String> result = jedis.hmget("h2", "age"."idcare");
        for(String str : result)
            System.out.println(str);
    }

zset

    @Test
    public void testse() {
        jedis.zadd("z", 99,"v3");
        jedis.zadd("z", 80,"v2");
        
        Set<String> s1 = jedis.zrange("z", 0, -1);
        for(Iterator iterator = s1.iterator();iterator.hasNext();) {
            String str = (String) iterator.next();
            System.out.println(str);
        }
    }

Redis事务

Redis事务是一个单独的隔离操作,命令会被序列化,按顺序执行,会串联多个命令防止别的命令插队;

具体操作Multi ,Exec ,discare

输入Mutli命令开始,输入的命令会被依次加入到命令队列中,但不会执行,知道输入Exec后,Redis会将之间的命令队列依次执行,在组队的过程中,可以使用discard放弃组队;

注意:当组队中某个命令出现错误,执行的所有的队列会被取消,如果执行的过程中某个命令出错,则只有报错的命令不会被执行,其他的命令都会执行,不会回滚。

问题

当在进行事务1操作某个K时,如果在别的事务2中访问该K,并进行了修改,则在事务1中,对于该值的使用还是原先没有修改的值,即使事务2执行完毕,会造成逻辑错误。为了避免这种情况,则使用命令WATCH key[key ...]可以避免这种情况。

WATCH key[key]:

在执行multi之前,先执行WATCH key1,可以监视一个或多个K,如果事务在执行的过程中,如果有该K有变动,则该事务会被打断。

unwatch

取消WATCH命令对所有K的监视;

Redis事务的三特性:

不保证原子性:Redis同一个事务中如果有一条命令执行失败,其他命令仍然会执行,没有回滚;

没有隔离级别概念:因为在Mysql,Oracle中,在开启事务后,会执行相应的命令,最后才会被同步,会涉及到提交前查询和查询后的不一致,但是在Redis中,队列中的命令没有提交之间是不会实际的被执行,疑问事务提交前任何命令都不会被实际执行,也就不存在事务内,和事务外。

单独的隔离操作:事务的所有的命令会被序列化,按顺序 执行,不会被其他客户端的命令请求打断;

Java中操作Redis事务

    @Test
    public void testtr() {
        //开启事务
        Transaction transaction = jedis.multi();
        //组队
        transaction.sadd("k1","2");
        transaction.decr("k1");
        //执行
        transaction.exec();
    }

Redis持久化

虽然Redis是基于内存,但是内存中数据不能长久的保存,为了保存数据,需要将数据持久化到磁盘,Redis提供了两个形式的持久化方式。RDB(Redis Data Base),AOF(Append Of File)。

RDB

在指定的时间间隔内将内存中的数据写入到磁盘,即Snapshot快照,,恢复时将快照文件直接读到内存里。

RDB的备份机制:(怎么)

会单独创建一个子进程来进行持久化,会将数据写入到临时文件中,待持久化过程结束,在用临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作,这样确保了Redis性能,RDB方式比AOF高效,但RDB缺点是会最后一次持久化后的数据可能丢失。(在Linux,子进程和父进程共享一个存储空间,只有子进程内容发生变化,才会将父进程的内容复制一份给子进程。)

RDB文件:(何地)

在配置文件中,默认为dump.rdb,可修改;

rdb文件的保存路径,可修改,默认为Redis启动时命令行所在的目录,eg:

./redis-server 启动redis ,则rdb文件保存在redis-server目录下。

RDB保存策略:(何时)

在配置文件中,可修改,为每隔多久进行保存.

save 900 1
save 300 10
save 60 10000

即:eg: save 900 1 ,在900秒内,如果10个K发生变化,就进行保存。

手动保存:

使用save命令,立刻进行保存

stop-writes-on-bgsave-error yes : 当Redis无法写入磁盘,直接关掉Redis的写操作
​
rdbcompression yes : 即rdb保存,将文件压缩
​
rdbchecksum yes:存储快 照后,使用CRC64算法来进行数据效验。

RDB备份:

将dmp.rdb文件放到准备好的备份目录中即可。

RDB恢复:

关闭Redis -》 把备份的文件放入工作目录下(即启动命令的目录)-》启动Redis,备份数据会自动加载。

RDB优点:

节省磁盘,恢复速度快

RDB缺点:

当Redis发生宕机,会丢失最后一个快照。

AOF

以日志的形式来记录每个写指令,对于读指令不予以记录,只需追加文件,不可以改写文件。Redis在启动时,会去读取该文件,将指令从前到后执行一次去重构数据。

AOF配置:

AOF默认不开启,需要在配置文件中进行开启。

appendonly yes

默认AOF的文件名为appendonly.aof,可在配置文件中进行修改

appendfilename  "appendonly.aof"

AOF文件的目录和RDB文件一致。

AOF的备份机制:(在配置文件中)

appendfsync always      // 始终同步,每次Redis的写入都立刻计入日志
appendfsync everysec    //每秒同步
appendfsync no          //不主动同步,把同步交给OS

AOF的故障备份和恢复:

AOF的备份和恢复的操作的操作同RDB一样,都是拷贝备份文件。恢复时,在拷贝到Redis的工作目录下即可。

注意:AOF采用文件追加方式,当文件越来越大,使用重写机制,当AOF文件的大小超过所设定的阈值时,Redis会启动AOF文件的内容压缩,只保留可恢复数据的最小指令集,可使用命令:bgrewriteaof

何时重写:

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

需要满足一定的条件才会进行重写,系统载入时或上次重写完成时,Redis会记录此时AOF的大小,设为base_size,如果Redis的AOF当前的大小>= base_size+ base_size*100%,且当前大小>=64mb的情况下,Redis才会对AOF进行重写。注意上面的配置文件都可以在配置文件中进行修改。

AOF优点:

丢失数据率低,易读性高;

AOF缺点:

占更过的空间,恢复备份慢,存在个别BUG,造成恢复不能。

 

官方推荐:

推荐两个都用,不建议单独使用AOF,如果只做内存缓存,可以都不用。

 

Redis的主从复制

Master以写为主,Slave以读为主,用于读写分离,备灾恢复。

如果在一台机器上进行配置多个Redis实例,则可以进行以下修改:

配置从服务器不配置主服务器;

拷贝多个redis.conf文件include,

inclue /xxx/redis.conf    // 引用主服务器的配置文件

开启daemonize yes

Pid文件名字pidfile

指定端口prot

Log文件名字

Dump.rdb名字dbfilename

Appendonly 关掉或替换名字。

eg:

include /xxx/redis.conf
pidfile /var/xx/xxx.pid
prot xxx
dbfilename dumpxxx.rdb

服务端启动时,指定其配置文件 ,客户端只需在启动的时候,指定相应的端口即可:

redis-cli -p 6399

相关命令:

info replication  //打印主从复制的相关信息
slaveof ip port  //成为某个实例的从服务器

相关问题:

当主服务器增加一些数据,而之后有增加了一些从服务器,则从服务器时从头开始复制主服务器的数据;

从服务器只能进行读操作,主服务器则进行写操作;

主机shutdown后,从机的关系不变,主机回来后,增加新的数据,从机则进行复制;

当从机shutdown后,主机增加新的数据,从机启动后,并不能获取新增的数据,因为从机的身份重新变为默认的master,如果要更新,则需要将其加入到主机的从机中,这样会从头开始复制主机的数据。可以将从机加入主机的命令写入到从机的配置文件中,这样在从机启动后,则自动变为主机的从机,就不需要手动改变。

 

复制原理:

每次从机连通,会给主机发送sync指令,主机存盘,发送RDB文件给从机,从机根据RDB文件进行全盘加载,之后,每次主机写操作,都会立刻发送给从机,从机执行相同的命令。

手动设置从机为主机:

当一个master宕机后,后面的slave可立刻升为master,qi 其后面的slave不做任何修改,使用slaveof on one将从机变为主机。

自动设置从机为主机:

使用哨兵,监视主机是否故障,如果故障,则根据投标将从机转为主机;

  1. 首先配置一主二仆模式;

  2. 在自定义目录下创建sentinel.conf文件,在文件中写入:

sentinel monitor mymaster masterip masterport 哨兵同意迁移数量,eg:
sentinel monitor mymaster 127.0.0.1 6379 1
  1. 启动哨兵

redis-sentinel /path/sentinel.conf

故障恢复:

从从服务中选择满足一下条件的从机做为主机:依次为:优先级靠前的(优先级可以在配置文件中设置)-》偏移量最大的(数据最全的)-》选择runid最小的从服务。

当主机宕机,则从机被选择为新主机,当原来的主机恢复后,会成为新主机的从机。

Redis的集群

集群可以解决读写的压力,以及内存存储大小的问题。

Redis集群,启动N个Redis节点,将整个数据分布存储在N个节点中,每个节点存储总数据的1/N。

集群的配置

1.安装

yum install ruby
yum install rubygems

2.将redis-3.2.0.gem到自定义目录,然后执行

gem install --local redis-3.2.0.gem

3.配置6个实例(为什么6个,因为必须配置3个master,每个至少有一个slave)

拷贝redis.conf,

开启daemonize yes

pid 文件名字

指定端口

log文件名

Dump.rdb名字

Appendonly 关闭

4.进行实例的cluster配置修改

cluster-enabled yes    //打开集群模式
cluster-config-file nodesxxx.conf  // 设置节点配置文件名
cluster-node-timeout 15000   //设置节点失联时间,超时的娿,则集群进行主从切换。

5.将6个节点组成一个整体

执行命令:(在redis-3.2.5/src/目录下)

./redis-trib.rb create --replicas 1  ip:port xxx xxx xxx xxx xxx

注意:这里IP地址使用外网ip地址,

 

slots:

一个Redis集群包括16384个卡槽(slot),每个K都存储在一个卡槽中。集群中每个节点负责处理一部分卡槽。eg:

A:0-5500 B: 5501-11000 C:11001 - 16384

 

cluster nodes:

该命令可以查看节点的信息。

 

集群中输入值

redis-cli中录入值,查询键值,redis会去计算该K的插槽,如果不是该实例对应的插槽,则redis报错;

redis-cli -c 实现自动重定向到K对应的插槽;

不在一个slot下的键值,不能使用mget,mset等操作;可通过{}来定义组的概念,从而使用K中{}内相同的内容的键值放到一个slot中。eg:

mset k1{q} v1 k2{q} v2 k3{q} v3

查询集群中的值 CLUSTER KEYSLOT <K> 计算键K应放在哪个槽上

CLUSTER COUNTKEYSINSLOT <slot> 返回槽目前包含的键值对数量

CLUSTER GETKEYSINSLOT <slot> <count> 返回count 个slot槽中的键。

故障恢复

如果主节点下线,则从节点上位为mater节点;

主节点恢复后,则会成为新的master节点的从节点;

如果某一段插槽的主从界定啊都宕掉,redis服务器有两种策略:1.不关心,但会出现某个数据无法找到;2.在redis.conf中的参数:cluster-require-full-coverage,即全部覆盖,只要有一个不能用,则全部不能用。

 

Redis集群的优缺点

优点:无中心配置,扩容,分压

缺点:多建操作不支持,多键的Redis事务不支持,lua脚本不支持;迁移复杂度高。

 

Java连接集群

public static void main(String args[]){
    Set<HostAndPort> set = new HashSet<HostAndPort>();
    set.add(new HostAndPort("ip",port));
    JedisCluster jedisCluster = new JedisCluster(set);
    
    jedisCluster.set("k1","v1");
    System.out.println(JedisClutster.get("k1"));
}

 

个人笔记,如有问题,敬请指出,与君共勉

全部评论

相关推荐

面试摇了我吧:啊哈哈面试提前五个小时发,点击不能参加就是放弃
点赞 评论 收藏
分享
不愿透露姓名的神秘牛友
11-20 19:57
已编辑
某大厂 golang工程师 23.0k*16.0, 2k房补,年终大概率能拿到
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务