Redis学习笔记
1.Redis是什么?
Redis(Remote Dictionary Server),即远程字典服务
是一个开源的使用ANSI C语言编写的、支持网络、可基于内存、持久化的日志型、key-value型数据库,并提供多种语言API
Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并在此基础上实现了主从同步(master-slave)。
Redis是免费的、开源的,是当下最热门的NoSQL技术之一,也被人们称之为结构化数据库!
2.Redis能干嘛?
-
内存存储、持久化,因为内存中的数据断电即失,所以持久化很重要(rdb、aof)
-
效率高,可用于高速缓存
-
发布订阅系统
-
地图信息分析
-
计时器、计数器、浏览量……
3.Redis基础知识学习
1.String
127.0.0.1:6379[2]> keys * # 获取所以的科研
1) "name"
127.0.0.1:6379[2]> set k1 v1 #设置key
OK
127.0.0.1:6379[2]> get k1 #获取值
"v1"
127.0.0.1:6379[2]> exists k1 #判断是否存在
(integer) 1
127.0.0.1:6379[2]> append k1 "hello,world" #追加字符串,
(integer) 13
127.0.0.1:6379[2]> get k1
"v1hello,world"
127.0.0.1:6379[2]> append k2 "hello,world,,,," #不存在的key 就新建key
(integer) 15
127.0.0.1:6379[2]> get k2
"hello,world,,,,"
127.0.0.1:6379[2]> strlen k1 # 获取key对应值的长度
(integer) 13
2.List
###############################################################
## list常用命令
# lpush
# rpush
127.0.0.1:6379[2]> lpush list1 one #将一个值或多个值插入列表头部,(头插法,左侧插入)
(integer) 1
127.0.0.1:6379[2]> lpush list1 two
(integer) 2
127.0.0.1:6379[2]> lpush list1 three
(integer) 3
127.0.0.1:6379[2]> lrange list1 0 -1 #获取list1 所有的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379[2]> rpush list1 right #将一个值或多个值插入,尾插法(右侧插入)
(integer) 4
127.0.0.1:6379[2]> lrange list1 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
##################################################################
# lpop
# rpop
127.0.0.1:6379[2]> lpop list1 #从左侧移除数据
"three"
127.0.0.1:6379[2]> rpop list1 #从左侧移除数据
"right"
127.0.0.1:6379[2]> lrange list1 0 -1
1) "two"
2) "one"
3.Set
#Set 集合(无序不重复)
###########################################################
127.0.0.1:6379[2]> sadd k1 "hello" # sadd 添加元素
(integer) 1
127.0.0.1:6379[2]> sadd k1 "world"
(integer) 1
127.0.0.1:6379[2]> smembers k1 # smembers 查看指定set中的所有元素
1) "world"
2) "hello"
127.0.0.1:6379[2]> sismember k1 hahha # sismember 判断是否存在,没有就为0,有为1
(integer) 0
127.0.0.1:6379[2]> sismember k1 hello
(integer) 1
############################################################
127.0.0.1:6379[2]> scard k1 # 获取set集合中的内容元素个数!
(integer) 2
127.0.0.1:6379[2]> sadd k1 "ssss"
(integer) 1
127.0.0.1:6379[2]> scard k1
(integer) 3
127.0.0.1:6379[2]> srem k1 ssss # 移除set集合中的指定元素
(integer) 1
127.0.0.1:6379[2]> scard k1
(integer) 2
127.0.0.1:6379[2]> smembers k1
1) "world"
2) "hello"
127.0.0.1:6379[2]> smembers k1
1) "world"
2) "hello"
4.Hash
############################################################
127.0.0.1:6379[2]> hset k1 field1 shizhiming # set一个key-value
(integer) 1
127.0.0.1:6379[2]> hget k1 field1 #获取值
"shizhiming"
127.0.0.1:6379[2]> hmset k1 name szm age 18 sex man
OK
127.0.0.1:6379[2]> hmget k1 name sge sex
1) "szm"
2) (nil)
3) "man"
127.0.0.1:6379[2]> hmget k1 name age sex
1) "szm"
2) "18"
3) "man"
127.0.0.1:6379[2]> hgetall k1 # 获取全部的数据
1) "field1"
2) "shizhiming"
3) "name"
4) "szm"
5) "age"
6) "18"
7) "sex"
8) "man"
127.0.0.1:6379[2]> hdel k1 field1 # 删除hash指定key字段!对应的value值也就消失了!
(integer) 1
############################################################
127.0.0.1:6379[2]> hlen k1 # 获取hash表的字段数量!
(integer) 3
############################################################
127.0.0.1:6379[2]> hexists k1 name # 判断hash中指定字段是否存在!
(integer) 1
127.0.0.1:6379[2]> hexists k1 sss
(integer) 0
##################################################################
# 只获得所有field
# 只获得所有value
127.0.0.1:6379[2]> hkeys k1
1) "name"
2) "age"
3) "sex"
127.0.0.1:6379[2]> hvals k1
1) "szm"
2) "18"
3) "man"
5.Zset
#(有序集合)
#zset k1 score1 v1
127.0.0.1:6379[2]> zadd myzset 1 one # 添加一个值
(integer) 1
127.0.0.1:6379[2]> zadd myzset 2 two
(integer) 1
127.0.0.1:6379[2]> zadd myzset 3 three
(integer) 1
127.0.0.1:6379[2]> zrange myzset 0 -1 #列出所有的值
1) "one"
2) "two"
3) "three"
127.0.0.1:6379[2]> zadd salary 2500 xiaoming
(integer) 1
127.0.0.1:6379[2]> zadd salary 2000 xihong
(integer) 1
127.0.0.1:6379[2]> zadd salary 1500 xiaohua
(integer) 1
################################################################
127.0.0.1:6379[2]> zrangebyscore salary -inf inf # 显示全部的用户 从小到大!
1) "xiaohua"
2) "xihong"
3) "xiaoming"
127.0.0.1:6379[2]> zrangebyscore salary -inf inf withscores # 显示全部的用户并且附带成 绩
1) "xiaohua"
2) "1500"
3) "xihong"
4) "2000"
5) "xiaoming"
6) "2500"
127.0.0.1:6379[2]> zrange salary 0 -1
1) "xiaohua"
2) "xihong"
3) "xiaoming"
127.0.0.1:6379[2]> zrem salary xiaohua # 移除有序集合中的指定元素
(integer) 1
127.0.0.1:6379[2]> zrange salary 0 -1
1) "xihong"
2) "xiaoming"
- Geospatial 地理位置
- Hyperloglog 统计基数(不重复的元素)
- Bitmap 位图
4.Redis事务处理
Redis 事务本质:一组命令的集合! 一个事务中的所有命令都会被序列化
在事务执行过程的中,会按照顺序执行!
一次性、顺序性、排他性!执行一些列的命令!
--------------队列 set1 set2 set3 执行---------------
Redis事务没有没有隔离级别的概念!
所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行!Exec
Redis单条命令式保存原子性的,但是事务不保证原子性!
redis的事务:
-
开启事务(multi)
-
命令入队(......)
-
执行事务(exec)
正常执行事务
127.0.0.1:6379[2]> multi ##开启事务
OK
127.0.0.1:6379[2]> set k1 v1
QUEUED ## 入队
127.0.0.1:6379[2]> set k2 v2
QUEUED
127.0.0.1:6379[2]> get k2
QUEUED
127.0.0.1:6379[2]> set k3 v3
QUEUED
127.0.0.1:6379[2]> exec ## 执行
1) OK
2) OK
3) "v2"
4) OK
放弃事物
127.0.0.1:6379[2]> multi
OK
127.0.0.1:6379[2]> set k1 v1
QUEUED
127.0.0.1:6379[2]> set k2 v2
QUEUED
127.0.0.1:6379[2]> set k3 v3
QUEUED
127.0.0.1:6379[2]> discard ## 放弃事务,没有执行队列
OK
127.0.0.1:6379[2]> get k3
(nil)
编译型异常(代码有问题命令错误),所有的命令都不会执行
127.0.0.1:6379[2]> multi
OK
127.0.0.1:6379[2]> set k1 v1
QUEUED
127.0.0.1:6379[2]> set k2 v2
QUEUED
127.0.0.1:6379[2]> get k4 ##不存在K4
QUEUED
127.0.0.1:6379[2]> exec
(error) EXECABORT Transaction discarded because of previous errors.
运行时异常(1/0) 如果事务中存在语法性错误,执行命令时,其他命令可以正常执行,错误命令抛出
127.0.0.1:6379[2]> multi
OK
127.0.0.1:6379[2]> set k1 v1
QUEUED
127.0.0.1:6379[2]> incr k1
QUEUED
127.0.0.1:6379[2]> set k2 v2
QUEUED
127.0.0.1:6379[2]> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
监控! Watch
127.0.0.1:6379[2]> set money 100
OK
127.0.0.1:6379[2]> set out 0
OK
127.0.0.1:6379[2]> watch money ##监控,如果事务期间没有发生变动,就正常想运行
p
OK
127.0.0.1:6379[2]> multi
OK
127.0.0.1:6379[2]> decrby money 10
QUEUED
127.0.0.1:6379[2]> incrby out 10
QUEUED
127.0.0.1:6379[2]> exec
1) (integer) 90
2) (integer) 10
5.Jedis
Jedis是 Redis 官方推荐的 java连接开发工具!类似于jdbc
测试使用
1.导入对应的依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
2、测试基本使用
package com.zjjg;
import redis.clients.jedis.Jedis;
public class TestPing {
public static void main(String[] args) {
// 连接Redis
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 切换数据库
jedis.select(2);
jedis.set("name","szm");
String name = jedis.get("name");
System.out.println(name);
// 关闭连接
jedis.close();
}
}
测试事务
package com.zjjg;
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
/**
* @author xiaoming
* @version 1.0
* @date 2021/4/25 10:06
*/
public class TestTask {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.select(2);
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("name","szm");
jsonObject.put("gender","male");
String user = jsonObject.toJSONString();
Transaction multi = jedis.multi();
try {
multi.set("user1",user);
multi.set("user2",user);
int i=1/0;
multi.exec();
}catch (Exception e){
multi.discard(); //放弃事务
e.printStackTrace();
}finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close(); // 关闭连接
}
}
}
正常运行时
6.SpringBoot整合
1.导入依赖
<!-- 操作redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.6</version>
</dependency>
2、配置连接
# 配置redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=2
3、使用RedisTemplate方式
package com.zjjg.springbootredis;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class SpringbootRedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
// redisTemplate 操作不同的数据类型,api和我们的指令是一样的
// opsForValue 操作字符串 类似String
// opsForList 操作List 类似List
// opsForSet
// opsForHash
// opsForZSet
// opsForGeo
// opsForHyperLogLog
// 清空当前数据库
redisTemplate.getConnectionFactory().getConnection().flushDb();
redisTemplate.opsForValue().set("k2","hello,world");
System.out.println(redisTemplate.opsForValue().get("k2"));
}
}
发现存储的数据都是乱码的,这关系到存储对象的序列化问题
在RedisTemplate类中,发现默认的序列化方式是采用jdk序列化器,
我们可以自定义RedisTemplate模板,自定义序列化的方式
package com.zjjg.springbootredis.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @author xiaoming
* @version 1.0
* @date 2021/4/25 9:04
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 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序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet(); return template;
}
@Bean
public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
RedisCacheWriter redisCacheWriter =RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
}
}
再次运行程序
4、使用注解的方式
@EnableCache
开启缓存
用于
@ Cacheable
主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
当调用注解修饰的方法时,先从缓存中查询,如果不存在,则执行实际方法,将结果存入缓存中,如果存在,返回缓存中的对象
参数
- value 缓存名称
- key:缓存的key,可以为空,指定需按照SPEL表达式编写(#{})
- condition:缓存条件
- cacheManager:用于指定缓存管理器
- cacheResolver:指定缓存解析器
- unless:否定缓存,当unless指定的条件为true 就不会缓存
- sync:是否使用异步模式,默认是false
@CachePut
能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用
参数
-
value 缓存名称
-
key:缓存的key,可以为空,指定需按照SPEL表达式编写(#{})
-
condition:缓存条件
@CacheEvict
能够根据一定的条件对缓存进行清空
参数
- value 缓存名称
- key:缓存的key,可以为空,指定需按照SPEL表达式编写(#{})
- condition:缓存条件
- allEntries:是否清空所有的缓存,默认是false
- beforeInvocation:是否在方法执行前就清空,默认为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存
7.Redis持久化
Redis是内存数据库,如果不将内存中的数据保存到磁盘,一旦服务器进程结束,数据也会消失
1、RDB
RDB=(Redis DataBase)
在指定的时间间隔,将内存中的数据集快照写入数据库,恢复时,直接读取快照文件进行数据恢复
在默认情况下,Redis将数据库快照保存在dump.rdb 二进制文件中,文件名可以在配置文件中自定义
工作原理
- Redis 调用forks。同时拥有父进程和子进程。
- 子进程将数据集写入到一个临时 RDB 文件中。
- 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件
触发机制
- save的规则满足的情况下,会自动触发rdb原则
- 执行flushall命令,也会触发我们的rdb原则
- 退出redis,也会自动产生rdb文件
优缺点
优点:
- 适合大规模的数据恢复
- 对数据的完整性要求不高
缺点:
- 需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次修改的数据就没有了。
- fork进程的时候,会占用一定的内容空间。
2、AOF
AOF=(Append Only File)
将我们的所有命令都记录下来,恢复的时候就把这个文件全部在执行一遍!
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件
但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件
的内容将写指令从前到后执行一次以完成数据的恢复工作
优缺点
优点:
1、每一次修改都同步,文件的完整会更加好!
2、每秒同步一次,可能会丢失一秒的数据
3、从不同步,效率最高的!
缺点:
1、相对于数据文件来说,aof远远大于 rdb,修复的速度也比 rdb慢!
2、Aof 运行效率也要比 rdb 慢,所以我们redis默认的配置就是rdb持久化
8.Redis主从复制
概念
主从复制,是指将一台Reids服务器的数据,复制到其他的Redis服务器上
前者称之为主节点(Master/Leader)
后者称之为从节点(Slave/Follower)
数据的复制是单向的,只能由主节点复制到从节点,一般主节点以写为主,从节点以读为主
作用
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
- 故障恢复:当主节点服务器发生故障时,从节点可以暂时代替主节点提供服务
- 负载均衡:在主从复制的基础上,配合读写分离,主节点进行写操作,从节点进行读操作,分担服务器负载
进行测试
主节点6379 从节点6380
# info replication 查看当前库的信息
127.0.0.1:6379[3]> info replication
role:master #角色
connected_slaves:0 #从机数量
master_replid:e204c88ff98046164f30edb278139d15c4577afb
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
#使用`SLAVEOF host port`就可以为从机配置主机了 还可以通过配置文件进行配置
##使用SLAVEOF no one 断开关系
127.0.0.1:6380> slaveof 127.0.0.1 6379
OK
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:5
master_sync_in_progress:0
slave_repl_offset:14
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:a38efbc915f817bee7e9365922be53ecc4e5bbb8
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:14
127.0.0.1:6379[3]> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=490,lag=0
master_replid:a38efbc915f817bee7e9365922be53ecc4e5bbb8
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:490
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:490
使用规则
- 从机只能读不能写,主机可读可写,多用于写
127.0.0.1:6380> set k123 v123
(error) READONLY You can't write against a read only replica.
#主机读写
127.0.0.1:6379[3]> set k123 v123
OK
127.0.0.1:6379[3]> get k123
"v123"
#从机读
127.0.0.1:6380> select 3
OK
127.0.0.1:6380[3]> keys *
1) "k123"
127.0.0.1:6380[3]> get k123
"v123"
9.Redis使用规范
1、key命名设计
规范性:以业务名(或数据库名)为前缀,用冒号分隔开,如(数据库名:表名:id)
简洁性:在保证语义的前提下,控制key的长度,当key数量较多时,所占的内存同样不可忽视
注意:不要包含特殊字符,如空格、换行、单引号、双引号、转义字符等
2、value设计
拒绝bigkey,防止慢查询,单个string类型的值大小控制在10kb以内,hash、lish、set、zset元素个数不要超过5000
3、控制key的生命周期
建议使用expire设置过期时间,尽量打散过期时间,不要集中过期
4、禁用命令
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。因为redis是单线程执行,容易导致线上不可用.
5、使用批量命令提高效率
原生命令:例如mget、mset
6、不建议过多使用Redis事务功能