Redis
Redis
一、Redis的安装和启动和关闭
安装:在Linux中使用 wegt 命令下载,或使用 xftp 软件传入Linux中,使用如下命令解压安装
1 | //使用wegt命令下载或使用xftp传输文件 |
启动
命令 | 作用 |
---|---|
redis-benchmark | 性能测试工具,可以在自己本子运行,看看自己本子性能如何 |
redis-check-aof | 修复有问题的AOF文件,rdb和aof后面讲 |
redis-check-dump | 修复有问题的dump.rdb文件 |
redis-sentinel | Redis集群使用 |
redis-server(不建议使用) | Redis服务器启动命令 |
redis-cli | 客户端,操作入口 |
前台启动(不推荐)
使用 redis-server ,窗口不能关闭,服务器停止
后台启动(推荐)
- 备份 redis.cong文件,这里拷贝一份文件到 etc目录下面
- 需要将 redis.conf 文件中的 daemonize no 改成yes
- 启动命令:redis-server /etc/redis.conf
- 客户端访问:redis-cli / redis-cli —raw 【 方式一:不能读取中文,方式二,可以读取中文】
关闭
单实例关闭:redis-cli shutdown:
redis-cli 进入终端关闭:
多实例关闭:指定端口号关闭
方式一:
1
redis-cli -p 端口号 shuwdown
方式二:
1
2
3
4ps -ef | grep '进程名字'
例如:ps -ef | grep tomcat
kill -9 `通过进程查询id号,通过查到的id删除j`
二、常用五大数据类型
redis数据库常用操作命令:http://www.redis.cn/commands.html
Redis键值(key)
命令 | 作用 |
---|---|
keys 【keys 2】 | 查询当前库所有key 【查询指定库】 |
exists key | 判断某个key是否存在 |
type key | 查看你的key是什么类型 |
del key | 删除指定的key数据 |
unlink key | 根据value选择非阻塞删除,仅将keys从keyspace元数据中删除,真正的删除会在后续异步操作。 |
expire key 10 | 10秒中:为指定的key设置过期时间 |
ttl key | 查看还有多少秒过期,-1 表示永不过期,-2 表示已过期 |
select 指定数据库序号 |
切换数据库 |
dbsize | 查看当前数据库的key的数量 |
flushdb | 清空当前数据库 |
flushall | 通杀全部数据库 |
Redis 字符串(String)
String是Redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value。
String类型是二进制安全的。意味着Redis的string可以包含任何数据。比如jpg图片或者序列化的对象。
String类型是Redis最基本的数据类型,一个Redis中字符串value最多可以是512M
命令 | 作用 |
---|---|
set |
添加键值对 |
get |
查询对应的键值 |
append |
将给定的 |
strlen |
获取值的长度 |
incr |
将key中存储的数字值增1,只能对数字操作,如果为空,新增值为1 |
decr |
将key中存储的数字值减1,只能对数字值操作,如果为空,新增值为-1 |
incrby / decrby |
将key中存储的数字值增减,自定义步长 |
mset |
同时设置一个或多个key-value |
mget |
同时获取一个或多个value |
msetnx |
同时设置一个或多个key-value,当所有的key都不存在时将插入,如果有一个key已存在,所有的key将插入失败 |
getrange |
获得值的范围,类似Java中substring,前包,后包 |
setrange |
用 |
setex |
设置键的同时,设置过期时间,单位秒 |
getset |
以旧换新,设置了新值的同时获得旧值 |
set 命令中的属性值的问题
属性 | 作用 |
---|---|
*EX | key的超时秒数 |
*PX | key的超时秒数,与EX互斥 |
*NX | 当数据库中的key不存在时,可以将key-value添加到数据库中 |
*XX | 当数据库中的key存在时,可以将key-value添加到数据库中,与NX参数互斥 |
Redis列表(List)
在Redis中列表是一个双向链表
命令 | 作用 |
---|---|
lpush / rpush |
从左边 / 右边插入一个或多个值 |
lpop / rpop |
从左边 / 右边吐出一个值。值在键在,值亡键亡,值取出就没了 |
rpoplpush |
从 |
lrange |
按照索引下标获得元素(从左到右) |
lrange mylist 0 -1 | 0 左边第一个,-1 右边第一个 ( 0 -1)表示获取所有 |
lindex |
按照索引下标获得元素(从左到右) |
lien |
获得列表长度 |
linsert |
在 |
lrem |
从左边删除第n个value(从左到右) |
lset |
将列表key下标为index的值替换成为value |
Redis集合(Set)
当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择
sadd |
将一个或多个 member 元素加入到集合 key 中,已经存在的 member 元素将被忽略 |
---|---|
smembers |
取出该集合的所有值。 |
sismember |
判断集合 |
scard |
返回该集合的元素个数。 |
srem |
删除集合中的某个元素。 |
spop |
随机从该集合中吐出一个值。 |
srandmember |
随机从该集合中取出n个值。不会从集合中删除 。 |
smove | 把集合中一个值从一个集合移动到另一个集合 |
sinter |
返回两个集合的交集元素。 |
sunion |
返回两个集合的并集元素。 |
sdiff |
返回两个集合的差集元素(key1中的,不包含key2中的) |
三、SpringBoot整合Redis
导入Jar包
1
2
3
4
5
6
7
8
9
10
11
12<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>配置文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18#Redis服务器地址
spring.redis.host=192.168.140.136
#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
#使用springboot集群需要设置spring.redis.cluster.nodes的值
spring.redis.cluster.nodes=192.168.162.132:6380
编写配置类:
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
65
66
67
68package com.lf.config;
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.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;
/**
* Redis的配置类
* User: 李飞
* Date: 2021/5/27
* Time: 8:28
*/
public class RedisConfig {
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;
}
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
22package com.lf.controller;
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.RestController;
public class RedisController {
private RedisTemplate redisTemplate;
public String testRedis() {
//设置值到redis
redisTemplate.opsForValue().set("name", "lucy");
//从redis获取值
String name = (String) redisTemplate.opsForValue().get("name");
return name;
}
}
四、Redis_事务
五、秒杀问题
1、每次只有一个用户进行秒杀
导包:看使用SpringMVC还是SpringBoot再导其他包
1
2
3
4<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>Java代码
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
53public boolean doSecKill( String prodId, String userId) {
//1、userId和prodId非空判断
if (userId == null || prodId == null) {
return false;
}
//2、连接jedis
Jedis jedis = new Jedis("192.168.162.132", 6379);
//3 拼接key
// 3.1 库存key
String kcKey = "sk:" + prodId + ":qt";
// 3.2 秒杀成功用户key
String userKey = "sk:" + prodId + ":user";
//监视库存
jedis.watch(kcKey);
Object key = jedis.get(kcKey);
//4、获取库存,如果库存null,秒杀还没有开始
if (key == null) {
System.out.println("秒杀还没有开始,请等待");
jedis.close();
return false;
}
//5、判断用户是否重复秒杀操作,判断用户的key是否已经存在了,存在则不能秒杀了
if (jedis.sismember(userKey, userId)) {
System.out.println("已经秒杀成功,不能重复秒杀");
jedis.close();
return false;
}
//6、判断如果剩余商品数量,库存数量小于1,秒杀结束
if (Integer.parseInt(jedis.get(kcKey)) < 1) {
System.out.println("秒杀已结束");
jedis.close();
return false;
}
//7、秒杀过程,将用户的key存入set集合中,set集合中的值不能有重复的
jedis.sadd(userKey, userId);
System.out.println("秒杀成功");
//7.1、库存-1,decr:使数字类型的数据减 1
jedis.decr(kcKey);
jedis.close();
//7.2、把秒杀成功用户添加到清单里面
return true;
}
2、模拟高并发下的场景进行秒杀
模拟多个用户同时进行秒杀
在高并发的场景下面可能会出现的问题和解决方案
模拟高并发:在Linux中ab模拟测试工具:
1
2
3
4
5
6
7
8
9
10
11
12
13
14#选择安装
yum install httpd-tools
#使用:
ab
-n 填写请求数量
-c 填写并发数量
-p 填写提交的参数,可以传入文件
-T 提交的参数的类型,就是表单中enctype的值
最后再填写请求的路径
#例:
ab -n 1000 -c 100 -p ~/postfile -T application/x-www-form-urlencoded http://http://192.168.58.1/:8080/
ab -n 1000 -c 100 http://192.168.58.1:8080/spike/1/1
解决库存超卖问题,这种方法虽然解决了库存超卖问题,但是还是会出现库存遗留问题,还是需要使用Lua脚本来解决库存遗留问题:
1
2
3
4
5
6
7
8
9
10
11#使用监听和事务,事务监听在事务之前就要使用
#进行键监听
jedis.watch('需要监听的key');
#开启乐观锁,在需要进行事务操作的部分将使用原来的jedis使用为Transaction来进行操作
Transaction multi = jedis.multi();
#使用返回的multi来使用乐观锁
multi.sadd(key,value);
#没有问题之后执行exec方法提交执行的语句
List<Object> results = multi.exec();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
65
66
67
68
69
70
71/**
* 该方法会出现库存遗留问题
*/
public boolean doSecKill( { String prodId, String userId)
//userId和prodId非空判断
if (userId == null || prodId == null) {
return false;
}
userId = String.valueOf(new Random().nextInt());
//连接jedis
//Jedis jedis = new Jedis("192.168.162.132", 6379);
//获取连接池
JedisPool jedisPool = JedisPoolUtil.getJedisPoolInstance();
//通过连接池获取jedis对象
Jedis jedis = jedisPool.getResource();
//3 拼接key。 3.1 库存key
String kcKey = "sk:" + prodId + ":qt";
// 3.2 秒杀成功用户key
String userKey = "sk:" + prodId + ":user";
//增加乐观锁,监视库存
jedis.watch(kcKey);
Object key = jedis.get(kcKey);
//4、获取库存,如果库存null,秒杀还没有开始
if (key == null) {
System.out.println("秒杀还没有开始,请等待");
jedis.close();
return false;
}
//5、判断用户是否重复秒杀操作
if (jedis.sismember(userKey, userId)) {
System.out.println("已经秒杀成功,不能重复秒杀");
jedis.close();
return false;
}
//6、判断如果商品数量,库存数量小于1,秒杀结束
if (Integer.parseInt(jedis.get(kcKey)) < 1) {
System.out.println("秒杀已结束");
jedis.close();
return false;
}
//7、秒杀过程
//使用事务
Transaction multi = jedis.multi();
//组队操作
multi.decr(kcKey);
multi.sadd(userKey, userId);
//执行
List<Object> results = multi.exec();
if (results == null || results.size() == 0) {
System.out.println("秒杀失败了 ..... ");
jedis.close();
return false;
}
//秒杀成功
System.out.println("秒杀成功");
jedis.close();
return true;
}
解决高并发下的库存遗留问题,用户的数量超过库存的数量,结果因为乐观锁导致库存没有卖完,使用
Lua
脚本语言来解决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
55import org.slf4j.LoggerFactory;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
public class SpikeRedisByScript {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(SpikeRedisByScript.class);
//这是一段Lua脚本
static String secKillScript ="local userid=KEYS[1];\r\n" +
"local prodid=KEYS[2];\r\n" +
"local qtkey='sk:'..prodid..\":qt\";\r\n" +
"local usersKey='sk:'..prodid..\":usr\";\r\n" +
"local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" +
"if tonumber(userExists)==1 then \r\n" +
" return 2;\r\n" +
"end\r\n" +
"local num= redis.call(\"get\" ,qtkey);\r\n" +
"if tonumber(num)<=0 then \r\n" +
" return 0;\r\n" +
"else \r\n" +
" redis.call(\"decr\",qtkey);\r\n" +
" redis.call(\"sadd\",usersKey,userid);\r\n" +
"end\r\n" +
"return 1" ;
static String secKillScript2 =
"local userExists=redis.call(\"sismember\",\"{sk}:0101:usr\",userid);\r\n" +
" return 1";
public static boolean doSecKill(String uid, String prodid) throws IOException {
JedisPool jedispool = JedisPoolUtil.getJedisPoolInstance();
Jedis jedis = jedispool.getResource();
String sha1 = jedis.scriptLoad(secKillScript);
Object result = jedis.evalsha(sha1, 2, uid, prodid);
String reString = String.valueOf(result);
if ("0".equals(reString)) {
System.err.println("已抢空!!");
} else if ("1".equals(reString)) {
System.out.println("抢购成功!!!!");
} else if ("2".equals(reString)) {
System.err.println("该用户已抢过!!");
} else {
System.err.println("抢购异常!!");
}
jedis.close();
return true;
}
}
持久化-RDB
持久化-AOF
AOF(Append Only File):追加文件,不允许改写文件,记录编写的指令和数据
- RDB默认开启的,AOF默认是不开启的,需要在配置文件中开启,将appendonly的值设置为 yes,
- AOF的文件生成的路径是和RDB文件的生成路径是一致的
- 如果AOF和RDB同时开启,系统默认读取AOP的数据
评论