Redis-day2

Redis的事务

是什么?

可以一次性执行多个命令,本质是一组命令的集合,一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其他命令插入,不许加塞

能干嘛?

一个队列中,一次性、顺序性、排他性的执行一系列命令。

在这里插入图片描述

正常执行:

在这里插入图片描述

放弃事务:

在这里插入图片描述

全体连坐:

在这里插入图片描述

冤头债主:

在这里插入图片描述

watch监控:

悲观锁/乐观锁/CAS(Check And Set)

悲观锁:

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

乐观锁:

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。
乐观锁策略:提交版本必须大于记录当前版本才能执行更新

CAS

watch命令:

类似乐观锁,事务提交时,如果key的值已被别的客户端改变,比如某个list已被别的客户端push/pop过了,整个事务队列都不会执行

通过watch命令在事务执行之前监控了多个Keys,倘若在watch之后有任何的key值发生了变换,EXEC命令执行的事务都将被放弃,同时返回nullmulti-bulk应答以通知调用者事务执行失败

3阶段:

开启:以MULTI开启一个事务
入队:将多个命令入队倒事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
执行:由EXEC命令触发事务

3特性:

单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行, 也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题
不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

不遵循传统的ACID中的AI

消息订阅发布简介:

redis的发布订阅

是什么?

进程间的一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。在这里插入图片描述

主从复制:
也就是我们所说的主从复制,主机数据更新后根据配置和策略, 自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主

能干嘛:读写分离、容灾恢复

配从(库)不配主(库)

从库配置 slaveof 主库IP 主库端口
每次与master断开之后,都需要重新连接,除非配置进redis.conf文件
Info replication

修改配置文件细节操作
拷贝多个redis.conf文件,按’redis[port].conf’重命名
开启daemonize yes
pid文件名字
指定端口
log文件名字
dump.rdb名字

常用3招:

1.一主二仆

2.薪火相传
上一个slave可以是下一个slave的master,slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了链条中下一个的master,可以有效减轻master的写压力
中途变更转向:会清除之前的数据,重新建立拷贝最新的
slaveof 新主库ip 新主库端口

3.反客为主
slaveof no one 使当前数据库停止与其他数据库的同步,转成主数据库

哨兵模式:

一组sentinel能同时监控多个master

是什么?

反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库

使用步骤:

1.调整结构,6379带着6380、6381
2.新建sentinel.conf文件,名字绝不能错
3.配置哨兵,填写内容
sentinel monitor 被监控数据库名字(自己起名字) 127.0.0.1 6379 1
上面最后一个数字1,表示主机挂掉后salve投票看让谁接替成为主机,得票数多少后成为主机(PS. 跟官网的描述有出入,下面有官方文档说明)
4.启动哨兵
redis-sentinel /sentinel.conf(上述目录依照各自的实际情况配置,可能目录不同)
5.正常主从演示
6.原有的master挂了
7.投票新选
8.重新主从继续开工,info replication查查看

问:如果之前挂了的master重启回来,会不会双master冲突?
答: 不会,原master,变成slave

导入依赖:

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

创建新类,测试连通,源码如下:

package com.lun.shang;

import redis.clients.jedis.Jedis;

public class TestPing {
	public static void main(String[] args) 
	{
		Jedis jedis = new Jedis("127.0.0.1",6379);
		//输出PONG,redis连通成功
		System.out.println(jedis.ping());
	}
}
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class TestTX {
	public boolean transMethod() throws InterruptedException {
		Jedis jedis = new Jedis("127.0.0.1", 6379);
		int balance;// 可用余额
		int debt;// 欠额
		int amtToSubtract = 10;// 实刷额度

		jedis.watch("balance");
		// jedis.set("balance","5");//此句不该出现,讲课方便。模拟其他程序已经修改了该条目
		Thread.sleep(7000);
		balance = Integer.parseInt(jedis.get("balance"));
		if (balance < amtToSubtract) {
			jedis.unwatch();
			System.out.println("modify");
			return false;
		} else {
			System.out.println("***********transaction");
			Transaction transaction = jedis.multi();
			transaction.decrBy("balance", amtToSubtract);
			transaction.incrBy("debt", amtToSubtract);
			transaction.exec();
			balance = Integer.parseInt(jedis.get("balance"));
			debt = Integer.parseInt(jedis.get("debt"));

			System.out.println("*******" + balance);
			System.out.println("*******" + debt);
			return true;
		}
	}

	/**
	 * 通俗点讲,watch命令就是标记一个键,如果标记了一个键, 在提交事务前如果该键被别人修改过,那事务就会失败,这种情况通常可以在程序中 重新再尝试一次。
	 * 首先标记了键balance,然后检查余额是否足够,不足就取消标记,并不做扣减; 足够的话,就启动事务进行更新操作,
	 * 如果在此期间键balance被其它人修改, 那在提交事务(执行exec)时就会报错, 程序中通常可以捕获这类错误再重新执行一次,直到成功。
	 * 
	 * @throws InterruptedException
	 */
	public static void main(String[] args) throws InterruptedException {
		TestTX test = new TestTX();
		boolean retValue = test.transMethod();
		System.out.println("main retValue-------: " + retValue);
	}
}

事务:

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

public class Test03 {
	public static void main(String[] args) {
		Jedis jedis = new Jedis("127.0.0.1", 6379);

		// 监控key,如果该动了事务就被放弃
		/*
		 * 3 jedis.watch("serialNum"); jedis.set("serialNum","s#####################");
		 * jedis.unwatch();
		 */

		Transaction transaction = jedis.multi();// 被当作一个命令进行执行
		Response<String> response = transaction.get("serialNum");
		transaction.set("serialNum", "s002");
		response = transaction.get("serialNum");
		transaction.lpush("list3", "a");
		transaction.lpush("list3", "b");
		transaction.lpush("list3", "c");

		transaction.exec();
		// 2 transaction.discard();
		System.out.println("serialNum***********" + response.get());

	}
}

加锁:

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

public class TestTX {
	public boolean transMethod() throws InterruptedException {
		Jedis jedis = new Jedis("127.0.0.1", 6379);
		int balance;// 可用余额
		int debt;// 欠额
		int amtToSubtract = 10;// 实刷额度

		jedis.watch("balance");
		// jedis.set("balance","5");//此句不该出现,讲课方便。模拟其他程序已经修改了该条目
		Thread.sleep(7000);
		balance = Integer.parseInt(jedis.get("balance"));
		if (balance < amtToSubtract) {
			jedis.unwatch();
			System.out.println("modify");
			return false;
		} else {
			System.out.println("***********transaction");
			Transaction transaction = jedis.multi();
			transaction.decrBy("balance", amtToSubtract);
			transaction.incrBy("debt", amtToSubtract);
			transaction.exec();
			balance = Integer.parseInt(jedis.get("balance"));
			debt = Integer.parseInt(jedis.get("debt"));

			System.out.println("*******" + balance);
			System.out.println("*******" + debt);
			return true;
		}
	}

	/**
	 * 通俗点讲,watch命令就是标记一个键,如果标记了一个键, 在提交事务前如果该键被别人修改过,那事务就会失败,这种情况通常可以在程序中 重新再尝试一次。
	 * 首先标记了键balance,然后检查余额是否足够,不足就取消标记,并不做扣减; 足够的话,就启动事务进行更新操作,
	 * 如果在此期间键balance被其它人修改, 那在提交事务(执行exec)时就会报错, 程序中通常可以捕获这类错误再重新执行一次,直到成功。
	 * 
	 * @throws InterruptedException
	 */
	public static void main(String[] args) throws InterruptedException {
		TestTX test = new TestTX();
		boolean retValue = test.transMethod();
		System.out.println("main retValue-------: " + retValue);
	}
}

主从复制:
1.6379,6380启动,先各自先独立
2.主写,从读

import redis.clients.jedis.Jedis;

public class TestMS {
	public static void main(String[] args) {
		Jedis jedis_M = new Jedis("127.0.0.1", 6379);
		Jedis jedis_S = new Jedis("127.0.0.1", 6380);

		jedis_S.slaveof("127.0.0.1", 6379);

		jedis_M.set("class", "1122V2");

		String result = jedis_S.get("class");//可能有延迟,需再次启动才能使用
		System.out.println(result);
	}
}

Jedis_JedisPool
JedisPoolUtil

1.获取Jedis实例需要从JedisPool中获取
2.用完Jedis实例需要返还给JedisPool
3.如果Jedis在使用过程中出错,则也需要还给JedisPool

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.setMaxActive(1000);
					poolConfig.setMaxIdle(32);
					poolConfig.setMaxWait(100 * 1000);
					poolConfig.setTestOnBorrow(true);

					jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379);
				}
			}
		}
		return jedisPool;
	}

	public static void release(JedisPool jedisPool, Jedis jedis) {
		if (null != jedis) {
			jedisPool.returnResourceObject(jedis);
		}
	}

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

public class TestPool {

	public static void main(String[] args) {
		JedisPool jedisPool = JedisPoolUtil.getJedisPoolInstance();
		JedisPool jedisPool2 = JedisPoolUtil.getJedisPoolInstance();

		System.out.println(jedisPool == jedisPool2);

		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.set("aa", "bb");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JedisPoolUtil.release(jedisPool, jedis);
		}
	}
}

配置总结:
JedisPool的配置参数大部分是由JedisPoolConfig的对应项来赋值的。

maxActive:控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted。
maxIdle:控制一个pool最多有多少个状态为idle(空闲)的jedis实例;
whenExhaustedAction:表示当pool中的jedis实例都被allocated完时,pool要采取的操作;默认有三种。
WHEN_EXHAUSTED_FAIL --> 表示无jedis实例时,直接抛出NoSuchElementException;
WHEN_EXHAUSTED_BLOCK --> 则表示阻塞住,或者达到maxWait时抛出JedisConnectionException;
WHEN_EXHAUSTED_GROW --> 则表示新建一个jedis实例,也就说设置的maxActive无用;
maxWait:表示当borrow一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛JedisConnectionException;
testOnBorrow:获得一个jedis实例的时候是否检查连接可用性(ping());如果为true,则得到的jedis实例均是可用的;
testOnReturn:return 一个jedis实例给pool时,是否检查连接可用性(ping());
testWhileIdle:如果为true,表示有一个idle object evitor线程对idle object进行扫描,如果validate失败,此object会被从pool中drop掉;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义;
timeBetweenEvictionRunsMillis:表示idle object evitor两次扫描之间要sleep的毫秒数;
numTestsPerEvictionRun:表示idle object evitor每次扫描的最多的对象数;minEvictableIdleTimeMillis
minEvictableIdleTimeMillis:表示一个对象至少停留在idle状态的最短时间,然后才能被idle object evitor扫描并驱逐;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义;
softMinEvictableIdleTimeMillis:在minEvictableIdleTimeMillis基础上,加入了至少minIdle个对象已经在pool里面了。如果为-1,evicted不会根据idle time驱逐任何对象。如果minEvictableIdleTimeMillis>0,则此项设置无意义,且只有在timeBetweenEvictionRunsMillis大于0时才有意义;
lifo:borrowObject返回对象时,是采用DEFAULT_LIFO(last in first out,即类似cache的最频繁使用队列),如果为False,则表示FIFO队列;

其中JedisPoolConfig对一些参数的默认设置如下:

testWhileIdle=true
minEvictableIdleTimeMills=60000
timeBetweenEvictionRunsMillis=30000
numTestsPerEvictionRun=-1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qtayu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值