文章目录
Redis安装
参考:
Redis启动关闭
前端方式启动:
直接运行服务脚本redis/bin/redis-server
该方式启动使用的是redis-server这个shell脚本中的默认配置,即官方默认配置
后端模式启动:
该启动方式可以使redis按指定配置文件启动
需要注意的是,redis默认安装完成后没有任何配置文件,需要在源码目录中复制redis.conf配置文件到安装目录
修改redis.conf配置文件
修改redis.conf配置文件,
daemonize yes
开启守护线程
将daemonized设置为yes代表开启守护线程,这样启动redis将不会占用会话窗口
执行以下命令按配置文件启动redis
-
跳转到redis安装目录:
cd /usr/local/redis
-
指定配置文件启动:
./bin/redis-server ./conf/redis.conf
redis默认使用6379端口
如需更改端口号,可在redis.conf文件中进行修改
关闭Redis:
因为Redis中的数据在内存中,在未完成持久化前,强行终止Redis进程可能会导致redis中的数据丢失。
正确停止Redis的方式应该是向Redis服务发送SHUTDOWN命令,方法为:
cd /usr/local/redis
./bin/redis-cli shutdown save
连接Redis客户端
redis是服务端,要操作服务端(存取数据)是通过客户端
在redis的安装目录中有redis的客户端,即redis-cli(Redis Command Line Interface),它是Redis自带的基于命令行的Redis客户端
./redis.cli -h [指定ip地址] -p [指定端口]
./redis.cli
测试响应:
Redis提供了PING命令来测试客户端与Redis的连接是否正常,如果连接正常会收到回复PONG
Redis多实例多数据库
redis实例
一个redis进程就是一个redis实例,一台服务器可以同时有多个redis实例,不同的redis实例提供不同的服务端口对外提供服务,每个redis实例之间互相隔离。每个redis实例都包括自己的数据库,数据库中可以存储自己的数据
一个redis实例最多可提供16个数据库,下标从0到15,客户端默认连接第0号数据库
可以通过select [0->16]
选择连接哪个数据库
注意:redis不支持修改数据库的名称,只能通过select 0、select 1…选择数据库
原则上:
不同的应用系统要使用不同的redis实例而不是使用同一个redis实例下的不同数据库
同一实例下,与库相关的两组清空命令:
FlushDB -- 清空当前库下所有key
FlushAll -- 清空当前实例下所有库的key
与Key相关的N组命令
1.DEL指令
- 语法 :
DEL key [key ...]
- 作用 : 删除给定的一个或多个key 。不存在的key 会被忽略。
- 可用版本: >= 1.0.0
- 返回值: 被删除key 的数量。
2.EXISTS指令
- 语法:
EXISTS key
- 作用: 检查给定key 是否存在。
- 可用版本: >= 1.0.0
- 返回值: 若key 存在,返回1 ,否则返回0。
3.EXPIRE
- 语法:
EXPIRE key seconds (单位:秒)
- 作用: 为给定key 设置生存时间,当key 过期时(生存时间为0 ),它会被自动删除。
- 可用版本: >= 1.0.0
- 时间复杂度: O(1)
- 返回值:设置成功返回1 。
4.KEYS
- 语法 :
KEYS pattern(类正则表达式)
- 作用 : 查找所有符合给定模式pattern 的key 。
- 语法:
KEYS * 匹配数据库中所有key 。
KEYS h?llo 模糊一个字符,h匹配hello ,allo 和hxllo 等。
KEYS h*llo 模糊0 ~ n个字符 匹配hllo 和heeeeello 等。
KEYS h[ae]llo 限定[]内的字符 匹配hello 和hallo ,但不匹配hillo 。特殊符号用 “” 隔开 - 可用版本: >= 1.0.0
- 返回值: 符合给定模式的key 列表。
5.MOVE
- 语法 :
MOVE key db
- 作用 : 将当前数据库的key 移动到给定的数据库db 当中。
- 可用版本: >= 1.0.0
- 返回值: 移动成功返回1 ,失败则返回0 。
6.PEXPIRE
- 语法 :
PEXPIRE key milliseconds
- 作用 : 这个命令和EXPIRE 命令的作用类似,但是它以毫秒为单位设置key 的生存时间,而不像EXPIRE 命令那样,以秒为单位。
- 可用版本: >= 2.6.0
- 时间复杂度: O(1)
- 返回值:设置成功,返回1 key 不存在或设置失败,返回0
8.TTL
- 语法 :
TTL key
- 作用 : 以秒为单位,返回给定key 的剩余生存时间(TTL, time to live)。
- 可用版本: >= 1.0.0
- 返回值:
当key 不存在时,返回-2 。
当key 存在但没有设置剩余生存时间时,返回-1 。
否则,以秒为单位,返回key 的剩余生存时间。 - Note : 在Redis 2.8 以前,当key 不存在,或者key 没有设置剩余生存时间时,命令都返回-1 。
9.PTTL
- 语法 :
PTTL key
- 作用 : 这个命令类似于TTL 命令,但它以毫秒为单位返回key 的剩余生存时间,而不是像TTL 命令那样,以秒为单位。
- 可用版本: >= 2.6.0
- 返回值: 当key 不存在时,返回-2 。当key 存在但没有设置剩余生存时间时,返回-1 。
- 否则,以毫秒为单位,返回key 的剩余生存时间。
- 注意 : 在Redis 2.8 以前,当key 不存在,或者key 没有设置剩余生存时间时,命令都返回-1 。
10.RANDOMKEY
- 语法 :
RANDOMKEY
- 作用 : 从当前数据库中随机返回(不删除) 一个key 。
- 可用版本: >= 1.0.0
- 返回值:当数据库不为空时,返回一个key 。当数据库为空时,返回nil 。
11.RENAME
- 语法 :
RENAME key newkey
- 作用 : 将key 改名为newkey 。当key 和newkey 相同,或者key 不存在时,返回一个错误。当newkey 已经存在时,RENAME 命令将覆盖旧值。
- 可用版本: >= 1.0.0
- 返回值: 改名成功时提示OK ,失败时候返回一个错误。
12.TYPE
- 语法 :
TYPE key
- 作用 : 返回key 所储存的值的类型。
- 可用版本: >= 1.0.0
- 返回值:
none (key 不存在)
string (字符串)
list (列表)
set (集合)
zset (有序集)
hash (哈希表)
Jedis客户端连接Redis
- 修改redis.conf中的bind字段
- 将127.0.0.1修改为0.0.0.0 表示允许一切客户端访问
- 重启redis
在Win下,可以使用图形化界面的客户端连接我们的redis,如图
Redis不仅是使用命令与图形化界面来操作,现在基本上主流的语言都有客户端支持,比如java、C、C#、C++、php、Node.js、Go等。
在官方网站里列一些Java的客户端,有Jedis、Redisson、Jredis、JDBC-Redis、等其中官方推荐使用Jedis和Redisson。 在企业中用的最多的就是Jedis,下面我们就重点学习下Jedis。
Jedis同样也是托管在github上,地址:https://github.com/xetorthio/jedis
导包
- Maven Repositiry:
<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>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
单例连接
package com;
import redis.clients.jedis.Jedis;
class Main{
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.31.128", 6379);
jedis.set("name1", "bar");
String name = jedis.get("name1");
System.out.println(name);
jedis.close();
}
}
连接池连接
package com;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
class Main{
public static void main(String[] args) {
JedisPoolConfig config = new JedisPoolConfig();
//最大连接数
config.setMaxTotal(30);
//最大连接空闲数
config.setMaxIdle(2);
JedisPool pool = new JedisPool(config, "192.168.31.128", 6379);
Jedis jedis = null;
try {
jedis = pool.getResource();
jedis.set("name", "lisi");
String name = jedis.get("name");
System.out.println(name);
}catch(Exception ex){
ex.printStackTrace();
}finally{
if(jedis != null){
//关闭连接
jedis.close();
}
}
}
}
Redis_String相关操作
常用操作命令:
(多是针对key的值进行处理)
命令 | 说明 |
---|---|
set | 设置一个key/value |
get | 根据key获得对应的value |
mset | 一次设置多个key value |
mget | 一次获得多个key的value |
getset | 获得原始key的值,同时设置新值 |
strlen | 获得对应key存储value的长度 |
append | 为对应key的value追加内容 |
getrange 索引0开始 | 截取value的内容 |
setex | 设置一个key存活的有效期(秒)setex + 存活时间 + 值 |
psetex | 设置一个key存活的有效期(毫秒) |
setnx | 存在则不做任何操作,不存在再添加 |
msetnx原子操作(只要有一个存在不做任何操作) | 可以同时设置多个key,只有有一个存在都不保存 |
decr | 进行数值类型的-1操作 如针对age的值 - 1 |
decrby | 根据提供的数据进行减法操作 decrby age 10 |
Incr | 进行数值类型的+1操作 |
incrby | 根据提供的数据进行加法操作 |
Incrbyfloat | 根据提供的数据加入浮点数 |
Jedis String 相关API
//测试String相关
@Test
public void testString(){
//set
jedis.set("name","小陈");
//get
String s = jedis.get("name");
System.out.println(s);
//mset
jedis.mset("content","好人","address","海淀区");
//mget
List<String> mget = jedis.mget("name", "content", "address");
mget.forEach(v-> System.out.println("v = " + v));
//getset
String set = jedis.getSet("name", "小明");
System.out.println(set);
//............
}
Redis_List相关操作
列表类型(list)可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素,或者获得列表的某一个片段
列表类型内部是使用双向链表(double linked list)实现的,所以向列表两端添加元素的时间复杂度为0(1),获取越接近两端的元素速度就越快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是极快的
常用操作指令:
说明:
命令 | 说明 |
---|---|
lpush | 将某个值加入到一个key列表头部 |
lpushx | 同lpush,但是必须要保证这个key存在 |
rpush | 将某个值加入到一个key列表末尾 |
rpushx | 同rpush,但是必须要保证这个key存在 |
lpop | 返回和移除列表左边的第一个元素 |
rpop | 返回和移除列表右边的第一个元素 |
lrange | 获取某一个下标区间内的元素 (0 -1)获取全部 |
llen | 获取列表元素个数 |
lset | 设置某一个指定索引的值(索引必须存在) |
lindex | 获取某一个指定索引位置的元素 |
lrem | 删除重复元素 |
ltrim | 保留列表中特定区间内的元素 |
linsert | 在某一个元素之前,之后插入新元素 需要指明前插还是后插 Before/After |
Jedis List 相关API其他操作
package com;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.*;
class Main{
public static void main(String[] args) {
System.out.println("==Hash==");
Jedis jedis = new Jedis("192.168.31.128", 6379);
try {
Map<String, String> pairs = new HashMap<String, String>();
jedis.del("messages");
jedis.rpush("messages", "Hello how are you?");
jedis.rpush("messages", "Fine thanks. I'm having fun with redis.");
jedis.rpush("messages", "I should look into this NOSQL thing ASAP");
// 再取出所有数据jedis.lrange是按范围取出,
// 第一个是key,第二个是起始位置,第三个是结束位置,jedis.llen获取长度 -1表示取得所有
List<String> values = jedis.lrange("messages", 0, -1);
System.out.println(values);
// 清空数据
System.out.println(jedis.flushDB());
// 添加数据
jedis.lpush("lists", "vector");
jedis.lpush("lists", "ArrayList");
jedis.lpush("lists", "LinkedList");
// 数组长度
System.out.println(jedis.llen("lists"));
// 排序
//System.out.println(jedis.sort("lists"));
// 字串
System.out.println(jedis.lrange("lists", 0, 3));
// 修改列表中单个值
jedis.lset("lists", 0, "hello list!");
// 获取列表指定下标的值
System.out.println(jedis.lindex("lists", 1));
// 删除列表指定下标的值
System.out.println(jedis.lrem("lists", 1, "vector"));
// 删除区间以外的数据
System.out.println(jedis.ltrim("lists", 0, 1));
// 列表出栈
System.out.println(jedis.lpop("lists"));
// 整个列表值
System.out.println(jedis.lrange("lists", 0, -1));
} catch (Exception e) {
e.printStackTrace();
} finally {
jedis.close();
}
}
}
Redis_Hash相关操作
类似Java中的HashMap<String HashMap<String,String>
特点: value 是一个map结构 存在key value key 无序的
特点: value 是一个map结构 存在key value key 无序的
命令 | 说明 |
---|---|
hset | 设置一个key/value对 |
hget | 获得一个key对应的value |
hgetall | 获得所有的key/value对 |
hdel | 删除某一个key/value对 |
hexists | 判断一个key是否存在 |
hkeys | 获得所有的key |
hvals | 获得所有的value |
hmset | 设置多个key/value |
hmget | 获得多个key的value |
hsetnx | 设置一个不存在的key的值 |
hincrby | 为value进行加法运算 |
hincrbyfloat | 为value加入浮点值 |
# hset mymap name zhangsan ...
Jedis hash 相关API:
package com;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.*;
class Main{
public static void main(String[] args) {
System.out.println("==Hash==");
Jedis jedis = new Jedis("192.168.31.128", 6379);
try {
Map<String, String> pairs = new HashMap<String, String>();
pairs.put("name", "Akshi");
pairs.put("age", "2");
pairs.put("sex", "Female");
jedis.hmset("kid", pairs);
List<String> name = jedis.hmget("kid", "name");// 结果是个泛型的LIST
System.out.println(name);
jedis.hdel("kid","age"); //删除map中的某个键值
System.out.println(jedis.hmget("kid", "pwd")); // 因为删除了,所以返回的是null
System.out.println(jedis.hlen("kid")); // 返回key为user的键中存放的值的个数
System.out.println(jedis.exists("kid"));// 是否存在key为user的记录
System.out.println(jedis.hkeys("kid"));// 返回map对象中的所有key
System.out.println(jedis.hvals("kid"));// 返回map对象中的所有value
Iterator<String> iter = jedis.hkeys("kid").iterator();
while (iter.hasNext()) {
String key = iter.next();
System.out.println(key + ":" + jedis.hmget("kid", key));
}
List<String> values = jedis.lrange("messages", 0, -1);
values = jedis.hmget("kid", new String[] { "name", "age", "sex" });
System.out.println(values);
Set<String> setValues = jedis.zrange("hackers", 0, -1);
setValues = jedis.hkeys("kid");
System.out.println(setValues);
values = jedis.hvals("kid");
System.out.println(values);
pairs = jedis.hgetAll("kid");
System.out.println(pairs);
// 清空数据
System.out.println(jedis.flushDB());
// 添加数据
jedis.hset("hashs", "entryKey", "entryValue");
jedis.hset("hashs", "entryKey1", "entryValue1");
jedis.hset("hashs", "entryKey2", "entryValue2");
// 判断某个值是否存在
System.out.println(jedis.hexists("hashs", "entryKey"));
// 获取指定的值
System.out.println(jedis.hget("hashs", "entryKey")); // 批量获取指定的值
System.out.println(jedis.hmget("hashs", "entryKey", "entryKey1"));
// 删除指定的值
System.out.println(jedis.hdel("hashs", "entryKey"));
// 为key中的域 field 的值加上增量 increment
System.out.println(jedis.hincrBy("hashs", "entryKey", 123l));
// 获取所有的keys
System.out.println(jedis.hkeys("hashs"));
// 获取所有的values
System.out.println(jedis.hvals("hashs"));
} catch (Exception e) {
e.printStackTrace();
} finally {
jedis.close();
}
}
}
Redis_Set相关操作
在set集合中的每个元素都是不同的,且没有顺序
集合类型的常用操作是向集合中加入或删除元素、判断某个元素是否存在等,由于集合类型的Redis内部是使用值为空的散列表实现,所有这些操作的时间复杂度都为0(1)
Redis还提供了多个集合之间的交集、并集、差集的运算
命令 | 说明 |
---|---|
sadd | 为集合添加元素 |
smembers | 显示集合中所有元素 无序 |
scard | 返回集合中元素的个数 |
spop | 随机返回一个元素 并将元素在集合中删除 |
smove | 从一个集合中向另一个集合移动元素 必须是同一种类型 |
srem | 从集合中删除一个元素 |
sismember | 判断一个集合中是否含有这个元素 |
srandmember | 随机返回元素 |
sdiff | 去掉第一个集合中其它集合含有的相同元素 |
sinter | 求交集 |
sunion | 求和集 |
Jedis Set相关API:
//测试SET相关
@Test
public void testSet(){
//sadd
jedis.sadd("names","zhangsan","lisi");
//smembers
jedis.smembers("names");
//sismember
jedis.sismember("names","xiaochen");
//...
}
Redis_ZSet相关操作
与Java中的TreeMap/TreeMap类似,与Set的区别是ZSet可排序
常用命令:
命令 | 说明 |
---|---|
zadd | 添加一个有序集合元素 |
zcard | 返回集合的元素个数 |
zrange (升序) zrevrange (降序) | 返回一个范围内的元素 |
zrangebyscore | 按照分数查找一个范围内的元素 |
zrank | 返回排名 |
zrevrank | 倒序排名 |
zscore | 显示某一个元素的分数 |
zrem | 移除某一个元素 |
zincrby | 给某个特定元素加分 |
- zadd myzet 10 xiaoming 15 xiaohong ... 需要给每个值赋上一个rank
Jedis Zset 相关API
//测试ZSET相关
@Test
public void testZset(){
//zadd
jedis.zadd("names",10,"张三");
//zrange
jedis.zrange("names",0,-1);
//zcard
jedis.zcard("names");
//zrangeByScore
jedis.zrangeByScore("names","0","100",0,5);
//..
}
Redis_持久化
快照(Snapshot)
- 特点
这种方式可以将某一时刻的所有数据都写入硬盘中,当然这也是redis的默认开启持久化方式,保存的文件是以.rdb形式结尾的文件因此这种方式也称之为RDB方式。
2.快照生成方式
- 客户端方式: BGSAVE 和 SAVE指令
- 服务器配置自动触发
客户端方式之BGSAVE:
-
客户端可以使用BGSAVE命令来创建一个快照,当接收到客户端的BGSAVE命令时,redis会调用fork来创建一个子进程,然后子进程负责将快照写入磁盘中,而父进程则继续处理命令请求。
名词解释: fork当一个进程创建子进程的时候,底层的操作系统会创建该进程的一个副本,在类unix系统中创建子进程的操作会进行优化:在刚开始的时候,父子进程共享相同内存,直到父进程或子进程对内存进行了写之后,对被写入的内存的共享才会结束服务
客户端方式之SAVE:
- 客户端还可以使用SAVE命令来创建一个快照,接收到SAVE命令的redis服务器在快照创建完毕之前将不再响应任何其他的命令
- 注意: SAVE命令并不常用,使用SAVE命令在快照创建完毕之前,redis处于阻塞状态,无法对外服务
BGSAVE自动触发:
如果用户在redis.conf中设置了save配置选项,redis会在save选项条件满足之后自动触发一次BGSAVE命令,如果设置多个save配置选项,当任意一个save配置选项条件满足,redis也会触发一次BGSAVE命令
服务器接收客户端shutdown指令:
当redis通过shutdown指令接收到关闭服务器的请求时,会执行一个save命令,阻塞所有的客户端,不再执行客户端执行发送的任何命令,并且在save命令执行完毕之后关闭服务器
配置生成快照名称和位置:
#1.修改生成快照名称
- dbfilename dump.rdb
# 2.修改生成位置
- dir ./ # ./ 代表同级目录下
AOF 只追加日志文件
特点:
这种方式可以将所有客户端执行的写命令记录到日志文件中,AOF持久化会将被执行的写命令写到AOF的文件末尾,以此来记录数据发生的变化,因此只要redis从头到尾执行一次AOF文件所包含的所有写命令,就可以恢复AOF文件的记录的数据集.
2.开启AOF持久化
在redis的默认配置中AOF持久化机制是没有开启的,需要在配置中开启
- 修改
appendonly yes
开启持久化 - [选]修改
appendfilename "appendonly.aof"
指定生成文件名称
日志追加频率:
1.always 【谨慎使用】
- 说明: 每个redis写命令都要同步写入硬盘,严重降低redis速度
- 解释: 如果用户使用了always选项,那么每个redis写命令都会被写入硬盘,从而将发生系统崩溃时出现的数据丢失减到最少;遗憾的是,因为这种同步策略需要对硬盘进行大量的写入操作,所以redis处理命令的速度会受到硬盘性能的限制;
- 注意: 转盘式硬盘在这种频率下200左右个命令/s ; 固态硬盘(SSD) 几百万个命令/s;
- 警告: 使用SSD用户请谨慎使用always选项,这种模式不断写入少量数据的做法有可能会引发严重的写入放大问题,导致将固态硬盘的寿命从原来的几年降低为几个月。
2.everysec 【推荐】
- 说明: 每秒执行一次同步显式的将多个写命令同步到磁盘
- 解释: 为了兼顾数据安全和写入性能,用户可以考虑使用everysec选项,让redis每秒一次的频率对AOF文件进行同步;redis每秒同步一次AOF文件时性能和不使用任何持久化特性时的性能相差无几,而通过每秒同步一次AOF文件,redis可以保证,即使系统崩溃,用户最多丢失一秒之内产生的数据。
3.no 【不推荐】
- 说明: 由操作系统决定何时同步
- 解释:最后使用no选项,将完全有操作系统决定什么时候同步AOF日志文件,这个选项不会对redis性能带来影响但是系统崩溃时,会丢失不定数量的数据,另外如果用户硬盘处理写入操作不够快的话,当缓冲区被等待写入硬盘数据填满时,redis会处于阻塞状态,并导致redis的处理命令请求的速度变慢。
修改同步频率:
# 1.修改日志同步频率
- 修改appendfsync everysec|always|no 指定
AOF文件的重写:
AOF的方式也同时带来了另一个问题。持久化文件会变的越来越大。例如我们调用incr test命令100次,文件中必须保存全部的100条命令,其实有99条都是多余的。因为要恢复数据库的状态其实文件中保存一条set test 100就够了。为了压缩aof的持久化文件Redis提供了AOF重写(ReWriter)机制。
触发重写方式:
1.客户端方式触发重写
- 执行
BGREWRITEAOF
命令 不会阻塞redis的服务
2.服务器配置方式自动触发
- 配置redis.conf中的
auto-aof-rewrite-percentage
选项 参加下图↓↓↓ - 如果设置
auto-aof-rewrite-percentage
值为100和auto-aof-rewrite-min-size 64mb
,并且启用的AOF持久化时,那么当AOF文件体积大于64M,并且AOF文件的体积比上一次重写之后体积大了至少一倍(100%)时,会自动触发,如果重写过于频繁,用户可以考虑将auto-aof-rewrite-percentage设置为更大
重写原理:
注意:重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,替换原有的文件
这点和快照有点类似。
重写流程:
-
- redis调用fork ,现在有父子两个进程 子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令
-
- 父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。
-
- 当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。
-
- 现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。
持久化总结
两种持久化方案既可以同时使用(aof),又可以单独使用,在某种情况下也可以都不使用,具体使用那种持久化方案取决于用户的数据和应用决定。
无论使用AOF还是快照机制持久化,将数据持久化到硬盘都是有必要的,除了持久化外,用户还应该对持久化的文件进行备份(最好备份在多个不同地方)。
[扩展]Redis_设计分析
在互联网项目中我们需要提高数据的访问速度,关系型数据库就满足不了我们的要求,所有我们需要使用非关系型数据库来提高查询速度
将DB关系型数据库的数据同步到Redis数据库中,用户直接查询Redis‘数据库,以提高我们的访问速度
但是我们该如何同步数据库到redis中呢
示例数据模型
我们需要把关系型数据库变成键值对存储
- 球队的存储
Key的设计:在key中我们可以使用list来存储所有的球队的id
结构类型 | Key | value |
---|---|---|
list | team:team_id | 1 2 3 4… |
team:team_id : [1, 2, 3, 4….]
- 每一支球队的存储使用hash
结果类型 | Key | Key_attr | Value |
---|---|---|---|
hash | team:[team_id值] | name | 公牛 |
hash | team:[team_id值] | addr | 芝加哥 |
hash | team:[team_id值] | team_id | 101 |
team:101 : [{team_id:101},{name : 公牛},{addr:芝加哥}]
team:102 : [{team_id:102},{name : 骑士},{addr:克利夫兰}]
- 每一个雇员使用hash
结果类型 | Key | Key_attr | Value |
---|---|---|---|
hash | emp:[emp_id值] | emp_id | 1 |
hash | emp:[emp_id值] | name | 张三 |
hash | emp:[emp_id值] | birthday | 2016-12-12 |
hash | emp:[emp_id值] | gender | 101 |
hash | emp:[emp_id值] | team_id | 101 |
emp:001 : {emp_id:001, name:张三, birthday:2016-12-12, gender:1, team_id:101}
emp:002 : {emp_id:002, name:李四, birthday:2016-12-13, gender:1, team_id:101}
- 从球队方向关注与雇员的关系:是一对多的关系,一的一端如何获取多的一端:
一对多存储类型 | Key | Value |
---|---|---|
list | team:[team_id值]:emp | [001, 002,……] |
team:101:emp : [001, 002]
- 多对一关系可以直接在员工的hash结构中来获得一的一端
emp:002 : {emp_id:002, name:李四, birthday:2016-12-13, gender:1, team_id:101}
[进阶]SpirngBoot Date Redis客户端
注意事项
Spring Boot Data(数据) Redis 中提供了RedisTemplate和StringRedisTemplate,其中StringRedisTemplate是RedisTemplate的子类,两个方法基本一致,不同之处主要体现在操作的数据类型不同,RedisTemplate中的两个泛型都是Object,意味着存储的key和value都可以是一个对象
RedisTemplate(Object Object);
而StringRedisTemplate的两个泛型都是String,意味着StringRedisTemplate的key和value都只能是字符串。
StringRedisTemplate(Stirng String);
注意: 使用RedisTemplate默认是将对象序列化到Redis中,所以放入的对象必须实现对象序列化接口
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
application.properties
spring.redis.host= your redis emviorment ip address
spring.redis.port=6379
spring.redis.database=1
server.port=8899
相关测试代码
//启动springboot应用
@SpringBootTest(classes = RedisApplication.class)
public class TestStringRedisTemplate {
//注入字符串模板
@Autowired
private StringRedisTemplate stringRedisTemplate;
//注入对象模板
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testKey(){
//不用opsForXXX 直接操作的就是key
System.out.println(stringRedisTemplate.hasKey("name")); //是否存在该key
System.out.println(stringRedisTemplate.type("name")); //判断类型
System.out.println(stringRedisTemplate.keys("*")); //keys *
System.out.println(stringRedisTemplate.delete("name")); //删除
System.out.println(stringRedisTemplate.getExpire("name")); //获取key超时时间
//true
//STRING
//[name]
//true
//-2 (不存在)
}
@Test
public void testString(){
//opsForValue()代表操作字符串类型
stringRedisTemplate.opsForValue().set("name","HWL"); //设置key-value
stringRedisTemplate.opsForValue().set("name1","HWL",10, TimeUnit.SECONDS);//参数可设置超时时间
stringRedisTemplate.opsForValue().get("name");//获取key的value
}
//测试Redis中的list类型 .forList
@Test
public void testList(){
stringRedisTemplate.opsForList().leftPush("AGE","18","19"); //...
// ...
//支持以对象传输 自动序列化 泛型必须是String类型
List<String> names = new ArrayList<>();
names.add("HWL");
names.add("HHM");
stringRedisTemplate.opsForList().leftPushAll("names",names);
stringRedisTemplate.opsForList().trim("names",0,1); //截取指定区间
}
//测试Redis中的set类型 .forSet
@Test
public void testSet(){
stringRedisTemplate.opsForSet().add("names","HWL","HWL","HHM");
System.out.println(stringRedisTemplate.opsForSet().members("names"));
}
//测试Redis中的Zset类型 .forZSet
@Test
public void testZSet(){
stringRedisTemplate.opsForZSet().add("zst","HWL",100);
stringRedisTemplate.opsForZSet().add("zst","HHM",101);
Set<String> names = stringRedisTemplate.opsForZSet().range("zst", 0, -1); //指定范围查询
Set<ZSetOperations.TypedTuple<String>> withScores = stringRedisTemplate.opsForZSet().rangeWithScores("zst", 0, -1);
names.forEach(System.out::println);
withScores.forEach(typedTuple -> {
System.out.println(typedTuple.getValue());
System.out.println(typedTuple.getScore());
}
);
}
@Test
public void testHash(){
stringRedisTemplate.opsForHash().put("hash","names","HWL");
//...
}
//需要实现序列化
class User implements Serializable {
public String name;
public Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
}
@Test
public void testObject(){
//使用redisTemplate
//所有Value参数都应该实现序列化
//redisTemplate中key和value的序列化策略都是 JdkSerializationRedisSerializer
//方便起见,key的序列化策略需要修改为String序列化方案
redisTemplate.setKeySerializer(new StringRedisSerializer());
User user = new User("HWL",18);
redisTemplate.opsForValue().set("user",user);
redisTemplate.opsForList().leftPush("user",user);
redisTemplate.opsForSet().add("user",user);
//...
//修改hash key的序列化方案
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
}
}