Redis 教程
Redis 概述
Redis(Remote Dictionary Server),即远程字典服务。是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API。
与 memcached 一样,为了保证效率,数据都是缓存在内存中。区别的是 redis 会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了 master-slave (主从) 同步。
Redis 能该干什么?
- 内存存储、持久化,内存是断电即失的,所以需要持久化(RDB、AOF)
- 高效率、用于高速缓冲
- 发布订阅系统
- 地图信息分析等
Redis 安装
这里介绍 Docker 的安装方式:
# 启动 redis 服务
docker run -d -p 6379:6379 --name redis redis:latest
# 查看服务是否开启
docker ps
# 停止正在运行的 redis 服务
docker stop redis
# 重启 redis 服务
docker start redis
# 访问容器
docker exec -it redis /bin/bash
# 使用容器内客户端测试连接
redis-cli
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set name locke
OK
127.0.0.1:6379> get name
"locke"
127.0.0.1:6379> keys * # 查看当前数据库中所有的 key
127.0.0.1:6379> flushdb # 清空当前数据库
127.0.0.1:6379> flushall # 清空全部数据库的内容
127.0.0.1:6379> exit # 退出客户端
# 使用 redis 官方提供的性能测试工具
# 测试:100 个并发连接,100000 请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
五大数据类型
Redis 是一个开源、内存存储的数据结构服务器,可用作数据库、高速缓存和消息队列代理。它支持多种类型的数据结构,如:字符串 (strings)、哈希表 (hashes)、列表 (lists)、集合 (sets)、有序集合 (sorted sets) 与范围查询,bitmaps,位图, hyperloglogs,地理空间 (geospaial),索引半径查询等数据类型。内置复制、Lua 脚本、LRU 驱动时间、事务以及不同级别磁盘持久化功能,同时通过 Redis Sentinel 提供高可用,通过 Redis Cluster 提供自动分区。
String
127.0.0.1:6379> set key1 vl # 设置值
127.0.0.1:6379> get key1 # 获取值
127.0.0.1:6379> keys * # 获得所有的 key
127.0.0.1:6379> exists key1 # 判断某一个 key 是否存在
127.0.0.1:6379> strlen key1 # 获取字符串的长度
127.0.0.1:6379> set user:1 {name:zhangsan,age:30}
List
Redis 列表是最简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。一个列表最多可以包含 232 - 1 个元素(4294967295,每个列表超过 40 亿个元素)。
正如图 Redis 中 List 是可以进行双端操作的,所以命令也就分为了 LXXX 和 RLLL 两类,有时候 L 也表示 List 例如 LLEN
127.0.0.1:6379> lpush list one # 将一个值或者多个值,插入到列表头部(左边插入)
127.0.0.1:6379> lpush list two
127.0.0.1:6379> lpush list three
127.0.0.1:6379> rpush list right # 将一个值或者多个值,插入到列表尾部(右边插入)
127.0.0.1:6379> lpop list # 移除list的第一个元素
127.0.0.1:6379> lpop list 2 # 移除list的前两个元素
127.0.0.1:6379> rpop list 1 # 移除list的最后一个元素
127.0.0.1:6379> lrange list 0 -1 # 获取 list 中的值
127.0.0.1:6379> lrange list 0 1 # 通过区间获取具体的值
127.0.0.1:6379> lindex list 2 # 通过下标获得 list 中的某一个值
127.0.0.1:6379> llen list # 返回列表的长度
127.0.0.1:6379> lrem list 1 one # 移除 list 集合中指定个数的 value,精确匹配
总结:
- 它实际上是一个链表,before node after ,left , right 都可以插入值
- 如果 key 不存在,创建新的链表
- 如果 key 存在,新增内容
- 如果移除了所有值,空链表,也代表不存在
- 在两边插入或者改动值,效率最高,中间元素相对来说效率会低一点
事务
Redis 事务本质:一组命令的集合。一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行。
-
Redis 事务没有隔离级别的概念。
-
所有的命令在事物中,并没有直接被执行,只有发起执行命令的时候才会执行。
-
redis 单条命令是保证原子性的,但是事务不保证原子性
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379(TX)> set k1 v1 # 命令入队
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec # 执行事务
1) OK
2) OK
3) "v2"
4) OK
Jedis
使用 Java 来操作 Redis,Jedis 是 Redis 官方推荐使用的 Java 连接 redis 的客户端的开发工具,使用 java 操作 redis 中间件。
- 导入对应的依赖
<dependencies>
<!--导入jedis的包-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<!--fastjson 存一些数据用的-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.48</version>
</dependency>
</dependencies>
- 编码测试:
- 连接数据库
- 操作命令
- 断开连接
package com.jihu;
import redis.clients.jedis.Jedis;
public class TestPing {
public static void main(String[] args) {
// 1. new jedis 对象即可
Jedis jedis = new Jedis("192.168.56.130", 6379);
jedis.auth("123456");
// jedis 所有的命令就是我们之前学习的所有指令
System.out.println(jedis.ping());
}
}
- 事务的操作
package com.jihu;
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class TestTX {
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.56.130",6379);
jedis.auth("123456");
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","jihu");
// 开启事务
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
jedis.watch(result) // 给 result 加乐观锁
try {
multi.set("user1",result);
multi.set("user2",result);
int i = 1/0; // 代码抛出异常,事务执行失败
multi.exec(); // 执行事务
} catch (Exception exception) {
multi.discard(); // 放弃事务
exception.printStackTrace();
}finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close(); // 关闭连接
}
}
}
SpringBoot 整合
SpringBoot 操作数据:spring-data jpa jdbc mongodb redis
说明: 在 SpringBoot 2.x 之后,原来使用的 jedis 被替换为了 lettuce:
- jedis : 采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用 jedis pool 连接池! 更像 BIO 模式
- lettuce : 采用 netty,实例可以再多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据了,更像 NIO 模式
SpringBoot 整合 Redis 的具体操作如下:
- 构建 SpringBoot 项目,导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 配置连接:
application.yaml
spring:
data:
redis:
## 单机模式
host: 127.0.0.1 # 地址
port: 6379 # 端口
# 通用配置
username: # 用户名
password: # 密码
database: 0 # 指定数据库序号
connect-timeout: 1000 # 连接超时时间(毫秒)
timeout: 1000 # 操作超时时间(毫秒)
client-name: # 客户端名称(不知道干嘛用的)
client-type: lettuce # 驱动类型
# 连接池配置
lettuce:
pool:
min-idle: 1 # 最小空闲连接(默认0)
max-idle: 8 # 最大空闲连接(默认8)
max-active: 16 # 最大连接数(默认8,使用负值表示没有限制)
max-wait: -1ms # 最大阻塞等待时间(默认-1,负数表示没限制)
- 测试
@SpringBootTest
class Springboot10RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
// redisTemplate: 操作不同的数据类型,api和我们的指令是一样的
// redisTemplate.opsForValue(): 表示操作字符串 类似String
// redisTemplate.opsForList(): 表示操作List ,类似list
redisTemplate.opsForList();
redisTemplate.opsForValue().set("mykey","jihu");
System.out.println(redisTemplate.opsForValue().get("mykey"));
// 除了基本的操作,我们常用的方法都可以直接通过 redisTemplate 操作,比如事务和基本的 CRUD
}
}
- RedisUtil 配置 (CRUD 操作 string | map | list | set)
@Service
public class RedisUtil {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 将 list 放入缓存
*
* @param key 键
* @param value 值
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 获取 list 缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1 代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
- 再测试
@SpringBootTest
class Springboot10RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private RedisUtil redisUtil;
@Test
void contextLoads() throws JsonProcessingException {
// redisTemplate: 操作不同的数据类型,api 和我们的指令是一样的
// redisTemplate.opsForValue(): 表示操作字符串 类似 String
// redisTemplate.opsForList(): 表示操作 List ,类似 list
redisTemplate.opsForValue().set("user", "locke");
System.out.println(redisTemplate.opsForValue().get("user"));
// 除了基本的操作,我们常用的方法都可以直接通过 redisTemplate 操作,比如事务和基本的 CRUD
}
// 使用 redisUtil 对 redis 操作进行封装,使用起来更加简洁
@Test
public void testRedis() {
redisUtil.lSet("user", "lockegogo");
// 获取 key 的值
System.out.println(redisUtil.lGet("user", 0, -1));
}
}