Linux
环境下 Redis
的安装和配置
第一步:安装 Redis
依赖
Redis
是基于C语言编写的,因此首先需要安装Redis
所需要的gcc
依赖
yum install -y gcc tcl
第二步:上传安装包并解压
把
redis.tar.gz
安装包随便丢到Linux
的一个目录中然后解压缩
#解压到当前文件夹
tar -xzf redis-6.2.6.tar.gz
- 然后进入
redis
目录
cd redis-6.2.6
- 运行编译命令安装
- 默认安装路径
/usr/local/bin
可以用ll /usr/local/bin
查看是否安装成功
- 默认安装路径
make && make install
该目录以及默认配置到环境变量,因此可以在任意目录下运行这些命令。其中:
redis-cli
:是redis
提供的命令行客户端redis-server
:是redis
的服务端启动脚本redis-sentinel
:是redis
的哨兵启动脚本
第三步:启动 redis
第一种启动方式 【默认启动】
安装完成后,在任意目录输入
redis-server
命令即可启动Redis
这种启动属于
前台启动
,会阻塞整个会话窗口,窗口关闭或者按下CTRL + C
则Redis停止。不推荐使用。
第二种启动方式【指定配置启动】
如果要让
Redis
以后台方式启动,则必须修改Redis
配置文件,就在我们之前解压的redis
安装包下,名字叫redis.conf
- 先把配置文件备份
cp redis.conf redis.conf.bck
- 然后修改
redis.conf
文件中的一些配置
# 监听的地址,默认是127.0.0.1,会导致只能在本地访问。修改为0.0.0.0则可以在任意IP访问,生产环境不要设置为0.0.0.0
bind 0.0.0.0
# 守护进程,修改为yes后即可后台运行
daemonize yes
# 密码,设置后访问Redis必须输入密码
requirepass 123321
- 然后启动
redis
# 进入redis安装目录 注意这里是自己的安装目录不是我的!!!
cd /home/mangfu/redis-6.2.6/
# 启动
redis-server redis.conf
- 停止服务
# 利用redis-cli来执行 shutdown 命令,即可停止 Redis 服务,
# 因为之前配置了密码,因此需要通过 -u 来指定密码
redis-cli -u 123321 shutdown
# 或者直接杀掉进程也可以
#查看 redis 相关进程
ps -ef | grep redis
# 杀掉 redis 进程
kill -9 进程号
redis.conf
其他常用配置
# 监听的端口
port 6379
# 工作目录,默认是当前目录,也就是运行redis-server时的命令,日志、持久化等文件会保存在这个目录
dir .
# 数据库数量,设置为1,代表只使用1个库,默认有16个库,编号0~15
databases 1
# 设置redis能够使用的最大内存
maxmemory 512mb
# 日志文件,默认为空,不记录日志,可以指定日志文件名
logfile "redis.log"
第三种方式【开机自启动】
先关闭 redis 进程再操作
- 新建一个系统服务文件
vim /etc/systemd/system/redis.service
[Unit]
Description=redis-server
After=network.target
[Service]
Type=forking
#第一条是你 `redis-server` 所在位置
#第二条是你 `redis.conf` 所在位置
ExecStart=/usr/local/bin/redis-server /home/mangfu/redis-6.2.6/redis.conf
PrivateTmp=true
[Install]
WantedBy=multi-user.target
- 然后重载系统服务
systemctl daemon-reload
- 然后设置
redis
开机自启
systemctl enable redis
现在我们可以用这组命令操作 redis
了
# 启动
systemctl start redis
# 停止
systemctl stop redis
# 重启
systemctl restart redis
# 查看状态
systemctl status redis
Redis
客户端
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 123321
:指定redis
的访问密码
cli
选择仓库用 select 仓库号
选择仓库
options
都不指定
-h
默认就是127.0.0.1
-p
默认就是6379
-a
可以输入auth 密码
验证用
ping
测试是否连通
图形化桌面客户端 RESP
- 下载软件
https://github.com/lework/RedisDesktopManager-Windows/releases
- 然后在
linux
中开放6379
端口防火墙
# 检查 firewalld 服务状态
systemctl status firewalld
# 如果未运行,使用以下命令启动
systemctl start firewalld
# 设置 firewalld 开机自启
systemctl enable firewalld
# 开放 6379 端口(TCP 协议),--permanent 表示永久生效
firewall-cmd --zone=public --add-port=6379/tcp --permanent
# 重新加载防火墙规则使配置生效
firewall-cmd --reload
#验证端口是否生效
firewall-cmd --zone=public --list-ports
- 然后建立连接
- 名字随意
- 地址用
linux
中ip addr
查出来的ip
- 密码就是你设置的
redis
密码
Redis
常用命令
Redis
数据结构概述
Redis
通用命令
KEYS pattern
: 查看符合模板的所有key
DEL key...
:删除指定的key
EXISTS key...
:判断key
是否存在EXPIRE key seconds
:给一个key
设置有效期,有效期一到自动删除**TTL key
:查看一个KEY
的剩余有效期
通过 help [command]
可以查看命令具体用法
Key
的层级结构
假如需要存储用户.商品信息到
redis
,有一个用户id
是1
,有一个商品id
恰好也是1
,此时如果使用id
作为key
,那就会冲突了
一旦我们向
redis
采用这样的方式存储,那么在可视化界面中,redis
会以层级结构来进行存储,形成类似于这样的结构,更加方便Redis
获取数据
String
类型数据
以上命令除了 INCRBYFLOAT
都是常用命令
SET
: 如果 key
不存在则是新增,如果存在则是修改
Hash
类型数据
List
类型数据
Set
类型数据
SortedSet
类型数据
滚动分页
ZRANGEBYSCORE key min max [ WITHSCORES LIMIT 小于等于最大值的第一个元素,查几个元素 ]
//第一次查询固定 最大值 1000, 最小值 0 从 第一个元素开始查 查 3 个
ZRANGEBYSCORE z1 1000 0 WITHSCORES LIMIT 0 3
举例
数据
key z1
"m8" 8
"m7" 7
"m6" 6
"m6" 6
"m5" 5
"m4" 4
"m3" 3
最大值
第一次查询 3 条数据
ZRANGEBYSCORE z1 1000 0 WITHSCORES LIMIT 0 3
"m8" 8
"m7" 7
"m6" 6
第二次查询 3 条数据 出现重复数据了因为我们 LIMIT 后第一个参数 小于等于最大值的第一个元素
我们常规是 + 1 也就是 0 变成 1 了 但是小于等于最大值的第一个元素 6 有两个 我们 + 1 还是 6
ZRANGEBYSCORE z1 6 0 WITHSCORES LIMIT 1 3
"m6" 6
"m5" 5
"m4" 4
解决第二次查询: 我们的方案是 LIMIT 后第一个参数 改成 小于等于最大值的第一个元素的个数
ZRANGEBYSCORE z1 6 0 WITHSCORES LIMIT 2 3 这样就不会重复了
"m5" 5
"m4" 4
"m3" 3
公式总结
- 第一次查询: ZRANGEBYSCORE z1 1000 0 WITHSCORES LIMIT 0, 要查询的条数
- 后面的所有查询: ZRANGEBYSCORE z1 上一次查询的最小值 0 WITHSCORES LIMIT 小于等于最大值的第一个元素的个数, 要查询的条数
这是针对排序反转的情况。正常排序也是一样的思路
- java 代码
stringRedisTemplate.opsForZet().RangeByScoreWithScores(key, min, max, offset, count)
排序反转
stringRedisTemplate.opsForZet().reverseRangeByScoreWithScores(keym, min max, offset, count)
BitMap
数据类型
// 向指定位置存一个 0 或 1。如果跳过了某个位置就默认是 0
SETBIT bm1 0 1
"0"
SETBIT bm1 1 1
"0"
SETBIT bm1 5 1
"0"
SETBIT bm1 6 1
"0"
SETBIT bm1 7 1
"0"
-----------
java操作 0表示指定位置, true 表示 1
stringRedisTemplate.opsForValue().setBit(key, 0, true);
//查询指定位置的 bit 值
GETBIT bm1 0
"1"
//统计 BitMap 中值为 1 的 bit 位的数量
BITCOUNT bm1
"5"
//把某几位二进制转为 10进制
//u2 表示无符号 2 bit位 , 0就是从索引 0 开始获取
BITFIELD bm1 GET u2 0
"3"
---------
java代码
从获取 0 到 14 位二进制数,然后转为十进制
stringRedisTemplate.opsForValue().bitField(
key,
BitFieldSubCommands.create()
.get(BitFieldSubCommands.BitFieldType.unsigned(14)).valueAt(0)
//获取bit数组指定范围的内一个 0 出现的位置
//不指定返回就是从头查到尾
BITPOS bm1 0
HyperLogLog
数据类型
PFADD
:向指定的 HHL(key) 中添加元素。PFCOUNT
:估算一个或多个 HLL(key) 的近似基数(不重复元素数量)。PFMERGE
:合并多个 HLL(key) 到一个新的 HLL(key) 中。
不管加入多少重复元素,都只统计一次
Java
操作 Redis
Jedis
第一步:引入 Jedis
依赖
<!--jedis-->
<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.0</version>
<scope>test</scope>
</dependency>
第二步:建立连接
private Jedis jedis;
@BeforeEach
void setup() {
//1. 建立和 linux redis 的 ip 和 端口
jedis = new Jedis("192.168.88.130", 6379);
//2. 设置密码
jedis.auth("Ting123321");
//3. 选择库
jedis.select(0);
}
第三步:按照正常 Redis
语法操作
@Test
void testString() {
//存入数据
String result = jedis.set("name", "虎哥");
System.out.println("result = " + result);
//获取数据
String name = jedis.get("name");
System.out.println("name = " + name);
}
@Test
void testHash() {
// 插入 hash 数据
jedis.hset("user:1", "name", "Jack");
jedis.hset("user:1", "age", "21");
// 获取 hash 数据
//field 和 value 是一个 Map 类型数据
Map<String, String> map = jedis.hgetAll("user:1");
System.out.println(map);
}
Jedis
连接池
- 创建连接池工厂
public class JedisConnectionFacotry {
private static final JedisPool jedisPool;
static {
//创建配置连接池对象
JedisPoolConfig poolConfig = new JedisPoolConfig();
//最大连接
poolConfig.setMaxTotal(8);
//最大空闲连接
poolConfig.setMaxIdle(8);
//最小空闲连接
poolConfig.setMinIdle(0);
//设置最长等待时间, ms
poolConfig.setMaxWaitMillis(1000);
//创建连接池对象
//参数: 配置连接池对象,
// 有 redis 的 linux 的 ip 地址
// redis 端口号
// 尝试连接的等待时间 ms 这里就是 1s
// redis 的密码
jedisPool = new JedisPool(poolConfig,
"192.168.150.101",6379,1000,"123321");
}
//返回连接池对象
public static Jedis getJedis(){
return jedisPool.getResource();
}
}
- 使用连接池对象
@BeforeEach
void setUp(){
//建立连接
/*jedis = new Jedis("127.0.0.1",6379);*/
//从连接池获取连接对象
jedis = JedisConnectionFacotry.getJedis();
//选择库
jedis.select(0);
}
@AfterEach
void tearDown() {
//把连接对象归还连接池
if (jedis != null) {
jedis.close();
}
}
SpringDataRedis
RedisTemplate
操作语法
操作字符串类型数据
/**
* 操作字符串类型
*/
@Test
public void testString() {
//set get setex setnx
//set: 设置指定key的值
redisTemplate.opsForValue().set("city", "北京");
String city = (String) redisTemplate.opsForValue().get("city");
System.out.println(city);
//setex: 设置指定key的值,并指定过期时间
redisTemplate.opsForValue().set("city", "上海", 60, TimeUnit.MINUTES);
//setnx: 设置指定key的值,如果key不存在,则设置成功,如果key存在,则设置失败
redisTemplate.opsForValue().setIfAbsent("lock", "1");
redisTemplate.opsForValue().setIfAbsent("lock", "2");
String lock = (String) redisTemplate.opsForValue().get("lock");
//icr: 整形 key 自增 1
//对 key 位 key1 的 的值执行自增操作
redisTemplate.opsForValue().increment("key1");
}
操作哈希类型数据
/**
* 操作 hash类型
*/
@Test
public void testHash() {
//hset hget hdel hkeys
HashOperations hashOperations = redisTemplate.opsForHash();
//hset: 设置指定key的hash值
hashOperations.put("100", "name", "张三");
hashOperations.put("100", "age", "20");
//hget: 获取指定key的hash值
String name = (String) hashOperations.get("100", "name");
System.out.println(name);
System.out.println("--------------");
//hkey-hvals: 获取指定key的hash的所有k
Set keys = hashOperations.keys("100");
for (Object key : keys) {
System.out.println(key);
}
System.out.println("--------------");
//hvals: 获取指定key的hash的所有v
List values = hashOperations.values("100");
System.out.println(values);
System.out.println("--------------");
//hdel: 删除指定key的 k-v 值
hashOperations.delete("100", "age");
}
操作列表类型数据
/**
* 操作列表类型的数据
*/
@Test
public void testList(){
//lpush lrange rpop llen
ListOperations listOperations = redisTemplate.opsForList();
//lpush: 将一个或多个值插入列表
listOperations.leftPushAll("mylist","a","b","c");
listOperations.leftPush("mylist","d");
//lrange: 返回列表返回的值
List mylist = listOperations.range("mylist", 0, -1);
System.out.println(mylist);
//rpop: 移除并返回列表最后一个值
listOperations.rightPop("mylist");
//llen: 获取列表元素个数
Long size = listOperations.size("mylist");
System.out.println(size);
}
操作集合类型数据
/**
* 操作集合类型的数据
*/
@Test
public void testSet(){
//sadd smembers scard sinter sunion srem
SetOperations setOperations = redisTemplate.opsForSet();
//sadd: 向某个集合中添加元素
setOperations.add("set1","a","b","c","d");
setOperations.add("set2","a","b","x","y");
//smembers: 获取某个集合中的所有元素
Set members = setOperations.members("set1");
System.out.println(members);
//scard: 获取某个集合中的元素个数
Long size = setOperations.size("set1");
System.out.println(size);
//sinter: 求交集
Set intersect = setOperations.intersect("set1", "set2");
System.out.println(intersect);
//sunion: 求并集
Set union = setOperations.union("set1", "set2");
System.out.println(union);
//srem: 移除集合中的元素
setOperations.remove("set1","a","b");
}
}
操作有序集合类型数据
/**
* 操作有序集合类型的数据
*/
@Test
public void testZset(){
//zadd zrange zincrby zrem
ZSetOperations zSetOperations = redisTemplate.opsForZSet();
//zadd: 向有序集合中添加元素
//默认从score小到大排序
zSetOperations.add("zset1","a",10);
zSetOperations.add("zset1","b",12);
zSetOperations.add("zset1","c",9);
//zrange: 获取有序集合中指定范围的元素
Set zset1 = zSetOperations.range("zset1", 0, -1);
System.out.println(zset1);
//zincrby: 为有序集合中的元素增加分数
zSetOperations.incrementScore("zset1","c",10);
//zrem: 移除有序集合中的元素
zSetOperations.remove("zset1","a","b");
}
通用命令
/**
* 通用命令操作
*/
@Test
public void testCommon(){
//keys exists type del
//keys: 获取所有符合给定模式的键
Set keys = redisTemplate.keys("*");
System.out.println(keys);
//exists: 判断给定key是否存在
Boolean name = redisTemplate.hasKey("name");
Boolean set1 = redisTemplate.hasKey("set1");
//type: 返回给定key的类型
for (Object key : keys) {
DataType type = redisTemplate.type(key);
System.out.println(type.name());
}
//del: 删除给定key
redisTemplate.delete("mylist");
//expire key 时间: 设置有效期
//三个参数一个 key, 一个时间, 一个时间单位
stringRedisTemplate.expire(key, 2, TimeUnit.MINUTES);
}
}
第一步:引入依赖
<!--redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--common-pool-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
第二步:配置文件
spring:
date:
redis:
host: 192.168.150.101 #有 redis 的 linux 的 ip 地址
port: 6379 #redis 端口号
password: 123321 #redis 密码
lettuce: #选择底层 lettuce 方式
pool: #配置连接池
max-active: 8 #最大连接
max-idle: 8 #最大空闲连接
min-idle: 0 #最小空闲连接
max-wait: 1000 #连接等待时间
第三步:注入 RedisTemplate
编写测试
默认序列化到
redis
里面是转换成字节 ,也就是使用 JDK 默认序列化器,导致可读性差,内存占用较大,还会出现一些bug
比如get
不到想要的数据
第四步:配置 Redis 序列化器
原理就是改变这四个
Redis
序列化对象
Value
如果有可能是对象就用GenericJackson2JsonREdisSerializer
Key
和Value
都确定是字符串就用
.
.
StringRedisSerializer
这种配置 Value 的方式有潜在问题,后期我们K-V
都配为Stirng
,可以使用StringRedisTemplate
不用手动配置
- 配置
Redis
序列化器
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
// 创建RedisTemplate对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置连接工厂
template.setConnectionFactory(connectionFactory);
// 创建JSON序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer =
new GenericJackson2JsonRedisSerializer();
// 设置Key 和 hashkey 采用 String 序列化
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// 设置 value 和 hashValue 采用JSON的序列化
template.setValueSerializer(jsonRedisSerializer);
template.setHashValueSerializer(jsonRedisSerializer);
// 返回
return template;
}
}
- 重新注入
RedisTemplate
@SpringBootTest
class RedisDemoApplicationTests {
//这里注入自己配置的 RedisTemplate
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Test
void testString() {
redisTemplate.opsForValue().set("name", "胡歌");
System.out.println(redisTemplate.opsForValue().get("name"));
}
@Test
void testSaveUser() {
// 写入数据
redisTemplate.opsForValue().set("user:100", new User(21, "胡歌"));
//获取数据
User user = (User) redisTemplate.opsForValue().get("user:100");
System.out.println(user);
}
}
第五步:序列化的潜在问题和改进
- 用
FastJson
等工具进行序列化或反序列化JSON
格式