项目的DEMO代码:https://github.com/heyu52/-spring-cloud
Redis作为当下最火的缓存服务,在实际的应用中有很多,下面我们就在SpringBoot中看看是如何操作的。
Redis 数据类型
Redis支持五种数据类型:
- string(字符串) string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。
string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。
string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB。 - hash(哈希)是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象
- list(列表)列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
- set(集合)Set是string类型的无序集合。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
- zset(sorted set:有序集合) zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。
各个数据类型应用场景
类型 | 简介 | 特性 | 场景 |
---|---|---|---|
String(字符串) | 二进制安全 | 可以包含任何数据,比如jpg图片或者序列化的对象,一个键最大能存储512M | |
Hash(字典) | 键值对集合,即编程语言中的Map类型 | 适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值(Memcached中需要取出整个字符串反序列化成对象修改完再序列化存回去) | 存储、读取、修改用户属性 |
List(列表) | 链表(双向链表) | 增删快,提供了操作某一段元素的API | 1,最新消息排行等功能(比如朋友圈的时间线) 2,消息队列 |
Set(集合) | 哈希表实现,元素不重复 | 1、添加、删除,查找的复杂度都是O(1) 2、为集合提供了求交集、并集、差集等操作 | 1、共同好友 2、利用唯一性,统计访问网站的所有独立ip 3、好友推荐时,根据tag求交集,大于某个阈值就可以推荐 |
Sorted Set(有序集合) | 将Set中的元素增加一个权重参数score,元素按score有序排列 | 数据插入集合时,已经进行天然排序 | 1、排行榜 2、带权重的消息队列 |
接下来我们创建项目
打开项目后,我们查看pom文件,可以看到添加了下面的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
我们要使用redis,我们就必须先配置服务主机,和连接数据一样的道理,我们先在application.properties中配置服务连接
# Redis 数据库索引(默认为 0)
spring.redis.database=0
# Redis 服务器地址
spring.redis.host=192.168.0.123
# Redis 服务器连接端口
spring.redis.port=6379
# Redis 服务器连接密码(默认为空)
spring.redis.password=123456
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=-1
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=0
# 从配置也可以看出 Spring Boot 默认支持 Lettuce 连接池。
缓存配置
在这里可以为 Redis 设置一些全局配置,比如配置主键的生产策略 KeyGenerator,如不配置会默认使用参数 名作为主键。新建配置类RedisConfig
package com.csdn.demo.Config;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
}
这里一定要注意我们使用了注解:@EnableCaching 来开启缓存。
接下来我们测试一下String类型的存和取,新增MyController
package com.csdn.demo.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@Autowired
private RedisTemplate redisTemplate;
@RequestMapping("/GetString")
public String GetStringByKey(String key) {
return redisTemplate.opsForValue().get(key).toString();
}
@RequestMapping("/SetStringByKey")
public void SetStringByKey(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
}
运行项目,先将值存入Redis ,在浏览器输入:http://127.0.0.1:8080/SetStringByKey?key=csdn&value=20170921
从RedisDesktopManager可以看到
我们再从程序中把值给读出来,在浏览器输入:http://127.0.0.1:8080/GetString?key=csdn
这就是最简单的存取操作了。接下来,我们再对其它的类型进行测试。
对象存储
新建model
package com.csdn.demo.model;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String userName;
private String regTime;
public User() {
super();
}
public User(String email, String nickname, String password, String userName, String regTime) {
super();
this.userName = userName;
this.regTime = regTime;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getRegTime() {
return regTime;
}
public void setRegTime(String regTime) {
this.regTime = regTime;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", regTime='" + regTime + '\'' +
'}';
}
}
接下来在控制器中增加测试方法
@RequestMapping("/SetObjByKey")
public void SetObjByKey(){
User user=new User("csdn001","2019");
ValueOperations<String, User> operations=redisTemplate.opsForValue();
operations.set("csdn001", user);
}
@RequestMapping("/GetObjByKey")
public User GetObjByKey(){
ValueOperations<String, User> operations=redisTemplate.opsForValue();
User user=operations.get("csdn001");
return user;
}
运行项目,先将值存入Redis ,在浏览器输入:http://127.0.0.1:8080/SetObjByKey
从RedisDesktopManager可以看到(记得要先Reloda才能查看)
我们再从程序中把值给读出来,在浏览器输入:http://127.0.0.1:8080/GetObjByKey
对象和String其实操作起来,没有任何区别。能存数据,就一定能删除数,我们来写一个删除的方法
@RequestMapping("/DeleteObjByKey")
public String DeleteObjByKey(){
redisTemplate.delete("csdn001");
return "删除成功";
}
运行项目,在浏览器输入:http://127.0.0.1:8080/DeleteObjByKey
我们再从程序中把值给读出来,在浏览器输入:http://127.0.0.1:8080/GetObjByKey
这时候,值就没有了,再可以去RedisDesktopManager进行查看,reload后,数据也不存在了
让数据自动失效
我们如果想把数据存进去,规定的时间内数据就自动失效,可以设置过期时间
@RequestMapping("/SetExpireObjByKey")
public void SetExpireObjByKey(){
User user=new User("csdn001","2019");
ValueOperations<String, User> operations=redisTemplate.opsForValue();
operations.set("csdn001", user,100, TimeUnit.MILLISECONDS);
}
以上代码就设置了有效期为100ms。
Hash(哈希)
@RequestMapping("/SetHashObjByKey")
public void SetHashObjByKey() {
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
hash.put("hash001", "field001", "value001");
hash.put("hash001", "field002", "value002");
hash.put("hash002", "field001", "value003");
}
运行项目,在浏览器输入:http://127.0.0.1:8080/SetHashObjByKey
从RedisDesktopManager可以看到(记得要先Reloda才能查看)
读取数据方法
@RequestMapping("/GetHashObjByKey")
public String GetHashObjByKey() {
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
return hash.get("hash001", "field001").toString();
}
我们再从程序中把值给读出来,在浏览器输入:http://127.0.0.1:8080/GetHashObjByKey
一般我们存储一个键,很自然的就会使用 get/set 去存储,实际上这并不是很好的做法。Redis 存储一个 key 会有一个小内存,不管你存的这个键多小,都不会低于这个内存,因此合理的使用 Hash 可以帮我们节省 很多内存。Hash Set 就在哈希表 Key 中的域(Field)的值设为 value。如果 Key 不存在,一个新的哈希表被创建并进行 HSET 操作;如果域(field)已经存在于哈希表中,旧值将被覆盖。根据上面测试用例发现,Hash set 的时候需要传入三个参数,第一个为 key,第二个为 field,第三个为存储的值。一般情况下 Key 代表一组数据,field 为 key 相关的属性,而 value 就是属性对应的值。
List
我们来存储List再取出来看看
@RequestMapping("/SetListObjByKey")
public void SetListObjByKey() {
ListOperations<String, String> list = redisTemplate.opsForList();
list.leftPush("list001","1");
list.leftPush("list001","2");
list.leftPush("list001","3");
String value=(String)list.leftPop("list001");
System.out.println("list value :"+value.toString());
value=(String)list.rightPop("list001");
System.out.println("list value :"+value.toString());
}
可以看到,从左边取的是最后存进去的数据,从右边取是最早存进去的数据。
我们再看RedisDesktopManager
只有一个2了。这点是要注意的地方。因为数据被取走了。
我们先把刚才存进去的数据删除掉,并且,代码中将取数据的代码注释掉,执行存数据操作,然后我们查看RedisDesktopManager
你会发现,数据从上到下是3,2,1。从上到下,理解为从左到右。它还提供一种按范围的读取数据方式
@RequestMapping("/GetListObjByRange")
public void GetListObjByRange() {
ListOperations<String, String> list = redisTemplate.opsForList();
List<String> values=list.range("list001",1,2);
for (String v:values)
{
System.out.println(v);
}
}
range中的参数分别为key,起始位置,终止位置。从上面可以看到,使用 List 可以轻松的实现一个队列, List 典型的应用场景就是消息队列,可以利用 List 的 Push 操作,将任务存在 List 中,然后工作线程再用 POP 操作将任务取出进行执行。
Set
如果说我们对数据还有多一点的要求,那就是去重了,前面的例子都没有办法自动实现,但是Set却可以做到这点。我们来写一个例子
@RequestMapping("SetMSetObj")
public void SetMSetObj() {
String key="csdnSet001";
SetOperations<String, String> set = redisTemplate.opsForSet();
set.add(key,"2");
set.add(key,"0");
set.add(key,"6");
set.add(key,"5");
set.add(key,"2");
set.add(key,"3");
set.add(key,"6");
set.add(key,"6");
Set<String> values=set.members(key);
for (String v:values){
System.out.println("set value :"+v);
}
}
运行项目,在浏览器输入:http://127.0.0.1:8080/SetMSetObj
从RedisDesktopManager可以看到(记得要先Reloda才能查看)
上面的数据看起来,有个问题是,可能你心中所想,这顺序不对,应该是:2 0 6 5 3,但结果却是 2 6 0 5 3。因为它不是List,不要用List来认为它是应该怎么排序。上面的数据,都被去重了。Set还有更多的应用场景,我们来看看它的关于两个集合比较的问题
@RequestMapping("/SetDifferenceObj")
public void SetDifferenceObj() {
SetOperations<String, String> set = redisTemplate.opsForSet();
String key1 = "key1";
String key2 = "key2";
set.add(key1, "1");
set.add(key1, "2");
set.add(key1, "3");
set.add(key1, "4");
set.add(key2, "5");
set.add(key2, "4");
//第一个集合中的元素在第二个集合中不存在
Set<String> diffs = set.difference(key1, key2);
for (String v : diffs) {
System.out.println("diffs set value :" + v);
}
}
这里输出结是第一个集合中的元素在第二个集合中不存在。如果我们把刚才的两个合起来,会怎么样
@RequestMapping("/SetUnionObj")
public void SetUnionObj() {
SetOperations<String, String> set = redisTemplate.opsForSet();
String key1 = "key1";
String key2 = "key2";
Set<String> alls = set.union(key1, key2);
for (String v : alls) {
System.out.println("All value :" + v);
}
}
如果,我们希望,它是有序的,应该怎么做呢?
@RequestMapping("/SetOrderObj")
public void SetOrderObj(){
String key="csdn201907";
redisTemplate.delete(key);
ZSetOperations<String, String> zset = redisTemplate.opsForZSet();
zset.add(key,"key001",3);
zset.add(key,"key002",2);
zset.add(key,"key003",1);
zset.add(key,"key004",4);
Set<String> zsets=zset.range(key,0,3);
for (String v:zsets){
System.out.println("zset value :"+v);
}
Set<String> zsetB=zset.rangeByScore(key,0,3);
for (String v:zsetB){
System.out.println("zsetB value :"+v);
}
}
可以从上面结果看出,add最后定义的是顺序,rangeByScore按顺序取。
以上就是对Redis的基本操作了。