前言
本片博客是本人在网上看文学习Redis所产出的学习笔记,内容均为本人实操所得希望对正在学习Redis的各位有所帮助!
Redis的安装(MAC)
1.官网下载
2.安装
以下是Redis安装的命令,对自己下载的版本进行修改
# 解压(我下载的版本是5.0.7)
tar -zxvf redis-5.0.7.tar.gz
# 拷贝的local目录下
sudo cp -rf redis-5.0.7 /usr/local/
# 进入相应目录下
cd /usr/local/redis-*
# 编译
sudo make test
# 安装
sudo make install
# 建立相应目录
sudo mkdir bin etc db
# 拷贝启动文件
sudo cp src/mkreleasehdr.sh src/redis-benchmark src/redis-check-rdb src/redis-cli src/redis-server bin/
###############################################
# 如果在编译时报错了,清理了再编译一下:
sudo make distclean && sudo make && sudo make test
3.配置
看注释(没有硬性要求可以不做修改)
# 拷贝配置文件(在redis目录下)
sudo cp redis.conf etc/
sudo vi etc/redis.conf
####内容参照如下:(选择性修改就好了,不用全都照着改)
#修改为守护模式(后台启动)
daemonize yes
#设置进程锁文件
pidfile /usr/local/redis-5.0.7/redis.pid
#端口
port 6379
#客户端超时时间
timeout 300
#日志级别
loglevel debug
#日志文件位置
logfile /usr/local/redis-5.0.7/log-redis.log
#设置数据库的数量,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id
databases 16
##指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
#save <seconds> <changes>
#Redis默认配置文件中提供了三个条件:
save 900 1
save 300 10
save 60 10000
#指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,
#可以关闭该#选项,但会导致数据库文件变的巨大
rdbcompression yes
#指定本地数据库文件名
dbfilename dump.rdb
#指定本地数据库路径
dir /usr/local/redis-5.0.7/db/
#指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能
#会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有
#的数据会在一段时间内只存在于内存中
appendonly no
#指定更新日志条件,共有3个可选值:
#no:表示等操作系统进行数据缓存同步到磁盘(快)
#always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
#everysec:表示每秒同步一次(折衷,默认值)
appendfsync everysec
# 设置连接密码
requirepass yourpassword
4.服务启动
sudo /usr/local/redis-5.0.7/src/redis-server /usr/local/redis-5.0.7/etc/redis.conf
5.服务关闭
结束redis服务
- 1.可以直接结束进程
- 2.在客户端执行
SHUTDOWN
或SHUTDOWN NOSAVE
可关闭 redis 服务
6.客户端
redis自带客户端工具,可以直接使用
# 进入相应目录
cd /usr/local/redis-4.0.10/src/
# 启动客户端连接
./redis-cli
# 注意:如果后续操作的时候存储数据时出现中文乱码,可在启动客户端时加上 --raw
# ./redis-cli --raw
# 如果有密码
sudo ./redis-cli auth yourpassword
客户端常用命令:
命令 | 用途 |
keys * | 查看所有的 key |
exists key | 查看此 key 是否存在 |
get key | 获取 key 的值 |
set key value | 设置 key 的值 |
flushall | 清空所有 |
7.可视化工具
Redis桌面管理工具Mac版:http://www.pc6.com/mac/486661.html
8.其他常用的命令
# 查找redis-server的PID
ps -ef |grep redis
# 根据PID暂用服务
kill -9 PID
# 查看6379是否被暂用
netstat -lntp | grep 6379
Redis介绍:
1.概述
Redis是一种速度非常快的非关系数据库(NO-Relational Database),可以将存储在内存中的键值对数据持久化到硬盘,可以使用复制特性来扩展性能,还可以使用客户端来分片来扩展写性能
为了满足高性能,Redis采用内存(in-memory)数据集(Dataset),根据使用场景,可以通过每隔一段时间转储数据集到磁盘,或者最佳每条命令到日志来持久化。持久化也可以被禁用,如果你只是需要一个功能丰富,网络化的内存缓存。
2.数据模型
Redis数据模型不仅与关系数据库管理系统(RDBMS)不同,也不同于任何简单的NoSQL键值数据存储。Redis数据类型类似于变成语言的基础数据类型,因此开发人员感觉很自然,每个数据类型都支持适用于其类型的操作,受支持的数据类型包括:
- String(字符串)
- Hash(哈希)
- List(列表)
- Set(集合)
- Zset(Sorted Set:有序集合)
3.关键优势
Redis的优势包括它的速度,对富数据的·支持,操作的原子性,以及通用性:
- 性能极高,它每秒可执行约100000个Set以及约100000个Get操作
- 丰富的数据类型,Redis对大多数的开发人员已知的大多数数据类型提供了原生支持,这使得各种问题得以轻松解决
- 原子性,因为所有的Redis操作都是原子性的,所以多个客户端会并发地访问一个Redis访问器,获取相同的更新值
- 丰富的特性,Redis是一个多效用的工具,有非常多的应用场景,包括缓存,消息队列(Redis原生支持发布/订阅),短期应用程序数据(比如web会话,web页面命中技术)等
4. spring-boot-starter-data-redis
Spring Boot 提供了对 Redis 集成的组件包:spring-boot-starter-data-redis,它依赖于 spring-data-redis 和 lettuce。Spring Boot 1.0 默认使用的是 Jedis 客户端,2.0 替换成了 Lettuce,但如果你从 Spring Boot 1.5.X 切换过来,几乎感受不大差异,这是因为 spring-boot-starter-data-redis 为我们隔离了其中的差异性。
- Lettuce:是一个可伸缩线程安全的 Redis 客户端,多个线程可以共享同一个
- Spring Data:是 Spring 框架中的一个主要项目,目的是为了简化构建基于 Spring 框架应用的数据访问,包括非关系数据库、Map-Reduce 框架、云数据服务等,另外也包含对关系数据库的访问支持。
- Spring Data Redis:是 Spring Data 项目中的一个主要模块,实现了对 Redis 客户端 API 的高度封装,使对 Redis 的操作更加便捷
- 可以用以下方式来表达它们之间的关系:
Lettuce → Spring Data Redis → Spring Data → spring-boot-starter-data-redis
因此 Spring Data Redis 和 Lettuce 具备的功能,spring-boot-starter-data-redis 几乎都会有。
快速上手
项目目录结构
1.导入相关依赖
<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>
引入 commons-pool 2 是因为 Lettuce 需要使用 commons-pool 2 创建 Redis 连接池。
2.application 配置
# Redis 数据库索引(默认为 0)
spring.redis.database=0
# Redis 服务器地址
spring.redis.host=localhost
# Redis 服务器连接端口
spring.redis.port=6379
# Redis 服务器连接密码(默认为空)
spring.redis.password=123
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=0
从配置可以看出来,SpringBoot是默认支持 Lettuce 连接池的
3.缓存配置
在这里可以为 Redis 设置一些全局配置,比如配置主键的生产策略 KeyGenerator,如不配置会默认使用参数名作为主键。
package com.jn.config;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
/**
* @Author ScholarTang
* @Date 2020/2/3 5:43 PM
* @Desc redis缓存配置
*/
@Configuration
@EnableCaching //开启缓存
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
}
4.测试使用
在SpringBoot测试类中注入RedisTemplate
//TODO 注入RedisTemplate
@Autowired
private RedisTemplate redisTemplate;
1.String
String是最常见的一种数据类型,普通的key/value存储都可以归为此类,value其实不仅是String也可以是数字
@Test
void testString() {
//向缓存中存储一对键值
redisTemplate.opsForValue().set("jn","Jimmy");
//根据key获取value进行,期望值比较
Assert.assertEquals("Jimmy",redisTemplate.opsForValue().get("jn"));
}
2.Redis对pojo的支持,新建一个User对象,放到缓存中,再取出来
创建实体类User.java
package com.jn.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
/**
* @Author ScholarTang
* @Date 2020/2/3 6:12 PM
* @Desc 用户对象
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User implements Serializable {
private String username;
private Integer age;
private String sex;
private String email;
}
测试:
@Test
public void testObj() {
User user = new User("jimmy", 23, "1", "zs123@163.com");
ValueOperations<String, User> operations = redisTemplate.opsForValue();
operations.set("com.zs", user);
User zs = operations.get("com.zs");
System.out.println(zs);
}
3.超时失效
Reids在存入每一个数据的时候都可以设置一个超时时间,过来这个时间就会自动删除数据,这种特性非常适合我们对阶段数据的存储
/**
* 新建一个User对象,存入Redis的同时设置100毫秒后失效,
* 设置一个线程暂停,1000毫秒之后,判断数据是否操作并打印结果
*/
@Test
public void testExpire() throws InterruptedException {
User user = new User("Lucy", 20, "0", "luck@163.com");
ValueOperations<String, User> operations = redisTemplate.opsForValue();
operations.set("lucy",user,100,TimeUnit.MILLISECONDS);
Thread.sleep(1000);
Boolean lucy = redisTemplate.hasKey("lucy");
if (lucy) {
System.out.println("lucy is true");
} else {
System.out.println("lucy is false");
}
}
4.删除数据
对过期的缓存进行删除
@Test
public void testDelete() {
redisTemplate.opsForValue().set("deleteKey", "thisData");
redisTemplate.delete("deleteKey");
Boolean deleteKey = redisTemplate.hasKey("deleteKey");
if (deleteKey) {
System.out.println("thisData is true");
} else {
System.out.println("thisData is false");
}
}
5. Hash(哈希)
一般我们存储一个键,很自然的就会使用get/set去存储,实际上这并不是最好的做法,Redis存储一个key会有一个最小内存,不管你存储的这个键多小,都不会低于这个内存,因此合理的使用Hash可以帮我们节省很多内存
Hash Set 就是在哈希表 Key中的域(Field)的值设为value。如果key不存在,一个行的哈希表被创建并进行Hast操作;如果域(Field)已经存在于哈希表中,旧值将被覆盖
@Test
public void testHash () {
/**
* Hash set需要传入三个值,
* 第一个为key,
* 第二个为Field,
* 第三个为存储的值,
* 一般情况下key代表一组数据,
* Field为key相关的属性二Value就是属性对应的值
*/
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
//值1
hash.put("hash", "you", "data one");
//值2
hash.put("hash", "you", "data two");
//得到的是值2,值2将值1覆盖了
String value = (String) hash.get("hash", "you");
System.out.println("hash value:" + value);
}
6.List
Redis List的应用场景非常多,也是Redis最重要的数据结构之一。使用List可以轻松实现一个队列,可以利用List的Push操作,将任务存在List中,然后工作线程再用POP操作将任务进行执行
Redis List的实现为一个双向链表,即可以支持反向查找和遍历,更方便的操作,不过带来的部分的内存开销。Redis内部有很多实现,包括发送缓冲队列等也都是用的这个数据结构
@Test
public void testList() {
ListOperations<String,String> list = redisTemplate.opsForList();
list.leftPush("list", "data one");
list.leftPush("list", "data two");
list.leftPush("list", "data there");
String value = list.leftPop("list");
System.out.println("list value:"+value);
/**
* 在如上代码中我们从左侧插入了一个key为list的队列,然后取出左侧最近的一条数据。
* 其实List有很多API可以操作,比如由此坚信插入队列从右侧进行读取,或者通过方法range读取队列的一部分。
* 演示使用range读取队列的一部分数据:
* range后面的两个参数就是插入数据的位置,输入不同的参数就可以取出队列中对应的数据
*/
List<String> values = list.range("list", 0, 2);
for (String s : values) {
System.out.println("list range:" + s);
}
}
7.Set
Redis Set对外提供的功能于List类似是一个列表功能,特殊之处在于Set是可以自动排抽的,当你需要存储一个列表数据,又不希望出现重复数据时,Set是一个很好的选择,并且Set提供了判断某个成员是否在一个Set集合内的重要接口这个也是List所不能提供的
/**
* 测试结果:
* set value :banana
* set value :apple
* 通过这个案例我们可以发现,输入了两个相同的值banana,全部读取的时候只剩下了一条,
* 说明Set对队列进行了自动排重操作
*/
@Test
public void testSet() {
String key = "set";
SetOperations<String, String> set = redisTemplate.opsForSet();
set.add(key,"apple");
set.add(key,"banana");
set.add(key,"banana");
Set<String> values = set.members(key);
for (String value : values) {
System.out.println("set value :" + value);
}
}
Redis为集合提供了求交集,并集,差集等操作,可以非常方便的使用。
@Test
public void test() {
SetOperations<String, String> set = redisTemplate.opsForSet();
String key1 = "setMore1";
String key2 = "setMore2";
set.add(key1, "apple");
set.add(key1, "banana");
set.add(key1, "orange");
set.add(key2, "apple");
set.add(key2, "orange");
//TODO 测试difference(差集)
/**
* 根据这个例子可以看出,difference()函数会把key1中不同于key2的数据对比出来,
* 这个特性很适合我们在金融场景中对账的时候使用
*/
Set<String> diffs = set.difference(key1, key2);
for (String diff : diffs) {
System.out.println("diffs set value :" + diff); //diffs set value :banana
}
//TODO 测试unions(并集)
/**
* 根据这个例子可以看出来,unions会取两个集合中的合集
* Set的内部实现是一个Value永远为null的HashMap,实际上是通过计算Hash的方式来快速排重的,
* 这也是Set能提供判断一个成员是否在集合内的原因
*/
Set<String> unions = set.union(key1, key2);
for (String union : unions) {
System.out.println("unions set value :" + union);
}
//TODO 测试intersect(交集)
/**
*根据这个例子可以看出来,intersect会取两个集合的相同的集(交集)
*/
Set<String> intersects = set.intersect(key1, key2);
for (String intersect : intersects) {
System.out.println("intersects set value :" + intersect);
}
}
8.ZSet
Redis Sorted Set的使用场景与Set类似,区别是Set不是自动有序的,而SortedSet可以通过用户额外提供的优先级(Score)的参数来进行成员排序,并且是插入有序,集自动排序在使用ZSet的时候需要额外的输入一个参数Score,ZSet会自动根据Score的只对集合进行排序,我们可以利用这特性来做具有权重的队列,
比如普通消息的Score为1,重要消息的Score为2,然后工作线程可以选择Score的倒叙来获取工作任务
/**
* 测试结果:
* zsetA value :apple
* zsetA value :strawberry
* zsetA value :banana
* zsetA value :orange
* zsetB value :apple
* zsetB value :strawberry
* zsetB value :banana
*
* 根据这个例子我们发现插入到ZSet的数据会自动根据Score进行排序,根据这个特性我们可以优先队列等各种常见的场景。
* 另外Redis还提供了rangeByScore这样的一个方法,可以只获取Score范围内排序后的数据
*
* Redis Sorted Set 的内部使用HashMap和跳跃表(SkipList)来保证数据的存储有序,
* HashMap里放的是所有的成员,排序依据是HashMap里存的Score,
* 使用跳跃表的结构可以获得比较高的查找效率,并在实现上比较简单
*/
@Test
public void testZset() {
String key = "zset";
ZSetOperations<String, String> zset = redisTemplate.opsForZSet();
zset.add(key, "apple", 1);
zset.add(key, "banana", 3);
zset.add(key, "orange", 4);
zset.add(key, "strawberry", 2);
Set<String> zsetsA = zset.range(key, 0, 3);
for (String value : zsetsA) {
System.out.println("zsetA value :" + value);
}
/**
* 获取指定索引范围内的元素(含头不含尾)
*/
Set<String> zsetsB = zset.rangeByScore(key, 0, 3);
for (String value : zsetsB) {
System.out.println("zsetB value :" + value);
}
}
9.封装
在我们实际的使用过程中,不会给每一个使用的类都注入RedisTemplate来直接使用,一般都会对业务进行简单的包装,最后提供出来对外使用
RedisService.java
package com.jn.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import java.io.Serializable;
import java.util.Set;
/**
* @Author ScholarTang
* @Date 2020/2/3 9:31 PM
* @Desc RedisService
*/
@Service
public class RedisService {
@Autowired
private RedisTemplate redisTemplate;
/**
* 封装简单插入操作
* @param key
* @param value
* @return
*/
public boolean set(String key,Object value) {
boolean result = false;
try {
ValueOperations<String, Object> operations = redisTemplate.opsForValue();
operations.set(key,value);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 封装简单的删除操作
* @param pattern
*/
public void removePattern(final String pattern) {
//使用redis的pattern来匹配出一批符合条件的缓存,然后批量进行删除
Set<Serializable> keys = redisTemplate.keys("pattern");
if (keys.size() > 0) {
redisTemplate.delete(keys);
}
}
}
测试:
@Autowired
private RedisService service;
/**
* 创建一个RedisService服务,将RedisTemplate注入到类中,
* 在类中编写一个简单的插入方法,和一个删除方法。
* 最后在测试方法中调用这个服务中的两个方法
*/
@Test
public void testModel() {
boolean temp = service.set("jack", "jack@163.com");
System.out.println(temp);
service.removePattern("jack");
}
码云地址