Redis学习笔记
- 一.将Redis安装在Linux上
- 二、Redis自带压力测试工具:redis-benchmark
- 三、Redis的基础
- 4、Redis配置文件详解
- 5、持久化
- 6、Redis 两种消息通信模式
- 7、主从复制
- 8、 哨兵模式
- 9、缓存穿透和雪崩(后期看书)
- 10、redis的应用场景
一.将Redis安装在Linux上
1_1、使用
1.先将Redis下载到本地
点击进入Redis官网
2.再通过xftp把redis安装包传送到Linux服务器中
3.把安装包移到/opt,并解压
- mv redis-6.2.6.tar.gz /opt
cd /opt
tar -zx redis-6.2.6.tar.gz
- ls
3.安装gcc和c++环境
- yum install gcc-c++
gcc -v
- make
- 确认是否安装成功make install
4.拷贝一份redis.conf到/usr/local/bin/jjconfig
- redis的默认安装路径:
/usr/local/bin/
。进入redis的安装路径cd /usr/local/bin
- 将原来的redis.conf拷贝一份到/usr/local/bin/jjconfig中
mkdir jjconfig
cp /opt/redis-6.2.6/redis.conf jjconfig
5.修改配置文件
- 开始编辑拷贝的redis.conf
cd /usr/local/bin/jjconfig
vim redis.conf
- 把配置文件改为后台可以运行
daemonize no改为daemonize yes.修改完后保存并退出(:wq)。
6.启动redis服务器(通过拷贝后的配置文件)
即我们不改动原安装包的redis.cof,而是在拷贝后的文件上修改并使用它来启动redis服务器
【注意】一个配置文件只能创建一个redis-server。
redis-server /usr/local/bin/jjconfig/redis.conf
7.启动redis客户端来连接redis服务器
开启一个redis的客户端(redis-cli)
redis-cli -p 6379
【问题1】为什么redis的端口号是6379呢?
8 观察redis内启动的进程(ps -ef|grep redis
)
确定redis的服务端和客户端是否开启
ps -ef|grep redis
7.关闭redis
1、在redis客户端环境下的关闭redis服务器:
先关闭客户端shutdown
再关闭服务器exit
2、在主机环境下关闭redis服务器 pkill redis-server
8.进程观察(观察服务端和客户端)
ps -ef|grep redis
netstat -nlt|grep 6379
1_2、使用Docker下载redis
先在Linux上下载Docker,点击查看下载Docker流程
docker pull redis
二、Redis自带压力测试工具:redis-benchmark
- 开启redis服务端(redis-server)和客户端(redis-cli)
redis-server /usr/local/bin/jjconfig/redis.conf
redis-cli -p 6379
2.对单机进行测试
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
通过测试我们会发现redis处理请求是非常快的。
三、Redis的基础
3.1Redis的基础知识
- Redis默认有16个数据库(0-15),且默认使用其中的第0个。
vim /usr/local/bin/jjconfig/redis.conf
【问题1】Redis为什么是单线程的?
Redis是基于内存操作的而不是CPU,Redis的瓶颈是机器的内存和网络带宽。单线程可以避免保存冲突问题,那么可以用单线程为何不用呢。
【问题2】Redis为什么单线程还这么快呢?
多线程就一定比单线程效率高吗?这个是不一定的。关于读取速度:CPU>内存>硬盘。而Redis是把所有的数据都放在内存里面的,多路复用??
3.2 Redis的基本指令
1.select 切换数据库
127.0.0.1:6379[3]> select 0
OK
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> select 16
(error) ERR DB index is out of range
127.0.0.1:6379[1]> select 15
OK
2.dbsize查看数据库大小
127.0.0.1:6379[15]> dbsize
(integer) 0
3.设置 和获取数据库名set,get
127.0.0.1:6379[15]> set name junjun
OK
127.0.0.1:6379[15]> get name
"junjun"
4.查看某个数据库的所有的(keys)
127.0.0.1:6379[1]> keys *
1) "name"
5.清空当前数据库中所有的内容(flushdb)
127.0.0.1:6379[1]> flushdb
OK
6. 清空全部的数据库(flushall)
127.0.0.1:6379[1]> flushall
OK
7.判断是否存在name这么一个key(exists)
127.0.0.1:6379> exists name
(integer) 1
8.使得name在10s之后过期(expire)
127.0.0.1:6379> expire name 10
(integer) 1
9.查看name还有多久过期(-1不会过期,-2已经过期)(ttl)
127.0.0.1:6379> ttl age
(integer) -1
10.查看数据类型(type)
127.0.0.1:6379> type age
string
11.设置密码(config set requirepass)
172.17.17.120:6379> config set requirepass "3333"
OK
12.通过密码登入
172.17.17.120:6379> auth 3333(auth 密码)
OK
3.2 Redis的密码设置
3.3 Redis-key的五大基本类型
redis是一个典型的key-value键值对
3.3.1 String常用命令
1.拓展字符串内容(返回字符串的总长度)
127.0.0.1:6379> append name 11
(integer) 4
2.查看字符串的长度
127.0.0.1:6379> strlen name
(integer) 4
3.实现value中数字加 、减的效果(incr、decr、incrby、decrby)
127.0.0.1:6379> set view 0
OK
127.0.0.1:6379> type view
string
127.0.0.1:6379> incr view
(integer) 1
127.0.0.1:6379> type view
string
127.0.0.1:6379> incr view
(integer) 2
127.0.0.1:6379> incr view #自增1
(integer) 3
127.0.0.1:6379> get view
"3"
127.0.0.1:6379> decr view
(integer) 2
127.0.0.1:6379> get view
"2"
127.0.0.1:6379> incrby view 10 #自增10
(integer) 12
4.截取字符串
127.0.0.1:6379> get key1
"hello,jjj"
127.0.0.1:6379> getrange key1 0 4 #截取字符串
"hello"
127.0.0.1:6379> getrange key1 0 -1 #获取全部的字符串
"hello,jjj"
5.替换字符串
127.0.0.1:6379> getrange key1 0 -1 #获取全部的字符串
"hello,jjj"
127.0.0.1:6379> setrange key1 2 aaa #偏移量为2,
(integer) 9
127.0.0.1:6379> get key1
"heaaa,jjj"
6.若不存在则创建key,否则这条指令将不生效(防止覆盖操作)
127.0.0.1:6379> setnx key1 "111"
(integer) 1
127.0.0.1:6379> get key1
"111"
127.0.0.1:6379> setnx key2 "222"
(integer) 0
127.0.0.1:6379> get key2
"60"
7.创建key的值,并设置xxs之后过期(setex)
127.0.0.1:6379> setex key1 50 "jun"
OK
127.0.0.1:6379> get key1
"jun"
127.0.0.1:6379> ttl key1
(integer) 42
8.多重创建和多重获取key值(set是一起成功或者一起失败)
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k3"
3) "k2"
127.0.0.1:6379> mget k1 k3
1) "v1"
2) "v3"
127.0.0.1:6379>
9.获取key的当前值然后附上新值
127.0.0.1:6379> getset k1 vvvvv
"v1"
127.0.0.1:6379> get k1
"vvvvv"
3.3.2list列表类型指令
1.左右入栈lpush,rpush(返回这个列表中元素的数量)
127.0.0.1:6379> lpush list1 a
(integer) 1
127.0.0.1:6379> lpush list1 b
(integer) 2
127.0.0.1:6379> lpush list2 aa
(integer) 1
127.0.0.1:6379> lpush list2 bb
(integer) 2
127.0.0.1:6379> keys *
1) "list2"
2) "list1"
2.左右出栈lpop,rpop(返回出栈的元素)
127.0.0.1:6379> lpop list1
"b"
3.根据下标查找(lindex,下标从0开始)
127.0.0.1:6379> lindex list1 1
"b"
4.得到列表的长度
127.0.0.1:6379> llen list1
(integer) 4
5.获取列表的“子串”
127.0.0.1:6379> lrange list1 0 -1
1) "b"
2) "a"
6.删除列表中的一个或多个具体的值。
127.0.0.1:6379> lrange list1 0 -1
1) "c"
2) "c"
3) "b"
4) "a"
5) "d"
127.0.0.1:6379> lrem list1 1 "c" #删除list1中的1个"c"
(integer) 1
127.0.0.1:6379> lrange list1 0 -1
1) "c"
2) "b"
3) "a"
4) "d"
7.截取原列表保留子串。
127.0.0.1:6379> Rpush mylist "hello1"
(integer) 1
127.0.0.1:6379> Rpush mylist "hello2"
(integer) 2
127.0.0.1:6379> Rpush mylist "hello3"
(integer) 3
127.0.0.1:6379> Rpush mylist "hello4"
(integer) 4
127.0.0.1:6379> ltrim mylist 1 2 #只保留了mylist中下标是1和2的值
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello2"
2) "hello3"
8.复合指令。
127.0.0.1:6379> lrange mylist 0 -1
1) "hello2"
2) "hello3"
127.0.0.1:6379> rpoplpush mylist mylist #先出栈,再入栈,把两个语句合并了。
"hello3"
127.0.0.1:6379> lrange mylist 0 -1
1) "hello3"
2) "hello2"
9.只有当该列表和下标存在时才能生效,否则报错
127.0.0.1:6379> lset list 0 "hhh"
(error) ERR no such key
127.0.0.1:6379> lpush list "a"
(integer) 1
127.0.0.1:6379> lset list 0 "hhh"
OK
127.0.0.1:6379> lset list 1 "xxx"
(error) ERR index out of range
10.插入到列表中某个元素的前面或者后面
127.0.0.1:6379> linsert mylist before "world" "hello" #在"world"前面插入"hello"
(integer) 3
127.0.0.1:6379> linsert mylist after "world" "xx"
(integer) 4
3.3.3 set集合类型指令
3.3.4 Hash哈希类型指令
3.3.5 Zset有序集合类型指令
3.4 Redis的三大特殊类型
3.4.1 Geospatial地理位置
3.4.2Hyperloglog基数统计
3.4.3Bitmap位图场景
3.5 Redis基本的事务操作
3.5.1事务
- 事务的ACID特性:atomicity原子性 consistency一致性 isolation隔离性 durability持久性
3.5.2 Redis中的事务执行过程
Redis中的事务执行过程:1.开启事务 2.任务入队 3.执行事务
【注意】discord指令可以在任务编辑时取消事务。
3.5.3 Redis的异常命令处理
命令出现编译型异常:指令存在语法错误,那么事务中所有的指令都不会被执行
命令出现运行时异常:指令不存在语法错误,其他指令能够执行
【问题1】Redis的单条命令保证原子性,但是事务是不一定保证原子性?
事务的原子性是指:一组命令要都全部执行,要么全都不执行。而由于Redis并不支持事务回滚机制,当出现运行时异常时就不支持原子性了;而 MySQL 的事务是具有原子性的(能回滚),所以大家都认为 Redis 事务不支持原子性。
3.5.4 Redis实现乐观锁(watch,unwatch)
1、redis是单线程,但是在处理多个进程时也要考虑使用锁。
2、乐观锁与悲观锁的概念
【悲观锁】:只要我们开启事务时就将线程中的变量锁住,等事务提交后才解锁
【乐观锁】:当我们开启事务时不锁住变量,但是当我们提交事务时发现发生脏读、不可重复读和幻读等情况时就放弃提交。
3、watch可以看做是Redis的乐观锁操作。使用watch监视之后,它会保存被监视者的值,当我们准备执行更改被监视者值的事务时,watch会进行比较判断这个值是否被在事务开启之后被其他进程修改过。当事务提交完毕后就会自动解锁。
使用unwatch可以对被监视者进行解锁。当我们在事务中要用其他线程修改被监视者时,可以考虑先解除监视,在其他线程修改完后,再次进行监视。
3.6 Jedis
Jedis是我们通过java来操控redis的开发工具
3.6.1 导入jedis依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
</dependencies>
3.6.2 开启6379安全组和6379防火墙
1、打开安全组的6379
2、打开防火墙的6379
3.6.3 修改配置文件中的绑定ip地址
总而言之一句话,我们只能通过conf配置文件绑定的ip地址才能使用conf配置文件来开启redis。当然,我们是通过公网来访问Linux服务器的,但是conf配置文件中要绑定私网才行(不能绑定公网)。
1.复制一份配置来专门作为jedis远程连接使用。
cp /usr/local/bin/jjconfig/redis.conf /usr/local/bin/jjconfig/redis_jedis.conf
2.更改
vim /usr/local/bin/jjconfig/redis_jedis.conf而不是
vim /opt/redis-6.2.6/redis.conf哦
3.6.4 开启redis服务器
redis-server /usr/local/bin/jjconfig/redis_jedis.conf
ps -ef|grep redis
3.6.5 测试
jedis中的方法就是redis中的指令。
3.6.6 jedis操作redis的常见指令
3.7 springboot启动redis
3.7.1勾选或添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3.7.2 配置连接
spring.redis.host=127.0.0.1
spring.redis.port=6379
3.7.3 连接测试
@SpringBootTest
class Redis01ApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
System.out.println(redisTemplate.getConnectionFactory().getConnection().ping());
}
}
3.8常用方法
//1. 选择操作对象
ValueOperations valueOperations = redisTemplate.opsForValue();//操作字符串
ListOperations listOperations = redisTemplate.opsForList();//操作List
//2.常用操作和Linux一样的指令
valueOperations.set("k1","v1");
//3.获取redis的连接对象
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.flushDb();
connection.flushAll();
System.out.println(redisTemplate.opsForValue().get("k1"));
3.9关于如何在Redis中保存对象
1.将对象转换成json格式再传入.输出结果形式:
{"name":"菌菌","age":3}
2.将对象实现Serializable接口,再直接传入。输出结果形式:User(name=菌菌, age=3)
3.10自定义redisTemplate
@Configuration
public class RedisConfig {
//编写自己的redisTemplate配置文件,实现让原User不用实现Serializable接口也能正常输出。
@Bean(name = "redisTemplate01")
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
// JSON序列化配置 不需要被,不需要知道具体参数含义 大概知道是做什么的就可以 这个就是采用JSON序列化对象
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// String的序列化配置
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// 所有的Key通过String序列化
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// 所有的value通过JSON序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
// 调用刚才看的序列化源码中默认的方法,重新设置序列化
template.afterPropertiesSet();
return template;
}
}
3.11redis工具类
设计java工具类是进一步封装 redisTemplate 的操作。
【注意】这是个从网上扒下来的样板,有一个报错 .能看懂即可,每个公司都会有自己的封装方式。
2.传入String... key
是指传入一个不定参数的数组。
一维数组的类型是[Ljava.lang.String
,二维数组的类型是`
package com.example.redis_01.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));//报错
}
}
}
// ============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于 如果time小于等于 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于)
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @param time 过期时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
* @param key 键
* @param item 项
* @param by 要增加几(大于)
* @return
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
* @param key 键
* @param item 项
* @param by 要减少记(小于)
* @return
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
* @param key 键
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) {
expire(key, time);
}
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
* @param key 键
* @return
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
* @param key 键
* @param start 开始
* @param end 结束 到 -代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
* @param key 键
* @return
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
* @param key 键
* @param index 索引 index>=时, 表头, 第二个元素,依次类推;index<时,-,表尾,-倒数第二个元素,依次类推
* @return
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 过期时间(秒)
* @return
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存,并设置过期时间
* @param key 键
* @param value 值
* @param time 过期时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
4、Redis配置文件详解
4.1 开头简单说明(内容默认为空)
4.2、网络配置
4.3、SSL配置(内容默认为空)
4.4、通用配置
4.5、快照(rdb)
快照机制就是一种持久化的机制:即当在规定的时间内执行了多少次操作就会将数据库中数据持久化一次。redis是内存数据库,那没有持久化不就断电就丢失了吗。
4.6、主从复制
4.7、安全
redis默认没有密码我们可以设置密码
4.8、客户限制
4.9、内存配置
noeviction:直接返回错误,不淘汰任何已经存在的redis键
allkeys-lru:所有的键使用lru算法进行淘汰
volatile-lru:有过期时间的使用lru算法进行淘汰
allkeys-random:随机删除redis键
volatile-random:随机删除有过期时间的redis键
volatile-ttl:删除快过期的redis键
volatile-lfu:根据lfu算法从有过期时间的键删除
allkeys-lfu:根据lfu算法从所有键删除
4.10.aof的配置
5、持久化
Redis支持RDB和AOF两种持久化机制有效避免数据丢失问题。RDB可以看作在某一时刻Redis的快照(snapshot),非常适合灾难恢复。AOF则是写入操作的日志。
5.1持久化之rdb(redis DateBase)
rdb 保存的文件的默认名是dump.rdb,当然我们也可以修改redis.conf中的内容。
5.1.1 手动触发rdb
关于rdb可以有手动保存和自动保存这两种方式。
手动保存可以用save,或shutdown。客户端编辑模式下如果是意外退出(如ctrl+c)就不会持久化了,但是如果是用shutdown命令正常退出就能够实现持久化(会生成或更新dump.rdb文件)。默认用shutdown save,当然我们也可以用shutdown nosave不保存而退出。
5.1.2 自动触发rdb(重点)
查看redis.conf源码就能发现,我们可以使用save来配置redis的rdb的自动触发方式。
5.2持久化之aof(Append Only File)
5.2.1 appendonly.aof的内容分析
如果说rdb是保存的内容,那么aof其实就是一个日志文件。
aof的思想是把我们的所有命令(写操作)都记录下来,当需要恢复时只需要就把这个文件中的指令全部执行一遍就行了。
5.2.2修复appendonly.aof
当appendonly.aof出现错误导致我们不能成功开启redis,我们就能够使用
redis-check-aof --fix
来进行修复。
redis-check-aof --fix
5.2.1先开启aof功能
6、Redis 两种消息通信模式
6.1 队列模式
6.2 发布订阅模式(pub/sub)
1.就像是广播模型,有点像是公众号的形式,所有订阅者都能接受到发布者的发送信息。
2.关于redis-server的内部原理。
7、主从复制
7.1、基本理论
- 主从复制是什么?
主从复制是指把(Master)主节点上的内容复制到(slave)从节点上去(只能单项复制)。
主节点用来只写,从节点用来只读。
一个主节点至少要两个从节点,一个从节点只能有一个子节点- 主从复制的作用是什么?
…
7.2、集群的配置(多台redis服务器共同工作)
我们只要配置Slave,因为在主机上配置的redis服务器默认都是Master。
info replication
7.2.1配置6379、6380、6381这三个配置文件
7.2.2 配置主从复制
我们将6380,6381配置为6379的从机
slaveof 127.0.0.1 6379
1.如果我们直接在配置文件里面改就能实现永久的效果了,而使用命令配置只能实现暂时实现配置主节点的效果。
2.slaveof no one可以实现从从机转变成主机
7.2.3 主从复制效果
1、从机只能读不能写
2、主机断掉了,从机还是从机,不能写入数据。主机断线重连,主机还是主机。
3、暂时从机断线重连就不是从机了,永久从机断线重连仍是从机
3、一台新的从机生成时,会立马执行主从复制效果(全量复制)。
7.2.4 实现主从复制模式
8、 哨兵模式
当主机崩了,从机会自动选出一个新的主机。
8.1 配置哨兵
1.创建sentinel.conf文件vim sentinel.conf,编辑内容如下
# sentinel monitor 被监视的名称 ip 端口 1(代表要选举出1个master) sentinel monitor myredis 127.0.0.1 6379 1
8.2 启动哨兵
/usr/local/bin/redis-sentinel ./jjconfig/sentinel.conf
8.3 测试哨兵
1、我们可以把主机给关了,这时就会发现哨兵会进行选举,其中一台从机变成了主机。
2、在暂时主从复制下:主机重连后,也不能成为老大,只能以成为从机的方式加入集群(slaveof)。、
在永久主从复制下:没测试过。
8.4 哨兵的全部配置(sentinel.conf)
#配置端口
port 26379
#以守护进程模式启动
daemonize yes
#日志文件名
logfile "sentinel_26379.log"
#存放备份文件以及日志等文件的目录
dir "/opt/redis/data"
#监控的IP 端口号 名称 sentinel通过投票后认为mater宕机的数量,此处为至少2个
sentinel monitor mymaster 192.168.14.101 6379 2
#30秒ping不通主节点的信息,主观认为master宕机
sentinel down-after-milliseconds mymaster 30000
#故障转移后重新主从复制,1表示串行,>1并行
sentinel parallel-syncs mymaster 1
#故障转移开始,三分钟内没有完成,则认为转移失败
sentinel failover-timeout mymaster 180000
9、缓存穿透和雪崩(后期看书)
原理:查找时在缓存中没有找到就会自动在数据库中查找,找到后就会写入缓存,否则不写入缓存并返回查找失败。
【缓存穿透】:查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透
【雪崩】:
10、redis的应用场景
redis最典型的应用场景就是作为mysql的缓存:
缓存作用1:加快数据的读取。缓存保存在内存中,其读取速率要远远高于在磁盘中的读取速率
缓存作用2:降低数据库压力。减少对于数据库的读写操作。