Redis

2021-11-19  本文已影响0人  Exception_Cui

安装使用

    安装:     https://www.runoob.com/redis/redis-install.html (windos 和linux 都有)
    中文官网   http://www.redis.cn
    Redis底层使用的是 单线程 + 多路IO复用技术
    操作中不会被打断
    
    Redis默认端口 6379
    
    Redis 是基于 key -value 存储的
服务 开启 和 关闭
    修改  redis.conf 里面 daemonize 为yes
    
    ./redis-server redis.conf  启动
    ./redis-cli shutdown  关闭
    
    ps aux| grep redis 查看redis的pid
    kill -9 ***(pid)   杀死id

key 操作

    keys *       查看当前库的所有的key
              
    exists key   判断key是否存在   1存在    0不存在
    type   key   查看key类型
    
    del    key   删除key指定的数据  1删除成功
    unlink key   删除key(非阻塞删除,后续异步删除)
    
    //时效 失效 
    expire key   10 给指定key设置过期时间 单位秒 (过期就删除)
    ttl    key    查看key还有多少秒过期  -1 永不过期 -2已经过期
    
    dbsize       查看key数量

    select       选择库
    
    flushdb      清空当前库(慎用)
    flushall     通杀全部库(慎用)

Redis 数据类型

string(字符串)

最多可以存512M (图片也可以转换为2进制来存储)
基本命令:
         key        value
set       k1        v100          设置值 (设置相同key 会覆盖 value)
get       k1                      取值  = v100 
append    k1        abc           取值  = v100abc
strlen    k1                      值长度  7 
    
setnx     k3        123           k3 不存在才设置
     
incr      k3                      k3 =124  增加 1 (数字类型)
decr      k3                      k3 =123  减 1  
incrby    k3        10            k3 =133  增加10
decrby    k3        10            k3 =123  减 10  
    
mset      k1 v1 k2 v2 k3 v3      设置多个k-v
mget      k1 k2 k3                取多个key的值
    

msetnx                            同setnx 可以设置多个,都不存在设置
    

set       k5       123456
getrange  k5   0  3               值为1234 和 string.substring()一样 1 23456取0-3的值    
setrange  k5   3  aa              值 123 aa 456  在第三个位置插入
                                     
//时效 失效    
setex     k6  10 v5               设置值的同时 设置过期时间   k6 10秒后过期 
数据结构
底层类似于Stringbuffer  动态分配空间,第一次如果分配了100k,超过了就在分配 100k,
如果超过了 1M 每次都只分配1M 最大为512M

list(列表)

list 存储的是单键多
如: k1 = v1 v2 v3 
值按照的插入顺序排序
基本操作
          key        value      
lpush      k1      v1  v2  v3         左增加     值为 v3 v2 v1  向左追加
rpush      k2      v11 v22 v33        右增加     值为 v11 v22 v33  向右追加
    
lpop       k1                         左弹出(取出删除) 值为 v1   k2值为v2 v3     
rpop       k1                         左弹出(取出删除) 值为 v3   k2值为v2
                                           当 k2 值 取完了,k2也就不存在
                            
rpoplpush  k2  k1                      值为 v33 v1 v2 v3
                                           从左边key取值插入到右边key
                            
lrange     k1  0 -1                   值为 v1 v2 v3  取出全部的值 
    
lindex     k2  0                      值为 v11 取出下标为0 的值(不删除)
llen       k2                         列表长度 值为3
    
linsert    k2 before v22  leftV22     在 v22 之前插入 leftV22
              after  v22  rightV22    在 v22 之后插入 rightV22
    
lrem       k2   2  leftv22            从左边开始删除2个值为 leftv22 的数据
    
lset       k2   1  newV11             替换下标为1的值为 newV11   
数据结构
底层是双向链表 如 一个⚪ 形
对两端的操作性能高,通过索引很慢,如linkedLisd

set(集合 )

和list区别
    set是无序的,自动排除重复的,底层是hash表,如果不需要重复数据,就用set
用hash的 field 来作为value  hash的value 为null
基本操作
            key          value
sadd         k1          v1 v2 v3             增加k1 值为 v1 v2 v3 
srem         k1          v2 v3                删除 k1 里面的v2和v3,删除集合中的某个元素
smembers     k1                               取出k1 的值
sismember    k1          v1                   表示k1是否存在 v1值       有1 没有0
scard        k1                               返回 k1 值的数量
spop         k1                               随机取出一个值,并且在原k 删除,k值取完,就删除key
srandmember  k1          2                    随机从集合取出n个值,不会从集合删除  
smove        k1 k2       v1                   将k1的v1值 移动到 k2

sinter       k1 k2                            集合交集
sunion       k1 k2                            集合并集

hash(哈希)

类似于java中的map<String,Object>
key        value 
user       id     1
user       name zhangsan
特别适合存储对象                          
    第一种:序列化 或者 用json存储对象 如 user  {id=2,name=zhangsan,age=20}  String类型的
                                          
                                          key   value(是  field value的方式)
    第二种:使用 hash                     user      id    2
                                          user      name  zhangsan
                                          user      age   20
基本操作
               key         value( field value)         
hset           user               id    1                 
               user               name  zhangsan                   增加 user 值为 id 1, name 张三的value

hget           user               id                               取出 user 中 field 为 id 的值   为 1    

hmset          user1            id 1 name lisi age 20              增加 user1的值 id  1 ,name lisi,age 20

hexists        user                id                               查询 user 的id 是否存在?  1存在 0不存在

hkeys          user                                                查询 集合的所有 field ,此处值为 id  name
hvals          user                                                查询 集合中 的所有value

hsetnx                                                             field 存在不添加,不存在添加                                                

zset(有序集合)

和 set 基本一样,不过是有顺序的
基本操作
                       key          value(score  value)
zadd                   k1            1 java   3 mysql  2 c++                 添加元素 到有序列表中,(排序根据score排序)


            
zrange                 k1    0  -1                                            取出元素值 为  java  c++   mysql 已经排序了
zrange                 k1    0  -1 whithscores                                取出元素值 也显示 scores 值为  java 1 c++ 2 mysql 3
        
zrangebyscore          k1    1   3                                            从小到大取出score (1到3) 的值 java c++ ky
                                                                 
zrevrangebyscore       k1    1   3                                            从大到小取出score  

zubcrby                k1    50   java                                        将java的score 增加 50 现在就是 51

zrem                   k1    java                                             删除集合中的java

zcount                 k1    1    50                                          统计score (1-50) 的个数

zrank                  k1    c++                                              查询 c++ 的排名
数据结构
  跳跃表数据结构
    二分查找  + 索引 的方式
    

redis 的详细配置

注意:启动要以配置文件方式启动 ./redis-server redis.conf

redis.conf

port 6379               默认端口设置

bind 127.0.0.1          可以访问redis的 ip 地址,默认只有本机,注掉就可以全部访问
protected-mode no       保护模式,默认没有密码的情况下,只有本机可以访问,注掉没有密码的情况 其他地址也可以访问

tcp-backlog 511         tcp 握手的次数

timeout 0               表示redis的超时时间,0永不超时, 就说说 redis在没有地址访问的时候,过了多久,需要重新开启访问
tcp-keepalive 0         就是用来定时向client发送tcp_ack包来探测client是否存活的。默认不探测,官方建议值为60秒 
 
daemonize no            后台启动redis, 默认不允许,改为 yes 允许

pidfile /var/run/redis.pid   当redis在后台运行的时候,Redis默认会把pid文件放在/var/run/redis.pid,你可以配置到其他地址。当运行多个redis服务时,需要指定不同的pid文件和端口

loglevel                redis 日志的级别
logfile "Logs/redis_log.txt"  log输出的文件路径

databases 16            redis 默认有16个库,我只默认使用的是0 号库

# requirepass foobared  密码设置,默认没有密码
                        也可以通过 config 

redis 新数据类型

bitmaps (位操作)

hyperLogLog(基数 计数)

     快速计算不重复元素     

    ![image-20210917151023086](https://gitee.com/exception_cui/images/raw/master/202109171510679.png)
基本使用:
pfadd  k1  java  php  java c++ mysql c++
pfcount  k1       统计基数的数量  为 4 

Geospatial(经纬度)

Jedis 操作redis

快速入门

1.引入pom文件

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

2.连接测试

public class JedisDemo1 {
    public static void main(String[] args) {
        Jedis jedis=new Jedis("127.0.0.1", Integer.parseInt("6379"));  //这里使用的是windows
        String ping = jedis.ping();
        System.out.println(ping);   //PONG 就连接成功
    }
}
Linux连接不上 请查看redis.conf

3.操作jedis

jedis 操作(其他的请查看api)https://blog.csdn.net/zhangguanghui002/article/details/78770071

    @Test
    public void demo1() {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        Set<String> keys = jedis.keys("*");    //keys *

        jedis.set("name", "luck");    //set

        jedis.mset("k1","v1","k2","v2");  //mset

        System.out.println("name" + jedis.get("name"));  //get

        Boolean name = jedis.exists("name");  //key 是否存在

        jedis.expire("name", 10000);  //expire key
        System.out.println("失效时间" + jedis.ttl("name"));

        jedis.set("age", "20");
        jedis.incr("age"); //增加

        jedis.incrBy("age", 10);
        System.out.println("age" + jedis.get("age"));

        jedis.setnx("haha","test");
        
        jedis.close();//断开连接  有池的话,就是归还      
        
    }
模拟验证码操作

需求:

案例分析

public class CodeDemo {
    static String keyCode = "";
    static String keyCount = "";
    public static void main(String[] args) {
        System.out.println("请输入手机号:");
        Scanner scanner = new Scanner(System.in);
        String next = scanner.next();
        if (!sendCode(next)){
            return ;
        }
        String sendCode=getSendCode();
        System.out.println("已经向:" + next + "     发送了验证码:" +sendCode);

        System.out.println("请输入您的验证码:");
        String nextcode = scanner.next();
        if(nextcode.equals(sendCode) || nextcode==sendCode){
            System.out.println("验证成功!");
        }else{
            System.out.println("失败!");
        }

    }

    //获取验证码发送过了的验证码
    public static String getSendCode() {
        Jedis jedis = new Jedis();
        String code = jedis.get(keyCode);
        return code;
    }

    //模拟发送
    public static boolean sendCode(String phone) {
        Jedis jedis = new Jedis();
        keyCode = "keyCode:" + phone;
        keyCount = "keyCount:" + phone;

        /*实现只能发三次*/
        String count = jedis.get(keyCount);
        if (count == null) {
            jedis.setex(keyCount, getRemainSecondsOneDay(new Date()), "1");
            jedis.setex(keyCode, 120, getCode());
            return true;
        } else if (Integer.parseInt(count) < 3) {
            jedis.incr(keyCount);
            jedis.setex(keyCode, 120, getCode());
            return true;
        } else {
            System.out.println("当前发送已经上限!");
            jedis.close();
            return false;
        }

    }
    //模拟验证码
    public static String getCode() {
        Random random = new Random();
        String codes = "";
        for (int i = 0; i < 6; i++) {
            codes += random.nextInt(10);
        }
        return codes;
    }

    //当前时间 距离 明天还有多少秒
    public static Integer getRemainSecondsOneDay(Date currentDate) {
        LocalDateTime midnight = LocalDateTime.ofInstant(currentDate.toInstant(),
                        ZoneId.systemDefault()).plusDays(1).withHour(0).withMinute(0)
                .withSecond(0).withNano(0);
        LocalDateTime currentDateTime = LocalDateTime.ofInstant(currentDate.toInstant(),
                ZoneId.systemDefault());
        long seconds = ChronoUnit.SECONDS.between(currentDateTime, midnight);
        return (int) seconds;
    }

    //java 1.7
    public static Integer getRemainSecondsOneDay1_7(Date currentDate) {
        Calendar midnight = Calendar.getInstance();
        midnight.setTime(currentDate);
        midnight.add(midnight.DAY_OF_MONTH, 1);
        midnight.set(midnight.HOUR_OF_DAY, 0);
        midnight.set(midnight.MINUTE, 0);
        midnight.set(midnight.SECOND, 0);
        midnight.set(midnight.MILLISECOND, 0);
        Integer seconds = (int) ((midnight.getTime().getTime() - currentDate.getTime()) / 1000);
        return seconds;
    }

redis事务

Multi 组队阶段

Exec 执行阶段

Discard 放弃

事务出错

事务冲突

什么是事务冲突

解决事务冲突

悲观锁 (多写场景)

- 案例 3个人上厕所 只有一个坑。(**java中Synchronized 一样**)

乐观锁 (多读场景)

WATCH

UNWATCH

Redis的事务特性

Redis持久化

RDB (Redis DataBases)

RDB 配置文件

命令save VS bgsave

停止

RDB备份与恢复

优点:

缺点:

AOF (Append Only File)

AOP配置文件(aop默认不开启)

AOF备份与恢复

AOF 异常恢复

优点:

劣势:

总结

Redis主从复制(master-slave)

配置主从(windows), linux是一样的

1. 将redis的文件复制一份 为MyRedis

2.复制三个 配置文件:

3.启动服务器

4.查看服务器启动状况:(linux 使用 ps -ef |grep redis

5.连接服务器:

6.查看每个服务的状态(info replication)

目前来看,我们的redis没有任何的关系,都是master 配置

7.配置主从关系(slaveof ip prot)

                                    slave0:ip=****,state=**online**(**表示当前的从服务器可以使用**)

8.测试主从

一主二仆

从服务挂掉

特点:

从服务器挂掉重启后,是**单独的服务器**,和主服务没有任何关系 

重新建立主从关系后, 数据会**恢复**到**主服务器的数据**

主服务挂掉

![image-20210923135334528](https://gitee.com/exception_cui/images/raw/master/202109231354300.png)

特点:

    主服务挂掉后: **从服务器还是从服务器**,**不会上位。** 

    主服务器重启后,**恢复主从关系**

主从复制原理:

烽火相传

特点:

**和一主二仆 一样。**

反客为主(slaveof no one)

当 主服务器 挂掉后,从服务器可以 作为主服务器  

从服务器 :**slaveof  no  one** 就可以成为主服务器

缺点

还需要自己手动完成。

其他的服务器,也得手动  更改主服务器

哨兵模式(反客为主 自动版本)

创建 哨兵的配置文件

port 26479
sentinel monitor mymaster 127.0.0.1 6380 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 15000
sentinel config-epoch mymaster 1

启动哨兵监控

测试

自己指定 从->主的优先级(不指定,就按下面的规则)

重启旧服务器

特点:

当新的 从服务器上位成 主服务器后旧的主服务重启 就会变成 服务器

缺点:

复制延迟。如果系统很繁忙,那么同步的时间会 延迟很久

JAVA 端如何知道并且使用

redis集群

Redis集群详解(https://blog.csdn.net/miss1181248983/article/details/90056960/)

java操作集群

缓存穿透

场景分析:

数据库的id是自动增长的,所以不可能存在-1的id(那么redis也不可能会缓存这个key为-1的数据),这个时候假如黑客,一直使用-1的id一直去请求,那么每次请求都会穿过redis(redis没有-1的key),那么就会直接去请求数据库。请求多的话,会给数据库一定的压力、

解决方案:

缓存击穿

现象:

数据库突然的访问量增大,redis是正常的

解决方案

缓存雪崩

解决方案

缓存预热:

分布式锁(专业的请看redission)

因为锁值存在一单台机器,其他的机器不认识你这个锁,还是可以继续执行,所以就出现了分布式锁(就是共享锁

实现方式

redis实现分布式锁

setnx

问题:

redis 和 java对应关系

setnx  == java中的lock.lock()方法              这个就是一个悲观锁

{
    具体的业务:
}

del    ==  java中的lock.release()

存在问题:

A 和 B 两台 服务器使用锁

1. A 先 设置锁,并且设置过期时间为10s
2. A 然后执行具体的业务
3. A 的服务器突然压力剧增,具体业务缓慢执行
4. A 10s过后,释放了锁。业务还没有执行完成。
5. B 开始拿到锁
6. B 执行业务
7. A的服务器好了,然后执行完了业务。
8. A 手动释放了锁。(del )
9. 锁本来时在B的手里,但是被A 给释放了。

解决问题(UUID):

UUID解决问题:

在设置锁之前 使用UUID来判断

set lock  "uuid" nx ex 10
然后释放锁之前,通过获取uuid 和当前的是否一样,
一样就释放,不一样就不释放

UUID解决存在问题:

解决UUID的问题:

使用LUA脚本

 public static boolean releaseLock(Jedis jedis, String lockKey, String requestId) {

        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }
上一篇下一篇

猜你喜欢

热点阅读