Redis
文章目录
- Redis
- 数据类型
- string类型
- 基操
- 数字操作
- 时效操作
- hash类型
- 基操
- 数字操作
- list类型
- 基操
- 消息队列
- set类型
- 基操
- 随机操作
- 集合运算操作
- sorted_set类型
- 基操
- 条件操作
- 集合运算操作
- 通用操作
- key的操作
- 基操
- 时效操作
- db的操作
- 基操
- Jedis
- 基本使用
- 列表和哈希的使用
- 工具类的封装
- Linux下的基本使用
- 持久化
- rdb
- aof
- 对比
- 事务
- 基本操作
- 锁
- 分布式锁
- 删除策略
- 主从复制
- 基操
- 原理
- 哨兵机制
- 基本配置
- 启动
数据类型
查看帮助:help 命令名
;例如:help set
;
string类型
redis中只有字符串,没有num,但如果字符串里全是数字的话可以当成是数字使用;
数字上限是java中long类型的最大值;
数据容量的最大上限是512MB;
基操
存:set key value
;
取:get key
;
删:del key
;
存多个:mset key1 value1 key2 value2
;
取多个:mget key1 key2
;
字符串长度:strlen key
追加信息到原字符串:append key value
数字操作
自增1:incr key
自增任意整数:incrby key 任意整数
自增任意小数:incrbyfloat key 任意小数
自减1:decr key
自减任意整数:decrby key 任意整数
时效操作
设置秒数:setex key seconds value
设置毫秒数:psetex key miliseconds value
hash类型
hash的键在redis中称为field,而redis的键称为key;
hash类型的值就是字符串,不存在其他类型(不能嵌套);
基操
存:hset key field value
取:hget key field
取全部:hgetall key
删除:hdel key field1 field2 ……
存多个:hmset key field1 value1 field2 value2 ……
取多个:hmget key field1 field2 ……
获取字段数量:hlen key
查询指定字段是否存在:hexists key field
获取所有字段名:hkeys key
获取所有字段值:hvals key
没有这个字段就存,有的话啥也不干:hsetnx key field value
数字操作
自增任意整数:hincrby key field 任意整数
自增任意小数:hincrbyfloat key field 任意小数
list类型
list在redis中用双向链表实现
基操
左插:lpush key value
右插:rpush key value
按索引查询:lrange key index1 index2
查询全部:lrange key 0 -1
查询单个:lindex key index
查询长度:llen key
左删:lpop key
右删:rpop key
删除指定个数指定值:lrem key count value
消息队列
取的时候等一段时间:blpop key1 key2 …… seconds
与上面的同理也可以:brpop key2 key2 …… seconds
,如果能取到,就取出,否则只要在seconds秒中等待取出;
set类型
set类型的实现与hash类型相同,只不过将value全部设置成了nil
基操
添加:sadd key value
删除:srem key value
查询全部:smembers key
获取set长度:scard key
是否为set元素:sismember key value
随机操作
随机获取数据(不弹出set):srandmember key [count]
,可指定数量,不指定则默认获取1个;
随机获取数据(弹出set):spop key [count]
,可指定数量,不指定则默认弹出1个;
集合运算操作
求set交集:sinter key1 key2 [……]
求set并集:sunion key1 key2 [……]
求set差集:sdiff key1 key2 [……]
将set交集存到另一个集合中:sinterstrore 目标集合 key1 [……]
将set并集存到另一个集合中:sunionstrore 目标集合 key1 [……]
将set差集存到另一个集合中:sdiffstrore 目标集合 key1 [……]
set间的元素移动:smove source destination member
sorted_set类型
sorted_set是一个有序的集合,顺序由插入时给定的得分决定,注意我们并不使用得分来存储数据,它仅仅是为了排序
基操
添加:zadd key score1 value1 [……]
升序查询:zrange key index1 index2
降序查询:zrevrange key index1 index2
删除:zrem key value
查询时带上得分:zrange key index1 index2 withscores
获取set长度:zcard key
获取元素索引:zrank key element
获取元素索引(反向):zrevrank key element
获取元素得分:zscore key element
修改元素得分:zincr key increment element
条件操作
按得分区间升序查询:zrangebyscore key min max [limit]
,limit类似于mysql中的limit,可以限定查询个数
按得分区间降序查询:zrevrangebyscore key min max
按索引删除:zremrangebyrank key index1 index2
按得分区间删除:zremrangebyscore key min max
按得分查询set长度:zcount key min max
集合运算操作
交:zinterstore destination key1 [……]
并:zunionstore destination key1 [……]
通用操作
key的操作
基操
删除key:del key
是否存在:exists key
查看value类型:type key
查询key:keys pattern
,pattern类似于正则的写法,*匹配任意,?匹配单个字符,[]匹配指定符号
改名:rename key newkey
,若newkey本身存在会被覆盖
改名:renamenx key newkey
,只有newkey不存在时才改名
排序:sort key
,注意排序操作不会改变值本身的结构
时效操作
设置有效时长(秒):expire key seconds
设置有效时长(毫秒):pexpire key miliseconds
设置有效时长(时间戳):expireat key timestamp
设置有效时长(毫秒+时间戳):pexpireat key miliseconds-timestamp
获取有效时长(秒):ttl key
获取有效时长(毫秒):pttl key
将有效期设置为永久:persist key
db的操作
一个redis服务会将空间划分成16个区域,编号0~15;
基操
选择数据库:select num
,num的范围是0~15;
退出:quit
网络测试:ping
输出信息:echo message
数据移动:move key db
查看当前数据库key的总量:dbsize
删除当前库所有key:flushdb
删除所有库所有key:flushall
,慎用!!!
Jedis
基本使用
maven坐标:
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
简单来说就三个步骤:连接、操作、断开
package com.jedis;
import org.junit.Test;
import redis.clients.jedis.Jedis;
public class TestJedis1 {
@Test
public void testJedis() {
//1.连接
Jedis jedis = new Jedis("127.0.0.1",6379);
//2.操作
jedis.set("hello","hello world");
//3.断开
jedis.close();
}
}
列表和哈希的使用
jedis中方法的操作和redis中的指令是一样的,下面给出list和hash的例子;
list:
@Test
public void testList() {
Jedis jedis = new Jedis("127.0.0.1",6379);
jedis.lpush("list1","a","b","c");
List<String> list = jedis.lrange("list1", 0, -1);
for(String s:list) {
System.out.println(s);
}
System.out.println(jedis.llen("list1"));
jedis.close();
}
hash:
@Test
public void testHash() {
Jedis jedis = new Jedis("127.0.0.1",6379);
jedis.hset("hash1", "name","ljj");
jedis.hset("hash1", "age","24");
jedis.hset("hash1", "sex","male");
Map<String,String> map = jedis.hgetAll("hash1");
for(String s:map.keySet()) {
System.out.println(s+":"+map.get(s));
}
jedis.close();
}
工具类的封装
redis.properties
redis.host=127.0.0.1
redis.port=6379
redis.maxTotal=30
redis.maxIdle=10
工具类封装
package com.jedis;
import java.util.ResourceBundle;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class JedisUtils {
private static JedisPool jp = null;
private static String host = null;
private static int port;
private static int maxTotal;
private static int maxIdle;
static {
ResourceBundle rb = ResourceBundle.getBundle("redis");
host = rb.getString("redis.host");
port = Integer.valueOf(rb.getString("redis.port"));
maxTotal = Integer.valueOf(rb.getString("redis.maxTotal"));
maxIdle = Integer.valueOf(rb.getString("redis.maxIdle"));
JedisPoolConfig jpc = new JedisPoolConfig();
jpc.setMaxIdle(maxIdle);
jpc.setMaxTotal(maxTotal);
jp = new JedisPool(jpc, host, port);
}
public static Jedis getJedis() {
return jp.getResource();
}
}
Linux下的基本使用
ubuntu为例:apt install redis-server
默认配置文件在/etc/redis
里
启动:redis-server 配置文件
关闭:redis-cli shutdown
持久化
rdb
配置文件中配置属性dir,这个就是默认的rdb生成目录;
每当你在客户端里save一次,rdb文件就会更新一次;
默认的名称是dump.rdb
,可以修改;
相关配置如下:
port 6379
daemonize yes
logfile redis-6379.log
dir /etc/redis
dbfilename dump-6379.rdb
rdbcompression yes #是否压缩
rdbchecksum yes #是否校验
做一个试验你就会发现,即使服务端关了,重启之后还是能拿到之前save的数据。
但是save指令可能会很耗资源,所以就有了另一个异步 指令:bgsave
,在后台保存数据;
如何让redis自动save呢?
save seconds changes
配置文件中配上这个就行了,代表多少秒内发生了多少次变化就自动dbsave一次;
aof
这是redis持久化的另一种策略,他不记录数据,而是记录指令;当redis服务重启的时候重新执行一遍这些指令,那么这些数据就又回来了;
配置时需要开启aof功能:appendonly yes
同时需要指定记录指令的方式,一共有三种方式:
- always:总是记录;
- everysec:每秒记录一次;
- no:由系统控制;
一般我们都会使用everysec;即appendfsync everysec
当然文件名也是可以修改的:appendfilename filename
还有一个问题就是,aof文件可能会很大,有一条指令可以重写aof文件,合并一些操作,减小文件大小:
bgrewriteaof
对比
持久化方式 | RDB | AOF |
占用存储空间 | 小 | 大 |
存储速度 | 慢 | 快 |
恢复速度 | 快 | 慢 |
数据安全性 | 会丢失数据 | 依据策略决定 |
资源消耗 | 高 | 低 |
启动优先级 | 低 | 高 |
事务
基本操作
开启事务:multi
,此后的指令不会被执行,而是放到一个队列中;
执行事务:exec
,执行事务队列中的所有指令;
中途取消事务:discard
,退出事务;
注意redis中事务的指令队列中如果有语法错误的指令,那么这个事务本身不成立;如果语法正确但是执行出错,redis只会在出错的那条指令抛异常,后面的指令正常执行,不存在事务的回滚操作;
所以,持久化很重要;
锁
上锁:watch key [key]
解锁:unwatch
,解锁全部被watch的key
上锁的意义:如果一个key是被watch的,那么下一个事务执行之前,如果value被其他操作所改变了,那么该事务将不被执行(取出的值为nil);所以如果我们的事务中需要某个值不被别的客户端修改,那么在执行事务之前可以先watch那个key;
分布式锁
使用watch后别的客户端仍然可以修改,只不过你的事务取不到这个值了。
如果我就是要动某个数据,别的客户端不能碰它,怎么操作?
用之前的setnx
即可,比如这个数据是num,上锁与解锁的过程如下:
上锁:setnx lock-num 1
,如果成功则证明你有权利碰num,失败则证明别人正在使用这个数据,你先等着吧;
对数据的操作:incr num
解锁:del lock-num
我们要保证所有客户端都是用的lock-num
这同一把锁;
但其实这里面还有一个问题:如果有一哥们上了锁,但是停电了,忘记解锁了,怎么办?
所以解锁操作不能完全依赖于用户,我们在上锁之后可以给锁加上一条时间限制,例如
expire lock-num 10
给他设置个10s,当然实际情况不会这么长的;10s之后锁失效,别人就又能用num这个数据了;
删除策略
定时删除:一个数据到期后马上删除,对cpu不友好,对内存友好(用时间换空间);
惰性删除:一个数据到期后知道下次访问该数据时再删除,对cpu友好,对内存不友好(用空间换时间);
定期删除:定期随机抽取一定量的数据进行到期删除(折中方案);
redis内部使用的删除策略是惰性删除和定期删除;
逐出算法:如果有新的数据要添加进来,但是内存不够用了怎么办?
删!怎么删?
有以下策略可供选择:
-
volatile-lru
:定时数据中最近没使用的数据被优先淘汰; -
volatile-lfu
:定时数据中使用次数最少的数据被优先淘汰; -
volatile-ttl
定时数据中快过期的数据被优先淘汰; -
volatile-random
:定时数据随机淘汰; -
allkeys-lru
:所有数据中最近没的数据被优先淘汰; -
allkeys-lru
:所有数据中使用次数最少的数据被优先淘汰; -
allkeys-lru
:所有数据随机淘汰; -
no-enviction
:放弃淘汰,直接报OOM异常;
我们可以在配置文件中设置以上逐出策略:maxmemory-policy volatile-lru
主从复制
master主要负责写操作,slave主要负责读操作;
如果有多个redis,只要他们的数据是同步的,那么即使有一台挂了,仍能正常提供服务;
基操
redis-server redis-6379.conf
redis-server redis-6380.conf
# 开启两个redis服务,6379作为master,6380作为slave
redis-cli -p 6379
redis-cli -p 6380
# 开启两个客户端
# 在6379中添加一个数据
set name ljj
# 在6380里是一定拿不到的(返回nil)
get name
# 现在我们将6380作为6379的slave
slaveof 127.0.0.1 6379
# 然后再获取那个数据就能拿到了
get name
# 我们也可以直接在启动服务的时候将6380作为slave
redis-server redis-6380.conf --slaveof 127.0.0.1 6379
# 最常用的方法还是直接把这句话写在配置文件里
slaveof 127.0.0.1 6379
显然slave是不能对数据进行写操作的,如果你试图在slave写,那么会报error:
(error) READONLY You can't write against a read only slave.
原理
主从复制分为全量复制和增量复制两个阶段;
全量复制阶段就是slave把master中原本的数据都拿过来;
增量复制就是slave把master的缓冲区中的指令都拿过来;
原理还有很多,但是我不想写了,都是些理论,没啥意思;
哨兵机制
主从复制解决了redis挂掉的问题,如果一台slave挂掉,那么不会影响整体的服务;
但是如果master挂掉了呢?
哨兵机制解决了这个问题。哨兵会在redis运行过程中一直监视master和slave,做一些消息的通知,如果master挂掉了,那么哨兵会将一个slave推选为新的master,所以哨兵一般设置为单数个,放置同票。
基本配置
哨兵基本类似于server,也可以用redis-cli连接,用redis-sentinel 配置文件
的方式启动;
简单的配置如下:
port 26379
dir /etc/redis
daemonize yes
# 2代表2个哨兵票即可确定master,因为我只配了3个哨兵
sentinel monitor mymaster 127.0.0.1 6379 2
# master挂掉多久后重新推选
sentinel down-after-milliseconds mymaster 30000
# 每次同步多少
sentinel parallel-syncs mymaster 1
# 多少时间内未同步完成视为失败
sentinel failover-timeout mymaster 180000
另外的哨兵除了端口号其他配置一样即可;
启动
先启动master,在启动slave,再启动哨兵;
redis-server redis-6379.conf
;
redis-server redis-6380.conf
;
redis-server redis-6381.conf
;
redis-sentinel sentinel-26379.conf
;
redis-sentinel sentinel-26380.conf
;
redis-sentinel sentinel-26381.conf
;
如果我们把master6379进行shutdown,根据我们上面的配置,30秒后,哨兵们就会推选出一个新的master;