Redis6实用笔记
NoSQL数据库简介
前言
随着Web2.0的时代的到来,用户访问量大幅度提升,同时产生了大量的用户数据。加上后来的智能移动设备的普及,所有的互联网平台都面临了巨大的性能挑战。
解决CPU及内存压力
解决IO压力
概述
NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,泛指非关系型的数据库。 NoSQL 不依赖业务逻辑方式存储,而以简单的key-value模式存储。因此大大的增加了数据库的扩展能力。常用于:
- 对数据高并发的读写;
- 海量数据的读写;
- 对数据高可扩展性的;
典型的NoSQL数据库
- Memcache
- Redis
- MongoDB
Redis简介
- Redis是一个开源的key-value存储系统,它支持存储的类型相对较多,包括string(字符串)、list(链表)、set(集合)、set(sorted set –有序集合)和hash(哈希类型)
- Redis支持各种不同方式的排序,数据都是缓存在内存
- Redis会周期性的把更新的数据写入磁盘或者把修改操作写入记录文件中
- 实现了master-slave(主从)同步
- 应用场景:
- 配合关系型数据库做高速缓存
- 分布式架构,做session共享
- 存储持久化数据
- 排行榜
- 时效性数据
- 计数器、秒杀
- 配合关系型数据库做高速缓存
Redis安装
官网下载:https://redis.io/(一般为Linux版本,需要安装到Linux虚拟机下)
打开Linux虚拟机,连接网络,在windows下使用
xftp
将下载的压缩文件传输到Linux的/opt
目录下在Linux下安装
gcc
环境(如果为Centos6等低版本Linux,则无法直接使用yum,可自行搜索解决方案)1
yum install gcc
检验gcc环境
1
gcc --version
解压redis压缩文件
1
[root@hadoop1 opt]# tar -zxvf redis-6.2.6.tar.gz
进入解压后的文件
1
[root@hadoop1 opt]# cd redis-6.2.6
执行
make
编译命令1
[root@hadoop1 opt]# make
再执行安装
1
[root@hadoop1 redis-6.2.6]# make install
安装完成可查看安装目录
1
2
3
4[root@hadoop1 redis-6.2.6]# cd /usr/local/bin
[root@hadoop1 bin]# ls
redis-benchmark redis-check-rdb redis-sentinel
redis-check-aof redis-cli redis-server- 其中:
- redis-server:Redis服务器启动命令
- redis-cli:客户端,操作入口
- 其中:
启动
前台启动
直接在控制台启动,关闭控制台就会自动关闭,因此不推荐,方式:
1 | [root@hadoop1 bin]# redis-server |
后台启动
拷贝一份
redis.conf
到其他目录1
[root@hadoop1 redis-6.2.6]# cp redis.conf /etc/redis.conf
修改启动设置
- 到
/etc
目录下cd /etc
- 编辑后台配置文件
[root@hadoop1 etc]# vi redis.conf
- 修改
daemonize
配置为yes
(可通过搜索快速查找)
- 到
进入安装目录下启动
cd /usr/local/bin
启动
1
[root@hadoop1 bin]# redis-server /etc/redis.conf
访问/测试
1
2
3
4redis-cli
ping
exit #退出
redis-cli shutdown #关闭
Redis基本知识
- 默认端口:6379
- 默认16个数据库,类似数组下标从0开始,初始默认使用0号库
- 使用命令
select #
来切换数据库。如: select 8 - Redis是单线程+多路IO复用技术
key键操作
1 | [root@hadoop1 ~]# redis-cli |
清除
1 | flushdb #清空当前库 |
数据类型
String
String是Redis最基本的类型,是二进制安全的。意味着Redis的string可以包含任何数据。比如jpg图片或者序列化的对象。
一个Redis中字符串value最多可以是512M
常用命令
1
2
3
4
5
6
7
8
9
10set <key> <value> #添加键值对
get <key> #查询对应键值
append <key> <value> #将给定的<value> 追加到原值的末尾
strlen <key> #获得值的长度
setnx <key> <value> #只有在 key 不存在时 设置 key 的值
mget <key1> <key2> <key3> #同时设置一个或多个 key-value对
msetnx <key1> <value1> <key2> <value2> #同时设置一个或多个 value
getrange <key> <起始位置> <结束位置> #获得值的范围,类似java中的substring
setrange <key> <起始位置> <value> #用 <value> 覆写<key>所储存的字符串值,从<起始位置>开始(索引从0开始)。
setex <key> <过期时间> <value> #设置键值的同时,设置过期时间,单位秒。原子性:指不会被线程调度机制打断
内部实际分配的空间一般要高于实际字符串长度。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为512M。
List
单键多值,Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部或者尾部,底层实际是个双向链表.
常用命令
1
2
3
4
5
6
7lpush/rpush <key> <value1> <value2> <value3> # 从左边/右边插入一个或多个值
lpop/rpop <key> #从左边/右边弹出一个值。值在键在,值光键亡
lrange <key> <start> <stop> #按照索引下标获得元素(从左到右)
lindex <key> <index> #按照索引下标获得元素(从左到右)
llen <key> #获得列表长度
lrem <key> <n> <value> #从左边删除n个value(从左到右)
lset <key> <index> <value> #将列表key下标为index的值替换成value连续存储和分散存储结合
Set
不存在重复数据
常用命令
1
2
3
4
5
6
7
8sadd <key> <value1> <value2> #将一个或多个元素加入到集合 key 中,已经存在的元素将被忽略
smembers <key> #取出该集合的所有值
sismember <key> <value> #判断集合<key>是否为含有该<value>值,有则为1,没有则为0
scard <key> #返回该集合的元素个数
srem <key> <value1> <value2> #删除集合的一些元素
sinter <key1> <key2> #返回两个集合的交集元素
sunion <key1> <key2> #返回两个集合的并集元素
sdiff <key1> <key2> #返回两个集合的差集元素(key1中的,却不在key2中的)基于哈希表实现,但其value指向同一个对象
Hash
Hash是一个String类型的field和value的映射表,hash特别适合用于存储对象。类似Java里面的Map<String,Object>.
常用命令
1
2
3
4
5
6
7hset <key> <field> <value> #给<key>集合中的<field>键赋值<value>
hget <key1> <field> #从<key1>集合<field>取出value
hmset <key1> <field1> <value1> <field2> <value2> ... #批量设置hash的值
hexists <key1> <field> #查看哈希表 key 中,给定域 field 是否存在。
hkeys <key> #列出该hash集合的所有field
hvals <key> #列出该hash集合的所有value
hsetnx <key> <field> <value> #将哈希表key中的域field的值设置为value,当且仅当域field不存在
Zset
和Set的不同之处是有序集合Zset的每个成员都关联了一个评分,这个评分被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复了的。
常用命令
1
2
3
4zadd <key> <score1> <value1> <score2> <value2> #将一个或多个 member 元素及其 score 值加入到有序集 key 当中
#返回有序集 key 中,下标在<start><stop>之间的元素带WITHSCORES,可以让分数一起和值返回到结果集。
zrange <key> <start> <stop> [WITHSCORES]
zrangebyscore key min max #返回有序集key中,所有score值介于min和max之间的成员。有序集成员按 score 值递增(从小到大)次序排列。基于哈希和跳跃表
Bitmaps
可以把Bitmaps想象成一个以位为单位的数组, 数组的每个单元只能存储0和1, 数组的下标在Bitmaps中叫做偏移量;
常用命令:
1
2
3
4setbit <key> <offset> <value> #设置Bitmaps中某个偏移量的值(0或1)
getbit <key> <offset> #获取Bitmaps中某个偏移量的值
bitcount <key> [start end] #统计字符串从start字节到end字节比特值为1的数量
bitop and(or/not/xor) <destkey> [key…] #bitop是一个复合操作, 它可以做多个Bitmaps操作并将结果保存在destkey中
HyperLogLog
用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
常用命令:
1
2
3pfadd <key> <element> [element ...] #添加指定元素到 HyperLogLog 中
pfcount <key> [key ...] #计算的近似基数
pfmerge <destkey> <sourcekey> [sourcekey ...] #将一个或多个HLL合并后的结果存储在另一个HLL中
Geospatial
元素的2维坐标,在地图上就是经纬度。redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作。
常用命令:
1
2
3
4geoadd <key> <longitude> <latitude> <member> [longitude latitude member...] #添加地理位置(经度,纬度,名称)
geopos <key> <member> [member...] #获得指定地区的坐标值
geodist <key> <member1> <member2> [m|km|ft|mi] #获取两个位置之间的直线距离
georadius <key> <longitude> <latitude> radius m|km|ft|mi #以给定的经纬度为中心,找出某一半径内的元素
配置文件
单位
- 支持bytes,不支持bit
网络配置
- 默认情况bind=127.0.0.1只能接受本机的访问请求,不写的情况下,无限制接受任何ip地址的访问;
- 如果开启了protected-mode,那么在没有设定bind ip且没有设密码的情况下,Redis只允许接受本机的响应
- tcp-backlog,backlog队列总和=未完成三次握手队列 + 已经完成三次握手队列。在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。
- timeout:一个空闲的客户端维持多少秒会关闭,0表示关闭该功能。即永不关闭。
- tcp-keepalive:对访问客户端的一种心跳检测,每个n秒检测一次。建议设置成60
- daemonize:是否为后台进程
LIMITS限制
- maxclients:设置redis同时可以与多少个客户端进行连接;
- maxmemory:建议必须设置,否则,将内存占满,造成服务器宕机;
其他
- pidfile
- 存放pid文件的位置,每个实例会产生一个不同的pid文件
- dump.rdb
- 在redis.conf中配置文件名称,默认为dump.rdb
发布与订阅
打开一个客户端订阅channel1
1
SUBSCRIBE channel1
打开另一个客户端,给channel1发布消息
1
publish channel1 hello
第一个客户端可以看到发送的消息
Jedis
准备
- 需要在redis.conf中注释掉
bind 127.0.0.1
,且设置protected-mode no
- 关闭防火墙,或设置密码等方式(可自行搜索)(我的Linux为Centos6,则关闭防火墙:
service iptables stop
)
开始
测试连接
添加依赖
1
2
3
4<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14package com.xlh.redis;
import redis.clients.jedis.Jedis;
public class RedisDemo {
public static void main(String[] args) {
//创建jedis对象
Jedis jedis=new Jedis("192.168.243.129",6379);
//测试连接
String log=jedis.ping();
// 成功则会输出pong
System.out.println(log);
}
}
测试操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42@Test
public void test01(){
//创建jedis对象
Jedis jedis=new Jedis("192.168.243.129",6379);
jedis.set("k1", "v1");
jedis.set("k2", "v2");
jedis.set("k3", "v3");
System.out.println(jedis.exists("k1"));
System.out.println(jedis.type("k1"));
System.out.println(jedis.get("k1"));
Set<String> keys = jedis.keys("*");
System.out.println(keys.size());
for (String key : keys) {
System.out.println(key);
}
jedis.close();
}
@Test
public void demo02(){
Jedis jedis=new Jedis("192.168.243.129",6379);
jedis.mset("str1","v1","str2","v2","str3","v3");
System.out.println(jedis.mget("str1","str2","str3"));
jedis.close();
}
@Test
public void demo03(){
Jedis jedis=new Jedis("192.168.243.129",6379);
jedis.hset("hash1","userName","lisi");
System.out.println(jedis.hget("hash1","userName"));
Map<String,String> map = new HashMap<String,String>();
map.put("telphone","13810169999");
map.put("address","atguigu");
map.put("email","abc@163.com");
jedis.hmset("hash2",map);
List<String> result = jedis.hmget("hash2", "telphone","email");
for (String element : result) {
System.out.println(element);
}
jedis.close();
}
验证码案例
输入手机号,点击发送后随机生成6位数字码,2分钟有效
输入验证码,点击验证,返回成功或失败
每个手机号每天只能输入3次
存在不足之处,仅作为学习案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65package com.xlh.redis;
import redis.clients.jedis.Jedis;
import java.util.Random;
public class PhoneCode {
public static void main(String[] args) {
//先执行1
//verifyCode("13243129626");
//再执行2
checkCode("13243129626","085292");
}
//生成6位数字验证码
public static String getCode(){
Random random=new Random();
StringBuilder code= new StringBuilder();
for(int i=0;i<6;i++){
code.append(random.nextInt(10));
}
return code.toString();
}
//验证码存储到redis中并设置过期时间,最多验证三次
public static void verifyCode(String phone){
//连接
Jedis jedis=new Jedis("192.168.243.129",6379);
//拼接key
//验证次数
String countKey=phone+":count";
//验证码
String codeKey=phone+":code";
String count=jedis.get(countKey);
if(count==null){
//第一次发送,则设置发送次数为1
jedis.setex(countKey,24*60*60,"1");
//发送验证码并存储
jedis.setex(codeKey,2*60,getCode());
}
else if(Integer.parseInt(count)<=2){
jedis.incr(countKey);
//发送验证码并存储
jedis.setex(codeKey,2*60,getCode());
}
else {
System.out.println("您的发送次数已经超过三次!");
}
jedis.close();
}
public static void checkCode(String phone,String code){
//连接
Jedis jedis=new Jedis("192.168.243.129",6379);
String j_code=jedis.get(phone+":code");
if(j_code.equals(code)){
System.out.println("ok!");
}
else{
System.out.println("error!");
}
jedis.close();
}
}
Spring Boot整合 Redis
添加依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>application.properties配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#Redis服务器地址
spring.redis.host=192.168.243.129
#Redis服务器连接端口
spring.redis.port=6379
#Redis数据库索引(默认为0)
spring.redis.database= 0
#连接超时时间(毫秒)
spring.redis.timeout=1800000
#连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=20
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-wait=-1
#连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=5
#连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0添加redis配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65package com.xlh.redis;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
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.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
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);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
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);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package com.xlh.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@GetMapping("/1")
public String testRedis() {
//设置值到redis
redisTemplate.opsForValue().set("name","lucy");
//从redis获取值
String name = redisTemplate.opsForValue().get("name");
System.out.println(name);
return name;
}
}
事务
Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。Redis事务的主要作用就是串联多个命令防止别的命令插队。
Multi、Exec、discard
- 从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。
- 组队的过程中可以通过discard来放弃组队。
事务的错误处理
- 组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消
- 执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚
悲观锁
悲观锁就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁
乐观锁就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种机制实现事务的。
特性
- 单独的隔离操作
- 事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
- 没有隔离级别的概念
- 队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
- 不保证原子性
- 事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
持久化
RDB
在指定的时间间隔内将内存中的数据集快照写入磁盘
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能 如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。
优势
- 适合大规模的数据恢复
- 对数据完整性和一致性要求不高更适合使用
- 节省磁盘空间
- 恢复速度快
劣势
- Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
- 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。
- 如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改
AOF
以日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录)。换言之,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
- AOF默认不开启
- AOF和RDB同时开启,系统默认取AOF的数据(数据不会存在丢失)
- 优势
- 备份机制更稳健,丢失数据概率更低
- 可读的日志文本,可以处理误操作
- 劣势
- 比起RDB占用更多的磁盘空间
- 每次写都同步的话,有一定的性能压力
- 恢复备份速度要慢
主从复制
简介
- 主机数据更新后根据配置和策略, 自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主
- 读写分离,性能扩展
- 容灾快速恢复
集群
简介
- Redis 集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。
- Redis 集群通过分区来提供一定程度的可用性: 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。