Redis 入门教程

116 阅读27分钟

这是我参与8月更文挑战的第14天,活动详情查看:8月更文挑战

数据库的概念

存放数据的仓库

分类

关系型: 结构化存贮

举例

关系型数据库

mysql (Mariadb) Oracle DB2 Sqlserver

非关系型数据库

nosql 非结构化 场景: 缓存,粉丝数或者点赞,排行榜,打卡。。。 举例:redis === k-v 储存 Mogodb 介于关系型和非关系型直接文档形式 Hbase 大数据,列族

数据库拆分

纵向,横向

redis介绍

REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统。

Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型

数据类型

String==字符串 List ===列表(栈,队列) Hashmap==键值对 Set========无序集合,共同好友,好友推荐 Zset=======有序集合 Typeloglog==基数 Geo=======附近人,面对面建群,地图定位。。 Bitmap=====位图

e) 性能问题

i. 是单线程的,线程之间切换很浪费资源 ii. 他不是非常严格的原子性—所有的操作如果不是语法的错误(也就是说其他错误时候)没有原子性,不做回滚 iii. 有事务===乐观锁 CAS

链接java 的

String 的

import org.junit.Before;
import org.junit.Test;
import redis.clients.jedis.Jedis;

public class DEmo {
    public Jedis jedis;
    @Before
    public void createJedis(){
//        获取jedsis 放入端口号
       jedis = new Jedis("127.0.0.1", 6379);
//        放入密码
        jedis.auth("root");
    }
    @Test
    public void text1(){
//        增加
        jedis.set("username1","fengjiaojiao");
//        查询
        String username1 = jedis.get("username1");
        System.out.println("username1 = " + username1);
//        修改
        jedis.set("username1","1234");
        String username12 = jedis.get("username1");
        System.out.println("username1 = " + username12);
//        删除
        jedis.del("username1");
        String username123 = jedis.get("username1");
        System.out.println("username1 = " + username123);
    }
}

通用 的命令

dump key 序列化 在这里插入图片描述

exists key 存在返回0 不在放回1

//    通用的命令
//    通用的命令
    @Test
    public void text2(){
        Boolean username = jedis.exists("username");
//        比如:假如有一个天气预报的接口,在请求的时候可以存在redis 中 可以用可以 直接获取
        System.out.println("username = " + username);

    }

结果

username = true

过期的命令

//过期的命令
    @Test
    public void text3(){
        Long username = jedis.expire("username", 30);
        System.out.println("username = " + username);

    }

```java
    @Test
    public void text3(){
        Long username = jedis.expire("username", 30);
        System.out.println("username = " + username);
//不想过期 ,移除过期时间
        Long username1 = jedis.persist("username");
        System.out.println("username1 = " + username1);
    }
//        查看剩余的时间 如果有剩余时间的话,就返回剩余的时间,如果没有的话就返回 -1
        Long username2 = jedis.ttl("username");
        System.out.println("username2 = " + username2);
//更改名字
    @Test
    public void text4(){
        jedis.rename("username", "fengjiaojiao");
        Set<String> keys = jedis.keys("*");
        for (String key : keys) {
            System.out.println(key);
        }
//        System.out.println("rename = " + rename);
    }

禁用 一般不使用!!!

//更改名字
    @Test
    public void text4(){
//        jedis.rename("username", "fengjiaojiao");
        jedis.renamenx("fengjiaojiao","username");
        Set<String> keys = jedis.keys("*");
        for (String key : keys) {
            System.out.println(key);
        }
//        System.out.println("rename = " + rename);
    }

建议用这个

随机出现key 的

//    随机出现key
    @Test
    public void text7(){
//        随机
        String s = jedis.randomKey();
        System.out.println("我是随机的 = " + s);
    }

过期时间的

   String usernameset = jedis.setex("usernameset", 60, "77777");
        System.out.println("usernameset = " + usernameset);

自己增加的

//        增加的e
        Long usernameset11 = jedis.incr("usernameset1");
        System.out.println("usernameset11 = " + usernameset11);

定量自增加的

//        定量自增加的
        Long usernameset111 = jedis.incrBy("usernameset11", 5);
        System.out.println("usernameset111 = " + usernameset111);

自增加小数的

//        自增加 小数的
        Double usernameset1111 = jedis.incrByFloat("usernameset111", 3.12);
        System.out.println("usernameset1111 = " + usernameset1111);

自动减少的

//        自动减少的
        Long usernameset11111 = jedis.decr("usernameset1111");
        System.out.println("usernameset11111 = " + usernameset11111);

定量减少的

//        定量减少
        Long usernameset2 = jedis.decrBy("usernameset", 3);
        System.out.println("usernameset2 = " + usernameset2);

追加的的

//        追加
        Long usernameset3 = jedis.append("usernameset", "1111");
        System.out.println("usernameset3 = " + usernameset3);

list 常用的命令

//list 的
    @Test
    public void demo(){
//        增加
        jedis.lpush("feng", "左边的");
//        查看
        jedis.rpush("feng","右边的");
        //、   删除左边的
        String feng = jedis.lpop("feng");
        System.out.println("feng = " + feng);
//        删除右边的
        String feng1 = jedis.rpop("feng");
        System.out.println("feng1 = " + feng1);
//        删除中间的值
//        count >0 从表头向尾部检索,移除与value 相等的元素 数量为count
//         count <0 从表尾向表头检索,移除与value 相等的元素 数量为count
//         count =0 移除与value 相等的元素
        Long lrem = jedis.lrem("feng", 3, "jiaojiao");
        System.out.println("lrem = " + lrem);
//        删除两边的
        String feng2 = jedis.ltrim("feng", 0, 1);
        System.out.println("feng2 = " + feng2);
//        查询指定的 lindex
        String feng3 = jedis.lindex("feng", 0);
        System.out.println("feng3 = " + feng3);
//        修改  lset 通过索引设置列表的元素
        jedis.lset("feng",0,"cccc");
//长度
        Long feng4 = jedis.llen("feng");
        System.out.println("feng4 = " + feng4);
        List<String> list = jedis.lrange("feng", 0, -1);
        System.out.println(list);

    }

set的常用命令

//set 的
    @Test
    public void demo2(){
//        增加sadd"
        jedis.sadd("hh","111","222","333","444","555");
//        获取总元素 scard
        Long hh = jedis.scard("hh");
        System.out.println("hh = " + hh);
//        查全部的
        Set<String> set = jedis.smembers("hh");
        System.out.println("set = " + set);
//        不可以根据索引查单个
//        遍历迭代
        ScanResult<String> hh1 = jedis.sscan("hh", "0");
//        System.out.println("hh1 = " + hh1);
        for (String s : set) {
            System.out.println("s = " + s);
        }
//        指定的删除
        Long hh2 = jedis.srem("hh", "222");
        System.out.println("hh2 = " + hh2);
        //    随机删除
        String hh3 = jedis.spop("hh");
        System.out.println("hh3 = " + hh3);
//        返回随机值 不会删除
        String hh4 = jedis.srandmember("hh");
        System.out.println("hh4 = " + hh4);
/        判断成员是否在集合中
        Boolean hh5 = jedis.sismember("hh", "222");
        System.out.println("hh5 = " + hh5);    
}

set 的重要命令

并集

@Test
    public void demo3(){
//        并集 sunion  key 1  和 key2
    jedis.sadd("ee","11","22","33","44","55");
    Set<String> sunion = jedis.sunion("hh", "ee");
    System.out.println("sunion = " + sunion);


}

结果

sunion = [11, 22, 33, 44, 55, 111, 333, 444]

差集

@Test
    public void demo3(){
//        并集 sunion  key 1  和 key2
//    jedis.sadd("ee","111","22","33","44","55");
//差集 SDIFF key1 key2  第一个key 为准
    Set<String> sdiff = jedis.sdiff("hh", "ee");
    System.out.println("sdiff = " + sdiff);


}

结果 sdiff = [444, 111, 333]] 交集

@Test
    public void demo3(){
//        并集 sunion  key 1  和 key2
//    jedis.sadd("ee","111","22","33","44","55");
//差集 SDIFF key1 key2  第一个key 为准
//    Set<String> sdiff = jedis.sdiff("hh", "ee");
//    System.out.println("sdiff = " + sdiff);
//交集 微博 共同的好友 sinter 交集
    Set<String> sinter = jedis.sinter("hh", "ee");
    System.out.println("sinter = " + sinter);
//    交集存储  sinterstore
    Long sinterstore = jedis.sinterstore("hh", "ee");
    System.out.println("sinterstore = " + sinterstore);
    
}

zset 的

//有序集合
    @Test
//    set 集合的升级版 可以有序 但是不重复
//    每个都有一个double 类型的分数 通过这个排序
    public void demo4(){
//        增加
        jedis.zadd("jj",1,"jiaojiao1");
        jedis.zadd("jj",2,"jiaojiao2");
//查看
        Set<String> set = jedis.zrange("jj", 0, -1);
        System.out.println("set = " + set);
//        条数
        Long jj = jedis.zcard("jj");
        System.out.println("jj = " + jj);
//        按分数区间查数量
        Long jj1 = jedis.zcount("jj", 1, 2);
        System.out.println("jj1 = " + jj1);
//自增加分值
        Double zincrby = jedis.zincrby("jj", 1, "jiaojiao1");
        System.out.println("zincrby = " + zincrby);
//        删除
        jedis.zrem("jj","jiaojiao1");
        //查看
        Set<String> set1 = jedis.zrange("jj", 0, -1);
        System.out.println("set = " + set1);

        
    }

后期学习补出来的 String 命令

127.0.0.1:6379> FLUSHdb   # 清空表
OK
127.0.0.1:6379> set name "fengjiaojiao,haokan"   # 设置键
OK
127.0.0.1:6379> GETRANGE name 0 3   #获取指定的长度
"feng"
127.0.0.1:6379> GETRANGE name 0 -1  # 获取全部的,相当于是get key 了
"fengjiaojiao,haokan"
127.0.0.1:6379> set key1 fjjisverygood   #设置一个键
OK
127.0.0.1:6379> SETRANGE key1 3 xx   #设置要替换的值
(integer) 13
127.0.0.1:6379> get key1 #查看
"fjjxxverygood"
127.0.0.1:6379> setex key1 30 "fjj"  #设置过期时间
OK
127.0.0.1:6379> ttl key1  #查看还有多久过期
(integer) 25
127.0.0.1:6379> setnx mykey "love"  # 如果不存在就创建成功!
(integer) 1
127.0.0.1:6379> keys *
1) "mykey"
2) "key1"
127.0.0.1:6379> ttl key1 # -2 已经过期了
(integer) -2
127.0.0.1:6379> setnx mykey "love1" # 此时存在了就创建失败了,而不会覆盖
(integer) 0
127.0.0.1:6379> get mykey
"love"
127.0.0.1:6379> mset k1 v1 k2 v2  #同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k2"
2) "k1"
127.0.0.1:6379> mget k1 k2 #同时获取多个值
1) "v1"
2) "v2"
127.0.0.1:6379> msetnx k1 v1 k3 v3  #跟原子性差不多,有一个不能成功都不成功!
(integer) 0
127.0.0.1:6379> get k3
(nil)        
127.0.0.1:6379> mset user:1:name zhaangsan user:1:age 18  # 通过对象的形式设置
OK
127.0.0.1:6379> mget user:1:name user:1:age #同时获取对象里的数据
1) "zhaangsan"
2) "18"   
127.0.0.1:6379> getset db redis  #先查看在设置不存在放回nil
(nil)
127.0.0.1:6379> get db  
"redis"
127.0.0.1:6379> getset db fjj  #先查看后设置
"redis"
127.0.0.1:6379> get db
"fjj"
###################String结束#############################################                                                                                                                                                                         

List 开始

127.0.0.1:6379> lpush list one   # 从左边添加值
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1  #查看所有的值
1) "three"
2) "two"
3) "one"

127.0.0.1:6379> lrange list 0 1 #查看指定区间的值
1) "three"
2) "two"
127.0.0.1:6379> rpush list right #从右侧添加值
(integer) 4
127.0.0.1:6379> lrange list 0 -1  #再次查看的时候,发现右侧添加的在最下面
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> lpop list  # 从左侧删除
"three"
127.0.0.1:6379> rpop list  #从右侧删除
"one"
127.0.0.1:6379> lrange list 0 -1
1) "two"
127.0.0.1:6379> lindex list 0  #下标索引获取
"two"
127.0.0.1:6379> lrem list 0 two  #删除指定的
(integer) 1
127.0.0.1:6379> lrange list 0 -1
(empty list or set)
127.0.0.1:6379> lpush mylist f
(integer) 1
127.0.0.1:6379> lpush mylist j
(integer) 2
127.0.0.1:6379> lpush mylist j
(integer) 3
127.0.0.1:6379> lpush mylist l
(integer) 4
127.0.0.1:6379> lpush mylist love
(integer) 5
127.0.0.1:6379> lpush mylist you
(integer) 6
127.0.0.1:6379> ltrim mylist 1 2  #通过下标截取指定的长度,这个list 已经被改变了截断了只剩下截取的元素
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "love"
2) "l"
127.0.0.1:6379> rpoplpush mylist myotherlist  #移除列表的最后一个元素,将他移动到新的列表中
"l"
127.0.0.1:6379> lrange mylist 0 -1  #查看原来的列表
1) "love"
127.0.0.1:6379> lrange myotherlist 0 -1 #查看目标的列表中,有我们的值
1) "l"
127.0.0.1:6379> exists list  #  检查是否报错
(integer) 0
127.0.0.1:6379> lset list 0 item  #如果不存在就会报错
(error) ERR no such key
127.0.0.1:6379> lpush list value
(integer) 1
127.0.0.1:6379> lrange list 0 0
1) "value"
127.0.0.1:6379> lset list 0 item  #如果存在,更新当前的下标的值
127.0.0.1:6379> lrange list 0 0
1) "item"
127.0.0.1:6379> lset list 1 other #如果不存在,就报错
(error) ERR index out of range     
##################linsert 使用
127.0.0.1:6379> rpush mylist hello
(integer) 1
127.0.0.1:6379> rpush mylist world
(integer) 2
127.0.0.1:6379> linsert mylist before world other  #在world之前加上other
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "other"
3) "world"
127.0.0.1:6379> linsert mylist after world other
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "other"
3) "world"
4) "other"     
######################List 结束                                                                                                

Set

127.0.0.1:6379> sadd myset hello  # 往set 里添加元素
(integer) 1
127.0.0.1:6379> sadd myset fjj
(integer) 1
127.0.0.1:6379> sadd myset love
(integer) 1
127.0.0.1:6379> sadd myset hh
(integer) 1
127.0.0.1:6379> smembers myset # 查看set 元素里的值
1) "hh"
2) "love"
3) "fjj"
4) "hello"
127.0.0.1:6379> sismember myset hello  #检查元素里有没有这个元素
(integer) 1
127.0.0.1:6379> sismember myset hellss
(integer) 0
127.0.0.1:6379> scard myset #查看一共有多少的元素个数
(integer) 4
127.0.0.1:6379> srem myset hello  #随机移除一个元素
(integer) 1
127.0.0.1:6379> smembers myset
1) "hh"
2) "love"
3) "fjj"

127.0.0.1:6379> srandmember myset #随机出来一个 元素
"hh"


127.0.0.1:6379> srandmember myset 2 #随机出来指定的几个元素
1) "love"
2) "hh"
127.0.0.1:6379> sadd myset one  #添加元素
(integer) 1
127.0.0.1:6379> sadd myset two
(integer) 1
127.0.0.1:6379> sadd myset three
(integer) 1
127.0.0.1:6379> spop myset  #随机删除一个元素
"three"
127.0.0.1:6379> smembers myset
1) "two"
2) "one"
127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset2 set2
(integer) 1
127.0.0.1:6379> smove myset myset2 one  #移动元素到新的set 集合中
(integer) 1
127.0.0.1:6379> smembers myset
1) "hello"
2) "two"
127.0.0.1:6379> smembers myset2
1) "set2"
2) "one"
SDIFF #差集
SINTER #交集
SUNION #并集
###################################################SET 结束

hash 开始

127.0.0.1:6379> hset myhash fjj1 jiaojiao  #设置值
(integer) 1
127.0.0.1:6379> hget myhash fjj1  #获取值
"jiaojiao"
127.0.0.1:6379> hmset myhash key1 hello key2 world # 设置多个值
OK
127.0.0.1:6379> hmget myhash key1 key2  #获取多个值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash #获取全部的值
1) "fjj1"
2) "jiaojiao"
3) "key1"
4) "hello"
5) "key2"
6) "world"
127.0.0.1:6379> hdel myhash key1 #删除指定的值
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "fjj1"
2) "jiaojiao"
3) "key2"
4) "world"
#####hash 其他的跟String 差不多。先结束吧

zset

127.0.0.1:6379> zadd myzset 1 one   #添加值
(integer) 1
127.0.0.1:6379> zadd myzset 2 two 3 three  #添加多个值
(integer) 2
127.0.0.1:6379> zrange myzset 0 -1  #遍历查看所有
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> zadd grade 99 jiaojiao #添加3个成绩
(integer) 1
127.0.0.1:6379> zadd grade 0  hh
(integer) 1
127.0.0.1:6379> zadd grade 66  zyh
(integer) 1
127.0.0.1:6379> zrangebyscore grade -inf +inf #排序 从小到大
1) "hh"
2) "zyh"
3) "jiaojiao"

127.0.0.1:6379> zrangebyscore grade -inf +inf withscores #排序带结果
1) "hh"
2) "0"
3) "zyh"
4) "66"
5) "jiaojiao"
6) "99"
127.0.0.1:6379> zrangebyscore grade -inf 80 withscores #按条件,筛选出来不符合的!
1) "hh"
2) "0"
3) "zyh"
4) "66"
127.0.0.1:6379> zrevrange grade 0 -1 #从大到小的排序
1) "jiaojiao"
2) "zyh"
3) "hh"
127.0.0.1:6379> zrange grade 0 -1
1) "hh"
2) "zyh"
3) "jiaojiao"
127.0.0.1:6379> zrem grade hh  #移除某个元素
(integer) 1
127.0.0.1:6379> zrange grade 0 -1
1) "zyh"
2) "jiaojiao"
127.0.0.1:6379> zcard grade  #查看一共有多少个元素
(integer) 2
127.0.0.1:6379> zcount grade 0 1 #区间总数 
(integer) 0
127.0.0.1:6379> 

三种特殊数据类型 geospatial 地理位置

#添加一些城市
127.0.0.1:6379> geoadd china:city 113.6401 34.72468 zhengzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 113.81908 36.08308 linzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 111.59244 26.46098 hh
(integer) 1
127.0.0.1:6379> geoadd china:city 113.88308 22.55329 shenzhen
(integer) 1
#查看单个城市的经纬度
127.0.0.1:6379> geopos china:city zhengzhou
1) 1) "113.64010244607925415"
   2) "34.72467993544626808"
   #也可以看多个
127.0.0.1:6379> geopos china:city zhengzhou linzhou
1) 1) "113.64010244607925415"
   2) "34.72467993544626808"
2) 1) "113.8190808892250061"
   2) "36.08308009499572222"
#计算出来两个城市的距离
127.0.0.1:6379> geodist china:city linzhou zhengzhou km
"151.9585"
#给一个经纬度然后出来附近多少km 的地方
 127.0.0.1:6379> GEORADIUS china:city 110 40  1000 km
1) "zhengzhou"
2) "linzhou"
3) "beijin"
#筛选出然后指定的出来几个城市
127.0.0.1:6379> GEORADIUS china:city 110 40  1000 km withdist withcoord count 2
1) 1) "beijin"
   2) "545.3578"
   3) 1) "116.39712899923324585"
      2) "39.91652647362980844"
2) 1) "linzhou"
   2) "549.1660"
   3) 1) "113.8190808892250061"
      2) "36.08308009499572222"
      #从我们一共添加的城市中来筛选符合范围的
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijin 1000 km
1) "zhengzhou"
2) "linzhou"
3) "beijin"   

其实这个的底层是zset 我们可以通过zset 的命令来删除我们添加的元素

127.0.0.1:6379> zrange china:city 0 -1
1) "hh"
2) "shenzhen"
3) "zhengzhou"
4) "linzhou"
5) "beijin"
127.0.0.1:6379> zrem china:city hh
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "shenzhen"
2) "zhengzhou"
3) "linzhou"
4) "beijin"

**hyperloglog ** redis 的hyperloglog 基数统计的算法 优点:占用的内存是固定的。只需要废12kb 内存!如果要从内存角度来比较的话,这个是首选。 像做网页的uv (一个人多次访问,算一个人的!) 传统的方式,set 方式保存用户id ,然后统计set 中的元素数量作为标准判断 这个方式如果保存大量的用户id 就会比较麻烦!我们的目的为了计数,而不是保存用户id

127.0.0.1:6379> pfadd mykey q w e r t y u i o p  #设置一个hyperloglog 的key
(integer) 1
127.0.0.1:6379> pfcount mykey  #查看 key
(integer) 10
127.0.0.1:6379> pfadd mykey1 a t g y h u s j k f i l #在设置一个hyperloglog 的key
(integer) 1
127.0.0.1:6379> pfmerge mykey2 mykey mykey1 #合并求出来交集
OK
127.0.0.1:6379> pfcount mykey2
(integer) 17

bitmap 位图 在这里插入图片描述 测试代码

127.0.0.1:6379> setbit sign 0 1  #添加数据到 bitmap 里面
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 1
(integer) 0
127.0.0.1:6379> setbit sign 3 0
(integer) 0
127.0.0.1:6379> setbit sign 4 0
(integer) 0

127.0.0.1:6379> getbit sign 3  #查看指定的天数
(integer) 0
127.0.0.1:6379> getbit sign 1
(integer) 1
127.0.0.1:6379> bitcount sign #求总的天数
(integer) 3

事务 redis 事务的本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行的过程中,回按照顺序执行@ ! 次性,顺序性,排他性,执行一系列的命令

注意:redis 事务没有隔离级别 所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会执行! Exec redis 单条命令式保存原子性,但是事务不保证原子性! redis 的事务 1,开启事务(multi) 2,命令入队 3,执行事务(exec)

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set f feng
QUEUED
127.0.0.1:6379> set j jiao
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
127.0.0.1:6379>         
#放弃事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key 1
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get key
(nil)
127.0.0.1:6379>    
#编译型异常(代码有问题),事务中的所有命令都不会执行
  127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> getset k1
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set key2 v2
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.  #直接就失败了
#运行时异常,如果存在语法性,那么执行命令的时候,其他命令式可以正常执行的,错误命令抛出异常
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key "cccc"
QUEUED
127.0.0.1:6379> incr key
QUEUED
127.0.0.1:6379> set key1 kk
QUEUED
127.0.0.1:6379> get key1
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
4) "kk"
                                                                                                                       
                                                                                                                                                                                                                                                                                                                                                             

监控

在mysql 的时候我们与乐观锁和悲观锁 悲观锁顾名思义很悲观,认为hi一直出现问题。做什么都加锁 乐观锁,乐观,认为什么时候都没有问题,所以不会上锁,更新数据的时候去判断一下,在此期间是否有人修改一下数据

#单线程正常的情况
127.0.0.1:6379> set money 1000
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec
1) (integer) 990
2) (integer) 10
127.0.0.1:6379>  

在这里插入图片描述

持久化的两种方式

方式一

RDB 在这里插入图片描述 在指定的时间间隔内存中的数据集快照写入磁盘,也就是snapshot 快照,他恢复时是将快照文件直接读到内存里,redis 会单独创建(fork) 一个子进程来进行持久化,会先将数据写入到一个临时文件中,带持久化过程都结束了,再用这个临时文件代替上次持久化好的文件,整个过程中,主进程是不进行任何io 操作的,这就确保了极高的性能,如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,用RDB 合适而且高效。缺点就是最后一次持久化的数据容易丢失。

默认保存的文件是dump.rdb 都是在配置文件中进行配置的

触发的机制

1,sava 的规则满足情况下,会触发rdb 规则 在这里插入图片描述 (每900s 写入的数据) 2,执行flushall 命令,会触发我们的rdb 规则 3,退出redis 也会产生rdb 文件

备份就会自动生产一个dump.db

如果恢复rdb 文件

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

127.0.0.1:6379> config get dir
1) "dir"
2) "C:\\Users\\\xbf\xc9\xb0\xae\xb5\xc4\xd0\xa1\xcf\xc9\xc5\xae\\Desktop\\Redis-x64-5.0.10"  #如果这个剥下没有dump.rdb 文件,启动会自动恢复,我没配置liunx 的所以是本地的
127.0.0.1:6379>

默认的他的配置是够用的

优点

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

缺点

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

AOF

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

aof 保存的是appendonly.aof

重写规则 aof 默认就是文件的无限追加,文件越来越大

优点和缺点

在这里插入图片描述 优点 1,每一次修改都同步,文件的完整会更加好! 2,每秒同步一次,可能会丢失一秒的数据 3,从不同步,效率最高的! 缺点 1相对于数据文件来说,aof 远远大于rdb 修复的速度也比rdb 慢 2,aof 运行效率也要比rdb 慢,所以我们默认的是rdb

持久化的总结

1、RDB持久化方式能够在指定的时间间隔内对你的数据进行快照存储 2、AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据, AOF命令以Redis协 议追加保存每次写的操作到文件末尾, Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。 3、只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化 4、同时开启两种持久化方式 ●在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB 文件保存的数据集要完整。 ●RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者建议不要,因为RDB更适合 用于备份数据库( AOF在不断变化不好备份) , 快速重启,而且不会有AOF可能潜在的Bug ,留着作为- -个万- -的手段。 5、性能建议 ●因为RDB文件只用作后备用途,建议只在Slave.上持久化RDB文件,而且只要15分钟备份一 次就够了,只保留save 900 1这条 规则。 ●如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价 一是带来 了持续的I0 ,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要 硬盘许可,应该尽量减少AOF rewrite的频率, AOF重写的基础大小默认值64M太了,可以设到5G以上,默认超过原大小 100%大小重写可以改到适当的数值。 ●.如果不Enable AOF,仅 靠Master-Slave Repllcation实现高可用性也可以,能省掉一 大等10. 也减少 了rewrite时带来的系统 用于备份数据库( AOF在不断变化不好备份) , 快速重启,而且不会有AOF可能潜在的Bug ,留着作为- -个万- -的手段。 5、性能建议 ●因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份- -次就够了,只保留save 9001这条 规则。 ●如果Enable AOF, 好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价 一是带来了持续的I0 ,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要 硬盘许可,应该尽量减少AOF rewrite的频率, AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小 100%大小重写可以改到适当的数值。 ●如果不Enable AOF,仅靠 Master-Slave Repllcation实现高可用性也可以,能省掉一大笔I0 ,也减少了rewrite时带来的系统 波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载 入较新的那个,微博就是这种架构。

Redis 发布订阅

在这里插入图片描述

测试代码

订阅者

127.0.0.1:6379> subscribe fengjiaojiao  #关注  公众号
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "fengjiaojiao"
3) (integer) 1
1) "message"
2) "fengjiaojiao"
3) "love"
1) "message"
2) "fengjiaojiao"
3) "love"

订阅者

127.0.0.1:6379> ping
PONG
127.0.0.1:6379> publish fengjiaojiao "love"  #发布消息
(integer) 1
127.0.0.1:6379> publish fengjiaojiao love
(integer) 1
127.0.0.1:6379>   

发布订阅的原理

redis 是使用c实现的,看源码发现是通过 PUBLISH, SUBSCRIBE,PSUBSCRIBE 等命令实现发布和订阅功能的 1,通过SUBSCRIBE 命令订阅某频道后,redis-server 里面维护了一个字典,字典的键就是一个一个channel 而字典的值就是一个链表,链表中保存了所有订阅这个channel 的客户端,SUBSCRIBE 命令的关键,就是将客户端添加到给定channel 的订阅链表中,通过publish 命令向订阅者发送消息,redis-server 会使用给定的频道作为键,在它所维护的channel 字典中查找记录订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有的订阅者 pub /sub 从字面上理解就是发布与订阅。在redis 中。可以设定对某一个key 值进行消息发布及消息订阅,当一个key 值上进行消息发布后,所有订阅它的客户端会收到相应的消息。

主从复制

概念 主从复制,是指将一台redis 服务器的数据,复制到其他的redis 服务器,前者称为主节点,后者称为从节点;数据复制是单向的,只能由主节点到从节点,master 以写为主,slave 以读为主 默认情况下,每台redis 服务器都是主节点,且一个主节点可以有多个从节点(或者没有) 但一个从节点只能有一个主节点 主从复制的作用: 1,数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式 2,故障修复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障修复;实际上时一种服务的冗余 3,负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(写数据redis 数据时应用连接主节点,读redis 数据时应用连接从节点)分担服务器负载,尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高redis 服务器的并发量 4,高可用基石:除了上述作用外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是redis 高可用的基础

只使用一台redis 是万万不能的因为: 1,从结构上,单个redis 服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大 2,从容量上,单个redis 服务器内存容量有限,就算一台redis 服务器内容容量为256G ,也不能将所有内存作为redis 存储内存,一般来说,单台redis 最大使用的内存不应该超过20G 在这里插入图片描述 主从复制,读写分离!80% 的情况下都是进行读的操作,为了减缓服务器的压力,架构中经常使用,一主二从,

测试:情况

1,当主机断开连接,从机依旧连接到主机的,但是没有写操作,这个时候,主机如果回来,从机依旧可以直接获取到主机写的信息! 如果使用命令 行,来配置的主从,这个时候如果重启了,就会变成从机,立马机会从主机获取值

复制的原理

slave 启动成功连接到master 后发送一个syn 命令 master 接到命令,启动后台的存盘进程,同时手机所有的接收到的有用于修改数据集命令,在后台进程执行完毕后,master 将传送整个数据文件到slave 并完成一次完全同步

全量复制

而slave 服务在接收到数据库文件后,将其存盘并加载到内存中

增量复制 master 继续将新的所有收集到修改命令依次传给slave 完成同步 但是只要是重新连接master 一次完成同步(全量复制)将被自动执行

哨兵模式

主从切换技术方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用,这不是一种推荐的方式,所以我们会考虑哨兵模式。能够后台监控主机是否故障,如果故障了根据投票自动将从库转换为主库

哨兵模式是一种特殊的模式,首先redis 提供了哨兵的命令,哨兵是一个独立进程,作为进程他会独立运行原理是哨兵通过发送命令,等待redis 服务器响应,从而监控运行多个redis 实例 这里哨兵有两个作用 通过发送命令,让redis 服务器返回监控运行的状态,包括主服务器和从服务器 当哨兵监测到master 宕机,会自动将slave 切换成master 然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机 然而一个哨兵进程对redis 服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控,各个哨兵之间还会进行监控,这样就形成了多哨兵模式 假设主服务宕机,哨兵1 先检测到这个结果,系统并不会马上进行failover 过程,仅仅是哨兵1主管认为主服务器不可以了,这个现象成为主观下线,当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行投票,切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控从服务器实现切换主机,这个过程称为客观下线 如果主机回来了,只能归并到新的主机下,当作从机,这就是哨兵模式的规则

优点 1,哨兵集群,基于主从复制模式,所有的主从配置优点, 2,主从可以切换,故障可以转移,系统的可用性就会更好 3,哨兵模式就是主从模式的升级,手动到自动,更加健壮 缺点 1,redis 不好在线扩容,集群容量一旦到达上限,在线扩容就十分麻烦!

缓存穿透

概念 缓存穿透概念很简单,用户想要查询一个数据,发现redis 内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是再次查询失败,当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库,这会给数据层数据库造成很大的压力,这时候就是缓存穿透

解决方案

1,布隆过滤器是一种数据结构,对所有可能查询的参数以hash ,在控制层先进行检验,不符合则丢弃,从而避免了对底层存储的查询的压力 2,缓存空对象当储存层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源

缓存击穿

需要注意和缓存击穿的区别,缓存击穿,是指一个key 非常热点,再不停的扛这大并发,大并发集中对这一个点进行访问,当这个key 在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞 当某个key 在过期的瞬间,有大量的请求并发访问,这类数据一般都是热点数据,由于缓存过期,会同时访问数据库来查询最新数据并且回写缓存,会导致数据库瞬间压力过大

解决方案

1,设置热点数据永不过期,从缓存层面来看,没有设置过期时间,所以不会出现热点key 过期后产生的问题 2,加互斥锁 分布式锁:使用分布式锁,保证对每个key 同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可,这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大

缓存雪崩

缓存雪崩,是指在某一个时间段,缓存集中过期失效 产生雪崩的原因之一,比如在写文本的时候,马上就要到双十二零点,很快就会迎来抢购,这些商品时间比较集中的放入缓存,假设缓存一个小时,那么到了凌晨一点钟的时候,这批商品的缓存就都过期了,而对这批商品的访问查询,都落到了 数据库上,对于数据库而言,就会产生,周期性的压力波峰,于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂了,其实集中过期,不足以致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网,因为自然形成的缓存雪崩,一定是在某个时间端集中创建缓存,这个时候,数据库也是可以顶住压力的,无非就是对数据库产生周期性的压力而已,而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮

解决方法

1,redis 高可用我们可以多来几个redis 搭建集群 2,限流降级,这个解决方案的思想,在缓存失效后,通过加锁或者队列读数据库些缓存的线程数量,比如对某个key 只允许一个线程查询数据和写缓存,其他线程等待 3,数据预热,数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存,在即将发生并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效时间尽量均匀。