Redis 学习笔记总结

Posted IT_Holmes

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis 学习笔记总结相关的知识,希望对你有一定的参考价值。

文章目录

1. Redis 事务

1.1 Redis事务的作用


Redis 事务是一个单独的隔离操作,事务中的所有命令都会序列化,按顺序地执行(因为,单线程+IO多路复用)。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

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

1.2 Multi , Exec , discard 命令(事务常用的三个命令)


multi 和 exec 命令执行效果:

而discard命令是放弃组队,就类似mysql的回滚。

1.3 事务的错误处理


在组队阶段,如果我们某个命令出现了错误,整个队列中的命令都会被取消。

下面这段英文要知道!!


如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他正常的命令都会执行,不会回滚。

2. Redis 事务冲突的问题

2.1 什么是事务冲突?


像下面这种多个事务,同时对一个数据进行操作的情况,就是事务冲突。

2.2 如何解决事务冲突? 乐观锁和悲观锁


解决事务冲突就必须要用到锁。

锁一般分为悲观锁和乐观锁。


悲观锁:无论对数据进行什么操作的时候,都先会上锁,这样别人就不能对该数据进行增删改查操作了。除非解锁。

(这个锁的缺点就是效率很低。)


乐观锁:与悲观锁相反。但是版本号的机制要注意!!

版本号机制要知道!!!

2.3 watch 命令 和 unwatch命令


通过watch命令来监视一个或多个键。

unwatch命令就是取消watch命令对所有key的监视。

如果在执行watch命令之后,执行了exec命令或discard命令的话,那就不需要再执行unwatch了。

2.4 Redis 事务三个特性


首先,redis是不支持ACID四特性的。redis对于事务有专门自己的三个特性。

3. Redis 事务 秒杀案例

3.1 秒杀案例的 基本实现


首先,确定库存和成功用户的要使用redis的数据类型,如下:

package com.itholmes.jedis;

import java.util.Random;

import redis.clients.jedis.Jedis;

public class SecKill_redis 
	public static void main(String[] args) 
		
		Random random = new Random();
		
		//模拟随机用户
		String uid = "";
		for(int i=0;i<4;i++) 
			//随机生成10以内的一个数字
			int rand = random.nextInt(10);
			uid += rand;
		
		
		//商品标签为0101
		String prodid = "0101";
		
		//假设多个用户调用这个方法进行秒杀,那么数据库中的对应key-value就行操作的!
		doSecKill(uid, prodid);
	
	
	//秒杀过程,uid就是用户id,prodid就是库存id
	public static boolean doSecKill(String uid,String prodid) 
		
		//1.uid和prodid非空判断
		if(uid == null || prodid == null) 
			return false;
		
		
		//2.连接redis
		Jedis jedis = new Jedis("39.103.163.156",6379);
		
		//3.拼接key
		//3.1 库存key
		String kuncunKey = "sk:"+prodid+":qt";
		//3.2 秒杀成功用户key
		String userKey = "sk:"+prodid+":user";
		
		//4.获取库存,如果库存本身等于null,表示秒杀还没有开始
		String kuncun = jedis.get(kuncunKey);
		if(kuncun == null) 
			System.out.println("秒杀还没有开始,请等待!");
			jedis.close();
			return false;
		
		
		//5.判断用户是否重复秒杀操作(每个用户秒杀只能秒杀一次)
		Boolean sismember = jedis.sismember(userKey, uid);
		if(sismember) 
			System.out.println("已经秒杀成功了,不能重复秒杀!");
			jedis.close();
			return false;
		
		
		//6.判断如果商品数量,库存数量 小于1了,说明秒杀结束了
		int i = Integer.parseInt(kuncun);
		if(i<1) 
			System.out.println("秒杀已经结束了");
			jedis.close();
			return false;
		
		
		
		//7. 秒杀过程
		//7.1 库存要减1
		jedis.decr(kuncunKey);
		//7.2 把秒杀成功用户添加到清单里面
		jedis.sadd(userKey, uid);
		System.out.println("秒杀成功了");
		
		jedis.close();
		return true;
	

3.2 使用ab 来模拟并发秒杀


yum install httpd-tools 或 apt-get install httpd-tools 安装httpd-tools工具。

我们要使用它的ab,ab是apache性能测试工具。

ab 的参数。

  • -n 表示当前请求次数。
  • -c 表示当前的并发次数。
  • -p 表示post方法发送过来的参数存放位置。
  • -T 表示post方法设置类型。


通过这个方式来测试并发效果很好!!

就3.1的秒杀代码而言,有严重的并发问题!例如:库存成为了负数(超卖问题),再者就是太多的请求,会有超时问题!

3.3 通过连接池解决 连接超时的问题


redis不能同时处理多个请求,就有了连接超时的问题。

连接池:节省每次连接redis服务带来的消耗,把连接好的实例反复利用。

模拟一个jedis数据库池的效果:

package com.itholmes.jedis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class JedisPoolUtil 
	private static volatile JedisPool jedisPool = null;
	
	private JedisPoolUtil() 
		
	
	
	public static JedisPool getJedisPoolInstance() 
		if(null == jedisPool) 
			//加锁
			synchronized (JedisPoolUtil.class) 
				if(null == jedisPool) 
					
					JedisPoolConfig poolConfig = new JedisPoolConfig();
					//设置一堆参数
					poolConfig.setMaxTotal(200);
					poolConfig.setMaxIdle(32);
					poolConfig.setMaxWaitMillis(100*1000);
					poolConfig.setBlockWhenExhausted(true);
					poolConfig.setTestOnBorrow(true);
					
					jedisPool = new JedisPool(poolConfig,"39.103.163.156",6379,60000);
				
			
		
		return jedisPool;
	
	
	
	public static void release(JedisPool jedisPool, Jedis jedis) 
		if(null != jedis) 
			//关闭归还连接。
			jedis.close();
		
	

3.4 如何解决并发出现的超卖现象(库存为负数)


首先,可以通过watch乐观锁来解决!(记住乐观锁的版本号对应机制)

注意下面是如何开启redis事务(组或队列)的!!

package com.itholmes.jedis;

import java.util.List;
import java.util.Random;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Transaction;

public class SecKill_redis 
	public static void main(String[] args) 
		
		Random random = new Random();
		
		//模拟随机用户
		String uid = "";
		for(int i=0;i<4;i++) 
			//随机生成10以内的一个数字
			int rand = random.nextInt(10);
			uid += rand;
		
		
		//商品标签为0101
		String prodid = "0101";
		
		//假设多个用户调用这个方法进行秒杀,那么数据库中的对应key-value就行操作的!
		doSecKill(uid, prodid);
	
	
	//秒杀过程,uid就是用户id,prodid就是库存id
	public static boolean doSecKill(String uid,String prodid) 
		
		//1.uid和prodid非空判断
		if(uid == null || prodid == null) 
			return false;
		
		
		//2.连接redis
		//Jedis jedis = new Jedis("39.103.163.156",6379);
		
		//这样我们就可以通过连接池得到jedis对象,通过连接池解决超时问题。
		JedisPool jedisPool = JedisPoolUtil.getJedisPoolInstance();
		Jedis jedis = jedisPool.getResource();
		
		//3.拼接key
		//3.1 库存key
		String kuncunKey = "sk:"+prodid+":qt";
		//3.2 秒杀成功用户key
		String userKey = "sk:"+prodid+":user";
		
		
		//监视库存,相当于开启了乐观锁(记住乐观锁的版本号对应机制)
		jedis.watch(kuncunKey);
		
		//4.获取库存,如果库存本身等于null,表示秒杀还没有开始
		String kuncun = jedis.get(kuncunKey);
		if(kuncun == null) 
			System.out.println("秒杀还没有开始,请等待!");
			jedis.close();
			return false;
		
		
		//5.判断用户是否重复秒杀操作(每个用户秒杀只能秒杀一次)
		Boolean sismember = jedis.sismember(userKey, uid);
		if(sismember) 
			System.out.println("已经秒杀成功了,不能重复秒杀!");
			jedis.close();
			return false;
		
		
		//6.判断如果商品数量,库存数量 小于1了,说明秒杀结束了
		int i = Integer.parseInt(kuncun);
		if(i<1) 
			System.out.println("秒杀已经结束了");
			jedis.close();
			return false;
		
		
		
		//7. 秒杀过程
		
		//开始一个队列(组或者事务,都可以理解)
		Transaction multi = jedis.multi();
		
		//组队操作
		//7.1 库存要减1
		multi.decr(kuncunKey);
		//7.2 把秒杀成功用户添加到清单里面
		multi.sadd(userKey, uid);
		
		//开启事务后,就使用事务对应的方法!!来进行组队等等操作。就不单独来操作。
		//jedis.decr(kuncunKey);
		//jedis.sadd(userKey, uid);
		
		//执行队列命令。exec()方法会给我们返回一个list集合结果内容。
		List<Object> results = multi.exec();
		
		if(results == null || results.size() == 0) 
			System.out.println("秒杀失败!!");
			jedis.close();
			return false;
		
		
		System.out.println("秒杀成功了");
		
		jedis.close();
		return true;
	

3.5 库存的 遗留问题


乐观锁会造成库存遗留的问题!


解决办法,最先想到的是悲观锁,但是悲观锁在redis中,不行!!
redis中默认不能直接使用悲观锁(不支持悲观锁),只能使用乐观锁。

  • 原因:Redis 作为缓存服务器使用时,以读操作为主,很少写操作,相应的操作被打断的几率较少。不采用悲观锁是为了防止降低性能。

用嵌入式脚本语言Lua来,来解决库存遗留问题。

3.6 LUA脚本在Redis中的优势


就是将复杂的或者多步的redis操作,写为一个LUA脚本,一次提交给redis执行。

下面就是一个LUA脚本(了解):

4. Redis 持久化之 RDB(Redis DataBase)

4.1 持久化


什么是持久化?

  • 就是将数据写到硬盘中,能长时间存储!这个过程就叫做持久化。

Redis有两种不同形式的持久化方式:

  • RDB(Redis DataBase),redis默认持久化方式。
  • AOF(Append Of File)

4.2 RDB 持久化(备份)是如何进行的?


什么是RDB?

  • 在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是我们将Snapshot快照,它恢复时是将快照文件在直接读到内存里。这种方式就叫做RDB。

RDB执行过程:

RDB创建fork,以及RDB的缺点(最后一次持久化后的数据可能丢失) 要牢记。

4.3 Fork


RDB有一个非常重要的底层原理叫做写时复制技术。

fork的介绍:

4.4 redis配置文件 配置RDB相关信息


首先,我们找到redis.conf中的SNAPSHOTTING,这一块就是配置快照相关的信息。


配置dbfilename:配置持久化后的文件名称。


配置dir:默认是再当前启动redis服务的命令下生成持久化文件。


配置stop-writes-on-bgsave-error:当redis无法写入磁盘的话,直接关掉Redis的写操作。推荐yes。


配置 rdbcompression:压缩文件,推荐yes,压缩么节约空间。


配置rdbchecksum:检查完整性,就是一个数据效验的功能。


配置save [秒钟] [写操作次数]:就是在多少秒内,有多少写操作,就进行持久化操作。

解释一下上面英文内容内容:

  • 在1个小时内,有1个key发生了变化,就进行持久化操作同步。
  • 在5分钟内,有100个key发生了变化,就进行持久化操作同步。
  • 在60秒内,有10000个key发生了变化,就进行持久化操作同步。

对应的两个持久化的redis命令: save 和 bgsave 命令
(在加上一个lastsave命令。)

4.5 RDB 优劣比较


优点:周期性的进行持久化操作。


缺点:最后一次的快照信息可能丢失!

4.6 rdb的 备份和恢复


如果我们存储的dump.rdb出现了问题!我们就可以通过备份的文件来恢复。

下面的cp一份持久化的数据就是redis备份操作。将cp的数据改个成redis默认名字放到相同目录,重启redis服务就是redis恢复操作。

首先,我们cp一份dump.rdb数据,这样dump.rdb出现问题的时候,我们只需要将cp的备份数据,改个名字,放过来就好了。这就算是一个备份效果。

5. Redis 持久化之 AOF(Append Only File)

5.1 什么是AOF?


Append Only File的意思可以理解为仅仅向文件中追加信息。

AOF持久化的原理:

5.2 AOF的 开启


AOF持久化默认是不开启的。

配置AOF的相关信息,就在append only node模块下。


配置appendonly : 配置为yes表示开启AOF模式。


配置appendfilename:持久化后生成的文件名字。

当我们配置上面的信息,重启redis服务后,就开启了AOF持久化!

注意一个问题:

  • 其实这个时间段AOF和RDB是同时开启的,我们并没有在配置文件关闭RDB之类的操作。当AOF和RDB同时开启,系统默认取AOF的数据(数据不会存在丢失)

当我们开启了AOF,进行一个写操作就往aof文件中追加一个写操作命令。

5.3 AOF 备份和恢复


AOF 备份和恢复的效果和rdb一样的:


AOF多了一个异常恢复的效果。

异常恢复:

  • 通过/usr/local/bin/redis-check-aof --fix 可以将坏的aof文件恢复成为正常的。

例如:我们将appendonly.aof,随便改了改。

这样我们在进行redis-cli连接时,就会拒接连接。

然后,我们就可以通过rdis-check-aof --fix xxx.aof命令来修复aof文件了。

5.4AOF 同步频率设置


配置appendfsync:同步频率设置。

5.5 AOF rewrite重写操作(压缩)


rewrite重写 压缩操作:

说的简单一点就是 多个指令给他重写成为一个执行进行操作。


什么时候出发重写操作?

AOF 重写对应两个相关配置:

案例:

5.6 AOF执行流程 和 优缺点


AOF执行流程:


AOF的优点:


AOF的缺点:

6. Redis 主从复制


主机数据更新后根据配置和策越,自动同步到备份机的master/slaver机制。

其中,master以写为主,slave以读为主。

就像下面这张图,这就是主从复制,读写分离

主从复制的优点:

  • 实现读写分离,性能扩展。
  • 容灾快速恢复:就是当我们在 从数据库 进行读操作,如果这个 从数据库 出现问题,宕机了,就快速切换到另一个从数据库 进行读操作。这个操作就叫做容灾快速恢复。

容灾快速恢复是用来解决从数据库发生问题宕机后,切换另一个台从数据库。那么如果主数据库出现问题怎么办呢?

那么对应解决主数据库 如果出现问题宕机了该怎么办?就用到了Redis集群。

虽说,每个主从复制要求是一主多从的效果,就是一台主数据库,多台从数据库。而Redis集群就把多个一主多从 进行了联系起来。当用户访问的这台主数据库出现问题,就通过集群切换到另一个个一主多从的主数据库 中。

7. Redis 主从复制 搭建一主多从


这里实现一个一主两从的效果。


第一步:创建一个文件夹用来放置三个redis数据库的配置文件,持久化文件等等。这里我创建的是myredis文件夹。


第二步:复制cp我们的redis.conf配置文件到文件夹中,作为一个公共配置文件部分(配置文件可以去安装redis的目录下获取)。


第三步:配置一主两从,创建三个配置文件。

三个配置文件修改的信息分别是:

  • include导入redis.conf的公共部分。
  • 修改pidfile的路径和名称。
  • 修改port端口
  • 修改dbfilename持久化文件名称。

include的作用是引入公共部分。这里我们引入了之前copy的redis.conf配置文件。就是分别对三个redis数据库进行配置。

(一般我们命名就是在后面添加上对应的数字就行,我这里用的6379,6380,6381作为三个redis服务器,整好设置不同的端口。)


第四步:分别创建并配置好三个配置文件。


第五步:redis-server [对应的.conf文件] 启动三台redis服务器。并且使用slaveof命令来配置从服务器

如果我们想要连接其中一台redis服务器的话,就使用redis-cli -p [端口号] 就可以了。通过端口号来打开redis服务器的客户端。

我们可以使用info replication 命令来查看主从复制的相关信息。
REDIS04_主从复制概述及搭建反客为主薪火相传原理哨兵模式集群搭建

Redis 学习笔记总结

Redis 学习笔记总结

Docker 学习笔记总结

Docker 学习笔记总结

redis学习笔记(14)---redis基本命令总结