Redis-数据结构及持久化操作(CentOS)

Redis安装

参考:

Linux

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

  1. 修改redis.conf中的bind字段
  2. 将127.0.0.1修改为0.0.0.0 表示允许一切客户端访问
  3. 重启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)

  1. 特点

这种方式可以将某一时刻的所有数据都写入硬盘中,当然这也是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文件,替换原有的文件

这点和快照有点类似。

重写流程:

    1. redis调用fork ,现在有父子两个进程 子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令
    1. 父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。
    1. 当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件
    1. 现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。

持久化总结

两种持久化方案既可以同时使用(aof),又可以单独使用,在某种情况下也可以都不使用,具体使用那种持久化方案取决于用户的数据和应用决定。

无论使用AOF还是快照机制持久化,将数据持久化到硬盘都是有必要的,除了持久化外,用户还应该对持久化的文件进行备份(最好备份在多个不同地方)。

[扩展]Redis_设计分析

在互联网项目中我们需要提高数据的访问速度,关系型数据库就满足不了我们的要求,所有我们需要使用非关系型数据库来提高查询速度

将DB关系型数据库的数据同步到Redis数据库中,用户直接查询Redis‘数据库,以提高我们的访问速度

但是我们该如何同步数据库到redis中呢

示例数据模型

我们需要把关系型数据库变成键值对存储

  • 球队的存储

Key的设计:在key中我们可以使用list来存储所有的球队的id

结构类型Keyvalue
listteam:team_id1 2 3 4…

team:team_id : [1, 2, 3, 4….]

  • 每一支球队的存储使用hash
结果类型KeyKey_attrValue
hashteam:[team_id值]name公牛
hashteam:[team_id值]addr芝加哥
hashteam:[team_id值]team_id101

team:101 : [{team_id:101},{name : 公牛},{addr:芝加哥}]

team:102 : [{team_id:102},{name : 骑士},{addr:克利夫兰}]

  • 每一个雇员使用hash
结果类型KeyKey_attrValue
hashemp:[emp_id值]emp_id1
hashemp:[emp_id值]name张三
hashemp:[emp_id值]birthday2016-12-12
hashemp:[emp_id值]gender101
hashemp:[emp_id值]team_id101

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}

  • 从球队方向关注与雇员的关系:是一对多的关系,一的一端如何获取多的一端
一对多存储类型KeyValue
listteam:[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());
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值