Skip to content

Redis

redis-logo

Redis入门

NoSQL简介

NoSQL 概述

NoSQL概念

NoSQL,全称为 Not Only SQL,意为***不仅仅是SQL***,泛指非关系型数据库。NoSQL 是基于键值对的,而且不需要经过 SQL 层的解析,数据之间没有耦合性,性能非常高。

数据库分类
  • 关系型数据库

    实体与实体之间是有各种关系的

  • 非关系型数据库

    实体与实体之间是没有关系的

  • 对比

    特点关系型数据库非关系型数据库
    优点数据可以永久保存存取速度很快
    缺点读取速度相对比较慢
    1. 表有约束,数据添加校验过程。
    2. 如果多个DML操作,需要使用事务。
    3. 如果数据量大,访问速度越来越慢。
    有可能会导致数据丢失
    存储介质数据主要是存在硬盘上数据主要是在内存中
    存储数据关系型数据常读取,但不常修改

主流的NoSQL产品

NoSQL 数据库并没有一个统一的架构,两种 NoSQL 数据库之间的不同,甚至远远超过两种关系型数据库的不同。可以说,NoSQL 各有所长,成功的 NoSQL 必然特别适用于某些场合或者某些应用,在这些场合中会远远胜过关系型数据库和其他的 NoSQL。

  • 键值存储数据库,代表有 Redis、Memcache、Voldemort 和 Oracle BDB 等

  • 列存储数据库,代表有 Cassandra、HBase 和 Riak 等

  • 文档型数据库,代表有 CouchDB 和 MongoDB 等

  • 图形数据库,代表有 Neo4J,InfoGrid 和 Infinite Graph 等

  • Redis 是一个开源的使用 C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API。官方提供测试数据,50 个并发执行 10 万个请求,读的速度是 11 万次/s,写的速度是 8 万次/s 。

  • CouchDB 是一个开源的面向文档的数据库管理系统,具有高度可伸缩性,提供了高可用性和高可靠性,CouchDB 是一个 Apache Software Foundation 开源项目。

  • MongoDB 是一个基于分布式文件存储的数据库。由 C++语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。

为什么需要Redis

Forward-Cache-Pattern

  • High Throughput- 高吞吐量

    • Throughput - 吞吐量

    系统在单位时间内可以完成的工作总量

  • High Scalability - 高可升级性

    • Scalability - 可升级性

    修改系统提升吞吐量带来的成本,的反面度量

    • Extensibility - 可扩展性

    增加新特征的成本,的反面度量

  • High Performance - 高性能

    • Performance - 性能

    响应用户的应答时间

小结

  • 什么是 NoSQL【重点】

    Not only SQL

  • 为什么要使用 NoSQL【重点】

    高性能

    高吞吐量

    高可升级性

Redis的安装与使用

Docker版本Redis的安装与启动

  • docker-compose.yml
yml
version: '3.9'
services:
  redis:
    image: redis:6.0.9
    restart: always
    container_name: redis
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 6379:6379

networks:
  default:
    external: true
    name: global
  • shell

开启6379端口

shell
/sbin/iptables -I INPUT -p tcp --dport 6379 -j ACCEPT
firewall-cmd --zone=public --add-port=6379/tcp --permanent
systemctl restart firewalld

Windows版Redis的下载

Windows版Redis的安装与启动

Windows版Redis的安装

Windows 版的安装及其简单,解压 Redis 压缩包完成即安装完毕

l 安装的注意事项:

  1. 解压的目录不要有中文

  2. 目录结构层次不要太深

  3. 硬盘空间剩余空间最少要大于你的内存空间,建议 20G 以上。

Redis的目录文件
目录或文件作用
redis-benchmark.exeredis的性能测试工具
redis-check-aof.exeaof文件的检查和修复工具
redis-check-dump.exerdb文件的检查和修复工具
redis-cli.execlient 客户端访问命令
redis-server.exe服务器启动程序
redis.conf配置文件,这是个文本文件
Windows版Redis的启动与关闭
  1. 启动服务器:直接执行 redis-server.exe 文件可以了

  2. 默认端口号:6379

  3. 关闭服务器:直接关闭窗口

Windows版Redis的使用

启动客户端的方式: 直接运行即可

安装Redis到系统服务中

win + R --> 运行:services.msc

打开redis的官方安装文档:RedisService.docx

1536225394322

文档中有如下安装服务和卸载服务的说明:

【安装服务】

1536225530301

cmd进入到Redis的安装目录下,输入以下命令:
redis-server --service-install redis.windows.conf  --loglevel verbose

1536225707219

安装完成后,查看系统服务列表(win+r 输入:services.msc),找到 redis 后右击启动:

1536225861317

RedisDesktopManager

小结

  1. 启动 Redis 服务器的命令是什么

    redis-server

  2. 启动 Redis 客户端的命令是什么

    redis-cli

  3. Redis 服务器的端口号是多少?

    6379

Redis数据类型与操作命令

Redis的5种数据类型

redis 是一种高级的 key-value 的存储系统,其中 value 支持五种数据类型:

Redis支持的键值数据类型
stringMap<String, String> 字符串类型
hashMap<String, Map> 类似于Map,值由键和值组成
listMap<String, List> 类似于List,值由多个元素组成,有前后顺序,可以重复
setMap<String, Set> 类似于Set,值由多个元素组成,无序,不可重复
zsetMap<String, ZSet/OrderSet> 有序的Set,类于TreeSet,有序,不可重复

关于 key 的定义,注意如下几点:

  1. 键长度不能超过 1024,太长可读性低,查询速度也会有影响。
  2. 不建议太短,可维护性差。如果同名会被覆盖。
  3. 在企业中通常都有统一的命名规范

字符串类型string

概述

字符串类型是 Redis 中最为基础的数据存储类型,它在 Redis 中以二进制保存,没有编码和解码的过程。无论存入的是字符串、整数、浮点类型都会以字符串写入。在 Redis 中字符串类型的值最多可以容纳的数据长度是 512M,这是以后最常用的数据类型。

常用命令
命令行为
【重点】set 键 值向Redis数据为中添加指定的键和值
【重点】get 键从数据库中获取指定键的值
【重点】del 键删除指定的键
mset key value [key value...]批量添加
mget key [key...]批量获取
incr key相加命令(+1)
decr key相减命令(-1)
incrby key increment自增指定数量
decrby key increment自减指定数量
setex key second value设置值的同时,指定生存时间(每次向Redis中添加数据时,尽量都设置上生存时间)
setnx key value设置值,如果当前key不存在的话(如果这个key存在,什么事都不做,如果这个key不存在,和set命令一样)
append key value在key对应的value后,追加内容
strlen key查看value字符串的长度
命令演示

需求

  1. 添加一个键为 company,值为 future-weaver

    set company future-weaver

  2. 再设置一个键为 company,值为 rainbow-weaver

    set company rainbow-weaver

  3. 得到 company 的元素

    get company

  4. 删除 company 元素

    del company

  5. 再次删除 company 看返回值是否相同

    del company

  6. 得到 company 看返回值是多少

    get company

小结
  1. Redis 中值有哪五种类型

    • String
    • Hash
    • List
    • Set
    • ZSet
  2. 向 string 中添加,获取,删除值的命令是什么?

    • set key value

    • get key

    • del key

哈希类型hash

概述

Redis 中的 Hash 类型可以看成是键和值都是 String 类型的 Map 容器,每一个 Hash 可以存储 4G 个键值对。

所以该类型非常适合于存储对象的信息。如一个用户有姓名,密码,年龄等信息,则可以有 username、password 和 age 等键。它的存储结构如下:

常用命令
命令行为
【重点】hset 键 字段 值添加一个hash类型的键值对,值有字段和值
【重点】hget 键 字段通过键,字段得到一个值,如:hget user age
【重点】hmset 键 字段 值 字段 值 ...multiple 同时添加多个字段和值到某个键中
【重点】hmget 键 字段 字段 ...指定键和多个字段,同时取出多个值
【重点】hdel 键 字段 字段指定键和字段,删除一个或多个值
【重点】hgetall 键得到指定键的所有字段和值
hincrby key field increment自增(指定自增的值)
hsetnx key field value设置值(如果key-field不存在,那么就正常添加,如果存在,什么事都不做)
hexists key field检查field是否存在
hkeys key获取当前hash结构中的全部field
hvals key获取当前hash结构中的全部value
hlen key获取当前hash结构中field的数量
命令演示
  1. 创建 hash 类型的键为 user,并且添加一个字段为 username,值为 future-weaver

    hset user username future-weaver

  2. 向 user 中添加字段为 password,值为 12345

    hset user password 12345

  3. 向 user 中添加字段为 age,值为 18

    hset user age 18

  4. 分别得到 user 中的 username、password 和 age 的字段值

    hget user usernamehget user passwordhget user age

  5. 向 user 中同时添加多个字段和值,birthday 2018-01-01 gender male

    hmset user birthday 2018-01-01 gender male

  6. 同时取得多个字段:age 和 gender

    hmget user age gender

  7. 得到 user 中所有的字段和值

    hgetall user

  8. 删除 user 中的生日和密码字段

    hdel user birthday password

小结
  • hset 键 字段 值

    没有时添加和有时修改

  • hget 键 字段

    获取

  • hmset 键 字段 值 字段 值

    批量添加

  • hmget 键 字段 字段

    批量获取

  • hdel 键 字段 字段

    删除多个

  • hgetall 键

    获取所有键值对

列表类型list

概述

在 Redis 中,List 类型是按照插入顺序排序的字符串链表。和数据结构中的普通链表一样,我们可以在其左部(left)和右部(right)添加新的元素。在插入时,如果该键并不存在,Redis 将为该键创建一个新的链表。与此相反,如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。List 中可以包含的最大元素数量是 4G 个。

常用命令
命令行为
【重点】lpush 键 元素 元素从左边添加1个或多个元素
【重点】rpush 键 元素 元素从右边添加1个或多个元素
【重点】lpop 键从左边删除一个元素,返回被删除的元素
【重点】rpop 键从右边删除一个元素,返回被删除的元素
【重点】lrange 键 开始 结束指定开始和结束的索引,查询多个元素。 从左边数起是从0开始,从右边数起从-1开始 如果查询列表中所有元素,从0到-1就可以了
【重点】llen 键查询列表中元素的长度
【重点】LSET KEY_NAME INDEX VALUE通过索引来设置元素的值。
当索引参数超出范围,或对一个空列表进行 LSET 时,返回一个错误。
【重点】LREM key count VALUE根据参数 COUNT 的值,移除列表中与参VALUE 相等的元素。
COUNT 的值可以是以下几种:
count > 0 : 从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为 COUNT 。
count < 0 : 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值。
count = 0 : 移除表中所有与 VALUE 相等的值。
lpushx key value存储数据(如果key不存在,什么事都不做,如果key存在,但是不是list结构,什么都不做)
rpushx key value存储数据(如果key不存在,什么事都不做,如果key存在,但是不是list结构,什么都不做)
lindex key index获取指定索引位置的数据
ltrim key start stop保留列表中的数据(保留你指定索引范围内的数据,超过整个索引范围被移除掉)
rpoplpush list1 list2将一个列表中最后的一个数据,插入到另外一个列表的头部位置
命令演示
  1. 向 mylist 键的列表中,从左边添加 a b c 三个元素

    lpush mylist a b c

  2. 从右边添加 one two three 三个元素

    rpush mylist one two three

  3. 查询所有的元素

    lrange mylist 0 -1

  4. 从右边添加一个重复的元素 three

    rpush mylist three

  5. 删除最右边的元素 three

    rpop mylist

  6. 删除最左边的元素 c

    lpop mylist

  7. 获取列表中元素的个数

    llen mylist

小结
  • lpush 键 元素 元素

    从左边添加元素

  • rpush 键 元素 元素

    从右边添加元素

  • lpop 键

    从左边删除元素

  • rpop 键

    从右边删除元素

  • lrange 键 开始 结束

    获取范围内的元素

  • llen 键

    查个数

集合类型set

概述

在 Redis 中,我们可以将 Set 类型看作为没有排序的字符集合,和 List 类型一样,我们也可以在该类型的数据值上执行添加、删除或判断某一元素是否存在等操作。

Set 可包含的最大元素数量是 4G,和 List 类型不同的是,Set 集合中不允许出现重复的元素。

常用命令
命令行为
【重点】sadd 键 元素 元素向set中添加一个或多个元素
【重点】smembers 键查询指定键和所有元素
【重点】sismember 键 元素判断指定的元素是否存在set中,如果存在返回1,否则返回0
【重点】srem 键 元素 元素删除集合中一个或多个元素
spop key [count]随机获取一个数据(获取的同时,移除数据,count默认为1,代表弹出数据的数量)
sinter set1 set2 ...交集(取多个set集合交集)
sunion set1 set2 ...并集(获取全部集合中的数据)
sdiff set1 set2 ...差集(获取多个集合中不一样的数据)
命令演示
  1. 向 myset 集合中添加 A B C 1 2 3 六个元素

    sadd myset A B C 1 2 3

  2. 再向 myset 中添加 B 元素,看能否添加成功

    sadd myset B

  3. 显示所有的成员,发现与添加的元素顺序不同,元素是无序的

    smembers myset

  4. 删除其中的 C 这个元素,再查看结果

    srem myset C

    smember myset

  5. 判断 A 是否在 myset 集合中

    sismember myset A

  6. 判断 D 是否在 myset 集合中

    sismember myset D

小结
  • sadd 键 元素 元素

    添加

  • smembers 键

    查看所有元素

  • sismember 键 元素

    元素是否存在

  • srem 键 元素 元素

    删除元素

有序集合zset

概述

Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。它用来保存需要排序的数据。例如排行榜,一个班的语文成绩,一个公司的员工工资,一个论坛的帖子等。有序集合中,每个元素都带有 score(权重),以此来对元素进行排序。它有三个元素:key、member 和 score。以语文成绩为例,key 是考试名称(期中考试、期末考试等),member 是学生名字,score 是成绩。

有序集合有两大基本用途:排序和聚合

常用命令
命令行为
【重点】zadd key score1 member1 score2 member2 ...添加一个或多个成员
【重点】zcard key获取有序集合的成员数
【重点】zrange key start stop通过索引区间返回有序集合成指定区间内的成员
【重点】zrange key start stop withscores通过索引区间返回有序集合成指定区间内的成员【升序排列】
【重点】zrevrange key start stop [withscores]通过索引区间返回有序集合成指定区间内的成员【降序排列】
【重点】zrem key member [member1 ... ]移除有序集合中的一个或多个成员
zincrby key increment member修改member的分数(如果member是存在于key中的,正常增加分数,如果memeber不存在,这个命令就相当于zadd)
zscore key member查看指定的member的分数
zcount key min max根据score的范围查询member数量
zrangebyscore key min max [withscores] [limit offset count]根据分数的返回去获取member(withscores代表同时返回score,添加limit,就和MySQL中一样,如果不希望等于min或者max的值被查询出来可以采用 ‘(分数’ 相当于 < 但是不等于的方式,最大值和最小值使用+inf和-inf来标识)
zrangebyscore key max min [withscores] [limit offset count]根据分数的返回去获取member(withscores代表同时返回score,添加limit,就和MySQL中一样)
命令演示
  1. 向 stuScores 有序集合中添加 zhangsan 成员,成绩为 60; lisi 成员,成绩为 2; wangwu 成员,成绩为 90

    zadd stuScores 60 zhangsan 2 lisi 90 wangwu

  2. 获取 stuScores 有序集合中所有的成员

    zrange stuScores 0 -1

  3. 获取 stuScores 有序集合中所有的成员及分数

    zrange stuScore 0 -1 withscores

  4. 倒序获取 stuScores 有序集合中所有的成员及分数

    zrevrange stuScores 0 -1 withscores

  5. 获取 stuScores 有序集合中的成员个数

    zcard stuScores

  6. 移除 stuScores 有序集合中的 lisi 成员

    zrem stuScores lisi

小结
  • zadd key score1 member1 [score2 member2 ...]

    添加元素

  • zcard key

    获取个数

  • zrange key start stop [withscores]

    获取区间内的元素

  • zrevrange key start stop [withscores]

    降序获取,withscores带分数

  • zrem key member [member1 ... ]

    删除元素

Redis通用命令

常用命令
命令功能
【重点】keys 匹配字符查询数据库中有哪些键,支持以下两个通配符 *匹配多个字符; ? 匹配1个字符
【重点】del 键1 键2删除一个或多个键,键可以是任意的类型
【重点】exists 键判断指定的键是否存在
【重点】type 键判断指定的键是什么类型,返回字符串类型名
【重点】select 数据库编号选择数据库,编号0-15
【重点】move 键 数据库编号将指定的键移动到指定的数据库中
expire key second设置key的生存时间,单位为秒,设置还能活多久
pexpire key milliseconds设置key的生存时间,单位为毫秒,设置还能活多久
expireat key timestamp设置key的生存时间,单位为秒,设置能活到什么时间点
pexpireat key milliseconds设置key的生存时间,单位为毫秒,设置能活到什么时间点
ttl key查看key的剩余生存时间,单位为秒(-2 - 当前key不存在,-1 - 当前key没有设置生存时间,具体剩余的生存时间)
pttl key查看key的剩余生存时间,单位为毫秒(-2 - 当前key不存在,-1 - 当前key没有设置生存时间,具体剩余的生存时间)
persist key移除key的生存时间(1 - 移除成功,0 - key不存在生存时间,key不存在)
flushdb清空当前所在的数据库
flushall清空全部数据库
dbsize查看当前数据库中有多少个key
lastsave查看最后一次操作的时间
monitor实时监控Redis服务接收到的命令
命令演示
  1. 添加字符串 name 的值为 zhangsan

    set name zhangsan

  2. 显示所有的键

    keys *

  3. 显示所有以 my 开头的键

    keys my*

  4. 显示所有 my 后面有三个字符的键

    keys my???

  5. 添加一个字符串:name2 lisi

    set name2 lisi

  6. 添加一个 list:name3 a b c d

    rpush name3 a b c d

  7. 显示所有的键

    keys *

  8. 一次删除 name2 和 name3 这两个键,其中 name2 和 name3 是不同的类型,显示所有键

    del name2 name3

  9. 分别判断 name 和 name2 是否存在

    exists name

    exists name2

  10. 分别判断 name user myset mylist 分别是什么类型

    type name

    type user

    type myset

    type mylist

  11. 切换数据库到 15,向 15 中添加一个 name2 wangwu,得到 name2 的值显示。

    select 15

    set name2 wangwu

    get name2

  12. 将 15 中的 name2 移到 0 中

    move name2 0

  13. 切换到数据库 0,显示所有的键

    select 0

    keys *

小结
  1. keys 匹配字符

    查询键

  2. del 键 1 键 2

    删除键

  3. exists 键

    判断是否存在

  4. type 键

    判断键的类型

  5. select 数据库编号

    切换数据库

  6. move 键 数据库编号

    将某个键移到另一个库中

Jedis

Jedis的介绍

Redis 不仅可以使用命令来操作,现在基本上主流的语言都有 API 支持,比如 Java、C#、C++、PHP、Node.js、Go 等。在官方网站里列一些 Java 的客户端,有 Jedis、Redisson、Jredis、JDBC-Redis 等其中官方推荐使用 Jedis 和 Redisson。

使用 Jedis 操作 redis 需要导入 jar 包如下:

  • commons-pool2-2.3.jar
  • jedis-2.7.0.jar
  • junit-4.12.jar
  • hamcrest-core-1.3.jar

Jedis相关API概要

Jedis

Jedis快速入门

需求

使用 Jedis 上面的方法来访问 Redis,向服务器中写入字符串和 list 类型,并且取出打印控制台上。

分析
  1. 创建 Jedis 对象,指定服务器地址和端口号

  2. 向服务器写入

    a. set 字符串类型的数据,person=张三

    b. lpush 添加 list 类型的数据,cities=珠海,深圳,广州

  3. 从服务器中读取上面的数据打印输出

    a. get 得到字符串的值

    b. lrange 得到 list 所有的列表元素

  4. 关闭 Jedis 对象,释放资源

  5. 通过客户端查看数据库中是否有数据

代码
java
package com.futureweaver.jedis;

import org.junit.Test;
import redis.clients.jedis.Jedis;

import java.util.List;

/**
 * 使用Jedis上面的方法来访问Redis,向服务器中写入字符串和list类型,并且取出打印控制台上。
 */
public class JedisTester {

    @Test
    public void test1() {
        //1)创建Jedis对象,指定服务器地址和端口号
        Jedis jedis = new Jedis("localhost", 6379);
        //2)向服务器写入
        //a)set字符串类型的数据,person=张三
        jedis.set("person", "张三");
        //b)lpush添加list类型的数据,cities=珠海,深圳,广州
        jedis.lpush("cities", "珠海", "深圳", "广州");
        //3)从服务器中读取上面的数据打印输出
        //a)get得到字符串的值
        System.out.println(jedis.get("person"));
        //b)lrange得到list所有的列表元素
        List<String> cities = jedis.lrange("cities", 0, -1);
        System.out.println(cities);
        //4)关闭Jedis对象,释放资源
        jedis.close();
        //5)通过客户端查看数据库中是否有数据
    }
}

Jedis连接池

概述

Jedis 连接资源的创建与销毁是很消耗程序性能,所以 Jedis 为我们提供了 Jedis 的连接池技术,Jedis 连接池在创建时初始化一些连接对象存储到连接池中,使用 Jedis 连接资源时不需要自己创建 Jedis 对象,而是从连接池中获取一个资源进行 Redis 的操作。使用完毕后,不需要销毁该 Jedis 连接资源,而是将该资源归还给连接池,供其他请求使用。

API
JedisPoolConfig配置类功能说明
JedisPoolConfig()通过构造方法创建一个连接池配置对象
void setMaxTotal()设置连接池中最大连接数
void setMaxWaitMillis()设置连接池中得到连接的最长等待时间
JedisPool连接池类说明
JedisPool(配置对象,服务器名,端口号)通过构造方法创建连接池 参数1:上面配置对象 参数2:服务器名或IP地址 参数3:端口号6379
Jedis getResource()从连接池中得到一个连接对象
void close()关闭连接池,通常不关闭。
需求

使用连接池优化 jedis 操作,从连接池中得到一个创建好的 Jeids 对象,并且使用这个 Jedis 对象。向 Redis 数据库写入一个 set 集合,并且取出集合。打印到控制台,并且查看数据库中信息。

分析
  1. 创建连接池配置对象,设置最大连接数 10,设置用户最大等待时间 2000 毫秒

  2. 通过配置对象做为参数,创建连接池对象

  3. 从连接池里面获取 jedis 连接对象,执行 redis 命令。

  4. 执行 redis 命令 sadd 写入 set 集合类型的数据:students=白骨精,孙悟空,猪八戒

  5. 执行 redis 命令 smembers 读取集合中的数据

  6. 输出读取的数据

  7. 关闭连接对象(通常连接池不关闭)

代码
java
    /**
     * 连接池的配置
     */
    @Test
    public void test2() {
        //1)创建连接池配置对象,设置最大连接数10,设置用户最大等待时间2000毫秒
       JedisPoolConfig config = new JedisPoolConfig();
       config.setMaxTotal(10);
       config.setMaxWaitMillis(2000);
       //2)通过配置对象做为参数,创建连接池对象
       JedisPool jedisPool = new JedisPool(config, "localhost", 6379);
       //3)从连接池里面获取jedis连接对象,执行redis命令。
       Jedis jedis = jedisPool.getResource();
       //4)执行redis命令sadd写入set集合类型的数据:students=白骨精,孙悟空,猪八戒
       jedis.sadd("students", "白骨精", "孙悟空", "猪八戒");
       //5)执行redis命令smembers读取集合中的数据
       Set<String> students = jedis.smembers("students");
       //6)输出读取的数据
       System.out.println(students);
       //7)关闭连接对象(通常连接池不关闭)
       jedis.close();
       jedisPool.close();
   }

pipeline管道

java
    //1. 创建连接池
    JedisPool pool = new JedisPool("192.168.199.109",6379);
    long l = System.currentTimeMillis();

    //2. 获取一个连接对象
    Jedis jedis = pool.getResource();

    //3. 创建管道
    Pipeline pipelined = jedis.pipelined();

    //3. 执行incr - 100000次放到管道中
    for (int i = 0; i < 100000; i++) {
        pipelined.incr("qq");
    }

    //4. 执行命令
    pipelined.syncAndReturnAll();

    //5. 释放资源
    jedis.close();

    System.out.println(System.currentTimeMillis() - l);

案例

Jedis-Practice

修改综合练习,使其优先从Redis中查询数据

添加依赖
  • jedis-2.7.0.jar

  • commons-pool2-2.3.jar

  • jackson-annotation-2.9.0.jar

  • jackson-core-2.9.9.jar

  • jackson-databind-2.9.9.jar

编写jedis.properties
tex
host=localhost
port=6379
maxTotal=10
maxWaitMillis=1000
编写JedisUtil
java
package com.mobiletrian.utils;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.ResourceBundle;

public class JedisUtil {
    // Jedis池的初始化,需要:
    // 1. 读配置文件
    // 2. 创建JedisPoolConfig
    // 所以将JedisPool的初始化放到类静态代码块当中
    private static JedisPool pool;

    static {
        // 读配置
        ResourceBundle bundle = ResourceBundle.getBundle("jedis");
        String host = bundle.getString("host");
        int port = Integer.parseInt(bundle.getString("port"));
        int maxTotal = Integer.parseInt(bundle.getString("maxTotal"));
        int maxWaitMills = Integer.parseInt(bundle.getString("maxWaitMills"));

        // 创建JedisPoolConfig,并配置
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(maxTotal);
        config.setMaxWaitMillis(maxWaitMills);

        // 利用JedisPoolConfig与配置参数,创建JedisPool对象
        pool = new JedisPool(config, host, port);
    }

    public static Jedis getJedis() {
        // 从池当中获取jedis对象
        return pool.getResource();
    }
}
修改ContactInfo实体类
  • 为 ContactInfo 的getAgesetFormatBirthdaygetFormatBirthday方法,添加JsonIgnore注解
编写ContactInfoJedisDAO
java
package com.mobiletrian.dao.impl;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mobiletrian.dao.ContactInfoDAO;
import com.mobiletrian.model.ContactInfo;
import com.futureweaver.utils.JedisUtil;
import redis.clients.jedis.Jedis;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

// 只负责读写Jedis
// 暂时只做:
// 1. 查询(不分页的版本)
// 2. 添加
public class ContactInfoJedisDAO implements ContactInfoDAO {
    // redis-list类型,每个元素均为ContactInfo类型的JSON字符串
    private final static String key = "contacts";

    @Override
    public List<ContactInfo> queryAll() {
        // Redis-list中的元素都是String类型,需要先读出来,再转换成ContactInfo对象

        // 如果查不到的话,直接返回null
        List<ContactInfo> result = null;

        try (Jedis jedis = JedisUtil.getJedis();) {

            List<String> contacts = jedis.lrange(key, 0, -1);

            // 如果查到的话
            if (contacts != null) {

                ObjectMapper mapper = new ObjectMapper();
                result = new ArrayList<>();

                // 遍历所有的String,利用jackson,将String转换成ContactInfo对象,并放到result中
                for (String contactElement : contacts) {
                    ContactInfo contact = mapper.readValue(contactElement, ContactInfo.class);
                    result.add(contact);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            return result;
        }
    }

    @Override
    public int delContact(String id) {
        return 0;
    }

    @Override
    public int addContact(ContactInfo newContact) {
        // Redis-list中的元素都是String类型,需要将ContactInfo对象转换成JSON字符串,再存到jedis中

        int result = 0;
        try (Jedis jedis = JedisUtil.getJedis();) {
            ObjectMapper mapper = new ObjectMapper();
            String contactJSONString = mapper.writeValueAsString(newContact);
            jedis.rpush(key, contactJSONString);
            result = 1;
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        } finally {
            return result;
        }
    }

    @Override
    public int getRecordCount() {
        return 0;
    }

    @Override
    public List<ContactInfo> queryPage(int pageOffset, int pageSize) {
        return null;
    }
}
修改ContactInfoService
java
package com.futureweaver.service.impl;

import com.futureweaver.dao.ContactInfoDAO;
import com.futureweaver.dao.impl.ContactInfoDAOImpl;
import com.futureweaver.dao.impl.ContactInfoJedisDAO;
import com.futureweaver.model.ContactInfo;
import com.futureweaver.service.ContactInfoService;

import java.util.List;

public class ContactInfoServiceImpl implements ContactInfoService {
    private ContactInfoDAO dao = new ContactInfoDAOImpl();
    private ContactInfoDAO jedisDAO = new ContactInfoJedisDAO();

    @Override
    public List<ContactInfo> queryAll() {
        // 修改queryAll函数
        // 先到Redis中查找
        // 如果在Redis中查不到的话:
        //     1. 从数据库中查找
        //     2. 将从数据库中查到的数据,放到Redis当中
        //     3. 下一次再运行service的queryAll方法时,就不需要再从数据库中查了
        List<ContactInfo> result = jedisDAO.queryAll();
        if (result == null) {
            result = dao.queryAll();
            for (ContactInfo contact: result) {
                jedisDAO.addContact(contact);
            }
        }
        return result;
    }

    @Override
    public boolean delContact(String id) {
        int delResult = dao.delContact(id);
        return delResult == 1;
    }

    @Override
    public boolean addContact(ContactInfo newContact) {
        // 修改addContact函数,每添加一次通讯录记录,都要向Redis中同步一次
        int insertResult = dao.addContact(newContact);
        jedisDAO.addContact(newContact);
        return insertResult == 1;
    }

    @Override
    public int getRecordCount() {
        return dao.getRecordCount();
    }

    @Override
    public List<ContactInfo> queryPage(int pageOffset, int pageSize) {
        return dao.queryPage(pageOffset, pageSize);
    }
}

Redis进阶

准备工作

修改yml文件,以方便后期修改Redis配置信息

yml
version: '3.9'
services:
  redis:
    image: redis:6.0.9
    restart: always
    container_name: redis
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 6379:6379
    volumes:
      - ./conf/redis.conf:/etc/redis/redis.conf
    command: ["redis-server","/etc/redis/redis.conf"]

networks:
  default:
    external: true
    name: global

Redis的AUTH

设置方式一

准备配置文件
  1. 下载redis源码,导出配置文件
  2. https://download.redis.io/redis-stable/redis.conf
修改配置文件
requirepass redispass    #密码
bind 127.0.0.1     #注释掉
修改docker-compose
yml
version: '3.9'
services:
  redis:
    image: redis: 6.0.9
    restart: always
    container_name: redis
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 6379:6379
    volumes:
      - ./conf/redis.conf:/usr/local/redis/redis.conf
    command: ["redis-server","/usr/local/redis/redis.conf"]

networks:
  default:
    external: true
    name: global

设置方式二

在不修改redis.conf文件的前提下,在第一次链接Redis时,输入命令:Config set requirepass 密码

后续向再次操作Redis时,需要先AUTH做一下校验。

连接方式

  • redis-cli:在输入正常命令之前,先输入 auth 密码即可。

  • 图形化界面:在连接 Redis 的信息中添加上验证的密码。

  • Jedis 客户端:

java
jedis.auth(password);
  • 使用 JedisPool 的方式
java
// 使用当前有参构造设置密码
public JedisPool(final GenericObjectPoolConfig poolConfig, final String host, int port,int timeout, final String password)

Redis的事务

Redis的事务:一次事务操作,改成功的成功,该失败的失败。

先开启事务,执行一些列的命令,但是命令不会立即执行,会被放在一个队列中,如果你执行事务,那么这个队列中的命令全部执行,如果取消了事务,一个队列中的命令全部作废。

  • 开启事务:multi
  • 输入要执行的命令:被放入到一个队列中
  • 执行事务:exec
  • 取消事务:discard

Redis的事务向发挥功能,需要配置watch监听机制

在开启事务之前,先通过watch命令去监听一个或多个key,在开启事务之后,如果有其他客户端修改了我监听的key,事务会自动取消。

如果执行了事务,或者取消了事务,watch监听自动消除,一般不需要手动执行unwatch。

Redis持久化

问:把客户端和服务端都关闭了,再重新开启服务器和客户端,数据会不会丢失?

答:会部分丢失,因为在默认的情况下,服务器是每过一段时间保存到硬盘一次。

Redis持久化概述

什么是Redis的持久化

将内存中的数据写到硬盘上永久的保存下来,称为持久化。

Redis持久化的四种方式
  1. RDB (Redis DataBase )这是 Redis 默认的持久化方式,数据以二进制的方式保存。

    RDB文件的内容:

    二进制数据

  2. AOF (Append Only File)以日志的方式,文本文件的方式保存用户的所有操作。

    日志文件的内容:

    set name zhangsan

    set name lisi

  3. AOF 重写机制

    auto-aof-rewrite-percentage 100  # 指当前aof文件比上次重写的增长比例大小,达到这个大小就进行 aof 重写
    auto-aof-rewrite-min-size 64mb  # 最开始aof文件必须要达到这个文件时才触发,后面的每次重写就不会根据这个变量了
  4. Redis 4.0 混合持久化

    依赖于AOF的重写机制

    在重写时,将数据填充到RDB中

    能够保证:

    1. RDB文件较小
    2. 最新的数据保存在AOF中

RDB持久化机制

优点
  1. 方便备份与恢复

整个 Redis 数据库将只包含一个文件,默认是 dump.rdb,这对于文件备份和恢复而言是非常完美的。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。一旦系统出现灾难性故障,我们可以非常容易的进行恢复。

  1. 性能最大化

对于 Redis 的服务进程而言,在开始持久化时,它唯一需要做的只是分叉出子进程,由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行 IO 操作了。

  1. 启动效率更高

相比于 AOF 机制,如果数据集很大,RDB 的启动效率会更高

缺点
  1. 不能完全避免数据丢失

因为 RDB 是每隔一段时间写入数据,所以系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。

  1. 会导致服务器暂停的现象

由于 RDB 是通过子进程来协助完成数据持久化工作的,因此当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是 1 秒钟。一般在夜深人静的时候持久化会比较好。

RDB持久化机制的配置

在 redis.conf 配置文件中的 SNAPSHOTTING 中有如下说明:

语法说明
save <时间间隔> <修改键数>每过多久,修改了多少个键,则进行持久化。必须两个条件同时满足

如下面配置的是 RDB 方式数据持久化时机,必须两个条件都满足的情况下才进行持久化的操作:

关键字时间(秒)修改键数解释
save900115分钟内修改了1个键
save300105分钟内修改了10个键
save60100001分钟内修改了1万个键
演示

操作步骤

1. 修改redis.conf 文件的101行添加1行:save 20 3 (表示20秒内修改3个键,则写入到dump.rdb文件中) ~~2. 使用指定的配置文件启动服务器:redis-server redis.windows.conf ~~ 3. 向数据库中添加2个键,直接关闭服务器窗口。再开启服务器,查看所有的keys,刚才添加的数据丢失。4. 在客户端添加3个键,发现服务器端有如下输出信息,表示写入到数据库dump.rdb文件中5. 直接关闭服务器窗口,再开启服务器,查看所有的keys,数据没有丢失。

操作演示

  1. 给 redis.conf 添加一个行save 20 3
  2. 启动容器
  3. 修改两个键,观察 data 目录,dump.rdb 是否被刷新

没被刷新,原因是20秒、3次没被触发

  1. 在 20 秒以内,修改第 3 个键,观察 data 目录,dump.rdb 是否被刷新

被刷新了

  1. 重新启动容器,观察 redis 内部的文件是否被恢复了

被恢复了,因为存在了一个dump.rdb的文件,redis在启动时会找这个文件,找到了就恢复,找不到就不恢复(空的)

  1. 关闭容器,删除 dump.rdb,启动容器,并观察是否有数据被恢复

没有数据被恢复

AOF持久化机制

优点

AOF 包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。也可以通过该文件完成数据的重建。该机制可以带来更高的数据安全性,所有的操作都是异步完成的。

Redis中提供了3种同步策略说明
每秒同步每过1秒钟就写一次数据
每修改同步每个修改就写一次数据
不同步默认没有打开,不持久化
缺点
  1. 文件比 RDB 更大:对于相同数量的数据集而言,AOF 文件通常要大于 RDB 文件。

  2. 运行效率比 RDB 更慢:根据同步策略的不同,AOF 在运行效率上往往会慢于 RDB。

AOF持久化机制配置
开启AOF持久化

AOF 默认是关闭的,首先需要开启 AOF 模式。

参数配置说明
appendonly no/yes如果要开启,设置为yes。如果设置成yes,就会在硬盘上生成一文本文件 生成文件:appendonly.aof
AOF持久化时机
关键字持久化时机解释
appendfsynceverysec每秒同步
appendfsyncalways每修改同步
appendfsyncno不同步
演示
  1. 打开 AOF 的配置,找到 APPEND ONLY MODE 配置块,392 行。设置 appendonly yes

  2. 通过 redis-server redis.windows.conf 启动服务器,在服务器目录下出现 appendonly.aof 文件。大小是 0 个字节。

  3. 添加 3 个键和值

  4. 打开 appendonly.aof 文件,查看文件的变化。会发现文件记录了所有操作的过程。

小结
  • rdb
    • 二进制
    • 子进程
    • 基于时间和次数
    • 同步
  • AOF
    • 文件文件
    • 3 种不同策略,一般用每次
    • 异步

AOF 是把所有的变更存储在了基于文本的文档当中

RDB 是把所有的变更存储在了二进制文件当中

关键字持久化时机
appendfsyncalways(每次修改)
appendfsynceverysec(每秒)
appendfsyncno(不aof)

Redis的主从架构

单机版 Redis存在读写瓶颈的问题

主从架构
1586918773809

指定yml文件

yml
version: "3.9"
services:
  redis1:
    image: redis:6.0.9
    restart: always
    container_name: redis1
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7001:6379
    volumes:
      - ./conf/redis1.conf:/usr/local/redis/redis.conf
    command: ["redis-server","/usr/local/redis/redis.conf"]

  redis2:
    image: redis:6.0.9
    restart: always
    container_name: redis2
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7002:6379
    volumes:
      - ./conf/redis2.conf:/usr/local/redis/redis.conf
    links:
      - redis1:master
    command: ["redis-server","/usr/local/redis/redis.conf"]

  redis3:
    image: redis:6.0.9
    restart: always
    container_name: redis3
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7003:6379
    volumes:
      - ./conf/redis3.conf:/usr/local/redis/redis.conf
    links:
      - redis1:master
    command: ["redis-server","/usr/local/redis/redis.conf"]

networks:
  default:
    external: true
    name: global
sh
## redis2和redis3从节点配置
replicaof master 6379
bash
## redis-cli内查看节点信息
info

哨兵

哨兵可以帮助我们解决主从架构中的单点故障问题

添加哨兵
1586922159978

修改了以下docker-compose.yml,为了可以在容器内部使用哨兵的配置

yml
version: "3.9"
services:
  redis1:
    image: redis:6.0.9
    restart: always
    container_name: redis1
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7001:6379
    volumes:
      - ./conf/redis1.conf:/usr/local/redis/redis.conf
      - ./conf/sentinel1.conf:/data/sentinel.conf        # 添加的内容
    command: ["redis-server","/usr/local/redis/redis.conf"]
  redis2:
    image: redis:6.0.9
    restart: always
    container_name: redis2
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7002:6379
    volumes:
      - ./conf/redis2.conf:/usr/local/redis/redis.conf
      - ./conf/sentinel2.conf:/data/sentinel.conf        # 添加的内容
    links:
      - redis1:master
    command: ["redis-server","/usr/local/redis/redis.conf"]
  redis3:
    image: redis:6.0.9
    restart: always
    container_name: redis3
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7003:6379
    volumes:
      - ./conf/redis3.conf:/usr/local/redis/redis.conf
      - ./conf/sentinel3.conf:/data/sentinel.conf        # 添加的内容 
    links:
      - redis1:master
    command: ["redis-server","/usr/local/redis/redis.conf"]

networks:
  default:
    external: true
    name: global

准备哨兵的配置文件,并且在容器内部手动启动哨兵即可

## 哨兵需要后台启动
daemonize yes
## 指定Master节点的ip和端口(主)
sentinel monitor master localhost 6379 2
## 指定Master节点的ip和端口(从)
sentinel monitor master master 6379 2
## 哨兵每隔多久监听一次redis架构
sentinel down-after-milliseconds master 10000

在Redis容器内部启动sentinel即可

sh
redis-sentinel sentinel.conf

Redis的集群

Redis集群在保证主从加哨兵的基本功能之外,还能够提升Redis存储数据的能力。

Redis集群架构图
1586932636778

准备yml文件

yml
## docker-compose.yml
version: "3.9"
services:
  redis1:
    image: redis:6.0.9
    restart: always
    container_name: redis1
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7001:7001
      - 17001:17001
    volumes:
      - ./conf/redis1.conf:/usr/local/redis/redis.conf
    command: ["redis-server","/usr/local/redis/redis.conf"]
  redis2:
    image: redis:6.0.9
    restart: always
    container_name: redis2
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7002:7002
      - 17002:17002
    volumes:
      - ./conf/redis2.conf:/usr/local/redis/redis.conf
    command: ["redis-server","/usr/local/redis/redis.conf"]  
  redis3:
    image: redis:6.0.9
    restart: always
    container_name: redis3
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7003:7003
      - 17003:17003
    volumes:
      - ./conf/redis3.conf:/usr/local/redis/redis.conf
    command: ["redis-server","/usr/local/redis/redis.conf"]  
  redis4:
    image: redis:6.0.9
    restart: always
    container_name: redis4
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7004:7004
      - 17004:17004
    volumes:
      - ./conf/redis4.conf:/usr/local/redis/redis.conf
    command: ["redis-server","/usr/local/redis/redis.conf"]  
  redis5:
    image: redis:6.0.9
    restart: always
    container_name: redis5
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7005:7005
      - 17005:17005
    volumes:
      - ./conf/redis5.conf:/usr/local/redis/redis.conf
    command: ["redis-server","/usr/local/redis/redis.conf"]  
  redis6:
    image: redis:6.0.9
    restart: always
    container_name: redis6
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7006:7006
      - 17006:17006
    volumes:
      - ./conf/redis6.conf:/usr/local/redis/redis.conf
    command: ["redis-server","/usr/local/redis/redis.conf"]  

networks:
  default:
    external: true
    name: global

配置文件

sh
## redis.conf
## 指定redis的端口号
port 7001
## 开启Redis集群
cluster-enabled yes
## 集群信息的文件
cluster-config-file nodes-7001.conf
## 集群的对外ip地址
cluster-announce-ip 192.168.56.1
## 集群的对外port
cluster-announce-port 7001
## 集群的总线端口
cluster-announce-bus-port 17001

启动了6个Redis的节点。

随便跳转到一个容器内部,使用redis-cli管理集群

sh
redis-cli --cluster create 192.168.56.1:7001 192.168.56.1:7002 192.168.56.1:7003 192.168.56.1:7004 192.168.56.1:7005 192.168.56.1:7006 --cluster-replicas 1

Java连接Redis集群

使用JedisCluster对象连接Redis集群

java
@Test
public void test(){
    // 创建Set<HostAndPort> nodes
    Set<HostAndPort> nodes = new HashSet<>();
    nodes.add(new HostAndPort("192.168.56.114",7001));
    nodes.add(new HostAndPort("192.168.56.114",7002));
    nodes.add(new HostAndPort("192.168.56.114",7003));
    nodes.add(new HostAndPort("192.168.56.114",7004));
    nodes.add(new HostAndPort("192.168.56.114",7005));
    nodes.add(new HostAndPort("192.168.56.114",7006));

    // 创建JedisCluster对象
    JedisCluster jedisCluster = new JedisCluster(nodes);

    // 操作
    String value = jedisCluster.get("b");
    System.out.println(value);
}

Redis常见问题【重点

key的生存时间到了,Redis会立即删除吗?

redis并不会实时监控

不会立即删除。

  • 定期删除:Redis每隔一段时间就去会去查看Redis设置了过期时间的key,会再100ms的间隔中默认查看3个key。

  • 惰性删除:如果当你去查询一个已经过了生存时间的key时,Redis会先查看当前key的生存时间,是否已经到了,直接删除当前key,并且给用户返回一个空值。

Redis的淘汰机制

在Redis内存已经满的时候,添加了一个新的数据,执行淘汰机制。

lru: 最近最少使用

lfu: 最近最少频次使用

random: 随机

ttl: 剩余时间最少

volatile: 设置过了生存时间的key

allkeys: 所有的key

  • volatile-lru:在内存不足时,Redis会再设置过了生存时间的key中干掉一个最近最少使用的key。
  • allkeys-lru:在内存不足时,Redis会再全部的key中干掉一个最近最少使用的key。
  • volatile-lfu:在内存不足时,Redis会再设置过了生存时间的key中干掉一个最近最少频次使用的key。
  • allkeys-lfu:在内存不足时,Redis会再全部的key中干掉一个最近最少频次使用的key。
  • volatile-random:在内存不足时,Redis会再设置过了生存时间的key中随机干掉一个。
  • allkeys-random:在内存不足时,Redis会再全部的key中随机干掉一个。
  • volatile-ttl:在内存不足时,Redis会再设置过了生存时间的key中干掉一个剩余生存时间最少的key。
  • noeviction:(默认)在内存不足时,直接报错。

指定淘汰机制的方式:maxmemory-policy 具体策略,设置Redis的最大内存:maxmemory 字节大小

缓存的常问题

缓存穿透问题

*问题: 缓存里没有,数据库里也没有

解决方案: 让缓存里边有(个数为0)

缓存击穿问题

问题: (热点数据)缓存里没有,数据库里有

解决方案: 热点数据不要让它没有,即永不过期

缓存雪崩问题

问题: 缓存中的数据一起到期

解决方案: 不要让数据一起到期,即过期时间设置随机

缓存倾斜问题

问题: Redis集群架构,每个节点的负载不平均

解决方案: 尽量让每个节点的负载平均

spring-data-redis

导入依赖

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置

yml
spring:
  redis:
    host: localhost
    port: 6379
    password: requirepass

RedisTemplate泛型自动注入

java
package com.futureweaver.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

RedisTemplate开启pipeline

java
import com.fasterxml.jackson.core.JsonProcessingException;
import com.futureweaver.ebusiness.Category;
import com.futureweaver.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;

@Service("categoryService")
public class CategoryServiceImpl implements CategoryService {

    @Autowired
    private RedisTemplate<String, Category> redisTemplate;

    @Override
    public void add(List<Category> categories) throws JsonProcessingException {
        long begin = System.currentTimeMillis();
        redisTemplate.executePipelined(new RedisCallback() {
            RedisSerializer keyS = redisTemplate.getKeySerializer();
            RedisSerializer valueS = redisTemplate.getValueSerializer();

            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                connection.openPipeline();
                for (Category category: categories) {
                    connection.rPush(keyS.serialize("categories"), valueS.serialize(category));
                }
                connection.closePipeline();
                return null;
            }
        });
        long end = System.currentTimeMillis();
        System.out.println("with pipeline: " + (end - begin));
    }
}

Idea模板

spring-data-redis-config.java

java
# if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class ${NAME} {
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

学习目标总结

  • 能够理解 NoSQL 的概念
  • 能够说出 redis 的常用数据类型
  • 能够使用 redis 的 string 操作命令
  • 能够使用 redis 的 hash 操作命令
  • 能够使用 redis 的 list 操作命令
  • 能够使用 redis 的 set 操作命令
  • 能够使用 redis 的 zset 操作命令
  • 能够使用 jedis 对 redis 进行操作

参考资料

https://www.cnblogs.com/chichung/p/12687101.html