赞
踩
声明:文章内容皆取于学习黑马课程、若想了解原版内容请去B站了解黑马程序员Redis课程
Redis全称为Remote Dictionary Server,远程词典服务器,基于内存的键值型NoSQL数据库
NoSQL(Not only SQL)是对不同于传统的关系数据库的数据库管理系统的统称,即广义地来说可以把所有不是关系型数据库的数据库统称为NoSQL。
特征:
1.键值型,value支持多种不同数据结构
2.单线程,每个命令具有原子性
3.低延迟,速度快(基于内存、IO多路复用、良好编码)
4.支持数据持久化(RDB AOF)什么是RDB、AOF后面会提及
5.支持主从集群、分片集群
6.支持多语言客户端
Redis基于c语言编写所以安装gcc依赖
yum install -y gcc tcl
我个人使用的是Xftp传的就不作展示了
解压到当前目录
tar -zxvf redis-6.2.6.tar.gz
安装并编译
make && make install
我们默认安装成功的路径 /usr/local/bin
如果安装成功那么就会有很多命令例如redis server、redis-cli等等
redis-server
没有配置启动就是默认启动
进入redis软件目录
我个人是放在/opt/module
cd /opt/module/redis-6.2.6
我们先cp一下配置文件并备份
cp redis.conf redis.conf.bak
我个人改了一些配置例如
bind 0.0.0.0
# 监听地址,允许所有ip访问(这玩意云服务器和生产别这样搞除非有密码)
daemonize yes
# 后台运行
requirepass simon0414
#密码
maxmemory 512mb
#最多512mb
logfile "redis.log"
#日志目录(默认是"",也就是不记录)
redis-server redis.conf
ps -ef |grep redis
vim /etc/systemd/system/redis.service
注意那个配置文件填你自己的解压地址
[Unit]
Description=redis-server
After=network.target
[Service]
Type=forking
ExecStart=/usr/local/bin/redis-server /opt/module/redis-6.2.6/redis.conf
PrivateTmp=true
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable redis
-a 指定密码
-p 端口
-h ip地址
redis-cli -a simon0414
后续验证
auth simon0414
按个人喜好
后续补充
以及等等其他特殊类型(消息队列)
1.help
help @组名 | help 具体命令
作用:帮助文档
2.keys
作用:找到符合要求的key
keys 表达式
?代表一个字符、*代表多个字符
查找以a开头的key
keys a*
我们知道redis是一个单线程的程序,我们的keys后面是表达式自然就是模糊搜素引擎效率不高,如果查找时间过长就会长时间
阻塞我们的业务,所以生成环境最好别用
3.del
del key的名称 | del key1 key2
作用:删除一个key值、删除多个key值
即便是不存在的key也没关系(返回的是有效的删除数)
4.exists
exists key
key是否存在
5.expire、ttl
作用:给key设置有效时间,ttl查看key的有效时间
expire age 20
ttl age
给age设置20秒有效时间并查看age的有效时间
字符串类型,是Redis最基础的类型,也就是value是字符串
我们可以又能细分我们的value的类型为三种(最大512mb)
string、int、float
无论这三种什么格式,底层均采用字节数组来存储
在 Redis 中,字符串是一个字节数组。一个数字型字符串类型的数据占用的内存空间取决于其存储的文本长度。当一个字符串类型存储的是数字时,采用二进制的形式存储会降低存储和处理数字型字符串的空间和时间开销。基于这个原因,Redis对于数字型字符串类型进行了特殊处理,采用了一种称为整数编码的方式来优化存储效率。
整数编码包括三种形式:
1.set
set key value
作用:添加或者修改key的value
2.get
get key
作用:获取key的值
3.mset
mset k1 v1 k2 v2
作用:设置多个key的值
4.mget
mget k1 k2
作用:获取多个key值的一个数组
5.Incr
incr key
作用:给整型的key自增1
6.incrby
incrby key 步长
作用:给整型的key自增指定步长(可以为负数)
7.incrbyfloat
incrbyfloat key 步长
作用:给浮点型的key自增步长
浮点数没有默认增长
8.setnx
setnx key value
作用:若不存在则设置值、若存在则不设置值
9.setex
setex key expire value
作用:设置一个key的值并设置有效期
可以分为增改查模块1234、自增模块567、组合模块89来记忆
与HashMap很类似
Redis的key允许有多个单词形成层级结构,多个单词之间用":"隔开
例如:
simonstudy:user:1
这个就是二级结构
那么我们如果想要存储一个用户id为1的java对象数据怎么办呢
我们可以将我们的用户的对象变为json字符串序列化来存储到我们的value
Hash类型也叫散列,其value是一个无序字典,类似于java中的HashMap结构
String结构是将对象序列化后转化为json字符串后存储,当需要修改对象某个字段时很不方便
Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做crud
也就是我们的value又拆分成了两个字段
一个是field,一个是value
key | field | value |
---|---|---|
simonstudy:user:1 | name | simon |
simonstudy:user:1 | age | 18 |
常见命令
1.hset
hset key field value
作用:给key的某个字段field添加值value
2.hget
hget key field
作用:获取key的某个字段field的值
3.hmset
hset key1 field1 value1 filed2 value2
作用:给一个key设置多个field的值
4.hmget
hget key1 field1 filed2
作用:返回key的多个字段的值数组
5.hgetall
hgetall key
作用:返回key的所有filed和value的数组
6.hkeys
hkeys key
作用:返回key所有的field值
7.hvals
hvals key
作用:返回key所有的value值
8.hincrby
hincrby key field 步长
作用:给key的字段的值增长指定步长
9.hsetnx
hsetnx key field value
作用:判断key的字段filed值是否存在,如果存在则不设置,如果不存在则设置
Redis中的List和LinkedList类似,可以看作一个双向链表来操作
特点:
1.有序
2.元素可以重复
3.插入和删除快
4.查询速度一般
常用来保存有序的一个列表
常见命令
1.lpush
lpush key ele1 ele2 ele3
作用:将数据从左边push进去(上面的顺序自然就是ele3<->ele2<->ele1)
2.lpop
lpop key
作用:取出最左边的元素
3.rpush
rpush key ele1 ele2 ele3
作用:将数据从右边push进去(上面的顺序自然就是ele1<->ele2<->ele3)
4.rpop
rpop key
作用:取出最右边的元素
5.lrange
lrange key index1 index2
作用:取出范围为index1-index2的元素(从0开始)
blpop和brpop
blpop key time
broop key time
作用:阻塞等待获取
思考:
如何利用List结构模拟一个栈?
同向存取
如何利用List结构模拟一个队列
异向存取
如何利用List结构模拟一个阻塞队列
异向存阻塞取
与java中的HashSet类似
特点:
1.无序
2.元素不可重复
3.查找快
4.支持交集、并集、差集
常见命令:
1.sadd
sadd key member1 member2
作用:向set中添加一个或多个元素
2.srem
srem key a
作用:删除key集合中的某个元素
3.sismember
sismember key a
作用:key集合中是否存在a元素
4.scard
scard key
作用:返回set中元素的个数
5.smembers
smembers key
作用:获取set这种所有元素
6.sinter
sinter key1 key2
作用:获取key1和key2两个集的交集
7.sdiff
sdiff key1 key2
作用:获取key1和key2两个集的差集
8.sunion
sunion key1 key2
作用:获取key1和key2两个集的并集
练习:
1.张三的好友有:李四、王五、赵六
2.李四的好友有:王五、麻子、二狗
利用set命令完成下列功能
sadd zs ls ww zl
sadd ls ww mz eg
1.计算张三的好友有几个人
scard zs
2.计算张三和李四有哪些共同好友
求交集
sinter zs ls
3.查询哪些人是张三的好友而不是李四的
求差集
sdiff zs ls
4.查询张三和李四好友共有哪些人
求并集
sunion zs ls
5.判断李四是否是张三的好友
sismember zs ls
6.判断张三是否是李四的好友
sismember ls zs
7.将李四从张三的好友泪飙移除
srem zs ls
类似于java中的treeset类似,但是底层的数据结构差别很大,SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现
是一个调表skiplist+hash表
它具有一下特性:
1.可排序
2.元素不重复
3.查询速度快
因为可排序,所以经常被用来实现排行榜这样的功能
1.zadd
zadd key score member
作用:添加一个或多个元素到sorted set,如果已经存在就更新其score值
2.zrem
zrem key member
作用:删除sorted set中指定的一个元素
3.zscore
zscore key memeber
作用:获取指定元素的score
4.zrank
zrank key memeber
作用:获取指定元素的排名
5.zcard
zcard key
作用:获取sorted set中的个数
6.zcount
zcount key min max
作用:获取score值范围的所有元素个数
7.zincrby
zincrby key increment member
作用:给指定元素增加步长
8.zrange
zrange key min max
作用:按照score排序后获取指定排名范围的元素
9.zrangebyscore
zrangebyscore key min max
作用:按照score排序后获取指定score范围的元素
10.zdiff、zinter、zunion
交、差、并集
11.反转
z后面加上rev 表示reverse反转(默认升序)
练习:
jack 85 lucy 89 rose 82 tom 98 jerry 78 amy98 miles 76
zadd grade 85 jack 89 lucy 82 rose 95 tom 78 jerry 92 amy 76 miles
1.删除tom同学的
zrem grade tom
2.获取amy同学分数
zrem grade tom
3.获取rose同学排名
zrevrank grade rose
4.查询80分以下几个学生
zcount grade 0 80
5.给Amy同学加2分
zincrby grade 2 amy
6.查出成绩前3名同学
zrevrange grade 0 2
7.查出成绩80分以下同学
zrangebyscore grade 0 80
Java客户端最常见的三个客户端
以Redis命令作为方法名称简单适用,但是Jedis实例是线程不安全的,多线程环境需要基于连接池来使用
Lettuce是基于Netty实现,支持同步、异步或者响应式编程方式,并且是线程安全。支持哨兵模式、集群
模式和管道模式
基于Redis实现的分布式、可伸缩的Java数据结构集合。包含了诸如Map、Queue、Lock、Semaphore、
AtomicLong等强大功能
我们的Spring Data Redis定义的一套API既可以通过jedis实现也可以通过lettuce实现,所以我们后期会学
习Spring Data Redis
1.引入核心依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.7.1</version>
<scope>test</scope>
</dependency>
2.编写连接操作释放代码
package com.simon.test;
import com.simon.JedisConnectionFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
import java.util.HashMap;
import java.util.Map;
public class JedisTest {
private Jedis jedis;
@BeforeEach
void setUp(){
//建立连接
jedis = new Jedis("192.168.11.100",6379);
//设置密码
jedis.auth("simon0414");
jedis.select(0);
}
@Test
void testString() throws InterruptedException {
String result = jedis.set("name", "simon");
System.out.println(result);
String value = jedis.get("name");
System.out.println(value);
}
@Test
void testHash(){
HashMap<String,String> map = new HashMap<>();
map.put("name","simon");
map.put("age","18");
String result = jedis.hmset("user:1", map);
System.out.println(result);
Map<String, String> stringStringMap = jedis.hgetAll("user:1");
System.out.println(stringStringMap);
}
@AfterEach
void tearDown(){
if (jedis != null){
jedis.close();
}
}
}
JedisConnectionFactory
package com.simon;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* @author Simon
*/
public class JedisConnectionFactory {
private static final JedisPool JEDIS_POOL;
static {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
//最大连接
jedisPoolConfig.setMaxTotal(8);
//最大空闲连接
jedisPoolConfig.setMaxIdle(8);
//最小空闲连接
jedisPoolConfig.setMinIdle(8);
//最长等待时间
jedisPoolConfig.setMaxWaitMillis(1000);
JEDIS_POOL = new JedisPool(jedisPoolConfig,"192.168.11.100",6379,1000,"simon0414");
}
public static Jedis getJedis(){
return JEDIS_POOL.getResource();
}
}
JedisTest
package com.simon.test;
import com.simon.JedisConnectionFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
import java.util.HashMap;
import java.util.Map;
public class JedisTest {
private Jedis jedis;
@BeforeEach
void setUp(){
// //建立连接
// jedis = new Jedis("192.168.11.100",6379);
// //设置密码
// jedis.auth("simon0414");
//
// jedis.select(0);
jedis = JedisConnectionFactory.getJedis();
}
@Test
void testString() throws InterruptedException {
String result = jedis.set("name", "simon");
System.out.println(result);
String value = jedis.get("name");
System.out.println(value);
}
@Test
void testHash(){
HashMap<String,String> map = new HashMap<>();
map.put("name","simon");
map.put("age","18");
String result = jedis.hmset("user:1", map);
System.out.println(result);
Map<String, String> stringStringMap = jedis.hgetAll("user:1");
System.out.println(stringStringMap);
}
@AfterEach
void tearDown(){
if (jedis != null){
jedis.close();
}
}
}
我们使用Spring Data Redis有什么好处呢?
1.提供对不同Redis客户端的整合
2.提供了同一的RedisTemplate统一api来操作Redis
3.支持Redis的发布订阅模型
4.支持Redis哨兵和Redis集群
5.支持基于Lettuce响应式编程
6.支持基于JDK、Json、字符串、Spring对象的数据序列化和反序列化
7.支持基于Redis的JDKCollection实现(基于Redis实现的分布式的集合)
我们知道我们Redis中对命令做了分组
同样我们的Spring Data Redis也做了
1.redisTemplate 通用命令
redisTemplate.opsForValue 对String类型的命令
redisTemplate.opsForHash 对Hash类型的命令
redisTemplate.opsForList 对List类型的命令
redisTemplate.opsForSet 对Set类型的命令
redisTemplate.opsForSortedSet 对SortedSet类型的命令
因为我们SpringBoot对starter依赖做好了自动装配,所以我们使用SpringBoot快速整合
1.导入依赖(其实还有类似于lombok和SpringBootStarterTest依赖这里只展示核心的)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池依赖 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
这两个是依赖一个是SpringBoot的data-redis的起步依赖
第二个是连接池依赖
注意:我们SpringDataRedis默认的客户端是Lettuce奥,这一点我们看spring-boot-starter-data-redis里面包含的依赖也是可以发现的
2.配置我们的yml文件(当然你也可以写配置类非常麻烦)
我个人就上yml了
spring:
redis:
host: 192.168.11.100
port: 6379
password: "simon0414"
#我们的spring data redis 默认使用的是Lettuce奥,如果使用jedis就要引入jedis依赖
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: 100 #连接等待时间ms
写你自己的连接数据
3.编写测试类
package com.simon;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
public class RedisTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
void testString(){
redisTemplate.opsForValue().set("name","ximeng");
//获得String数据
Object name = redisTemplate.opsForValue().get("name");
System.out.println(name);
}
}
测试成功
但其实是有问题的
我们发现我们原有的name并没有被覆盖
反而多了一个很奇怪的key
\xAC\xED\x00\x05t\x00\x04name
他的value为\xAC\xED\x00\x05t\x00\x06ximeng
我们之间说过我们的SpringDataRedis会给我们自动进行序列化,我们传入set的时候都可以接受Object对象
这个时候它会将我们传入的String作为对象来处理,通过ObjectOutputStream输出的所以说就变成了这样
我们可以点进去看看
@Nullable
private RedisSerializer keySerializer = null;
@Nullable
private RedisSerializer valueSerializer = null;
@Nullable
private RedisSerializer hashKeySerializer = null;
@Nullable
private RedisSerializer hashValueSerializer = null;
public void afterPropertiesSet() {
super.afterPropertiesSet();
boolean defaultUsed = false;
//当我们的defaultSerializer为null时,那么就会将赋值为JdkSerializationRedisSerializer,而它序列化采用就是ObjectOutputStream
//可以打断点去看,我这里就不深入了
if (this.defaultSerializer == null) {
this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}
if (this.enableDefaultSerializer) {
if (this.keySerializer == null) {
this.keySerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.valueSerializer == null) {
this.valueSerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.hashKeySerializer == null) {
this.hashKeySerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.hashValueSerializer == null) {
this.hashValueSerializer = this.defaultSerializer;
defaultUsed = true;
}
}
if (this.enableDefaultSerializer && defaultUsed) {
Assert.notNull(this.defaultSerializer, "default serializer null and not all serializers initialized");
}
if (this.scriptExecutor == null) {
this.scriptExecutor = new DefaultScriptExecutor(this);
}
this.initialized = true;
}
我们总结下来我们如果想采用我传入啥值就用什么序列化器
我们发现我们的RedisSerializer的序列化器接口实现类有很多其他的实现类
例如:OxmSerializer、ByyeArrayRedisSerializer、GenericJackson2JsonRedisSerializer、GenricToStringSerializer、StringRedisSerializer、JdkSeializationRedisSerializer、Jackson2JsonRedisSerializer
倘若我们的key为String类型、值为对象
我们该如何去修改这个序列化器呢?
很简单,我们只需要写配置类就行了
package com.simon.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
/**
* @author Simon
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
//创建RedisTemplate对象
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
//设置连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
//创建Json序列化工具
GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer();
//设置key的序列化
//单例对象
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setHashKeySerializer(RedisSerializer.string());
//设置value的序列化
redisTemplate.setValueSerializer(valueSerializer);
redisTemplate.setHashValueSerializer(valueSerializer);
//返回
return redisTemplate;
}
}
我们针对RedisTemplate<String,Object>上述的情形的redisTemplate注入了我们自己的序列化器以及连接池
然后执行我们的测试代码,有的同学可能有问题会报错
即Jackson依赖的ClassNotFind的错误(我们都知道我们SpringMvc中有JackSon的依赖,因为处理对象请求体也会用到)
我们这里没有引入我们的SpringMvc所以我们就自然引入一下JackSon的依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
然后执行测试代码成功
我们知道我们配置的是key为String而value为对象的RedisTemplate,所以我们可以测试一下写入User
package com.simon.pojo;
import lombok.Data;
/**
* @author Simon
*/
@Data
public class User {
private String name;
private Integer age;
}
@Test
void testUser(){
User user = new User();
user.setName("simon");
user.setAge(18);
redisTemplate.opsForValue().set("user:1",user);
User user1 = (User) redisTemplate.opsForValue().get("user:1");
System.out.println(user1);
}
不仅写入了User的Json字符串,还可以反序列化我们的数据
怎么做到的呢?
我们来看看存储的数据
{
"@class": "com.simon.pojo.User",
"name": "simon",
"age": 18
}
原来把我们的类写入了,所以自动化地反序列化我们的数据了但是我们这样会浪费我们的内存空间
使用起来确实很方便,但是我们不太希望这样,如果我们的每一个对象都存储了这个信息就会多占
用浪费很多空间无法接受,所以我们基本不会使用JSON序列化器来处理value,所以统一使用String
序列化器,要求只能存取String类型的key和value,当需要存储Java对象时,我们手动完成对象的序
列化和反序列化。
我们为了解决问题,我们Spring默认提供了一个StringRdisTemplate类,它的key和value的序列化
方式默认为String,我们自动使用即可
1.配置连接池
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory){
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
stringRedisTemplate.setConnectionFactory(redisConnectionFactory);
return stringRedisTemplate;
}
2.测试
@Autowired
private StringRedisTemplate stringRedisTemplate;
private static final ObjectMapper MAPPER = new ObjectMapper();
@Test
void TestStringUser() throws JsonProcessingException {
User user = new User();
user.setName("StringSimon");
user.setAge(18);
//手动序列化
String userJson = MAPPER.writeValueAsString(user);
stringRedisTemplate.opsForValue().set("user:1",userJson);
//手动反序列化
String userGetJson = stringRedisTemplate.opsForValue().get("user:1");
User userGet = MAPPER.readValue(userGetJson, User.class);
System.out.println(userGet);
}
//Hash类似的只是改成了Put
测试成功
RedisTemplate序列化实践方案
一.
1.自定义RedisTemplate
2.修改RedisTemplate的序列化器为GennericJack2JsonRedisSerializer
二.
1.使用StringRedisTemplate
2.手动序列化和反序列化对象
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。