Redis通用的命令和数据结构
Redis通用命令
KEYS
:查看符合模板的所有key,可以使用通配符*(不建议在生产环境设备上使用)
DEL
:删除一个指定的key,返回值为一个integer类型的值,表示删除的数量
EXISTS
:判断key是否存在,返回值为一个integer类型的值,表示存在的数量
EXPIRE
:给一个key设置有效期,有效期到期时该key会被自动删除
TTL
:查看一个KEY的剩余有效期,返回值为一个integer类型的值,-1表示永久有效,-2表示不存在或失效,其它表示存活的秒数
Redis常见数据结构
String类型
string类型的常见命令
SET
:添加或者修改已经存在的一个String类型的键值对
GET
:根据key获取String类型的value
MSET
:批量添加多个String类型的键值对
MGET
:根据多个key获取多个String类型的value
INCR
:让一个整型的key自增1
INCRBY
:让一个整型的key自增并指定步长,例如:incrby num 2 让num值自增2
INCRBYFLOAT
:让一个浮点类型的数字自增并指定步长
SETNX
:添加一个String类型的键值对,前提是这个key不存在,否则不执行(等价于set + nx)
SETEX
:添加一个String类型的键值对,并且指定有效期(等价于set+ex)
关于key的层级关系,我们可以加:来对key进行分层,如下图,最后我们使用图形化工具,就可以看到在heima层的user层下有两个用户1和用户2
127.0.0.1:6379> set heima:user:1 '{"id":1,"name":"wyt"}'
OK
127.0.0.1:6379> set heima:user:2 '{"id":2,"name":"wyt"}'
OK
Hash类型
Hash类型的value类似于Java中的HashMap,它的value就类似一个无序字典
当我们使用String类型来存储对象序列化后的JSON字符串的时候,我们需要修改其中一个字段的值的时候非常不方便
但是当我们使用hash类型来存储的时候,hash可以将每个对象的每个字段独立存储,方便操作
hash的常见命令
HSET key field value
:添加或者修改hash类型key的field的值
HGET key field
:获取一个hash类型key的field的值
HMSET key field1 value1 field2 value2 ...
:批量添加多个hash类型key的field的值
HMGET key field1 field2 ...
:批量获取多个hash类型key的field的值
HGETALL key
:获取一个hash类型的key中的所有的field和value
HKEYS key
:获取一个hash类型的key中的所有的field
HVALS
:获取一个hash类型的key中的所有的value
HINCRBY
:让一个hash类型key的字段值自增并指定步长,例如,hincrby heima:user:4 age -2 (age自减2)
HSETNX
:添加一个hash类型的key的field值,前提是这个field不存在,否则不执行
List类型
Redis的List类型与Java中的LinkedList类似,可以看做是一个双向链表结构,既可以支持正向检索也可以支持反向检索。
特点也与LinkedList类似:有序、元素可重复、插入和删除快、查询速度一般。常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等
List常见命令
LPUSH key element ...
:向列表左侧插入一个或多个元素
LPOP num
:从左侧开始取,取出并移除num个元素,数量不够就返回nil
RPUSH key element ...
:向列表右侧插入一个或多个元素
RPOP num
:从右侧开始取,取出并移除num个元素,数量不够就返回nil
LRANGE key star end
:返回一段角标范围内的所有元素
BLPOP和BRPOP
:与LPOP和RPOP类似,只不过在没有元素时等待指定时间,而不是直接返回nil
备注:左边索引为1,右边依次递增
Set类型
Redis的set结构和Java中的HashSet类似,可以看做是一个value为null的HashMap,因为也是一个Hash表,因此也具备HashSet的特点:无序、元素不可重复、查找快、支持交集、并集、差集等功能。可以用于交友列表显示共同好友的功能
Set数据的常见命令:
SADD key member ...
:向set中添加一个或多个元素
SREM key member ...
: 移除set中的指定元素
SCARD key
: 返回set中元素的个数
SISMEMBER key member
:判断一个元素是否存在于set中
SMEMBERS
:获取set中的所有元素
SINTER key1 key2 ...
:求key1与key2的交集
SDIFF key1 key2 ...
:求key1与key2的差集
SUNION key1 key2 ...
:求key1和key2的并集
SortedSet类型
Redis的SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加 hash表。SortedSet具有的特点:可排序元素、不重复、查询速度快。因为SortedSet的可排序特性,经常被用来实现排行榜这样的功能
SortedSet常用命令:
ZADD key score membe
r:添加一个或多个元素到sorted set ,如果已经存在则更新其score值
ZREM key member
:删除sorted set中的一个指定元素
ZSCORE key member
: 获取sorted set中的指定元素的score值
ZRANK key member
:获取sorted set 中的指定元素的排名
ZCARD key
:获取sorted set中的元素个数
ZCOUNT key min max
:统计score值在给定范围内的所有元素的个数
ZINCRBY key increment member
:让sorted set中的指定元素自增,步长为指定的increment值
ZRANGE key min max
:按照score排序后,获取指定排名范围内的元素
ZRANGEBYSCORE key min max
:按照score排序后,获取指定score范围内的元素
ZDIFF、ZINTER、ZUNION
:求差集、交集、并集
注意:所有的排名默认都是升序,如果要降序则在命令的Z后面添加REV即可
其他的数据类型会放在黑马点评实战项目中介绍使用
Redis的Java客户端
这里我们平常只用Jedis
Jedis的使用
首先是Maven坐标
<!--jedis依赖-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
<!--junit依赖-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
练手
public class test {
private Jedis jedis;
@BeforeEach
public void init() {
jedis = new Jedis("localhost", 6379); // 初始加载,先连接Redis
jedis.select(0); // 选择仓库
}
@Test
public void testString() {
String result = jedis.set("name", "wyt");
System.out.println(result);
System.out.println(jedis.get("name"));
}
@Test
public void testHashMap() {
jedis.hset("user:1","name","jeck");
jedis.hset("user:1","age","12");
Map<String, String> stringStringMap = jedis.hgetAll("user:1");
System.out.println(stringStringMap);
}
@AfterEach
public void destroy() {
if (jedis != null) {
jedis.close(); // 测试结束关闭连接防止内存泄漏
}
}
}
连接池
在平常我们一般不会使用直接连接的方式,而是使用连接池,那么什么是连接池呢,就是在一个容器内,放置了许多连接,多少个连接我们可以进行定义。下面是一些配置名。
最小连接
:最小连接数是连接池一直保持的数据连接,可以理解为即使没有连接请求,也会有这么多连接对象存在最大连接
:最大连接数是连接池能申请的最大连接数。如果数据连接请求超过此数,后面的数据连接请求将被加入到等待队列中最小空闲连接
:连接池用于预留的连接对象,当空闲连接小于最小空闲连接时,会另外创建连接,直到空闲连接等于或大于最小空闲连接(一般都会设置为0)最大空闲连接
:连接池用于限制空闲连接对象,当空闲连接大于最大空闲连接时,会将多余的空闲连接销毁最大等待时间
:当连接时间超过了最大等待连接时间,就会放弃等待然后直接抛异常
配置文件
public class JedisConnectionFactory {
public static final JedisPool jedisPool;
static {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 最大连接数
jedisPoolConfig.setMaxTotal(10);
// 最大空闲数
jedisPoolConfig.setMaxIdle(10);
// 最小空闲连接
jedisPoolConfig.setMinIdle(0);
// 最多等待1000毫秒
jedisPoolConfig.setMaxWaitMillis(1000);
jedisPool = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379,1000);
}
public static Jedis getJedis() {
return jedisPool.getResource();
}
}
SpringBoot整合Redis
这里我们使用Spring Data Redis
Maven坐标
<dependencies>
// Spring Data Redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</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>
// 连接池
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependencies>
~~~~ 那么我们为什么非要用连接池呢,首先redis是单线程的,但是当我多个同时连接redis时是不是要排队吗?然后执行命令,和我们执行单个全局连接直接去一个执行redis指令,感觉差不多吗?此言差异,虽然redis是单线程的,但这并不表示使用连接池不能提高效率,只是不能通过多线程的方式提高效率。
~~~~ redis连接池单连接的效能提高很多,要了解为什么redis连接池能够这么大幅的提高性能,就要了解单连接的性能瓶颈在哪里?单线程不是redis的性能瓶颈,对redis而言,有两个性能所在,一个是计算机执行命令的速度,另一个是网络通信性。很显然,执行命令速度不是redis的性能瓶颈,通信才是其瓶颈。据我所知,redis每秒可执行10万次,因此,对于客户端将若干条命令传输给redis服务,命令执行时间和通信时间大概是比等于0,将设以1s举例,几条命令传输时间为40ms,而每秒可执行10万条命令,那么这些命令只是花费1ms来执行,其他39ms时间无事可做,等待下一个命令的到来,其中的间隙,造成redis的闲置。
~~~~ 综上,要提高redis的性能,可以降低单位时间内的通信成本,那么连接池就是一个不错的选择。客户端使用连接词+多线程方案,使得redis服务闲置时间降低,极大的提高了服务效率。
配置文件
spring.application.name: SpringBoot_Redis
spring:
data:
redis:
host: 127.0.0.1
port: 6379
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: 1000
RedisTemplate的序列化方式
API
RedisTemplate是SpringDataRedis封装的一个对象,类似于JDBCTemplate,都是一个用来操作数据库中数据的工具类。
RedisTemplate底层默认使用的是JDK序列化器,JDK序列化器是采用ObjectOutputStream实现的,这种实现方式存在缺点:存入的数据可读性差、内存占用较大。
这时候我们可以自定义序列化方式
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 创建对象
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 设置连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 创建JSON序列化工具
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// 设置key的序列化器
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setHashKeySerializer(RedisSerializer.string());
// 设置value
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
return redisTemplate;
}
}
key 用字符串的方式存储,value用json的格式存储
但是以这样的形式存储的对象,占用内存很大,每一次存储都带着@Class
StringRedisTemplate序列化方式
为了节省内存空间,就不能使用JSON序列化器来处理Value,而是统一使用String序列化器,这样就只能存String类型的key和value,而面对Object类型的value时,就手动完成对象的序列化和反序列化。那么是不是需要我们去重新定义一个RedisTemplate呢?答案是No,Spring默认提供了一个StringRedisTemplate类,它的key和value都默认是采用String序列化的方式
@Test
void testStringRedisTemplate2() throws JsonProcessingException {
User user = new User("wyt",12);
String json = mapper.writeValueAsString(user);
stringRedisTemplate.opsForValue().set("user:101",json);
String jsonUser = stringRedisTemplate.opsForValue().get("user:101");
User o = mapper.readValue(jsonUser,User.class);
System.out.println(o);
}
@Test
void testStringRedisTemplate3() throws JsonProcessingException {
stringRedisTemplate.opsForHash().put("user:102","name","谷歌");
stringRedisTemplate.opsForHash().put("user:102","age","12");
Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries("user:102");
System.out.println(entries);
}