Redis

Redis

redis安装

下载地址

http://redis.io/download

安装步骤

 # 安装gcc
 yum install gcc

 # 把下载好的redis‐5.0.3.tar.gz放在/usr/local文件夹下,并解压
 wget http://download.redis.io/releases/redis‐x.x.x.tar.gz
 tar xzf redis‐x.x.x.tar.gz
 cd redis‐x.x.x

 # 进入到解压好的redis‐5.0.3目录下,进行编译与安装
 make
 
 # 修改配置
 daemonize yes #后台启动
 protected‐mode no #关闭保护模式,开启的话,只有本机才可以访问redis
 
 # 需要注释掉bind
 # bind 127.0.0.1
 #(bind绑定的是自己机器网卡的ip,如果有多块网卡可以配多个ip,代表允许客户端通过机器的哪些网卡ip去访问,内网一般可以不配置bind,注释掉即可)

 # 启动服务
 src/redis‐server redis.conf

 # 验证启动是否成功
 ps ‐ef | grep redis

 # 进入redis客户端
 src/redis‐cli

 # 退出客户端
 quit

 # 退出redis服务:
 (1)pkill redis‐server
 (2)kill 进程号
 (3)src/redis‐cli shutdown

Redis的单线程和高性能

redis是否是单线程

Redis 的单线程主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程。但 Redis 的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。

redis高效的原因

它所有的数据都在内存中,所有的运算都是内存级别的运算,而且单线程避免了多线程的切换性能损耗问题。正因为 Redis 是单线程,所以要小心使用 Redis 指令,对于那些耗时的指令(比如keys),一定要谨慎使用,一不小心就可能会导致 Redis 卡顿。

redis的IO多路复用

redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放到文件事件分派器,事件分派器将事件分发给事件处理器。
在这里插入图片描述

Redis持久化

RDB快照(snapshot)

在默认情况下, Redis 将内存数据库快照保存在名字为 dump.rdb 的二进制文件中。你可以对 Redis 进行设置, 让它在“ N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动保存一次数据集。
在这里插入图片描述
关闭RDB只需要将redis.conf中所有的save保存策略注释掉即可。

# save 60 10000
# save 900 1
# save 300 10

还可以手动执行命令生成RDB快照,进入redis客户端执行命令savebgsave可以生成dump.rdb文件,每次命令执行都会将所有redis内存快照到一个新的rdb文件里,并覆盖原有rdb快照文件。

bgsave的写时复制(COW)机制

Redis 借助操作系统提供的写时复制技术(Copy-On-Write, COW),在生成快照的同时,依然可以正常处理写命令。简单来说,bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。此时,如果主线程对这些数据也都是读操作,那么,主线程和 bgsave 子进程相互不影响。但是,如果主线程要修改一块数据,那么,这块数据就会被复制一份,生成该数据的副本。然后,bgsave 子进程会把这个副本数据写入 RDB 文件,而在这个过程中,主线程仍然可以直接修改原来的数据。

AOF(append-only file)

快照功能并不是非常耐久(durable): 如果 Redis 因为某些原因而造成故障停机, 那么服务器将丢失最近写入、且仍未保存到快照中的那些数据。从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化,将修改的每一条指令记录进文件appendonly.aof中(先写入os cache,每隔一段时间fsync到磁盘。
可通过修改redis.conf配置文件实现

# appendonly yes 
# 打开 默认关闭

从现在开始, 每当 Redis 执行一个改变数据集的命令时(比如 SET), 这个命令就会被追加到 AOF 文件的末尾。这样的话, 当 Redis 重新启动时, 程序就可以通过重新执行 AOF 文件中的命令来达到重建数据集的目的。你可以配置 Redis 多久才将数据 fsync 到磁盘一次。
有三个选项:
1 appendfsync always:每次有新命令追加到 AOF 文件时就执行一次 fsync ,非常慢,也非常安全。
2 appendfsync everysec:每秒 fsync 一次,足够快,并且在故障时只会丢失 1 秒钟的数据。
3 appendfsync no:从不 fsync ,将数据交给操作系统来处理。更快,也更不安全的选择。
推荐(并且也是默认)的措施为每秒 fsync 一次, 这种 fsync 策略可以兼顾速度和安全性。这是一种resp协议格式数据,星号后面的数字代表命令有多少个参数,$号后面的数字代表这个参数有几个字符。
注意:如果执行带过期时间的set命令,aof文件里记录的是并不是执行的原始命令,而是记录key过期的时间戳。
比如执行

> set guan yu ex 1000

对应aof文件里记录如下
在这里插入图片描述

AOF重写

AOF文件里可能有太多没用指令,所以AOF会定期根据内存的最新数据生成aof文件。
如下两个配置可以控制AOF自动重写频率

 # auto‐aof‐rewrite‐min‐size 64mb //aof文件至少要达到64M才会自动重写,文件太小恢复速度本来就
 # auto‐aof‐rewrite‐percentage 100 //aof文件自上一次重写后文件大小增长了100%则再次触发重写

当然AOF还可以手动重写,进入redis客户端执行命令bgrewriteaof重写AOF
注意,AOF重写redis会fork出一个子进程去做(与bgsave命令类似),不会对redis正常命令处理有太多影响

RDB 和 AOF

RDBAOF
启动优先级
体积
启动优先级
恢复速度
数据安全性容易丢数据根据策略决定

生产环境可以都启用,redis启动时如果既有rdb文件又有aof文件则优先选择aof文件恢复数据,因为aof一般来说数据更全一点。

Redis 4.0 混合持久化

重启 Redis 时,我们很少使用 RDB来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 RDB来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。 Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。
通过如下配置可以开启混合持久化(必须先开启aof):

 # aof‐use‐rdb‐preamble yes

如果开启了混合持久化,AOF在重写时,不再是单纯将内存数据转换为RESP命令写入AOF文件,而是将重写这一刻之前的内存做RDB快照处理,并且将RDB快照内容和增量的AOF修改内存数据的命令存在一起,都写入新的AOF文件,新的文件一开始不叫appendonly.aof,等到重写完新的AOF文件才会进行改名,覆盖原有的AOF文件,完成新旧两个AOF文件的替换。
于是在 Redis 重启的时候,可以先加载 RDB 的内容,然后再重放增量 AOF 日志就可以完全替代之前的AOF 全量文件重放,因此重启效率大幅得到提升。混合持久化AOF文件结构如下

Redis数据备份策略:
  1. 写crontab定时调度脚本,每小时都copy一份rdb或aof的备份到一个目录中去,仅仅保留最近48小时的备份
  2. 每天都保留一份当日的数据备份到一个目录中去,可以保留最近1个月的备份
  3. 每次copy备份的时候,都把太旧的备份给删了
  4. 每天晚上将当前机器上的备份复制一份到其他机器上,以防机器损坏

Redis-Lock

@RequestMapping("/deduct_stock")
    public String deductStock() {
        String lockKey = "lock:product_101";
        /*String clientId = UUID.randomUUID().toString();
        //jedis.setnx(k,v)
        //setnx key vale 当且仅当key值不存在生效,若key已存在 setnx不做任何动作 保证多个线程操作时的数据不会被覆写
        // 为保证出现宕机情况 加超时时间 但当超过超时时间之后,其他线程可以获取锁成功 出现超卖情况 所以在finally中进行比较 clientId
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS); 
        if (!result) {
            return "error_code";
        }*/
        //获取锁对象
        RLock redissonLock = redisson.getLock(lockKey);
        //加分布式锁  == .setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
        redissonLock.lock(); 
        try {
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
            System.out.println(stock);
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
                System.out.println("扣减成功,剩余库存:" + realStock);
            } else {
                System.out.println("扣减失败,库存不足");
            }
        } finally {
        	// 比较clientId 相等释放锁
            /*if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
            	// 但是 当执行到判断方法时 发生卡顿等多种情况 导致超出超时时间未能执行释放锁操作,其他线程又获取到锁 会导致其他线程获取到的锁立刻被释放
                stringRedisTemplate.delete(lockKey);
            }*/
            //解锁
            redissonLock.unlock();
        }
        return "end";
    }

锁续命

在主线程中加入一个分线程定时任务,每隔一段时间判断一下主线程是否还在加锁,如还在加锁,代表任务还没结束,将主线程超时时间重新设置为初始值
在这里插入图片描述

// 加锁方法
public void lock() {
        try {
            this.lockInterruptibly();
        } catch (InterruptedException var2) {
            Thread.currentThread().interrupt();
        }
 }
// lock调用
public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
        long threadId = Thread.currentThread().getId();
   		// 尝试加锁
        Long ttl = this.tryAcquire(leaseTime, unit, threadId);
        // 如果ttl != null 即再来一个线程 ttl==当前线程剩余超时时间
        if (ttl != null) {
        	// 没有抢到的线程订阅一个channel  this. Subscribe() 监听这个channel
        	// this. Subscribe=()方法
        	// protected RFuture<RedissonLockEntry> subscribe(long threadId) {
            //   	return PUBSUB.subscribe(this.getEntryName(), this.getChannelName(), this.commandExecutor.getConnectionManager().getSubscribeService());
            // }
		   // this.getChannelName() 方法
		   // String getChannelName() {
      	   // 		return prefixName("redisson_lock__channel", this.getName());
           //  }
            RFuture<RedissonLockEntry> future = this.subscribe(threadId);
            this.commandExecutor.syncSubscription(future);

            try {
                while(true) {
                	// 再次尝试加锁 相当于刷新ttl时间
                    ttl = this.tryAcquire(leaseTime, unit, threadId);
                    if (ttl == null) {
                        return;
                    }
                    if (ttl >= 0L) {
                    	// getLatch()方法 信号量获取一个许可 等待超时时间 这个等待会让出CPU 超时时间结束会回到while循环 再次尝试加锁
                    	// public Semaphore getLatch() {
						//        return this. Latch;
						//    }
                        this.getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                    } else {
                        this.getEntry(threadId).getLatch().acquire();
                    }
                }
            } finally {
                this.unsubscribe(future, threadId);
            }
        }
    }

//
private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) {
        if (leaseTime != -1L) {
            return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
        } else {
            RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),        TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
            // 当RFuture方法执行完成后 会执行 addListener方法
            ttlRemainingFuture.addListener(new FutureListener<Long>() {
                public void operationComplete(Future<Long> future) throws Exception {
                    if (future.isSuccess()) {
                        Long ttlRemaining = (Long)future.getNow();
                        if (ttlRemaining == null) {
                        ·	//刷新超时时间	
                            RedissonLock.this.scheduleExpirationRenewal(threadId);
                        }
                    }
                }
            });
            return ttlRemainingFuture;
        }
    }

//刷新超时时间
private void scheduleExpirationRenewal(final long threadId) {
        if (!expirationRenewalMap.containsKey(this.getEntryName())) {
        	// 延时任务 run方法会在延时后执行
            Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
                public void run(Timeout timeout) throws Exception {
                	// RFuture执行完成后执行addListener方法
                    RFuture<Boolean> future = RedissonLock.this.commandExecutor.evalWriteAsync(RedissonLock.this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, 
                    // 判断主线程加的锁是否还存在 (name, this.getLockName(threadId)) 
                    "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then 
                    // 重新设置主线程超时时间为 this.internalLockLeaseTime
                    redis.call('pexpire', KEYS[1], ARGV[1]); 
                    // 返回true
                    return 1;
                    end; 
                    // 返回false
                    return 0;",
                     Collections.singletonList(RedissonLock.this.getName()), new Object[]{RedissonLock.this.internalLockLeaseTime, RedissonLock.this.getLockName(threadId)});
                     // RFuture执行完成后执行
                    future.addListener(new FutureListener<Boolean>() {
                        public void operationComplete(Future<Boolean> future) throws Exception {
                            RedissonLock.expirationRenewalMap.remove(RedissonLock.this.getEntryName());
                            if (!future.isSuccess()) {
                                RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", future.cause());
                            } else {
                            	// 判断RFuture返回结果
                                if ((Boolean)future.getNow()) {
                                	// 调用自己 延时执行 即锁续命
                                    RedissonLock.this.scheduleExpirationRenewal(threadId);
                                }
                            }
                        }
                    });
                }
            }, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS); //延时时间 = this.internalLockLeaseTime / 3
            if (expirationRenewalMap.putIfAbsent(this.getEntryName(), task) != null) {
                task.cancel();
            }

        }
    }
// 尝试加锁
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        this.internalLockLeaseTime = unit.toMillis(leaseTime);
        
        return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command, 
        		// Lua脚本
        		 // 判断redis中是否存在key1的值
        		"if (redis.call('exists', KEYS[1]) == 0) then 
        		 // 不存在的话 调用hset(key1,value2)
        		 redis.call('hset', KEYS[1], ARGV[2], 1); 
        		 // 设置超时时间为 value1
        		 redis.call('pexpire', KEYS[1], ARGV[1]); 
        		 // 返回 null
        		 return nil;
        		 end;
        		 if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('hincrby', KEYS[1], ARGV[2], 1); 
        		 redis.call('pexpire', KEYS[1], ARGV[1]); 
        		 return nil; 
        		 end; 
        		 // 再来一个线程 返回当前线程剩余超时时间
        		 return redis.call('pttl', KEYS[1]);", 
        		 //Key1 == this.getName()  value1 == this.internalLockLeaseTime  value2 ==  this.getLockName(threadId))
        		 Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});
    }

// 解锁
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
        return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
        	 // 如果当前线程还存在
        	 "if (redis.call('exists', KEYS[1]) == 0) then 
        	 // 不存在 发布解锁消息 LockPubSub.unlockMessage
        	 redis.call('publish', KEYS[2], ARGV[1]); 
        	 return 1;
        	  end;
        	  // 判断这把锁是不是自己加的
        	  if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
        	  return nil;
        	  end; 
        	  // this.getLockName(threadId)) == 1 则 counter  = 1 - 1  = 0
        	  local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); 
        	  if (counter > 0) then 
        	  redis.call('pexpire', KEYS[1], ARGV[2]); 
        	  return 0; 
        	  else 
        	  // delete 并发布解锁消息
        	  redis.call('del', KEYS[1]); 
        	  redis.call('publish', KEYS[2], ARGV[1]); 
        	  return 1; 
        	  end; 
        	  return nil;", 
        	  Arrays.asList(this.getName(), this.getChannelName()), new Object[]{LockPubSub.unlockMessage, this.internalLockLeaseTime, this.getLockName(threadId)});
    }

// LockPubSub类
public static final Long unlockMessage = 0L;

// 消息方法
protected void onMessage(RedissonLockEntry value, Long message) {
        if (message.equals(unlockMessage)) {
        // 如果消息时释放锁 调用getLatch() 信号量 唤醒等待的线程
            value.getLatch().release();

            while(true) {
                Runnable runnableToExecute = null;
                synchronized(value) {
                    Runnable runnable = (Runnable)value.getListeners().poll();
                    if (runnable != null) {
                        if (value.getLatch().tryAcquire()) {
                            runnableToExecute = runnable;
                        } else {
                            value.addListener(runnable);
                        }
                    }
                }

                if (runnableToExecute == null) {
                    return;
                }

                runnableToExecute.run();
            }
        }
    }
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值