事务:无事务、基本一致不能做到关系性数据库的ACID
NoSQL:数据写入时,就会根据数据的id进行hash运算,从而实现数据拆分,支持水平扩展
Redis官网地址
因为Redis基于C语言编写,因此必须先安装gcc依赖
yum -y install gcc tcl
上传压缩包到服务器
解压压缩包
tar -zxvf redis-6.2.11.tar.gz
进入Redis的安装目录
cd redis/
运行编译命令
make && make install
查看是否安装成功
进入默认安装目录:
cd /usr/local/bin/
启动
可以在任意位置运行,因为已经加入了环境变量。
前台启动:
redis-server
后台启动:
修改Redis配置文件,并指定以配置文件的形式启动。
Redis的配置文件在Redis的安装目录下。
先做备份
cp redis.conf redis.conf.bak
修改配置
#监听地址,127.0.0.1默认只能在本机访问,修改为0.0.0.0可以在任意IP访问
#bind 127.0.0.1 -::1
bind 0.0.0.0
#守护线程,yes表示后台运行
daemonize no
daemonize yes
#设置密码
requirepass 123456
其他常见配置
#监听端口 默认
port 6379
#工作目录
dir .
#数据库设置,默认16个库,0-15,设置为1表示只使用1个库
database 1
#设置Redis的最大内存
maxmemory 512mb
#日志文件,默认为空,可以指定文件名,会产生在工作目录下
logfile "redis.log"
启动Redis时指定配置文件
cd /root/development/redis
redis-server redis.conf
查看启动情况
ps -ef |grep redis
touch /etc/systemd/system/redis.service
文件内容:
[Unit]
Description=redis-server
After=network.target
[Service]
Type=forking
ExecStart=/usr/local/bin/redis-server /root/development/redis/redis.conf
PrivateTmp=true
[Install]
WantedBy=multi-user.target
重载系统服务:
systemctl daemon-reload
启动Redis
systemctl start redis
查看运行状态
systemctl status redis
systemctl stop redis
重启
systemctl restart redis
设置开机自启
systemctl enable redis
查看进程
ps -ef|grep redis
Redis安装完成后就自带了命令行客户端:redis-cli,使用方式如下:
redis-cli [options] [commonds]
常见的options有:
-h 127.0.0.1
:表示要连接的Redis的节点的IP地址,默认是127.0.0.1-p 6379
:指定Redis的节点端口,默认6379-a 123456
:指定Redis的访问密码其中的commond就是Redis的操作命令,例如:
我们可以先不输入命令,先进入redis-cli控制台:
输入密码
redis-cli -h 127.0.0.1 -p 6379 -a 123456
redis-cli -h 127.0.0.1 -p 6379
AUTH 123456
ping
返回pong表示连接成功!
RedisDesktopManage
下载地址
M1不可用,输入以下命令:
sudo spctl --master-disable
sudo xattr -rd com.apple.quarantine /Applications/
Redis是一种key-value类型的数据结构,key一般都是String类型,Value类型多种多样。
前五种较为常见,也称之为基本类型。
后三种为特殊类型,GEO是一种坐标,BitMap,HyperLog是一种特殊的按位去存储的一种数据结构。
官网:Commands
通过Redis客户端查看
例如:查看所有的string类型的命令:
help @string
查看通用命令:
help @generic
通用命令指部分数据类型都可以使用的命令,常见的有:
可以输入部分前缀,按Tab键补全。
#查看所有的key
keys *
#查看a开头的key
keys a*
#查看a开头,后面有两个字符的key
keys a??
当Redis数据量达到一定的规模时,数百万、数千万,使用模糊匹配查询,效率会很低, 会给服务器造成很大的负担。同时,因为Redis是单线程的,搜索这段时间内,就无法执行其他命令,等于整个Redis的服务就被阻塞了。
所以,不建议在生产环境使用keys去查询。但是,如果Redis是一个主从架构,在从节点使用该命令,倒可以考虑,但是不能在主节点使用该命令。
#删除指定的key
DEL key
#删除name这个key
del name
[]表示可以一次删除多个,返回值表示删除的数量。
EXISTS name
给一个key设置有效期,有效期到期时该key会被自动删除。
单位为秒
示例:
#设置name这个key的有效期为20秒
EXPIRE name 20
查看一个key的剩余有效期。
TTL key
#查看name这个key的剩余时间
TTL name
注意:
-2:表示已经过期
-1:表示永久有效
String类型,也就是字符串类型,是Redis中最简单的存储类型。
value字符串,根据字符串的格式不同,又可以分为3类:
底层都是通过字节数组形式进行存储,只不过是编码方式不同。
会将数字直接转为二进制的形式存储,更节省空间。
字符串只能将字符转为对应的字节码,更占用空间,最大空间不能超过512m。
常见命令:
#指定age这个key,自增,每次自增2
incrby age 2
我们可以指定为负数,即为自减
incrby age -2
专门针对浮点数进行自增:
通用命令中有个指令为expire
为设置有效期,expire key seconds
,前提是该key已经存在的情况下,必须先set进去才能操作。
setex
:相当于是将set和expire命令合二为一了。
#setex key seconds value
#设置key为name,有效期为30秒
setex name 30 123
与set key value ex seconds
功能类似:
注意:
Redis的key中允许有多个单词形成层级结构,多个单词之间用’:'进行分割,格式项目名:业务名:类型:id
Hash类型,也叫散列,其value是一个无序字典,类似于Java中的HashMap结构。
String类型是将对象序列化为Json字符串后进行存储,当需要修改对象的某个字段时间,不方便,
Hash结构可以将对象中的每个字段进行独立存储,可以针对单个字段做CRUD:
key | value | |
---|---|---|
field | value | |
test:user:1 | id | 1 |
name | Jack | |
test:user:2 | id | 2 |
name | Rose |
当我们去修改其中某个字段时,对其他字段没有任何影响。
示例:HSET
> ping
PONG
> hmset test:user:3 name xxname
OK
> hmset test:user:3 age 20
OK
> hset test:user:3 age 10
OK
> hmset test:user:4 name aaa age 10 gender 1
OK
> hget test:user:3 name
xxname
> hmget test:user:4 name age
aaa
10
返回一个key下所有的键值对,参数只需要传key即可。
> hgetall test:user:4
name
aaa
age
10
gender
1
获取一个key下的所有的field
> hkeys test:user:4
name
age
gender
获取一个key下所有的value
> hvals test:user:4
aaa
10
1
HINCRBY:让一个Hash类型的key的字段值自增并指定步长
> HINCRBY test:user:4 age 2
12
> hget test:user:4 age
12
HSETNX:添加一个Hash类型的key的field的值,前提是这个field不存在,否则不执行
> HSETNX test:user:4 age 2
Redis中的List类型与Java中的LinkedList类似,可以看作是一个双向链表结构。既可以支持正向检索也可以支持反向检索。
特征也与LinkedList类似:
业务场景:
朋友圈点赞(顺序展示),评论…
LPUSH示例:左侧插入元素,可以一次插入一个元素,也可以一次插入多个元素
> LPUSH users 1
1
> LPUSH users 2 3 4
4
查询结果:4 3 2 1
RPUSH示例:右侧插入元素,可以一次插入一个元素,也可以一次插入多个元素
> RPUSH users 4 5 6
7
当前顺序⬆️
LPOP:移除并返回列表左侧的第一个元素,没有则返回nil
> LPOP users 1
4
返回的是4,同时该元素也会被删除。
元素被移除:
RPOP:移除并返回列表右侧的第一个元素,没有则返回nil
> RPOP users 1
6
返回元素6,同时该元素也被移除。
LRANGE:返回一段角标范围内的所有元素
> LRANGE users 1 3
2
1
4
是按照角标顺序来返回的并不是前面的序号:
BLPOP与BRPOP:与LPOP和RPOP类似,只不过类似于阻塞式的,没有元素时等待指定时间,而不是直接返回nil
> BLPOP users2 1000
1000为timeout,超时时间,单位为:秒
添加元素
> LPUSH users2 1
1
BLPOP命令:阻塞结束
> BLPOP users2 1000
users2
1
插入元素
> LPUSH testZhan 1 2 3 4 5
5
> LPOP testZhan
5
> LPOP testZhan
4
> LPOP testZhan
3
> LPOP testZhan
2
> LPOP testZhan
1
另一种方式完全同理!
两种方式均可。
Redis的Set结构与Java中的HashSet类似,可以看作是一个value为null的HashMap。因为也是一个hash表,因此具备与HashSet类似的特征:
可用于好友列表功能!
常见命令:
添加多个元素,并查看该Set中的所有元素:
> SADD s1 a b c
3
> SMEMBERS s1
c
a
b
移除指定元素并查看是否存在:
> SREM s1 a
1
> SISMEMBER s1 a
0
查看当前Set中的元素个数:
> SCARD s1
2
SINTER查看两个集合之间的交集:
> SADD s2 a b c
3
> SADD s3 b c d
3
> sinter s2 s3
c
b
SDIFF查看差集:
顺序不同结果不同:
> SDIFF s2 s3
a
> SDIFF s3 s2
d
查看两个集合之间的并集:
> SUNION s2 s3
a
c
d
b
Redis的SortedSet是一个排序的Set集合,与Java中的TreeSet较为类似,但是底层数据结构差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表用来排序(SkipList)加Hash表。SortedSet具备以下特性:
因为SortedSet的可排序特性,经常被用来实现排行榜功能!
常见命令:
eg:
ZRANGE key min max
ZREVRANGE key min max
案例练习:
将班级的下列学生得分存入Redis的SortedSet中:
Jack 85,Lucy 89 Rose 82,Tom 95,Jerry 78,Amy 92,Miles 76
> ZADD stu 85 Jack 89 Lucy 82 Rose 95 Tom 78 Jerry 92 Amy 76 Miles
7
> ZREM stu Tom
1
> ZSCORE stu Amy
92
> ZREVRANK stu Rose
3
> ZRANK stu Rose
2
> ZCOUNT stu 0 80
2
> ZINCRBY stu 2 Amy
94
> ZREVRANGE stu 0 2
Amy
Lucy
Jack
> ZRANGEBYSCORE stu 0 80
Miles
Jerry
在Redis官网中提供了各种语言的客户端,地址:https://redis.io/docs/clients/
Jedis | 以Redis命令作为方法名称,学习成本低,简单实用。但是Jedis实例是线程不安全的,多线程环境下需要基于连接池来使用。 |
---|---|
Lettuce | Lettuce是基于Netty实现的,支持同步和异步、响应式编程方式,并且是线程安全的。支持Jedis的哨兵模式、集群模式和管道模式,Sping兼容了该方式。 |
Jedis的方法名与Redis命令保持一致,较为方便。
<modelVersion>4.0.0modelVersion>
<artifactId>JedisDemoartifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>3.7.0version>
dependency>
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiterartifactId>
<version>5.8.2version>
<scope>testscope>
dependency>
dependencies>
测试前准备工作
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
/**
* Jedis单元测试
*
* @author zhangzengxiu
* @date 2023/5/2
*/
public class JedisTest {
private Jedis jedis;
/**
* Jedis主机IP
*/
private String jedisHost = "172.16.138.100";
/**
* Jedis端口
*/
private int jedisPort = 6379;
/**
* Jedis连接密码
*/
private String jedisPwd = "123456";
/**
* Jedis库
*/
private int db = 0;
@BeforeEach
void setUp() {
//建立连接
jedis = new Jedis(jedisHost, jedisPort);
//设置密码
jedis.auth(jedisPwd);
//选择库
jedis.select(db);
}
测试后处理工作
@AfterEach
void tearDown() {
if (jedis != null) {
jedis.close();
}
}
测试代码:
测试字符串
/**
* 单元测试:字符串
*/
@Test
public void StringTest() {
//存数据
String res = jedis.set("testName", "abc");
System.out.println("res = " + res);
//取数据
String result = jedis.get("testName");
System.out.println("result = " + result);
}
测试Hash
/**
* 单元测试:Hash类型
*/
@Test
public void hashTest() {
jedis.hset("auth:1", "name", "zhangsan");
jedis.hset("auth:1", "age", "20");
String name = jedis.hget("auth:1", "name");
System.out.println("name = " + name);
Map<String, String> map = jedis.hgetAll("auth:1");
System.out.println(map);
}
Jedis本身线程不安全,频繁的创建和销毁连接会有性能损耗,并发环境下,推荐使用Jedis连接池代替Jedis直连的方式。
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* Jedis连接池工厂
*
* @author zhangzengxiu
* @date 2023/5/2
*/
public class JedisConnectionFactory {
/**
* Jedis连接池
*/
public static final JedisPool jedispool;
/**
* Jedis主机IP
*/
private static String jedisHost = "172.16.138.100";
/**
* Jedis端口
*/
private static int jedisPort = 6379;
/**
* Jedis连接密码
*/
private static String jedisPwd = "123456";
/**
* 设置超时时间
*/
private static int timeout = 1000;
/**
* Jedis库
*/
private static int db = 0;
static {
JedisPoolConfig jedispoolConfig = new JedisPoolConfig();
//最大连接
jedispoolConfig.setMaxTotal(8);
//最大空闲连接
jedispoolConfig.setMaxIdle(8);
//最小空闲连接(超过一段时间没有人访问,就会清理其他连接)
jedispoolConfig.setMinIdle(0);
//最长等待时间(ms) (没有连接时,是否等待,等多久,默认-1 无限制等待)
jedispoolConfig.setMaxWaitMillis(200);
jedispool = new JedisPool(jedispoolConfig, jedisHost, jedisPort, timeout, jedisPwd, db);
}
/**
* 获取Jedis实例
*
* @return
*/
public static Jedis getJedis() {
return jedispool.getResource();
}
}
之前的获取连接方式修改为:
@BeforeEach
void setUp() {
//获取连接
jedis = JedisConnectionFactory.getJedis();
//设置密码
jedis.auth(jedisPwd);
//选择库
jedis.select(db);
}
SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis。
SpringDataRedis官网
SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对于Redis的操作。并且将不同数据类型的操作API封装到不同的类型中。
原生Jedis操作时,命令与方法是实际对应的,但是这样的操作会显的十分臃肿,其实Redis本质是对命令进行了分组的,比如,通用、string、hash等等。
<modelVersion>4.0.0modelVersion>
<artifactId>SpringdDataRedisDemoartifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<version>2.1.5.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
<version>2.1.5.RELEASEversion>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
<version>2.10.0version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.24version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<version>2.1.5.RELEASEversion>
dependency>
dependencies>
project>
spring.redis.host=172.16.138.100
spring.redis.port=6379
spring.redis.password=123456
spring.redis.database=0
#连接池配置
#最大连接
spring.redis.lettuce.pool.max-active=8
#最大空闲连接
spring.redis.lettuce.pool.max-idle=8
#最小连接
spring.redis.lettuce.pool.min-idle=0
#连接等待时间
spring.redis.lettuce.pool.max-wait=100
Spring默认引用的是Lettuce,如果需要使用jedis的相关配置,则需要额外引入jedis的坐标。
lettuce必须配置,否则不会生效。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author zhangzengxiu
* @date 2023/5/2
*/
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author zhangzengxiu
* @date 2023/5/2
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void stringTest() {
redisTemplate.opsForValue().set("name:a", "qwr");
Object name = redisTemplate.opsForValue().get("name:a");
System.out.println("name = " + name);
}
}
上述操作中,操作结果:
但是,我们去Redis的客户端发现,结果并不是我们预料的
> get name
null
这是由于序列化的问题!
> keys *
\xac\xed\x00\x05t\x00\x04name
这才是我们刚存进去的name这个key。
> get "\xac\xed\x00\x05t\x00\x04name"
\xac\xed\x00\x05t\x00\x03qwr
redisTemplate的set方法接收的并不是字符串而是一个Object,SpringDataRedis的特殊功能,它可以接受任何类型,帮助我们转为Redis可以处理的字节。RedisTemplate底层默认处理方式就是用JDK的序列化工具。
默认序列化器:
默认序列化器处理,存在的问题:
我们程序设置的key并不是我们以为的key,会被序列化,value也会被序列化。
所以,我们需要修改RedisTemplate的序列化方式。
RedisSerializer�序列化器的具体实现:
正常来说,一般key、hashkey都是字符串,所以我们可以选择StringRedisSerializer�序列化器。
StringRedisSerializer是专门用来处理字符串的。
如果value有可能是对象,建议使用GenericJackson2JsonRedisSerializer�。转Json序列化的工具。
修改方式:
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 zhangzengxiu
* @date 2023/5/3
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> getRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
//设置连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
//设置序列化工具
GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
//key的序列化器为:StringRedisSerializer
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setHashKeySerializer(RedisSerializer.string());
return redisTemplate;
}
}
需要添加依赖,如果引入了SpringMVC的坐标就不要单独引入该坐标了。
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.9.8version>
dependency>
查看结果:
小坑:
必须带范型才生效,否则不生效。
因为我们的Bean也是RedisTemplate
@Autowired
private RedisTemplate<String,Object> redisTemplate;
测试序列化对象:
/**
* 单测:测试序列化对象
*/
@Test
public void jsonTest() {
redisTemplate.opsForValue().set("user:01", new User(1L, "zhangsan"));
User user = (User) redisTemplate.opsForValue().get("user:01");
System.out.println(user);
}
RDM结果:
序列化器会根据class自动进行序列化与反序列化。但我们查看结果时,依然是Json:
User(id=1, name=zhangsan)
上面序列化器,依然存在一些问题,Json串中的@class类型写入Json,存入Redis,会带来额外的内存开销。
字节码本身比我们自身的数据还要大,但是为了实现自动序列化与反序列化,又不能删除。
为了节省内存空间,我们并不会使用Json序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。当需要存储Java对象时,手动完成序列化和反序列化。
Spring默认提供了一个StringRedisTemplate类,它的key和value的序列化默认就是String。省去了我们自定义RedisTemplate的过程,此时就不需要自定义RedisTemplate了。
示例:
@Autowired
private StringRedisTemplate stringRedisTemplate;
private final ObjectMapper mapper = new ObjectMapper();
/**
* 单测:测试序列化对象
*/
@Test
public void jsonTest() throws Exception {
User user = new User(100L, "1000abc");
String s = mapper.writeValueAsString(user);
stringRedisTemplate.opsForValue().set("user:001", s);
String value = stringRedisTemplate.opsForValue().get("user:001");
User user1 = mapper.readValue(value, User.class);
System.out.println(user1);
}
/**
* 单测:测试Hash
*/
@Test
public void hashTest() {
//Hash存值
stringRedisTemplate.opsForHash().put("user:000", "name", "zhangsan");
stringRedisTemplate.opsForHash().put("user:000", "age", "100");
String name = (String) stringRedisTemplate.opsForHash().get("user:000", "name");
System.out.println(name);
//取多个值,获取所有的键值对
Map<Object, Object> entryMap = stringRedisTemplate.opsForHash().entries("user:000");
System.out.println(entryMap);
//获取所有的key
Set<Object> keys = stringRedisTemplate.opsForHash().keys("user:000");
keys.forEach(System.out::println);
//获取所有的value
List<Object> values = stringRedisTemplate.opsForHash().values("user:000");
values.forEach(System.out::println);
}
其他类型同理。