Redis学习笔记②

87 阅读5分钟

Jedis操作Redis6

即用java实现在linux命令行中的操作

引入依赖

image.png

测试代码

禁用Linux的防火墙:Linux(CentOS7)里执行命令

systemctl stop/disable firewalld.service

redis.conf中注释掉bind 127.0.0.1 ,然后 protected-mode no

总而言之:注意一是否修改配置注意二是否关闭防火墙

public static void main(String[] args) {
    Jedis jedis = new Jedis("192.168.44.168",6379);
    
    //ip地址和端口号创建Jedis对象
    //连接成功就返回值,不成功就报错
    String value = jedis.ping();
    System.out.println("连接成功:"+pong);
    
    jedis.**set**("k1", "v1");
    jedis.set("k2", "v2");
    jedis.set("k3", "v3");
    Set<String> keys = jedis.**keys**("*");
    System.out.println(keys.size());
    for (String key : keys) {
        System.out.println(key);
    }
    System.out.println(jedis.**exists**("k1"));
    System.out.println(jedis.**ttl**("k1"));               
    System.out.println(jedis.**get**("k1"));
    
    jedis.close();
    
}

实例:完成一个手机验证码功能

要求:
1、输入手机号,点击发送后随机生成6位数字码,2分钟有效
2、输入验证码,点击验证,返回成功或失败
3、每个手机号每天只能输入3次

/***********
**  1.生成一个随机的验证码
**    Random
**  2.验证码两分钟内有效
**    设置过期时间
**  3.验证码是否正确
**    redis获取验证码和输入的比较
**  4.每个手机只能尝试三次
**    incr+1,大于2不能提交
**********/
package com.atguigu.jedis;

public class PhoneCode {

    public static void main(String[] args) {
        //模拟发送验证码,发送三次后会失败的
        verifyCode("13678765435");

        getRedisCode("13678765435","982422");
    }
    //3.校验
    public static void getRedisCode(String phone, String code) {
        Jedis jedis = new Jedis("192.168.44.168",6379);
        String codeKey = "VerifyCode" + phone + ":code";
        String redisCode = jedis.get(codeKey);

        //判断
        if(code.equals(redisCode)) {
            System.out.println("成功");
        } else {
            System.out.println("失败");
        }
        jedis.close();
    }


    //2.验证
    public static void verifyCode(String phone) {
        //连接redis
        Jedis jedis = new Jedis("192.168.44.168",6379);


        //拼接key
        //手机发送次数
        String countKey = "VerifyCode" + phone + ":count";
        //验证码key
        String codeKey = "VerifyCode" + phone + ":code";

        //只发送三次
        String count = jedis.get(countKey);
        if(count == null) {
            //未发送过,设置发送次数为1
            jedis.setex(countKey, 24*60*60, "1");
        } else if (Integer.parseInt(count) <= 2) {
            jedis.incr(countKey);
        } else if (Integer.parseInt(count) > 2) {
            System.out.println("今天已经发送三次了");
            jedis.close();
            return;
        }
        //验证码要放到redis中
        String vcode = getCode();
        jedis.setex(codeKey, 120, vcode);
        jedis.close();
    }

    //1.生成验证码
    public static String getCode() {
        Random  random = new Random();
        //建议直接使用StringBuffer
        String code ="";
        for(int i = 0; i < 6; i++) {
            int rand = random.nextInt(10);
            code += rand;
        }
        return code;
    }
}

整合

SpringBoot,一个Spring的脚手架、简化工具。

引入依赖

1、 在pom.xml文件中引入redis相关依赖 image.png

配置

2、application.properties配置redis配置

image.png

3、 添加redis配置类 固定写法

image.png

image.png

测试

4、RedisTestController中添加测试方法

image.png

事物操作

Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

Redis事务的主要作用就是串联多个命令防止别的命令插队。

multi
exec
discard
watch
unwatch

秒杀案例

image.png

使用工具ab模拟测试:CentOS6 默认安装;CentOS7 需要手动安装。

public static boolean doSecKill(String uid, String prodid) throws IOException {

    //1.uid和prodid非空判断
    if(uid == null || prodid == null) {
        return false;
    }

    //2.连接redis
    Jedis jedis = new Jedis("192.168.44.168", 6379);

    //3.拼接key
    //3.1库存key
    String keKey = "sk:"+prodid+":qt";
    //3.2描述成功用户id
    String userKey = "sk:"+uid+":user";
    
    //4.获取库存,若null则秒杀未开始需等待
    String kc = jedis.get(kcKey);
    if(kc == null) {
        System.out.println("秒杀还未开始");
        jedis.close();
        return false;
    }

    //5.开始秒杀判断用户是否重复下单
    if(jedis.sismember(userKey,uid)) {
        System.out.println("已秒杀成功,不能重复秒杀");
        jedis.close();
        return false;
    }

    //6.判断如果商品数量,库存数量小于1,表示秒杀结束    
    if(Integer.parseInt(kc) <= 0) {
        System.out.println("秒杀已结束");
        jedis.close();
        return false;
    }

    //7.秒杀过程
    //7.1库存-1
    jedis.decr(kcKey);
    //7.2把秒杀成功用户添加到清单中
    jedis.sadd(userKey,uid);
    System.out.println("秒杀成功");
    jedis.close();
    return true;
}
flushdb
set sk:0101:qt 10
keys *
        (返回"sk:0101:user"
keys *
        (返回"sk:0101:user"
             "sk:0101:qt"
get sk:0101:qt
        (返回“9”
smembers sk:0101:user
        (返回“5421”
//修改为连接池
    JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
    Jedis jedis = jedisPoolInstance.getResource();

超卖问题

乐观锁

image.png

public static boolean doSecKill(String uid, String prodid) throws IOException {

    //1.uid和prodid非空判断
    if(uid == null || prodid == null) {
        return false;
    }

    //2.连接redis
    //Jedis jedis = new Jedis("192.168.44.168", 6379);
    //修改为连接池
    JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
    Jedis jedis = jedisPoolInstance.getResource();

    //3.拼接key
    //3.1库存key
    String keKey = "sk:"+prodid+":qt";
    //3.2描述成功用户id
    String userKey = "sk:"+uid+":user";
    
    //监视库存
    jedis.watch(kcKey);
    //4.获取库存,若null则秒杀未开始需等待
    String kc = jedis.get(kcKey);
    if(kc == null) {
        System.out.println("秒杀还未开始");
        jedis.close();
        return false;
    }

    //5.开始秒杀判断用户是否重复下单
    if(jedis.sismember(userKey,uid)) {
        System.out.println("已秒杀成功,不能重复秒杀");
        jedis.close();
        return false;
    }

    //6.判断如果商品数量,库存数量小于1,表示秒杀结束    
    if(Integer.parseInt(kc) <= 0) {
        System.out.println("秒杀已结束");
        jedis.close();
        return false;
    }

    //7.秒杀过程
    Transaction multi = jedis.multi();

    //组队操作
    multi.decr(kcKey);
    multi.sadd(userKey,uid);
    
    //执行
    List<Obeject> results = multi.ext();

    if(results == null || results.size() == 0) {
        System.out.println("秒杀失败");
        jedis.close();
        return false;
    }

    //7.1库存-1
    //jedis.decr(kcKey);
    //7.2把秒杀成功用户添加到清单中
    //jedis.sadd(userKey,uid);


    System.out.println("秒杀成功");
    jedis.close();
    return true;
}

库存遗留

乐观锁修改版本号的问题

image.png

但是redis不能直接使用悲观锁,需要LUA脚本,但LUA只支持redis2.6以上的版本。

持久化

RDB

fork写时复制技术

image.png

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

image.png

save bgsave

save :save时只管保存,其它不管,全部阻塞。手动保存。不建议。
bgsave:Redis会在后台异步进行快照操作, 快照同时还可以响应客户端请求。

rdb的备份

先通过config get dir  查询rdb文件的目录
将*.rdb的文件拷贝到别的地方
rdb的恢复

cp dump.rdb d.rdb
ll

# 关闭Redis
ps -ef | grep redis
kill -9 3524

# 先把备份的文件拷贝到工作目录下 cp dump2.rdb dump.rdb
rm -f dump.rdb
mv d.rdb dump.rdb

# 启动Redis, 备份数据会直接加载
redis-server /etc/redis.conf
redis-cli
key *

优势

  1. 适合大规模的数据恢复
  2. 对数据完整性和一致性要求不高更适合使用
  3. 节省磁盘空间
  4. 恢复速度快

劣势

  1. Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
  2. 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。
  3. 在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。

image.png

AOF

appendfsync always    #每次都记入,始终同步,但性能差
appendfsync everysec  #每秒都记入,但是本秒可能丢失
appendfsync no        #redis不主动,交给操作系统

image.png

优点

  1. 备份机制更稳健,丢失数据概率更低。
  2. 可读的日志文本,通过操作AOF稳健,可以处理误操作。

缺点

  1. 比起RDB占用更多的磁盘空间。
  2. 恢复备份速度要慢。
  3. 每次读写都同步的话,有一定的性能压力。
  4. 存在个别Bug,造成恢复不能。

image.png

官方推荐两个都启用。如果对数据不敏感,可以选单独用RDB,不建议单独用 AOF,因为可能会出现Bug,如果只是做纯内存缓存,可以都不用。

主从复制

读写分离、主写从读;容灾快速恢复

image.png

vi redis6379.conf

# 拷贝多个redis.conf文件include(写绝对路径)
include /myredis/redis.conf
# 开启daemonize yes
# Pid文件名字pidfile
pidfile /var/run/redis_6379.pid
# 指定端口port
port 6379
# Log文件名字
# dump.rdb名字dbfilename
dpfilename dump6379.rdb
# Appendonly 关掉或者换名字
#6379 6380 6381

ls
# 启动redis
redis-server /redis6379
redis-server /redis6380
redis-server /redis6381

# 查看进程
ps -ef | grep redis

# 打印主从复制的相关信息
info replication

# 在从机上配置主机
slaveof <主机ip><主机port>

一主两仆

主机挂掉,重启就行,一切如初
从机重启需重设:slaveof 127.0.0.1 6379,可以将配置增加到文件中。永久生效。

image.png

从机只能读,主机可写

薪火相传

image.png

反客为主

当一个master宕机后,后面的slave可以立刻升为master,其后面的slave不用做任何修改。 slaveof no one

哨兵模式

redis-sentinel setinel.conf

image.png

优先级,偏移量(同步度最高),runid

java判别主从

private static JedisSentinelPool jedisSentinelPool=null;

 
public static  Jedis getJedisFromSentinel(){
if(jedisSentinelPool==null){
            Set<String> sentinelSet=new HashSet<>();
            sentinelSet.add("192.168.11.103:26379");
 

            JedisPoolConfig jedisPoolConfig =new JedisPoolConfig();
            jedisPoolConfig.setMaxTotal(10); //最大可用连接数
jedisPoolConfig.setMaxIdle(5); //最大闲置连接数
jedisPoolConfig.setMinIdle(5); //最小闲置连接数
jedisPoolConfig.setBlockWhenExhausted(true); //连接耗尽是否等待
jedisPoolConfig.setMaxWaitMillis(2000); //等待时间
jedisPoolConfig.setTestOnBorrow(true); //取连接的时候进行一下测试 ping pong


jedisSentinelPool=new JedisSentinelPool("mymaster",sentinelSet,jedisPoolConfig);

return jedisSentinelPool.getResource();
        }else{
return jedisSentinelPool.getResource();
        }
}