一、Redis五大数据类型
1. 字符串String
setnx为redis分布式锁底层
2. 列表List
3. 集合Set
4. 哈希Hash
value为K-V形式
5. 有序集合Zset
真实需求:
充10元可享vip1;
充20元可享vip2;
充30元可享vip3;
二、持久化
1. RDB
在指定的时间间隔内,将内存中的数据集的快照写入磁盘;默认保存在/usr/local/bin中,文件名dump.rdb;(自动备份,也可以执行命令 save手动备份)
优势and劣势
- 优:适合大规模数据恢复,对数据完整性和一致行要求不高;
- 劣:一定间隔备份一次,意外down掉,就失去最后一次快照的所有修改
2. AOF
- 以日志的形式记录每个写操作;
- 将redis执行过的写指令全部记录下来(读操作不记录);
- 只许追加文件,不可以改写文件;
- redis在启动之初会读取该文件从头到尾执行一遍,这样来重新构建数据;
3. 总结
- RDB:只用作后备用途,建议15分钟备份一次就好
- AOF:
- 在最恶劣的情况下,也只丢失不超过2秒的数据,数据完整 性比较高,但代价太大,会带来持
续的IO - 对硬盘的大小要求也高,默认64mb太小了,企业级最少都是5G以上;
- master/slave才是新浪微博的选择
- 在最恶劣的情况下,也只丢失不超过2秒的数据,数据完整 性比较高,但代价太大,会带来持
三、事务
- 可以一次执行多个命令,是一个命令组,一个事务中,所有命令都会序列化(排队),不会被插
队; - 一个队列中,一次性,顺序性,排他性的执行一系列命令
reids-check-aof --fix appendonly.aof - 三特性
- 隔离性:所有命令都会按照顺序执行,事务在执行的过程中,不会被其他客户端送来的命令
打断 - 没有隔离级别:队列中的命令没有提交之前都不会被实际的执行,不存在“事务中查询要看到
事务里的更新,事务外查询不能看到”这个头疼的问题 - 不保证原子性:冤有头债有主,如果一个命令失败,但是别的命令可能会执行成功,没有回
滚
- 隔离性:所有命令都会按照顺序执行,事务在执行的过程中,不会被其他客户端送来的命令
- 三步走
开启multi
入队queued
执行exec
四、Redis的发布订阅
进程间的一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。例如:微信订阅号;订阅一个或多个频道
五、主从复制
- 就是 redis集群的策略
- 配从(库)不配主(库):小弟可以选择谁是大哥,但大哥没有权利去选择小弟
- 读写分离:主机写,从机读
1. 血脉相传
- 一个主机理论上可以多个从机,但是这样的话,这个主机会很累
- 我们可以使用java面向对象继承中的传递性来解决这个问题,减轻主机的负担,形成祖孙三代
2. 谋权篡位
- 1个主机,2个从机,当1个主机挂掉了,只能从2个从机中再次选1个主机
- 国不可一日无君,军不可一日无帅
- 手动选老大
- 模拟测试:1为master,2和3为slave,当1挂掉后,2篡权为master,3跟2
- 当1再次回归,2和3已经形成新的集群,和1没有任何的关系了。所以1成为了光杆司令
3. 复制原理
- 全量复制:Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份slave接收到数据
文件后,存盘,并加载到内存中;(步骤1234) - 增量复制:Slave初始化后,开始正常工作时主服务器发生的写操作同步到从服务器的过程;(步
骤56)- 但,只要是重新连接master,一次性(全量复制)同步将自动执行;
- Redis主从同步策略:主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。
- 当然,如果有需要,slave 在任何时候都可以发起全量同步。
- redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。
4. 哨兵模式
- 自动版的谋权篡位
- 有个哨兵一直在巡逻,突然发现!!!!!老大挂了,小弟们会自动投票,从众小弟中选出新的老
大 - Sentinel是Redis的高可用性解决方案(因为主机负责写入,不能没有主机):
- 由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器,以及所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级,为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求
六、Jedis(Java操作Redis)
java和redis打交道的API客户端
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
1. 连接redis
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.204.141",6379);
String pong = jedis.ping();
System.out.println("pong = " + pong);
}
// 运行前:
// 1.关闭防火墙 systemctl stop firewalld.service
// 2.修改redis.conf [ bind 0.0.0.0 ] 允许任何ip访问,以这个redis.conf启动redis服务
(重启redis)
// redis-server /opt/redis5.0.4/redis.conf
2. 事务
七、JedisPool
redis的连接池技术
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
“一个池”使用单例模式进行优化
public class JedisPoolUtil {
private JedisPoolUtil(){}
private volatile static JedisPool jedisPool = null;
private volatile static Jedis jedis = null;
// 返回一个连接池
private static JedisPool getInstance(){
// 双层检测锁(企业中用的非常频繁)
if(jedisPool == null){ // 第一层:检测体温
synchronized (JedisPoolUtil.class){ // 排队进站
if(jedisPool == null) { //第二层:查看健康码
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(1000);
config.setMaxIdle(30);
config.setMaxWaitMillis(60*1000);
config.setTestOnBorrow(true);
jedisPool = new JedisPool( config, "192.168.204.141",6379 );
}
}
}
return jedisPool;
}
// 返回jedis对象
public static Jedis getJedis(){
if(jedis == null){
jedis = getInstance().getResource();
}
return jedis;
}
}
测试类
public class Test_JedisPool {
public static void main(String[] args) {
Jedis jedis1 = JedisPoolUtil.getJedis();
Jedis jedis2 = JedisPoolUtil.getJedis();
System.out.println(jedis1==jedis2);
}
}
八、高并发下的分布式锁
- 经典案例:秒杀,抢购优惠券等
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<!--实现分布式锁的工具类-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.1</version>
</dependency>
<!--spring操作redis的工具类-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
<!--redis客户端-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
<!--json解析工具-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8002</port>
<path>/</path>
</configuration>
<executions>
<execution>
<!-- 打包完成后,运行服务 -->
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
synchronized锁的一个进程下的线程并发,如果分布式环
境,多个进程并发,这种方案就失效了!只能解决一个tomcat的并发问题
1. 实现分布式锁的思路
- . 因为redis是单线程的,所以命令也就具备原子性,使用setnx命令实现锁,保存k-v
- 如果k不存在,保存(当前线程加锁),执行完成后,删除k表示释放锁
- 如果k已存在,阻塞线程执行,表示有锁
- 如果加锁成功,在执行业务代码的过程中出现异常,导致没有删除k(释放锁失败),那么就会造
成死锁(后面的所有线程都无法执行)!- 设置过期时间,例如10秒后,redis自动删除
- 高并发下,由于时间段等因素导致服务器压力过大或过小,每个线程执行的时间不同
- 第一个线程,执行需要13秒,执行到第10秒时,redis自动过期了k(释放锁)
- 第二个线程,执行需要7秒,加锁,执行第3秒(锁 被释放了,为什么,是被第一个线程的
finally主动deleteKey释放掉了) - 。。。连锁反应,当前线程刚加的锁,就被其他线程释放掉了,周而复始,导致锁会永久失
效
- 给每个线程加上唯一的标识UUID随机生成,释放的时候判断是否是当前的标识即可
- 问题又来了,过期时间如果设定?
- 如果10秒太短不够用怎么办?
- 设置60秒,太长又浪费时间
- 可以开启一个定时器线程,当过期时间小于总过期时间的1/3时,增长总过期时间(吃仙丹续
命!)
九、Redisson
- Redis 是最流行的 NoSQL 数据库解决方案之一,而 Java 是世界上最流行(注意,我没有说“最
好”)的编程语言之一。 - 虽然两者看起来很自然地在一起“工作”,但是要知道,Redis 其实并没有对 Java 提供原生支持。
- 相反,作为 Java 开发人员,我们若想在程序中集成 Redis,必须使用 Redis 的第三方库。
- 而 Redisson 就是用于在 Java 程序中操作 Redis 的库,它使得我们可以在程序中轻松地使用
Redis。 - Redisson 在 java.util 中常用接口的基础上,为我们提供了一系列具有分布式特性的工具类。
@Controller
public class TestKill {
@Autowired
private Redisson redisson;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("kill")
// 只能解决一个tomcat的并发问题:synchronized锁的一个进程下的线程并发,如果分布式环境,多个进程并发,这种方案就失效了!
public @ResponseBody synchronized String kill() {
// 定义商品id
String productKey = "HUAWEI-P40";
// 通过redisson获取锁
RLock rLock = redisson.getLock(productKey); // 底层源码就是集成了setnx,过期时间等操作
// 上锁(过期时间为30秒)
rLock.lock(30, TimeUnit.SECONDS);
try{
// 1.从redis中获取 手机的库存数量
int phoneCount = Integer.parseInt(stringRedisTemplate.opsForValue().get("phone"));
// 2.判断手机的数量是否够秒杀的
if (phoneCount > 0) {
phoneCount--;
// 库存减少后,再将库存的值保存回redis
stringRedisTemplate.opsForValue().set("phone", phoneCount + "");
System.out.println("库存-1,剩余:" + phoneCount);
} else {
System.out.println("库存不足!");
}
}catch (Exception e){
e.printStackTrace();
}finally {
// 释放锁
rLock.unlock();
}
return "over!";
}
@Bean
public Redisson redisson(){
Config config = new Config();
// 使用单个redis服务器
config.useSingleServer().setAddress("redis://192.168.204.141:6379").setDatabase(0);
// 使用集群redis
// config.useClusterServers().setScanInterval(2000).addNodeAddress("redis://192.168.204.141:6379","redis://192.168.204.142:6379","redis://192.168.204.143:6379");
return (Redisson)Redisson.create(config);
}
}
- 实现分布式锁的方案其实有很多,我们之前用过的zookeeper的特点就是高可靠性,现在我们用的
redis特点就是高性能。 - 目前分布式锁,应用最多的仍然是“Redis”