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;@BeforeEachpublic void init() {jedis = new Jedis("localhost", 6379); // 初始加载,先连接Redisjedis.select(0); // 选择仓库}@Testpublic void testString() {String result = jedis.set("name", "wyt");System.out.println(result);System.out.println(jedis.get("name"));}@Testpublic 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);}@AfterEachpublic 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.1port: 6379lettuce:pool:max-active: 8max-idle: 8min-idle: 0max-wait: 1000
RedisTemplate的序列化方式
API
RedisTemplate是SpringDataRedis封装的一个对象,类似于JDBCTemplate,都是一个用来操作数据库中数据的工具类。
RedisTemplate底层默认使用的是JDK序列化器,JDK序列化器是采用ObjectOutputStream实现的,这种实现方式存在缺点:存入的数据可读性差、内存占用较大。
这时候我们可以自定义序列化方式
@Configuration
public class RedisConfig {@Beanpublic 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());// 设置valueredisTemplate.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);
}