一.初识Redis
1.什么是Redis与Redis配置
Redis是一种键值型的NoSql数据库。有两个关键词:
*键值型
*NoSql
特点:C语言开发 基于内存 开源 键值对(key-value)结构数据库
高性能,官方提供的数据是可以达到100000+的QPS(每秒内查询次数),简单说就是查询快。
但因为是基于内存的,内存又有限,所以一般用来存热点数据(短时间或者经常访问)。
【1】键值型
指Redis中存储的数据都是以key,value堆的形式存储,而value的形式多种多样,可以是字符串,数值,甚至是json:
【2】NoSql
NoSql可以翻译做Not Only Sql(不仅仅是SQL),或者是No Sql(非Sql的)数据库。称被之为非关系型数据库。NoSql数据库并不是要取代非关系型数据库,而是关系型数据库的补充。
(1)结构化与非结构化
传统关系型数据库是结构化数据,每一张表都有严格的约束信息:字段名,字段数据类型,字段约束等等信息,插入的数据必须遵循这些约束:
而NoSql数据库格式没有严格要求,可以是键值型,文档型,甚至可以是图格型。
(2)关联与非关联
传统数据库表与表之间往往存在关联,例如外键。而非关系型数据库不存在关联关系,要维护关系只能靠代码业务逻辑和数据之间的耦合。
(3)查询
非关系型数据库语法差异大。
(4)事务
非关系型数据库往往不支持事务,或者不能严格保证ACID的特性。
应用场景:缓存,任务队列,消息队列,分布式锁等。
2.Redis下载与安装
3.Redis服务启动与停止
【1】启动服务
(1)Linux服务端:Linux中使用redis-server命令启动redis服务,默认端口号为6379。(在所有位置都能使用redis-server)
ctrl+c停止Redis服务;或者ps -ef | grep 'redis'查看对应的进程 kill -9 进程号来停止Redis服务。
相关操作:。
(2)Linux客户端:定位到redis文件夹,然后输入redis-cli就可以启动客户端
(1)Windows服务端:在Windows上点击redis-server.exe,就可以启动服务端,
(2)Windows客户端:然后再点击redis-cli.exe就可以启用客户端输入命令。
【2】相关操作
通过配置将redis-server挂后台运行
通过配置实现开机自启
修改配置文件让客户端需要密码认证
修改配置文件让服务端可以被远程客户端连接
修改完配置文件后怎么重启redis
怎么用控制台让Windows的redis-cli远程连接linux的redis-server
图形化界面:
GitHub上的大神编写了Redis的图形化桌面客户端,地https://github.com/uglide/RedisDesktopManager
不过该仓库提供的是RedisDesktopManager的源码,并未提供windows安装包。
在下面这个仓库可以找到安装包:https://github.com/lework/RedisDesktopManager-Windows/releases
二.Redis数据类型
1.Redis的类型有哪些
Redis存储的是key-value数据类型,其中Key是字符串类型,value包含了很多不同的类型。
不同类型的命令称为一个group,我们也可以通过help命令来查看各种不同group的命令。
接下来,我们就学习常见的五种基本数据类型的相关命令:
2.通用命令
通用指令是部分数据类型的,都可以使用的指令,常见的有:
- KEYS:查看符合模板的所有key
- DEL:删除一个指定的key
- EXISTS:判断key是否存在
- EXPIRE:给一个key设置有效期,有效期到期时该key会被自动删除
- TTL:查看一个KEY的剩余有效期
通过help [command] 可以查看一个命令的具体用法。
3.Key的命名
Redis没有类似MySQL中的Table的概念,我们该如何区分不同类型的key呢?
例如,需要存储用户、商品信息到redis,有一个用户id是1,有一个商品id恰好也是1,此时如果使用id作为key,那就会冲突了,该怎么办?
我们可以通过给key添加前缀加以区分,不过这个前缀不是随便加的,有一定的规范:
Redis的key允许有多个单词形成层级结构,多个单词之间用':'隔开,格式如下:
[业务名称]:[数据名]:[id]
这个格式并非固定,也可以根据自己的需求来删除或添加词条。这样以来,我们就可以把不同类型的数据区分开了。从而避免了key的冲突问题。
例如:
我项目中key的定义:
public static final String USER_INFO_KEY="user:info:";
public static final Long USER_INFO_TTL=30L;
public static final String USER_BLACKLIST_KEY="user:blacklist:";
public static final Long USER_BLACKLIST_TTL = 30L;
public static final String EMAIL_VERIFYCODE_KEY="email:verifycode:";
public static final Long EMAIL_VERIFYCODE_TTL=120L;
public static final String MUSIC_RANKLIST_KEY="music:rankList:";
并且,在Redis的桌面客户端中,还会以相同前缀作为层级结构,让数据看起来层次分明,关系清晰。
key的长度不超过44字节:key是string类型,底层编码包含int、embstr和raw三种。embstr在小于44字节使用,采用连续内存空间,内存占用更小。当字节数大于44字节时,会转为raw模式存储,在raw模式下,内存空间不是连续的,而是采用一个指针指向了另外一段内存空间,在这段空间里存储SDS内容,这样空间不连续,访问的时候性能也就会收到影响,还有可能产生内存碎片
3.类型详解
1.String类型--主要用来存储整数,浮点数和字符串这些普通数据类型以及对象
String类型,也就是字符串类型,是Redis中最简单的存储类型。
其value是字符串,不过根据字符串的格式不同,又可以分为3类:
string:普通字符串
int:整数类型,可以做自增、自减操作
float:浮点类型,可以做自增、自减操作
不管是哪种格式,底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过512m.
如果Value是一个Java对象,例如一个User对象,则可以将对象序列化为JSON字符串后存储。
2.Hash类型--主要用来存储hash表和对象
Hash类型,也叫散列,其value是一个无序字典,类似于Java中的HashMap结构。
String结构是将对象序列化为JSON字符串后存储,当需要修改对象某个字段时很不方便。
Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD:
简单说存储的值是键值对,键值对套键值对,哈哈哈
问题:存储对象什么时候用String,什么时候用Hash
String一般用于缓存简单数据和对象,而Hash一般用于缓存Hash表和对象。
1.如果你经常需要更新对象的部分字段而不是整个对象,hash类型可以避免读取和写入整个对象的开销。例如,用户的在线状态、最后登录时间等信息需要独立更新。
2.对于包含多个字段的小对象,使用hash类型可以比使用多个string类型更节省内存。Redis对小的hash对象进行了优化,使其能够更高效地存储。
简单说在存储对象的内存方面一个String类型优于一个Hash类型优于多个字段分开存储的String类型
示例:
假设你有一个用户对象,包含用户名、年龄和地址三个字段:
使用String类型--这种方式适用于你不需要频繁更新单个字段的情况
SET user:1001 '{"username": "alice", "age": 30, "address": "Wonderland"}'
使用Hash类型:这种方式适用于你需要独立操作各个字段的情况
HMSET user:1001 username "alice" age 30 address "Wonderland"
比如只更新用户的地址:
HSET user:1001 address "New Wonderland"
总结来说,选择使用string类型还是hash类型主要取决于你的数据结构和操作需求。对于简单值和需要整体操作的数据,使用string类型更合适;对于多字段对象和需要部分更新的数据,使用hash类型更高效。
3.List类型--常用来存储一个有序数据
Redis中的List类型与Java中的LinkedList类似,可以看做是一个双向链表结构。既可以支持正向检索和也可以支持反向检索。
特征也与LinkedList类似:
- 有序
- 元素可以重复
- 插入和删除快
- 查询速度一般
常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等。
4.Set类型--集合
Redis的Set结构与Java中的HashSet类似,可以看做是一个value为null的HashMap。因为也是一个hash表,因此具备与HashSet类似的特征:
-无序
-元素不可重复
-查找快
-支持交集、并集、差集等功能
5.SortedSet类型--不可重复,便于排序
Redis的SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加 hash表。
SortedSet具备下列特性:
- 可排序
- 元素不重复
- 查询速度快
因为SortedSet的可排序特性,经常被用来实现排行榜这样的功能。
更多命令详情可以到这个网站学习:redis命令手册
三.在Java中操作Reidis
1.介绍
Redis的Java客户端很多,官方推荐的有三种:Jedis,Lettuce,Redisson
Spring对Redis客户端进行了整合,提供了Spring Data Redis,在Spring Boot项目中还提供了对应的Start,即spring-boot-start-data-redis。
2.Jedis(只是简单介绍一下)
导入依赖与一个小demo
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.0</version>
</dependency>
import org.junit.Test;
import redis.clients.jedis.Jedis;
import java.util.Set;
public class jedisTest {
@Test
public void testRedis(){
//1.获取连接
Jedis jedis=new Jedis("localhost",6379);
//2.执行具体的操作
//存放
jedis.set("username","xiaoming");
//获取
String value=jedis.get("username");
System.out.println(value);
//删除
//jedis.del("username");
jedis.hset("myhash","addr","bj");
String hValue = jedis.hget( "myhash", "addr");
System.out.println(hValue);
Set<String> keys=jedis.keys("*");
for(String key:keys){
System.out.println(key);
}
//3.关闭连接
jedis.close();
}
}
3.Spring Data Redis入门(底层是对jedis做的封装)
SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis。
-
提供了对不同Redis客户端的整合(Lettuce和Jedis)
-
提供了RedisTemplate统一API来操作Redis
-
支持Redis的发布订阅模型
-
支持Redis哨兵和Redis集群
-
支持基于Lettuce的响应式编程
-
支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化
-
支持基于Redis的JDKCollection实现
SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中:
【1】导入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
【2】Spring Data Redis的使用非常简单,只需要用@Autowired或@Resource注入RedisTemplate就可以调用RedisTemplate里面的方法操作redis了。
例如下面在redis中添加了键值对city:beijing
@SpringBootTest
@RunWith(SpringRunner.class)
public class SpringDataRedisTest {
@Resource
private RedisTemplate redisTemplate;
@Test
public void testString(){
redisTemplate.opsForValue().set("city","beijing");
}
}
【3】配置
spring.redis.host=localhost
spring.redis.port=6379
#spring.redis.password=123456
#redis为我们提供16个数据库(具体数量可以在配置文件下载),默认操作0号数据库
spring.redis.database=0
#Redis连接池配置
##最大连接数
#spring.redis.jedis.pool.max-active
##连接池最小空塞等待时间
#spring.redis.jedis.pool.max-wait
##连接池中的最大空闲时间
#spring.redis.jedis.pool.max-idle
##连接池中的最小空闲时间
#spring.redis.jedis.pool.min-idle
注:连接池配置
Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此我们推荐大家使用Jedis连接池代替Jedis的直连方式。
【4】添加配置类,替换JDK序列化为JSON序列化
前面的知识--序列化:将对象转化为字符串
打开redis客户端执行key *命令,我们会发现key不是city。因为在写入前会把Object序列化为字节形式,默认是采用JDK序列化。
类似于下面这样:
缺点:
- 可读性差
- 内存占用较大
我们可以自定义RedisTemplate的序列化方式,代码如下:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
// 创建RedisTemplate对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置连接工厂
template.setConnectionFactory(connectionFactory);
// 创建JSON序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer =
new GenericJackson2JsonRedisSerializer();
// 设置Key的序列化
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// 设置Value的序列化
template.setValueSerializer(jsonRedisSerializer);
template.setHashValueSerializer(jsonRedisSerializer);
// 返回
return template;
}
}
这里采用了JSON序列化来代替默认的JDK序列化方式。最终结果类似下图:
整体可读性有了很大提升,并且能将Java对象自动的序列化为JSON字符串,并且查询时能自动把JSON反序列化为Java对象。不过,其中记录了序列化时对应的class名称,目的是为了查询时实现自动反序列化。这会带来额外的内存开销。
【5】StringRedisTemplate--无序列化,自己手动序列化
为了节省内存空间,我们可以不使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。当需要存储Java对象时,手动完成对象的序列化和反序列化。
因为存入和读取时的序列化及反序列化都是我们自己实现的,SpringDataRedis就不会将class信息写入Redis了。
这种用法比较普遍,因此SpringDataRedis就提供了RedisTemplate的子类:StringRedisTemplate,它的key和value的序列化方式默认就是String方式。
省去了我们自定义RedisTemplate的序列化方式的步骤,而是直接使用【一般redis存储字符串类型都会使用StringRedisTemplate】:
@Autowired
private StringRedisTemplate stringRedisTemplate;
// JSON序列化工具
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void testSaveUser() throws JsonProcessingException {
// 创建对象
User user = new User("虎哥", 21);
// 手动序列化
String json = mapper.writeValueAsString(user);
// 写入数据
stringRedisTemplate.opsForValue().set("user:200", json);
// 获取数据
String jsonUser = stringRedisTemplate.opsForValue().get("user:200");
// 手动反序列化
User user1 = mapper.readValue(jsonUser, User.class);
System.out.println("user1 = " + user1);
}
【6】我们还会将RedisTemplate的方法进一步封装为工具类
这是我在网上找的一个工具类:
package com.flyingpig.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key)
{
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public long deleteObject(final Collection collection)
{
return redisTemplate.delete(collection);
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList)
{
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key)
{
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
{
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext())
{
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key)
{
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
{
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key)
{
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value)
{
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey)
{
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 删除Hash中的数据
*
* @param key
* @param hkey
*/
public void delCacheMapValue(final String key, final String hkey)
{
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.delete(key, hkey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
{
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern)
{
return redisTemplate.keys(pattern);
}
}
然后的话我们在别的类中
@Autowired
private RedisCache redisCache;
就可以使用这个工具类了。例如我们可以在登录的使用将用户信息存入redis中:
//如果认证通过了,使用userid生成一个jwt jwt存入ResponseResult返回
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userid = loginUser.getUser().getId().toString();
String jwt = JwtUtil.createJWT(userid);
//把完整的用户信息存入redis userid作为key
redisCache.setCacheObject("login:"+userid,loginUser);
4.Spring Cache框架
Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。
Spring Cache 提供了一层抽象,底层可以切换不同的缓存实现,例如:
【1】EHCache
【2】Caffeine
【3】Redis(常用)
起步依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
常用注解
在SpringCache中提供了很多缓存操作的注解,常见的是以下的几个:
注解 | 说明 |
---|---|
@EnableCaching | 开启缓存注解功能,通常加在启动类上 |
@Cacheable | 在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,调用方法并将方法返回值放到缓存中 |
@CachePut | 将方法的返回值放到缓存中 |
@CacheEvict | 将一条或多条数据从缓存中删除 |