Redis
Redis基础知识
一共五种类型
String
就像我们下面在springboot中的例子一样,单个USer在redis存储为String,如果多个用户的话,可能会存储为其他的类型
Hash
Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。
List
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
Set
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Zset
Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。
List和Set的区别
List方法与set方法的区别
(1)重复对象
list方法可以允许重复的对象,而set方法不允许重复对象
(2)null元素
list可以插入多个null元素,而set只允许插入一个null元素
(3)容器是否有序
list是一个有序的容器,保持了每个元素的插入顺序。即输出顺序就是输入顺序,而set方法是无序容器,无法保证每个元素的存储顺序
引入Redis依赖
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis 默认是lettuce客户端 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.4.5</version>
</dependency>
<!-- redis依赖commons-pool 这个依赖一定要添加 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
配置
spring:
redis:
database: 0
host: r-xxxxx.redis.rds.aliyuncs.com
port: 6379
password: xxxx
lettuce:
pool:
max-active: 8
max-wait: -1
# 连接池最大空闲连接数(默认是8)
max-idle: 8
min-idle: 0
# 连接超时时间(毫秒)
timeout: 3000
集群配置(与单机配置的区别???下次补充)
#集群连接示例
#spring.redis.cluster.nodes=192.168.157.135:7000,192.168.157.135:7001,192.168.157.135:7002,192.168.157.135:7003,192.168.157.135:7004,192.168.157.135:7005
自定义RedisTemplate对象
package com.crud.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* 1. 在Redis中,如存入的数据是中文,取出来可能会是乱码。
* ⑴ 关于Redis的中文乱码问题,这就涉及到RedisTemplate底层的数据类型问题*
* ⑵ 在RedisTemplate默认的序列化方式是JDK序列化,JDK序列化就会让我们的字符串
* 转义(这就是造成乱码的原因)
* ⑶ 所以就需要我们去自定义一个配置类,不使用Redis底层默认的JDK序列化配置方式。
* - 因此Redis官方表示:如果我们自己写一个RedisTemplate类,Redis底层的默认的
* RedisTemplate类就会失效!
* 默认情况下的模板只能支持 RedisTemplate<String,String>,只能存入字符串,很多时候,我们需要自定义 RedisTemplate ,
* 设置序列化器(需要实体对象实现Serializable),这样我们可以很方便的操作实例对象。如下所示:
* **/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory){
RedisTemplate<String,Object> redisTemplate=new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
/**
* 使用GenericJackson2JsonRedisSerializer序列化时,会保存序列化的对象的包名和类名,反序列化时以这个作为标示就可以反序列化成指定的对象。
* **/
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
return redisTemplate;
}
}
编写缓存配置类RedisConfig用于调优缓存默认配置,RedisTemplate<String, Object>的类型兼容性更高
spring-data-redis中序列化类有以下几个:
GenericToStringSerializer:可以将任何对象泛化为字符创并序列化
Jackson2JsonRedisSerializer:序列化Object对象为json字符创(与JacksonJsonRedisSerializer相同)
JdkSerializationRedisSerializer:序列化java对象
StringRedisSerializer:简单的字符串序列化
JdkSerializationRedisSerializer序列化被序列化对象必须实现Serializable接口,被序列化除属性内容还有其他内容,长度长且不易阅读,默认就是采用这种序列化方式
存储内容如下:
“\xac\xed\x00\x05sr\x00!com.oreilly.springdata.redis.User\xb1\x1c \n\xcd\xed%\xd8\x02\x00\x02I\x00\x03ageL\x00\buserNamet\x00\x12Ljava/lang/String;xp\x00\x00\x00\x14t\x00\x05user1”
JacksonJsonRedisSerializer序列化,被序列化对象不需要实现Serializable接口,被序列化的结果清晰,容易阅读,而且存储字节少,速度快
存储内容如下:
“{“userName”:“user1”,“age”:20}”
StringRedisSerializer序列化
一般如果key、value都是string字符串的话,就是用这个就可以了
测试
entity
其实我们应该在类上添加“implements Serializable”的,但是没有添加也没关系。why?
package com.crud.entity;
import lombok.Data;
@Data
public class User {
private Integer id;
private String username;
private String avatar;
private String email;
private String password;
private Integer status;
private String created;
private String last_login;
}
测试类
package com.crud.controller;
import com.crud.config.RedisConfig;
import com.crud.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.Test;
@SpringBootTest
public class RedisOneTest extends AbstractTestNGSpringContextTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testRedis(){
String key="waiwai";
User user =new User();
user.setUsername("zhazha");
user.setPassword("caicai");
user.setEmail("zhangyin@newlink.com");
user.setId(999);
user.setStatus(0);
user.setAvatar("www.baidu.com");
user.setCreated("2021-06-18 10:36:42");
user.setLast_login("2021-06-18 10:36:42");
ValueOperations<String,Object> valueOperations=redisTemplate.opsForValue();
valueOperations.set(key,user);
User userone= (User) redisTemplate.opsForValue().get(key);
System.out.println("userone"+":"+userone.toString());
}
}
在上面这个例子中我们使用redisTemplate调用了opsForValue会得到一个ValueOperations操作。这个是专门操作String类型的数据,所以里面的键值对中键为String,而值是我们的User。当然redisTemplate还为我们提供了下面几个。
redisTemplate.opsForValue();//操作字符串
redisTemplate.opsForHash();//操作hash
redisTemplate.opsForList();//操作list
redisTemplate.opsForSet();//操作set
redisTemplate.opsForZSet();//操作有序set
检查结果
进入redis数据库0(因为在yaml中我们配置的是0),根据key来查询,查询出的结果
{
@class:"com.crud.entity.User"
id:999
username:"zhazha"
avatar:"www.baidu.com"
email:"zhangyin@newlink.com"
password:"caicai"
status:0
created:"2021-06-18 10:36:42"
last_login:"2021-06-18 10:36:42"
}
添加util工具类
在企业开发中,要使用Redis操作各种类型,就将Redis里面各个指令的操作方法封装
到一个工具类中RedisUtil,直接调用这个RedisUtil工具类即可!
其实这个东西也可以不手写一遍,只是写一遍能加深印象,也可以直接复制我的,网上也有一大把,我也是照着写的,中间自己按照自己的理解改了一些。而且,自己写的工具类,自己用着舒服不是。
package com.crud.utils;
import com.sun.corba.se.spi.ior.ObjectKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtils {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
/**
* 指定缓存失效时间
* */
public boolean expire(String key,long time){
try{
if(time>0){
redisTemplate.expire(key,time, TimeUnit.SECONDS);
}
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 获取key过期时间
* */
public long getExpire(String key){
return redisTemplate.getExpire(key);
}
/**
* 判断key是否存在
* **/
public boolean hasKey(String key){
try{
return redisTemplate.hasKey(key);
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* 告诉编译器忽略 unchecked 警告信息,如使用List,ArrayList等未进行参数化产生的警告信息。
* String... key :不定个数的String参数
* key 可以传一个值 或多个
* **/
// @SuppressWarnings("unchecked")
// public void del(String... key){
// if (key !==null && key.length>0){
// if (key.length==1){
// redisTemplate.delete(key[0]);
// }else {
// redisTemplate.delete(CollectionUtils.arrayToList(key));
// }
// }
// }
//----------------------------------------字符串-----------------------------------------
/**
* 普通缓存获取
* **/
public Object get(String key){
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存存入
* **/
public boolean set(String key,Object object){
try{
redisTemplate.opsForValue().set(key,object);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 普通缓存存入并设置失效时间
* **/
public boolean set(String key,Object object,long time){
try{
if (time>0){
redisTemplate.opsForValue().set(key,object,time,TimeUnit.SECONDS);
}else {
set(key,object);
}
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 需求是限制IP频繁访问某接口,用的方案是使用redis记录访问IP的值,先设定好初始值,每次访问自增,达到某限定值后,进行阻止
* 递增
* delta 要增加几(大于0)
* **/
public long incrementUp(String key,long delta){
if (delta<=0){
throw new RuntimeException("递增因子必须大于0");
}else {
return redisTemplate.opsForValue().increment(key,delta);
}
}
/**
* 递减
*delta 要减少几(大于0)
* **/
public long deincrementDown(String key,long detla){
if (detla<0){
throw new RuntimeException("递减因子必须大于0");
}else {
return redisTemplate.opsForValue().increment(key,-detla);
}
}
//================================================Hash======================================================
/**
*HashGet
* 根据key和map的key(item),获取其中一个value
*/
public Object hashMapGetValueByItem(String key,String item){
return redisTemplate.opsForHash().get(key,item);
}
/**
* 获取该key下所有的键值对
* */
public Map<Object,Object> hashMapGetAllByKey(String key){
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* map 对应多个键值
* */
public boolean hashMapPutMap(String key, Map<String,Object> map){
try{
redisTemplate.opsForHash().putAll(key,map);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* HashSet,但是要加上过期时间
* map 对应多个键值
* **/
public boolean hashSetMapAndTime(String key,Map<String,Object> map,long time){
redisTemplate.opsForHash().putAll(key,map);
try {
if (time>0){
expire(key,time);
}
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* **/
public boolean hashSetOne(String key,String item,Object value){
try{
redisTemplate.opsForHash().put(key,item,value);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* **/
public boolean hashSetOneAndTime(String key,String item,Object value,long time){
try{
redisTemplate.opsForHash().put(key,item,value);
if (time>0){
expire(key,time);
}
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 删除hash中的值
* */
public void hashDel(String key,String item){
redisTemplate.opsForHash().delete(key,item);
}
/***
* 判断hash表中是否有该项的值
* */
public boolean hashHasKey(String key,String value){
return redisTemplate.opsForHash().hasKey(key,value);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
* 竟然没有value
* **/
public double hashIncrementUp(String key,String item,double dalte){
return redisTemplate.opsForHash().increment(key,item,dalte);
}
/**
* hash递减,如果不存在,就会创建一个 并把新增后的值返回
* 竟然没有value
* **/
public double hashIncrementDown(String key,String item,double dalte){
return redisTemplate.opsForHash().increment(key,item,-dalte);
}
//==================================================Set===============================================
/**
* 根据key获取所有set中的值
* **/
public Set<Object> setGet(String key){
try{
return redisTemplate.opsForSet().members(key);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个Set中查询是否存在
* **/
public boolean setHasKey(String key,Object value){
try{
redisTemplate.opsForSet().isMember(key,value);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 将数据放到Set中,可以放多个
* 返回的是成功的个数
* **/
public long setSet(String key, Object... objects){
try {
return redisTemplate.opsForSet().add(key,objects);
}catch (Exception e){
e.printStackTrace();
return 0;
}
}
/***
*将数据放入Set,并且设置过期时间
* **/
public long setSetAndTime(String key,long time,Object... objects){
try{
Long count =redisTemplate.opsForSet().add(key,objects);
if (time>0){
expire(key,time);
}
return count;
}catch (Exception e){
e.printStackTrace();
return 0;
}
}
/**
*获取set的长度
***/
public long setGetLength(String key){
try{
return redisTemplate.opsForSet().size(key);
}catch (Exception e){
e.printStackTrace();
return 0;
}
}
/***
* 从set中删除某一个或者多个value
* 返回被删除的个数
*
* */
public long setDelete(String key,Object... object){
try{
return redisTemplate.opsForSet().remove(key,object);
}catch (Exception e){
e.printStackTrace();
return 0;
}
}
//=============================list==================================
/***
* 获取list的内容
* 从第start个到end个
* **/
public List<Object> listGet(String key,long start,long end){
try {
return redisTemplate.opsForList().range(key,start,end);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* **/
public long listGetLengh(String key){
try{
return redisTemplate.opsForList().size(key);
}catch (Exception e){
e.printStackTrace();
return 0;
}
}
/**
* 通过索引获取list中的值
* index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* **/
public Object listGetByIndex(String key,long index){
try {
return redisTemplate.opsForList().index(key,index);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
/**
* 将单条记录放入到list缓存内,且不指定失效时间
* **/
public boolean listInsertOne(String key,Object value){
try {
redisTemplate.opsForList().rightPush(key,value);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 将单条记录放入到list缓存内,且指定失效时间
* **/
public boolean listInsertOneAndTime(String key,Object value,long time){
try {
redisTemplate.opsForList().rightPush(key,value);
if (time>0){
expire(key,time);
}
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 将List
*记录放入到list缓存内,不指定失效时间
* **/
public boolean listInsertAll(String key,List<Object> list){
try{
redisTemplate.opsForList().rightPushAll(key,list);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 将List
*记录放入到list缓存内,且指定失效时间
* **/
public boolean listInsertAllAndTime(String key,List<Object> list,long time){
try {
redisTemplate.opsForList().rightPushAll(key,list);
if (time>0){
expire(key,time);
}
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 根据索引,修改list中的某一条记录
* Set可没这个功能
* */
public boolean listUpdateByIndex(String key,long index,Object value){
try{
redisTemplate.opsForList().set(key,index,value);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 从存储在键中的列表中删除等于值的元素的第一个计数事件。
* count> 0:删除等于从左到右移动的值的第一个元素;
* count< 0:删除等于从右到左移动的值的第一个元素;
* count = 0:删除等于value的所有元素
* **/
public long listRemove(String key, Object value,long count){
try {
return redisTemplate.opsForList().remove(key,count,value);
}catch (Exception e){
e.printStackTrace();
return 0;
}
}
//=====================Zset===============================
/***
* 获取Zset中的所有元素,为什么没有对应的方法呢,set还有一个members方法呢
* **/
// public Set<Object> zsetGetAll(String key){
// try {
// return redisTemplate.opsForZSet().
// }
// }
/**
* ZSET有序集合添加内容,单个值,无有效期
* **/
public boolean zsetInsertOne(String key,Object value,double score){
try {
redisTemplate.opsForZSet().add(key,value,score);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 获取变量指定区间的元素
* */
public Set<Object> zsetRange(String key,long start,long end){
try {
return redisTemplate.opsForZSet().range(key,start,end);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
/**
* 获取某一个元素的权重
* **/
public double zsetGetScore(String key,Object value){
try{
return redisTemplate.opsForZSet().score(key,value);
}catch (Exception e){
e.printStackTrace();
return 0;
}
}
/**
* 获取某一个ZSET中的个数
* **/
public double zsetGetSize(String key){
try {
return redisTemplate.opsForZSet().zCard(key);
}catch (Exception e){
e.printStackTrace();
return 0;
}
}
/***
* range,根据下标获取元素
* **/
public Object zsetGetByindex(String key,long start,long end){
try {
return redisTemplate.opsForZSet().range(key,start,end);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
/***
* range,根据权重获取元素
* **/
public Object zsetGetByScore(String key,double start,double end){
try {
return redisTemplate.opsForZSet().rangeByScore(key,start,end);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
}
添加Controller层测试
package com.crud.controller;
import com.crud.entity.User;
import com.crud.service.impl.UserServiceImpl;
import com.crud.utils.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/crud")
public class UserController {
@Autowired
private UserServiceImpl userService;
@Autowired
private RedisUtils redisUtils;
@RequestMapping(value = "/redis",method = RequestMethod.POST)
public String redis(@RequestParam(value = "id")Integer id){
User user=userService.getUserById(id);
String username=user.getUsername();
boolean hasKey=redisUtils.hasKey(username);
if (hasKey){
Object value=redisUtils.get(username);
String str = value.toString();
System.out.println("从缓存中取出来的数据为:"+str);
return "从缓存中取出来的数据为"+str;
}else {
System.out.println("从数据库中取数据");
redisUtils.set(username,user,100000);
return "从数据库中取数据"+user.toString();
}
}
}
第一次请求接口前
根据数据库查询知道了id=1对应的username=markerhub,用markerhub在redis中查询,查询无结果
第一次请求后
请求参数 id=1
出参
从数据库中取数据User(id=1, username=markerhub, avatar=https://image-1300566513.cos.ap-guangzhou.myqcloud.com/upload/images/5a9f48118166308daba8b6da7e466aab.jpg, email=xxx@xxx.com, password=96e79218965eb72c92a549dd5a330112, status=0, created=2021-06-02 17:52:01, last_login=null)
根据markerhub在redis中查询,有结果
{
@class:"com.crud.entity.User"
id:1
username:"markerhub"
avatar:"https://image-1300566513.cos.ap-guangzhou.myqcloud.com/upload/images/5a9f48118166308daba8b6da7e466aab.jpg"
email:"xxx@xxx.com"
password:"96e79218965eb72c92a549dd5a330112"
status:0
created:"2021-06-02 17:52:01"
last_login:null
}
第二次请求后
出参
从缓存中取出来的数据为User(id=1, username=markerhub, avatar=https://image-1300566513.cos.ap-guangzhou.myqcloud.com/upload/images/5a9f48118166308daba8b6da7e466aab.jpg, email=xxx@xxx.com, password=96e79218965eb72c92a549dd5a330112, status=0, created=2021-06-02 17:52:01, last_login=null)