Redis笔记
概念
- Redis 是一个开源,高级的键值存储和一个适用的解决方案,用于构建高性能,可扩展的 Web 应用程序。
- 内存nosql数据库。
- 号称10W+的QPS;
- 多用于缓存,内存中读取,速度快。
- Redis 支持分布式锁、事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。
- 对比关系型数据库:
下载安装
安装redis和redis Desktop Manager.
再Win下(可以使用多个cmd窗口进行服务端和客户端的启动):
- redis.windoes.conf :配置文件
- redis-server.exe : redis服务器
- redis-cli : redis的客户端
命令操作
redis的数据结构
redis存储的是key-value格式的数据,key是字符串,value有5种不同的数据结构
value的数据结构
数据类型 | 说明 |
---|---|
String | 字符串 |
hash | 哈希(map格式) |
list | 列表(LinkedList,可重复) |
set | 集合(hashset,不可重复) |
zset | 有序集合(sortedset,不重复可自排序) |
上述图片中,list、set、zset三种类型都是一样的存储结构。
元素上,list允许元素重复;set和zset不允许元素重复;
有序方面,list和set无序;zset会排序
redis命令操作(参照中文网文档)
String
- 存储
redis> SET mykey "Hello"
OK
返回值
integer-reply:含义如下
--1如果field是一个新的字段
--0如果field原来在map里面已经存在
- 获取
redis> GET mykey
"Hello"
返回值
integer-reply:含义如下
--1如果field是一个新的字段
--0如果field原来在map里面已经存在
- 删除
redis> SET key1 "Hello"
OK
redis> SET key2 "World"
OK
redis> DEL key1 key2 key3
(integer) 2
返回值
integer-reply: 被删除的keys的数量
hash
- 存储
redis> HSET myhash field1 "Hello"
(integer) 1
hget key field : 获取field对应的值
hgetall key :获取所有的field和value
返回值
integer-reply:含义如下
--1如果field是一个新的字段
--0如果field原来在map里面已经存在
- 获取
redis> HGET myhash field1
"Hello"
返回值
integer-reply:含义如下
--1如果field是一个新的字段
--0如果field原来在map里面已经存在
- 删除
redis> HSET myhash field1 "foo"
(integer) 1
redis> HDEL myhash field1
(integer) 1
redis> HDEL myhash field2
(integer) 0
从 key 指定的哈希集中移除指定的域。在哈希集中不存在的域将被忽略。如果 key 指定的哈希集不存在,
它将被认为是一个空的哈希集,该命令将返回0。
返回值
integer-reply: 返回从哈希集中成功移除的域的数量,不包括指出但不存在的那些域
历史:在 2.4及以上版本中 :可接受多个域作为参数。小于 2.4版本 的 Redis 每次调用只能移除一个域
要在早期版本中以原子方式从哈希集中移除多个域,可用 MULTI/EXEC块。
list
- 添加
可以添加一个元素导列表的头部(左边 lpush )或者尾部(右边 rpush )
//将元素插入链表左边
lpush key value
//将元素插入链表右边
rpush key value
- 获取
//范围获取
lrange key start end
- 删除
//删除列表最左边元素,并将元素返回
lpop key
//删除列表最右边元素,并将元素返回
rpop key
set(存取不保证顺序)
- 添加
redis> SADD myset "Hello"
(integer) 1
redis> SADD myset "World"
(integer) 1
redis> SADD myset "World"
(integer) 0
redis> SMEMBERS myset
1) "World"
2) "Hello"
integer-reply:
返回新成功添加到集合里元素的数量,不包括已经存在于集合中的元素.
历史
= 2.4: 接受多个member 参数. Redis 2.4 以前的版本每次只能添加一个member元素.
- 获取
//获取全部 smember
redis> SMEMBERS myset
1) "World"
2) "Hello"
返回值
array-reply:集合中的所有元素.
- 删除
redis> SADD myset "one"
(integer) 1
redis> SADD myset "two"
(integer) 1
redis> SADD myset "three"
(integer) 1
redis> SREM myset "one"
(integer) 1
redis> SREM myset "four"
(integer) 0
redis> SMEMBERS myset
1) "three"
2) "two"
在key集合中移除指定的元素. 如果指定的元素不是key集合中的元素则忽略 如果key集合不存在则
被视为一个空的集合,该命令返回0.如果key的类型不是一个集合,则返回错误.
返回值
integer-reply:从集合中移除元素的个数,不包括不存在的成员.
历史
= 2.4: 接受多个 member 元素参数. Redis 2.4 之前的版本每次只能移除一个元素.
zset(sorted set有序集合)
不允许重复元素,且元素有序
- 添加
//会按照score对value进行排序
zadd key score value
将所有指定成员添加到键为key有序集合(sorted set)里面。 添加时可以指定多个分数/成员(score/member)
对。
如果指定添加的成员已经是有序集合里面的成员,则会更新改成员的分数(scrore)并更新到正确的排序位置。
如果key不存在,将会创建一个新的有序集合(sorted set)并将分数/成员(score/member)对添加到有序集合,
就像原来存在一个空的有序集合一样。
如果key存在,但是类型不是有序集合,将会返回一个错误应答。
分数值是一个双精度的浮点型数字字符串。+inf和-inf都是有效值。
- 获取
//范围获取
zrange key start end
redis 127.0.0.1:6379> ZADD w3ckey 1 redis
(integer) 1
redis 127.0.0.1:6379> ZADD w3ckey 2 mongodb
(integer) 1
redis 127.0.0.1:6379> ZADD w3ckey 3 mysql
(integer) 1
redis 127.0.0.1:6379> ZADD w3ckey 3 mysql
(integer) 0
redis 127.0.0.1:6379> ZADD w3ckey 4 mysql
(integer) 0
redis 127.0.0.1:6379> ZRANGE w3ckey 0 10 WITHSCORES
//withScores 可以连分数score一起获取
1) "redis"
2) "1"
3) "mongodb"
4) "2"
5) "mysql"
6) "4"
- 删除
zrem key value
通用命令
//查询所有的键。*可以匹配正则表达式
keys *
//查看键对应的value的类型(以名为“username”的key为例)
type username
//删除制定的key value
del key
持久化操作
Redis是一个内存数据库,当redis服务器重启或者电脑重启,数据会丢失,我们可以将redis内存中的数据持久化保存到硬盘的文件中。、
redis持久化机制
RDB
-
默认方式,不需配置,默认此种机制
-
在一定的间隔时间中,检测Key的变化情况,然后持久化数据;
- 编辑redis.windows.conf文件(以下为第98行左右配置及其注解)
#after 900 sec (15 min) if at least 1 key changed
#after 300 sec (5 min) if at least 10 keys changed
#after 60 sec if at least 10000 keys changedsave 900 1 #15分钟后有至少一个key改变就持久化一次
save 300 10 #5分钟后有至少10个key改变就持久化一次
save 60 10000 #60秒后有至少10000个key改变就持久化一次
AOF
- 日志记录的方式,可以记录每一条命令的操作。可以每一次命令操作后,持久化数据。
- 通过编辑redis.windows.conf文件实现
- 581行===appendonly no 参数改为yes,默认AOF是关闭的(no)
- 三个参数:
每一次操作都进行持久化
注释# appendfsync always
每隔一秒操作一次 持久化
appendfsync everysec
不进行持久化
注释# appendfsync no
使用Java客户端操作Redis–>Jedis
Jedis
定义:是java操作Redis数据库的工具。
使用步骤:
- 下载jedis的jar包
- 使用
对jedis在java程序中进行实操,实现对String、hash、list、sorted set的操作
以Spring Boot项目为例,需要使用到的有关redis的依赖有
<!-- Redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<!-- <version>2.1.3.RELEASE</version>-->
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
简单入门操作
简单的操作:
- 获取连接
- 操作
- 关闭连接
实现如下:
package com.example.demo.jedis;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
/**
* Jedis 测试类
*/
public class JedisTest {
/**
* 快速入门
* redis通过客户端使用,只需要三步操作即可
*/
@Test
public void test1(){
//1.获取连接
Jedis jedis = new Jedis("localhost",6379);
//2、操作
jedis.set("username","goldfish124");
//3.关闭连接
jedis.close();
}
}
jedis操作五种数据结构
代码使用方法和命令行客户端使用的方法方法名是一致的。
方法是嵌入上述结构的,代码实现:
/**
* 字符串数据结构的操作
* 运行结果:
* --------------------------
* goldfish124
* --------------------------
*/
@Test
public void test2(){
//1.获取连接
//这里的连接如果不写参数,默认值就是"localhost"和6379,分别ip和端口
Jedis jedis = new Jedis("localhost",6379);
//2、操作
//2.1 存储数据
jedis.set("username","goldfish124");
//2.2 取出参数
String username = jedis.get("username");
System.out.println(username);
//3.关闭连接
jedis.close();
}
/**
* hash哈希数据结构操作
* 运行结果:
* --------------------------
* goldfish124
* gender--保密
* age--22
* username--goldfish124
* --------------------------
*/
@Test
public void test3(){
//1.获取连接
//这里的连接如果不写参数,默认值就是"localhost"和6379,分别ip和端口
Jedis jedis = new Jedis("localhost",6379);
//2、操作
//2.1 存储hash数据
// 如果是相同的key反复存储,后面存储的value会覆盖前面的
jedis.hset("myhash","username","goldfish124");
jedis.hset("myhash","age","22");
jedis.hset("myhash","gender","保密");
//2.2 获取hash
String username = jedis.hget("myhash","username");
System.out.println(username);
//2.3 获取hash中所有map的数据
Map<String,String> user = jedis.hgetAll("myhash");
//keyset方式获取集合
Set<String> keyset = user.keySet();
for (String key : keyset) {
//获取value
String value = user.get(key);
System.out.println(key + "--"+value);
}
//3.关闭连接
jedis.close();
}
/**
* list 列表数据结构操作
* 最终运行结果:
* --------------------------
* [d, l, o, g, g, o, l, d]
* pop1 = d
* pop2 = d
* [l, o, g, g, o, l]
* --------------------------
*/
@Test
public void test4(){
//1.获取连接
//这里的连接如果不写参数,默认值就是"localhost"和6379,分别ip和端口
Jedis jedis = new Jedis("localhost",6379);
//2、操作
//2.1 list的存储
//左边存
jedis.lpush("mylist","g","o","l","d");
//右边存
jedis.rpush("mylist","g","o","l","d");
//存储结果为:[d, l, o, g, g, o, l, d]
//2.2 获取list中数据
//范围获取获取所有(0,-1)
List<String> mylist1 = jedis.lrange("mylist",0,-1);
System.out.println(mylist1);
//2.3 list弹出
String pop1 = jedis.lpop("mylist");
System.out.println("pop1 = "+ pop1);//g
String pop2 = jedis.rpop("mylist");
System.out.println("pop2 = " + pop2);//g
//2.4 获取弹出后的list中数据
List<String> mylist2 = jedis.lrange("mylist",0,-1);
System.out.println(mylist2);
//3.关闭连接
jedis.close();
}
/**
* set列表数据结构操作
* 最终运行结果:
* 存取不保证顺序,所以有以下结果
* --------------------------
* [goldfish, java, love]
* --------------------------
*/
@Test
public void test5(){
//1.获取连接
//这里的连接如果不写参数,默认值就是"localhost"和6379,分别ip和端口
Jedis jedis = new Jedis("localhost",6379);
//2、操作
// set存储
jedis.sadd("myset","goldfish","love","java");
// set 获取
Set<String> myset = jedis.smembers("myset");
System.out.println(myset);
//3.关闭连接
jedis.close();
}
/**
* sortedset列表数据结构操作
* 最终运行结果:
* score 的值改变,元素的顺序也会发生改变
* --------------------------
* [钟无艳, 大乔, 小乔]
* --------------------------
*/
@Test
public void test6(){
//1.获取连接
//这里的连接如果不写参数,默认值就是"localhost"和6379,分别ip和端口
Jedis jedis = new Jedis("localhost",6379);
//2、操作
//sortedset 存储
//score 的值改变,元素的顺序也会发生改变
jedis.zadd("mysortedset",3,"钟无艳");
jedis.zadd("mysortedset",30,"大乔");
jedis.zadd("mysortedset",60,"小乔");
//sortedset 获取
Set<String> mysortedset = jedis.zrange("mysortedset",0,-1);
System.out.println(mysortedset);
//3.关闭连接
jedis.close();
}
Jedis连接池
使用
- 创建JedisPool连接池对象
- 调用方法 getResource() 方法获取redis连接
/**
* jedis连接池的使用
*/
@Test
public void test7(){
//0.创建一个配置对象
//对连接池的相关属性进行配置
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(50);
config.setMaxIdle(10);
//1.创建jedis连接池对象
//没有参数的时候是默认配置,根据需要再对配置进行修改
JedisPool jedisPool = new JedisPool("localhost",6379);
//2.获取连接
Jedis jedis = jedisPool.getResource();
//3.使用,通过如上四种操作不同数据类型的方法进行使用
//对jedis的操作
jedis.set("name","goldfish111");
//4.关闭jedis对象,使资源归还到连接池中
jedis.close();
}
连接池配置参数详解
#最大活动对象数
redis.pool.maxTotle=1000
#最大能够保持idel状态的对象数
redis.pool.maxIdle=100
#最小能够保持idel状态的对象数
redis.pool.minIdle=50
#当池内没有返回对象时,最大等待时间
redis.pool.maxWaitMillis=10000
#当调用borrow Object方法时,是否进行有效性检查
redis.pool.testOnBorrow=true
#当调用return Object方法时,是否进行有效性检查
redis.pool.testOnReturn=true
#"空闲链接"检测县城,检测周期,毫秒数。如果为负值,表示不运行“检测线程”。默认为-1
redis.pool.timeBetweenEvictionRunsMillis=30000
#向调用者输出“链接”对象时,是否检测它的空闲时间超时
redis.pool.testWhileIdel=true
#对于“空闲链接”检测线程而言,每次检测的链接资源个数,默认为3
redis.pool.numTestsPerEvictionRun=50
#redis服务器的IP
redis.ip=XXXX
#redis服务器的port
redis.port=6379
抽取成连接池工具类
工具类为 JedisPoolUtil 类:
package com.example.demo.jedis;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* JedisPool工具类
* 加载配置文件
* 提供获取连接的方法
*/
public class JedisPoolUtil {
private static JedisPool jedisPool;
static{
//读取配置文件
//jedisPool配置文件jedis.properties内容
//----------------------
//host=127.0.0.1
//port=6379
//maxTotal=50
//maxIdle=10
//----------------------
// 可在相应目录文件添加需要加载的jedisPool配置文件
InputStream is = JedisUtils.class.getClassLoader().getResourceAsStream("jedis.properties");
//创建Properties对象
Properties pro = new Properties();
//关联文件并抛出相应的异常
try {
pro.load(is);
} catch (IOException e) {
e.printStackTrace();
}
//获取数据,设置到JedisPoolConfig中
JedisPoolConfig config = new JedisPoolConfig();
//这里pro拿到的是字符串,setMaxTotal方法的参数是int,需要将数值装换成int
config.setMaxTotal(Integer.parseInt(pro.getProperty("maxTotal")));
config.setMaxIdle(Integer.parseInt(pro.getProperty("maxIdle")));
//初始化JedisPool对象
//端口号要转成数字
jedisPool = new JedisPool(config,pro.getProperty("host"),
Integer.parseInt(pro.getProperty("port")));
}
/**
* 获取连接的方法
* @return
*/
public static Jedis getJedis(){
return jedisPool.getResource();
}
// TODO 还可以书写其他的公用方法,关闭方法等
}
测试工具类方法:
/**
* jedis连接池工具类的使用
*/
@Test
public void test8(){
//通过连接池工具类获取jedis
Jedis jedis = JedisUtils.getJedis();
//使用,通过如上四种操作不同数据类型的方法进行使用
//对jedis的操作
jedis.set("name","goldfish222");
//关闭jedis对象,使资源归还到连接池中
jedis.close();
}
在Spring Boot的yml配置文件中可以进行如下数据库配置(可替代工具类中的静态代码块的引入配置):
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver #数据库连接驱动
name: demo01
url: jdbc:mysql://localhost:3306/XXX
username: XXX
password: XXX
redis: ### Redis Configuration
jedis:
pool:
max-idle: 10
min-idle: 5
max-totle: 20
hostName: 127.0.0.1
port: 6379
redis案例实现
案例需求
后端实现功能:
查询一个选择省份的下拉框里面所有备选项的数据库内容。先从redis缓存中查询数据,如果缓存中没有数据,就在数据库中查询,存到redis中,再返回数据。如果缓存中有数据,则直接返回数据。
实现
主要实现部分(JedisServiceImpl ):
package com.example.demo.jedis;
import com.example.demo.dao.JedisTestDao;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import redis.clients.jedis.Jedis;
public class JedisServiceImpl implements JedisServive {
@Autowired
JedisTestDao jedisTestDao;
@Override
public List<Province> findAll() {
return jedisTestDao.findAllProvince();
}
/**
*使用数据库缓存
* @return
*/
@Override
public String findAllJson() {
//1. 先从redis中查询数据
//1.1 获取redis客户端连接--jedis
// Jedis jedis = new Jedis();
//也可以通过刚才的数据库连接池工具类获取连接
Jedis jedis = JedisUtils.getJedis();
String province_json = jedis.get("province");
//2.判断province_json是否为null
if (province_json == null || province_json.length() == 0){
//redis中没有数据
System.out.println("redis中无数据,查询数据库=======");
//2.1 从数据库中查询
List<Province> provinceList=jedisTestDao.findAllProvince();
//2.2 将list序列化为json
ObjectMapper mapper = new ObjectMapper();
try {
province_json = mapper.writeValueAsString(provinceList);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
//2.3 将json数据存入redis中,set方法
jedis.set("province",province_json);
//归还连接
jedis.close();
}else{
//测试,看是否有数据,是否正确
System.out.println("redis中有数据,查询缓存=========");
}
return province_json;
}
}
还有service的接口类,我是用的SpringBoot项目,使用Mybits对数据库进行相应的相应的查询语句的书写。
注意 !!!
在分布式使用redis存储值的时候,必须设置失效时间,没有失效时间的键值对会在缓存中占用空间!
在缓存空间中,当缓存满了需要清理的时候,有失效时间的键值对会被优先淘汰掉,而没有失效时间的键值对如果不会被用到就会很占用空间,同时也会降低存储反应效率。