Redis详细基础知识讲解,及整合springboot

Redis入门

  • 概述 Redis是什么 Redis:REmote DIctionary Server(远程字典服务器)

  • 是完全开源免费的,用C语言编写的,遵守BSD协议,是一个高性能的(Key/Value)分布式内存数据 库,基于内存运行,并支持持久化的NoSQL数据库,是当前最热门的NoSQL数据库之一,也被人们称为 数据结构服务器

  • Redis与其他key-value缓存产品有以下三个特点

    • Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使 用。

    • Redis不仅仅支持简单的 key-value 类型的数据,同时还提供list、set、zset、hash等数据结构的存 储。

    • Redis支持数据的备份,即master-slave模式的数据备份。

      Redis能干嘛:

      内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务 取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面 发布、订阅消息系统 地图信息分析 定时器、计数器 ......

      特性 :

      数据类型、基本操作和配置 持久化和复制,RDB、AOF 事务的控制

    什么是NoSQL

    NoSQL NoSQL = Not Only SQL,意思:不仅仅是SQL; 泛指非关系型的数据库,随着互联网Web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别 是超大规模和高并发的社交网络服务类型的Web2.0纯动态网站已经显得力不从心,暴露了很多难以克服 的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展,NoSQL数据库的产生就是为 了解决大规模数据集合多种数据种类带来的挑战,尤其是大数据应用难题,包括超大规模数据的存储。 (例如谷歌或Facebook每天为他们的用户收集万亿比特的数据)。这些类型的数据存储不需要固定的模 式,无需多余操作就可以横向扩展。

安装 Redis

windows安装

1、下载 Redis

Releases · tporadowski/redis (github.com)

windows安装启动教程: Redis 安装 | 菜鸟教程

2、解压,并在本地硬盘任意位置创建文件夹,在其中创建 3 个子文件夹

  • bin:放置启动 Redis 的可执行文件

    uploading.4e448015.gif

    正在上传…重新上传取消

  • db:放置数据文件

    • uploading.4e448015.gif

      正在上传…重新上传取消

      • daemonize 设置yes或者no区别 daemonize:yes redis采用的是单进程多线程的模式。当redis.conf中选项daemonize设置成yes时,代表开启 守护进程模式。在该模式下,redis会在后台运行,并将进程pid号写入至redis.conf选项 pidfile设置的文件中,此时redis将一直运行,除非手动kill该进程。 daemonize:no 当daemonize选项设置成no时,当前界面将进入redis的命令行界面,exit强制退出或者关闭 连接工具(putty,xshell等)都会导致redis进程退出。

    • uploading.4e448015.gif

      正在上传…重新上传取消

    • uploading.4e448015.gif

      正在上传…重新上传取消

  • etc:放置配置文件,设置 Redis 服务的端口、日志文件位置、数据文件位置...

Linux安装

下载地址 Download | Redis

uploading.4e448015.gif

正在上传…重新上传取消

安装步骤

1、下载获得 redis-6.2.7.tar.gz 后将它放到我们Linux的目录下 /opt

2、/opt 目录下,解压命令 : tar -zxvf redis-6.2.7.tar.gz

3、解压完成后出现文件夹:redis-6.2.7

uploading.4e448015.gif

正在上传…重新上传取消

4、进入目录: cd redis-6.2.7

5、在 redis-6.2.7 目录下执行 make 命令m

运行make命令时故意出现的错误解析:
1. 安装gcc (gcc是linux下的一个编译程序,是c程序的编译工具)
能上网: yum install gcc-c++
版本测试: gcc-v
2. 二次make
3. Jemalloc/jemalloc.h: 没有那个文件或目录
运行 make distclean 之后再make
4. Redis Test(可以不用执行)

uploading.4e448015.gif

正在上传…重新上传取消

uploading.4e448015.gif

正在上传…重新上传取消

6、如果make完成后继续执行 make instal

7、查看默认安装目录:usr/local/bin

uploading.4e448015.gif

正在上传…重新上传取消

/usr 这是一个非常重要的目录,类似于windows下的Program Files,存放用户的程序

8、拷贝配置文件(备用)

cd /usr/local/bin
ls -l
// 在redis的解压目录下备份redis.conf
mkdir myredis
cp /opt/redis-6.2.7/redis.conf myredis //拷一个备份,养成良好的习惯,我们就修改这个文件
//修改配置保证可以后台应用cd
vim redis.conf

uploading.4e448015.gif

正在上传…重新上传取消

修改配置redis.conf

uploading.4e448015.gif

正在上传…重新上传取消

A、redis.conf配置文件中daemonize守护线程,默认是NO。

B、daemonize是用来指定redis是否要用守护线程的方式启动。

daemonize 设置yes或者no区别

daemonize:yes

redis采用的是单进程多线程的模式。当redis.conf中选项daemonize设置成yes时,代表开启 守护进程模式。在该模式下,redis会在后台运行,并将进程pid号写入至redis.conf选项 pidfile 设置的文件中,此时redis将一直运行,除非手动kill该进程。

daemonize:no

当daemonize选项设置成no时,当前界面将进入redis的命令行界面,exit强制退出或者关闭 连 接工具(putty,xshell等)都会导致redis进程退出。

9.启动测试一下!

 //【shell】启动redis服务
[root@192 bin]cd /usr/local/bin
[root@192 bin]redis-server myredis/redis.conf
//redis客户端连接===> 观察地址的变化,如果连接ok,是直接连上的,redis默认端口号 6379
[root@192 bin]# redis-cli -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set k1 helloworld
OK
127.0.0.1:6379> get k1
"helloworld"
//【shell】ps显示系统当前进程信息
[root@192 myredis]// ps -ef|grep redis
root 16005 1 0 04:45 ? 00:00:00 redis-server
127.0.0.1:6379
root 16031 15692 0 04:47 pts/0 00:00:00 redis-cli -p 6379
root 16107 16076 0 04:51 pts/2 00:00:00 grep --color=auto redis
//【redis】关闭连接
127.0.0.1:6379> shutdown
not connected> exit
 //【shell】ps显示系统当前进程信息
[root@192 myredis]# ps -ef|grep redis
root 16140 16076 0 04:53 pts/2 00:00:00 grep --color=auto redis

uploading.4e448015.gif

正在上传…重新上传取消

uploading.4e448015.gif

正在上传…重新上传取消

启动 Redis 服务

1、进入 redis 目录,先启动 redis-server。

2、进入 redis 目录,启动 redis-cli,启动 Redis 的客户端管理窗口,在此窗口中即可操作 Redis 数据库。

3、对数据进行操作。

uploading.4e448015.gif

正在上传…重新上传取消

set key value
get key

4、关闭 Redis 服务。

shutdown

5、退出客户端,control+c。

基础知识说明

准备工作:开启redis服务,客户端连接

redis压力测试工具-----Redis-benchmark Redis-benchmark是官方自带的Redis性能测试工具,可以有效的测试Redis服务的性能。

uploading.4e448015.gif

正在上传…重新上传取消

redis 性能测试工具可选参数如下所示:

序号选项描述默认值
1-h指定服务器主机名127.0.0.1
2-p指定服务器端口6379
3-s指定服务器 socket
4-c指定并发连接数50
5-n指定请求数10000
6-d以字节的形式指定 SET/GET 值的数据大小3
7-k1=keep alive 0=reconnect1
8-rSET/GET/INCR 使用随机 key, SADD 使用随机值
9-P通过管道传输 请求1
10-q强制退出 redis。仅显示 query/sec 值
11--csv以 CSV 格式输出
12-l生成循环,永久执行测试
13-t仅运行以逗号分隔的测试命令列表。
14-iIdle 模式。仅打开 N 个 idle 连接并等待。

基本数据库常识

默认16个数据库,类似数组下标从零开始,初始默认使用0号库

uploading.4e448015.gif

正在上传…重新上传取消

Select命令切换数据库

127.0.0.1:6379> select 15  //切换数据库命令
OK
127.0.0.1:6379[15]> select 16
(error) ERR DB index is out of range
127.0.0.1:6379[15]>
// 不同的库可以存不同的数据

Dbsize查看当前数据库的key的数量

127.0.0.1:6379[15]> select 0   //我的0数据库里有key
OK
127.0.0.1:6379> dbsize
(integer) 11
127.0.0.1:6379> keys *  //查看具体的key
 1) "\xac\xed\x00\x05t\x00\tstududent"
 2) "\xac\xed\x00\x05t\x00\x04list"
 3) "\xac\xed\x00\x05t\x00\x03str"
 4) "key"
 5) "myset:__rand_int__"
 6) "key:__rand_int__"
 7) "\xac\xed\x00\x05t\x00\x04zset"
 8) "\xac\xed\x00\x05t\x00\x03set"
 9) "myKey"
10) "mylist"
11) "counter:__rand_int__"

Flushdb:清空当前库

Flushall:清空全部的库

127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> set key jiayu
OK
127.0.0.1:6379[1]> get key
"jiayu"
127.0.0.1:6379[1]> flushdb
OK
127.0.0.1:6379[1]> dbsize
(integer) 0
127.0.0.1:6379[1]>

为什么redis是单线程

我们首先要明白,Redis很快!官方表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就 顺理成章地采用单线程的方案了!

Redis采用的是基于内存的采用的是单进程单线程模型的 KV 数据库,由C语言编写,官方提供的数据是 可以达到100000+的QPS(每秒内查询次数)。这个数据不比采用单进程多线程的同样基于内存的 KV 数据库 Memcached 差!

Redis为什么这么快?

1)以前一直有个误区,以为:高性能服务器 一定是多线程来实现的 原因很简单因为误区二导致的:多线程 一定比 单线程 效率高,其实不然!

2)redis 核心就是 如果我的数据全都在内存里,我单线程的去操作 就是效率最高的,为什么呢,因为 多线程的本质就是 CPU 模拟出来多个线程的情况,这种模拟出来的情况就有一个代价,就是上下文的切 换,对于一个内存的系统来说,它没有上下文的切换就是效率最高的。redis 用 单个CPU 绑定一块内存 的数据,然后针对这块内存的数据进行多次读写的时候,都是在一个CPU上完成的,所以它是单线程处 理这个事。在内存的情况下,这个方案就是最佳方案。

因为一次CPU上下文的切换大概在 1500ns(毫微秒) 左右。从内存中读取 1MB 的连续数据,耗时大约为 250us, 假设1MB的数据由多个线程读取了1000次,那么就有1000次时间上下文的切换,那么就有1500ns * 1000 = 1500us ,我单线程的读完1MB数据才250us ,你光时间上下文的切换就用了1500us了,我还不 算你每次读一点数据 的时间。

五大数据类型

String (字符串类型)

String是redis最基本的类型,一个key对应一个value。 String类型是二进制安全的,意思是redis的string可以包含任何数据,比如jpg图片或者序列化的对象。 String类型是redis最基本的数据类型,一个redis中字符串value最多可以是512M。

应用场景

uploading.4e448015.gif

正在上传…重新上传取消

————————————————

List(列表)

Redis列表是简单的字符串列表,按照插入顺序排序,你可以添加一个元素到列表的头部(左边)或者尾 部(右边)。 它的底层实际是个链表 。

应用场景

1、微信朋友圈点赞,要求按照点赞顺序显示点赞好友信息 如果取消点赞,移除对应好友信息

2、文章列表或者数据分页展示的应用。比如,我们常用的博客网站的文章列表,当用户量越来越多时,而且每一个用户都有自己的文章列表,而且当文章多时,都需要分页展示,这时可以考虑使用redis的列表,列表不但有序同时还支持按照范围内获取元素,可以完美解决分页查询功能。大大提高查询效率。

3、twitter、新浪微博、腾讯微博,keep应用中个人用户的关注列表需要按照用户的关注顺序进行展示,粉丝列表需要将最 近关注的粉丝列在前面

————————————————

1、标签:比如我们博客网站常常使用到的兴趣标签,把一个个有着相同爱好,关注类似内容的用户利用一个标签把他们进行归并。

2、共同好友功能,共同喜好,或者可以引申到二度好友之类的扩展应用。

3、统计网站的独立IP。利用set集合当中元素不唯一性,可以快速实时统计访问网站的独立IP。

数据结构

set的底层结构相对复杂写,使用了intset和hashtable两种数据结构存储,intset可以理解为数组。 ————————————————

Set(集合)

Redis的Set是String类型的无序集合,它是通过HashTable实现的 。(数据不会重复)

应用场景

1、标签:比如我们博客网站常常使用到的兴趣标签,把一个个有着相同爱好,关注类似内容的用户利用一个标签把他们进行归并。

2、共同好友功能,共同喜好,或者可以引申到二度好友之类的扩展应用。

3、统计网站的独立IP。利用set集合当中元素不唯一性,可以快速实时统计访问网站的独立IP。

Zset(sorted set:有序集合)

Redis zset 和 set 一样,也是String类型元素的集合,且不允许重复的成员。 不同的是每个元素都会关联一个double类型的分数。 Redis正是通过分数来为集合中的成员进行从小到大的排序,zset的成员是唯一的,但是分数(Score) 却可以重复。

应用场景

1、 排行榜:有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜,榜单维护可能是多方面:按照时间、按照播放量、按照获得的赞数等。

2、用Sorted Sets来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。 ————————————————

Hash(哈希,类似 Java里的Map)

Redis hash 是一个键值对集合。 Redis hash 是一个String类型的field和value的映射表,hash特别适合用于存储对象。 类似Java里面的Map

{

key:value //正常情况

key:{ //key里面是一个哈希

key:value //哈希里面又是一个key:value

}

}

应用场景

1、 电商网站购物车设计与实现, redis 应用于购物车数据存储设计

2、经常被用来存储用户相关信息。优化用户信息的获取,不需要重复从数据库当中读取,提高系统性能。

3、双11活动日,销售手机充值卡的商家对移动、联通、电信的30元、50 元、100元商品推出抢购活动,每种商品抢购上限1000张

————————————————

实操

Redis键(key)

//keys * 查看所有key
127.0.0.1:6379> keys *
 1) "\xac\xed\x00\x05t\x00\tstududent"
 2) "\xac\xed\x00\x05t\x00\x04list"
 3) "\xac\xed\x00\x05t\x00\x03str"
 4) "key"
 5) "myset:__rand_int__"
 6) "key:__rand_int__"
 7) "\xac\xed\x00\x05t\x00\x04zset"
 8) "\xac\xed\x00\x05t\x00\x03set"
 9) "myKey"
10) "mylist"
11) "counter:__rand_int__"
    
 // exists key 的名字,判断某个key是否存在
127.0.0.1:6379> exists myKey
(integer) 1
 
 // move key db ---> 当前库就没有了,被移除了
 //expire key 秒钟:为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删
除。
 //ttl key 查看还有多少秒过期,-1 表示永不过期,-2 表示已过期
127.0.0.1:6379[2]> set name jiayu
OK
127.0.0.1:6379[2]> expire name 10
(integer) 1
127.0.0.1:6379[2]> ttl name
(integer) 5
127.0.0.1:6379[2]> ttl name
(integer) 3
127.0.0.1:6379[2]> ttl name
(integer) 1
127.0.0.1:6379[2]> ttl name
(integer) -2
127.0.0.1:6379[2]> keys *
1) "key"  //没有name库只有之前的key

字符串String

单值单Value 常用命令说明:

# ===================================================
# set、get、del、append、strlen
# ===================================================
127.0.0.1:6379>  set name jiayu   //设置值
OK
127.0.0.1:6379> get name //获得key
"jiayu"
127.0.0.1:6379> del name //删除key
(integer) 1
127.0.0.1:6379> keys * //查看全部的key
(empty list or set)
127.0.0.1:6379> exists name//确保 key1 不存在
(integer) 0
127.0.0.1:6379> append name "hello" //对不存在的 key 进行 APPEND ,等同于 SET
name "hello"
(integer) 5 //字符长度
127.0.0.1:6379> APPEND name "-2333" //对已存在的字符串进行 APPEND(拼接)
(integer) 10 //长度从 5 个字符增加到 10 个字符
127.0.0.1:6379> get name
"hello-2333"
127.0.0.1:6379>  strlen name # # 获取字符串的长度
(integer) 10
    
    
// ===================================================
//incr、decr 一定要是数字才能进行加减,+1 和 -1。
// incrby、decrby 命令将 key 中储存的数字加上指定的增量值。
// ===================================================
127.0.0.1:6379[10]> set view 0  //设置浏览量为0
OK
127.0.0.1:6379[10]> incr view  //浏览 + 1
(integer) 1
127.0.0.1:6379[10]> incr view  //浏览 + 1
(integer) 2
127.0.0.1:6379[10]> decr view   //浏览 - 1
(integer) 1
127.0.0.1:6379[10]> incrby view 10  //+10
(integer) 11
127.0.0.1:6379[10]> decrby view 10  //-10
(integer) 1

//===================================================
// range [范围]
//getrange 获取指定区间范围内的值,类似between...and的关系,从零到负一表示全部
//===================================================
127.0.0.1:6379> set key1 123456789 //设置key2的值
OK
127.0.0.1:6379> getrange key1 0 -1 //获得全部的值
"123456789"
127.0.0.1:6379> getrange key1 0 2 //截取部分字符串
"123"

    
// ===================================================
// setrange 设置指定区间范围内的值,格式是setrange key值 具体值
//===================================================
127.0.0.1:6379[10]> setrange key1 1 aa   //替换值
(integer) 9
127.0.0.1:6379[10]> get key1
"1aa456789"  
    
//===================================================
//setex(set with expire)键秒值
//setnx(set if not exist)
//===================================================
127.0.0.1:6379[10]> setex key2 60 jiayu  //设置过期时间
OK+
127.0.0.1:6379[10]> ttl key2  //查看剩余的时间
(integer) 53
127.0.0.1:6379[10]> setnx mykey "redis"  //如果不存在就设置,成功返回1
(integer) 1
127.0.0.1:6379[10]> setnx mykey "jiayu"  //如果存在就不设置,失败返回0
(integer) 0
127.0.0.1:6379[10]> get mykey
"redis"

//===================================================
//mset Mset 命令用于同时设置一个或多个 key-value 对。
// mget Mget 命令返回所有(一个或多个)给定 key 的值。
//如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。
// msetnx 当所有 key 都成功设置,返回 1 。
//如果所有给定 key 都设置失败(至少有一个 key 已经存在),那么返回 0 。原子操作
// ===================================================
 
127.0.0.1:6379[10]> mset k10 v10 k11 v11 k12 v12  //同时设置多个key-value
OK
127.0.0.1:6379[10]> keys * 
1) "name"
2) "view"
3) "key1"
4) "k10"
5) "mykey"
6) "k11"
7) "k12"
127.0.0.1:6379[10]>  mget k10 k11 k12 k13  //同时获取多个值
1) "v10"
2) "v11"
3) "v12"
4) (nil)
127.0.0.1:6379[10]>  msetnx k10 v10 k15 v15  //原子性操作,要么一起成功要么一起失败
(integer) 0
127.0.0.1:6379[10]> get key15
(nil)
// 传统对象缓存
127.0.0.1:6379[10]> get user:1
"value(name:jiayu,age:24)"
//可以用来缓存对象
mset user:1:name zhangsan user:1:age 2
ok
mget user:1:name user:1:age
1) "zhangsan"
2) "2"
// ===================================================
//getset(先get再set)
//===================================================
127.0.0.1:6379[10]> getset db redis   //会先返回之前的值,之前没有就返回nil,覆盖新的值redis
(nil)
127.0.0.1:6379[10]> get db   
"redis"
127.0.0.1:6379[10]> getset db mongodb  //会先返回之前的值redis,覆盖新的值mongodb
"redis"
127.0.0.1:6379[10]> get db
"mongodb"
    

String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。 常规key-value缓存应用: 常规计数:微博数,粉丝数等。

列表List 单值多Value

// ===================================================
// Lpush:将一个或多个值插入到列表头部。(左)
// rpush:将一个或多个值插入到列表尾部。(右)
// lrange:返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。
// 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。
// 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
//===================================================
 127.0.0.1:6379[1]> LPUSH list "one"  //左插入
(integer) 1
127.0.0.1:6379[1]>  LPUSH list "two"  
(integer) 2
127.0.0.1:6379[1]>  RPUSH list "right"   //右插入
(integer) 3
127.0.0.1:6379[1]> Lrange list 0 -1  //0 -1 是那全部数据
1) "two"
2) "one"
3) "right"
127.0.0.1:6379[1]> Lrange list 0 1  // 0到1 的数据
1) "two"
2) "one"

//===================================================
//lpop 命令用于移除并返回列表的第一个元素。当列表 key 不存在时,返回 nil 。
//rpop 移除列表的最后一个元素,返回值为移除的元素。
//===================================================
 127.0.0.1:6379> Lpop list  //左删除
"two"
127.0.0.1:6379> Rpop list  //右删除
"right"
127.0.0.1:6379> Lrange list 0 -1  
1) "one"
    
// ===================================================
// llen 用于返回列表的长度。
//===================================================

127.0.0.1:6379[1]> Lpush list "one"
(integer) 1
127.0.0.1:6379[1]> Lpush list "two"
(integer) 2
127.0.0.1:6379[1]> Lpush list "three"
(integer) 3
127.0.0.1:6379[1]> Llen list
(integer) 3

//===================================================
//lrem key 根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素。
//===================================================
127.0.0.1:6379[1]> lrem list 1 "two"
(integer) 1
127.0.0.1:6379[1]>  Lrange list 0 -1
1) "three"
2) "one"
// ===================================================
// Ltrim key 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区
//间之内的元素都将被删除。
//===================================================
127.0.0.1:6379[1]> RPUSH mylist "hello"
(integer) 1
127.0.0.1:6379[1]> RPUSH mylist "hello"
(integer) 2
127.0.0.1:6379[1]> RPUSH mylist "hello2"
(integer) 3
127.0.0.1:6379[1]> RPUSH mylist "hello3"
(integer) 4
127.0.0.1:6379[1]>  ltrim mylist 1 2   //保留1到2区间的元素,其他都删除
OK
127.0.0.1:6379[1]> lrange mylist 0 -1
1) "hello"
2) "hello2"

//===================================================
//rpoplpush 移除列表的最后一个元素,并将该元素添加到另一个列表并返回。
//===================================================
127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "foo"
(integer) 2
127.0.0.1:6379> rpush mylist "bar"
(integer) 3
127.0.0.1:6379> rpoplpush mylist myotherlist  //从mylist移除最后一个元素,移到myotherlist
"bar"
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "foo"
127.0.0.1:6379> lrange myotherlist 0 -1
1) "bar"
    
// ===================================================
// lset key index value 将列表 key 下标为 index 的元素的值设置为 value 。
//===================================================
127.0.0.1:6379> exists list //对空列表(key 不存在)进行 LSET
(integer) 0
127.0.0.1:6379> lset list 0 item // 报错
(error) ERR no such key
127.0.0.1:6379> lpush list "value1" // 对非空列表进行 LSET
(integer) 1
127.0.0.1:6379> lrange list 0 0 
1) "value1"
127.0.0.1:6379> lset list 0 "new" // 更新值
OK
127.0.0.1:6379> lrange list 0 0
1) "new"
127.0.0.1:6379> lset list 1 "new"//index 超出范围报错
(error) ERR index out of range
    
//===================================================
//linsert key before/after pivot value 用于在列表的元素前或者后插入元素。
// 将值 value 插入到列表 key 当中,位于值 pivot 之前或之后。
//===================================================
127.0.0.1:6379[1]> RPUSH mylist "Hello"
(integer) 1
127.0.0.1:6379[1]>  RPUSH mylist "World"
(integer) 2
127.0.0.1:6379[1]> LINSERT mylist BEFORE "World" "There"  //world之前插入there
(integer) 3
127.0.0.1:6379[1]> lrange mylist 0 -1
1) "Hello"
2) "There"
3) "World"
127.0.0.1:6379[1]> LINSERT mylist after "World" "here"  //world之后插入here
(integer) 4
127.0.0.1:6379[1]> lrange mylist 0 -1
1) "Hello"
2) "There"
3) "World"
4) "here"

性能总结

它是一个字符串链表,left,right 都可以插入添加 如果键不存在,创建新的链表 如果键已存在,新增内容 如果值全移除,对应的键也就消失了 链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就很惨淡了。 list就是链表,略有数据结构知识的人都应该能理解其结构。使用Lists结构,我们可以轻松地实现最新消 息排行等功能。List的另一个应用就是消息队列,可以利用List的PUSH操作,将任务存在List中,然后工 作线程再用POP操作将任务取出进行执行。Redis还提供了操作List中某一段的api,你可以直接查询,删 除List中某一段的元素。 Redis的list是每个子元素都是String类型的双向链表,可以通过push和pop操作从列表的头部或者尾部 添加或者删除元素,这样List即可以作为栈,也可以作为队列。

集合Set 单值多value

//===================================================
//sadd 将一个或多个成员元素加入到集合中,不能重复
// smembers 返回集合中的所有的成员。
// sismember 命令判断成员元素是否是集合的成员。
// ===================================================
127.0.0.1:6379> sadd myset "hello"  //添加数据
(integer) 1
127.0.0.1:6379> sadd myset "jiayu"
(integer) 1
127.0.0.1:6379> sadd myset "jiayu" 
(integer) 0                           //value不能重复所以添加失败
127.0.0.1:6379> smembers myset
1) "kuangshen"
2) "hello"
127.0.0.1:6379> sismember myset "hello"  //查看myset中是否有hello
(integer) 1
127.0.0.1:6379> sismember myset "world"		//查看myset中是否有world
(integer) 0
    
//===================================================
// scard,获取集合里面的元素个数
// ===================================================
127.0.0.1:6379> scard myset  //获取集合里面的元素个数
(integer) 2
    
// ===================================================
//srem key value 用于移除集合中的一个或多个成员元素
//===================================================
127.0.0.1:6379[1]> srem myset "jiayu"  //移除jiayu
(integer) 1
127.0.0.1:6379[1]> smembers myset
1) "hello"
    
//===================================================
// srandmember key 命令用于返回集合中的一个随机元素。
// ===================================================
127.0.0.1:6379[1]> sadd myset "there"
(integer) 1
127.0.0.1:6379[1]> sadd myset "here"
(integer) 1
127.0.0.1:6379[1]> smembers myset  //查看所有数据
1) "here"
2) "there"
3) "hello"
127.0.0.1:6379[1]> srandmember myset   //随机返回一个元素
"hello"
127.0.0.1:6379[1]> srandmember myset 2  //随机返回两个元素
1) "there"
2) "hello"
127.0.0.1:6379[1]> srandmember myset 2
1) "there"
2) "hello"
127.0.0.1:6379[1]> srandmember myset 2
1) "there"
2) "here"
//===================================================
//spop key 用于移除集合中的指定 key 的一个或多个随机元素
// ===================================================
127.0.0.1:6379[1]>  smembers myset
1) "here"
2) "there"
3) "hello"
127.0.0.1:6379[1]> spop myset   //随机删除一个
"hello"
127.0.0.1:6379[1]> spop myset
"there"
127.0.0.1:6379[1]> spop myset
"here"
127.0.0.1:6379[1]> get myset
(nil)
  
//===================================================
//smove SOURCE DESTINATION MEMBER
//将指定成员 member 元素从 source 集合移动到 destination 集合。
//===================================================
127.0.0.1:6379[1]> sadd myset "hello"
(integer) 1
127.0.0.1:6379[1]> sadd myset "world"
(integer) 1
127.0.0.1:6379[1]> sadd myset "jiayu"
(integer) 1
127.0.0.1:6379[1]>  sadd myset2 "set2"
(integer) 1
127.0.0.1:6379[1]> smove myset myset2 "jiayu"  //从myset中移动到myset2中,移动的value是jiayu
(integer) 1
127.0.0.1:6379[1]> SMEMBERS myset
1) "world"
2) "hello"
127.0.0.1:6379[1]> SMEMBERS myset2
1) "jiayu"
2) "set2"
//===================================================
- 数字集合类
- 差集: sdiff
- 交集: sinter
- 并集: sunion
// ===================================================
127.0.0.1:6379[1]> sadd key1 "a"
(integer) 1
127.0.0.1:6379[1]> sadd key1 "b"
(integer) 1
127.0.0.1:6379[1]>  sadd key1 "c"
(integer) 1
127.0.0.1:6379[1]> sadd key2 "c"
(integer) 1
127.0.0.1:6379[1]>  sadd key2 "d"
(integer) 1
127.0.0.1:6379[1]> sadd key2 "e"
(integer) 1
127.0.0.1:6379[1]>
127.0.0.1:6379[1]> sdiff key1 key2   //差集,拿出key1有key2没有的值
1) "a"
2) "b"
127.0.0.1:6379[1]> sinter key1 key2  //交集,key1 key2都有的值
1) "c"
127.0.0.1:6379[1]>  sunion key1 key2 //并集,展示key1 key2所有的值,重复值只显示一次
1) "a"
2) "b"
3) "e"
4) "d"
5) "c"

在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为 集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功 能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。

有序集合Zset

在set基础上,加一个score值。之前set是k1 v1 v2 v3,现在zset是 k1 score1 v1 score2 v2

// ===================================================
// zadd 将一个或多个成员元素及其分数值加入到有序集当中。
//zrange 返回有序集中,指定区间内的成员
// ===================================================
127.0.0.1:6379> zadd myset 1 "one"
(integer) 1
127.0.0.1:6379> zadd myset 2 "two" 3 "three"
(integer) 2
127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "two"
3) "three"
    
// ===================================================
// zrangebyscore 返回有序集合中指定分数区间的成员列表。有序集成员按分数值递增(从小到大)次序排列。
// ===================================================
127.0.0.1:6379> zadd salary 2500 xiaoming
(integer) 1
127.0.0.1:6379> zadd salary 5000 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 500 kuangshen
(integer) 1
//Inf无穷大量+∞,同样地,-∞可以表示为-Inf。
127.0.0.1:6379[1]>  zadd salary 2500 jiayu
(integer) 1
127.0.0.1:6379[1]>  zadd salary 20 yaoyin
(integer) 1
127.0.0.1:6379[1]> zadd salary 80000 houma
(integer) 1
127.0.0.1:6379[1]> zrangebyscore salary -inf +inf  // 显示整个有序集
1) "yaoyin"
2) "jiayu"
3) "houma"
127.0.0.1:6379[1]> zrangebyscore salary -inf +inf withscores //递增排列
1) "yaoyin"
2) "20"
3) "jiayu"
4) "2500"
5) "houma"
6) "80000"
127.0.0.1:6379[1]>  zrevrange salary 0 -1 WITHSCORES  //递减排列
1) "houma"
2) "80000"
3) "jiayu"
4) "2500"
5) "yaoyin"
6) "20"
127.0.0.1:6379[1]> zrangebyscore salary -inf 2500 WITHSCORES  //显示工资 <=2500的所有成员
1) "yaoyin"
2) "20"
3) "jiayu"
4) "2500"

// ===================================================
// zrem 移除有序集中的一个或多个成员
// ===================================================
127.0.0.1:6379[1]> zrange salary 0 -1  //查看所有数据
1) "yaoyin"
2) "jiayu"
3) "houma"
127.0.0.1:6379[1]> zrem salary jiayu  //删除jiayu
(integer) 1
127.0.0.1:6379[1]> zrange salary 0 -1  //查看所有数据
1) "yaoyin"
2) "houma"
// ===================================================
//zcard 命令用于计算集合中元素的数量。
//===================================================
127.0.0.1:6379> zcard salary
(integer) 2
OK
 
//===================================================
// zcount 计算有序集合中指定分数区间的成员数量。
// ===================================================
127.0.0.1:6379[1]> zadd myset 1 "hello"
(integer) 1
127.0.0.1:6379[1]> zadd myset 2 "world" 3 "jiayu"
(integer) 2
127.0.0.1:6379[1]> zcount myset 1 5  //1-5的区间有几个value
(integer) 3
127.0.0.1:6379[1]> zcount myset 1 2
(integer) 2
 
//===================================================
//zrank 返回有序集中指定成员的排名。其中有序集成员按分数值递增(从小到大)顺序排列。
//===================================================
 127.0.0.1:6379[1]> zrange salary 0 -1 withscores
1) "yaoyin"
2) "20"
3) "jiayu"
4) "2500"
5) "houma"
6) "80000"
127.0.0.1:6379[1]>  zrank salary yaoyin  //根据薪水从小到大yaoyin第一  从0开始
(integer) 0
127.0.0.1:6379[1]>  zrank salary jiayu
(integer) 1
127.0.0.1:6379[1]>  zrank salary houma
(integer) 2
    
//===================================================
// zrevrank 返回有序集中成员的排名。其中有序集成员按分数值递减(从大到小)排序。
//===================================================
 127.0.0.1:6379[1]> zrevrank salary houma   //从大到小排houma薪水最高
(integer) 0
127.0.0.1:6379[1]> zrevrank salary yaoyin   //老三
(integer) 2
127.0.0.1:6379[1]> zrevrank salary jiayu    //老二
(integer) 1

和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列,比如 一个存储全班同学成绩的sorted set,其集合value可以是同学的学号,而score就可以是其考试得分, 这样在数据插入集合的时候,就已经进行了天然的排序。可以用sorted set来做带权重的队列,比如普 通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让 重要的任务优先执行。 排行榜应用,取TOP N操作 !

哈希Hash

kv模式不变,但V是一个键值对

//===================================================
//hset、hget 命令用于为哈希表中的字段赋值 。
//hmset、hmget 同时将多个field-value对设置到哈希表中。会覆盖哈希表中已存在的字段。
// hgetall 用于返回哈希表中,所有的字段和值。
//hdel 用于删除哈希表 key 中的一个或多个指定字段
//===================================================
127.0.0.1:6379[1]> hset myhash field1 jiayu  //在myhash中赋值field1 jiayu,在field1 赋值jaiyu
(integer) 1
127.0.0.1:6379[1]> hget myhash field1  //查看myhash中的fieled中的值
"jiayu"
127.0.0.1:6379[1]> hmset myhash field1 "Hello" field2 "World"  //同时将多个field-value对设置到哈希表中。会覆盖哈希表中已存在的字段
OK
127.0.0.1:6379[1]> hget myhash field1
"Hello"
127.0.0.1:6379[1]> hget myhash field2
"World"
127.0.0.1:6379[1]> hgetall myhash  //查看myhash中所有键值对
1) "field1"
2) "Hello"
3) "field2"
4) "World"
127.0.0.1:6379[1]> hdel myhash field1  //删除field1-hello
(integer) 1
127.0.0.1:6379[1]> hgetall myhash
1) "field2"
2) "World"

//===================================================
//hlen 获取哈希表中字段的数量。
//===================================================
127.0.0.1:6379[1]> hlen myhash  //查看myhash中字段数量
(integer) 1
127.0.0.1:6379[1]> HMSET myhash field1 "Hello" field2 "World"  
OK
127.0.0.1:6379[1]> hlen myhash
(integer) 2
 
// ===================================================
// hexists 查看哈希表的指定字段是否存在。
// ===================================================
127.0.0.1:6379[1]>  hexists myhash field1
(integer) 1
127.0.0.1:6379[1]> hexists myhash field3
(integer) 0
    
// ===================================================
//hkeys 获取哈希表中的所有域(field)。
// hvals 返回哈希表所有域(field)的值。
//===================================================
127.0.0.1:6379[1]> hkeys myhash  //获取myhash中的所有域
1) "field2"
2) "field1"
127.0.0.1:6379[1]> hvals myhash  //获取myhash中的所有域中的所有值
1) "World"
2) "Hello"
 
//===================================================
//hincrby 为哈希表中的字段值加上指定增量值。
//===================================================
 127.0.0.1:6379[1]>  hset myhash field 5
(integer) 1
127.0.0.1:6379[1]> hincrby myhash field 1   //5+1
(integer) 6
127.0.0.1:6379[1]> HINCRBY myhash field -1  //6-1
(integer) 5
127.0.0.1:6379[1]> hincrby myhash field -10 //5-10
(integer) -5
    
// ===================================================
// hsetnx 为哈希表中不存在的的字段赋值 。
//===================================================
127.0.0.1:6379> HSETNX myhash field3 "hello"
(integer) 1    //设置成功,返回 1 。
127.0.0.1:6379> HSETNX myhash field3"world"
(integer) 0   //如果给定字段已经存在,返回 0 。
127.0.0.1:6379> HGET myhash field3
"hello"

Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。 存储部分变更的数据,如用户信息等。

三种特殊数据类型

GEO地理位置

简介:

Redis 的 GEO 特性在 Redis 3.2 版本中推出, 这个功能可以将用户给定的地理位置信息储存起来, 并对 这些信息进行操作。来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能。

geo的数据类型为 zset。 GEO 的数据结构总共有六个常用命令:geoadd、geopos、geodist、georadius、 georadiusbymember、gethash 官方文档:Redis GEOADD 命令_将指定的地理空间位置(纬度、经度、名称)添加到指定的key中

解析:

// 语法
geoadd key longitude latitude member ...
// 将给定的空间元素(纬度、经度、名字)添加到指定的键里面。
//这些数据会以有序集he的形式被储存在键里面,从而使得georadius和georadiusbymember这样的命令可以在之后通过位置查询取得这些元素。
//geoadd命令以标准的x,y格式接受参数,所以用户必须先输入经度,然后再输入纬度。
//geoadd能够记录的坐标是有限的:非常接近两极的区域无法被索引。
// 有效的经度介于-180-180度之间,有效的纬度介于-85.05112878 度至 85.05112878 度之间,当用户尝试输入一个超出范围的经度或者纬度时,geoadd命令将返回一个错误。

127.0.0.1:6379[1]> geoadd china:city 116.23 40.22 beijing  //纬度、经度、名字
(integer) 1
127.0.0.1:6379[1]> geoadd china:city 106.54 29.40 chongqing 108.93 34.23 xian 114.02 30.58 wuhan
(integer) 3
    
//语法
geopos key member [member...]
//从key里返回所有给定位置元素的位置(经度和纬度)
127.0.0.1:6379[1]> geopos china:city beijing   //通过城市名字看坐标
1) 1) "116.23000055551528931"
   2) "40.2200010338739844"
127.0.0.1:6379[1]> geopos china:city xian wuhan    //一次性可以查看多个城市坐标
1) 1) "108.92999857664108276"
   2) "34.23000121926852302"
2) 1) "114.01999980211257935"
   2) "30.58000021509926825"
    
//语法
geodist key member1 member2 [unit]
// 返回两个给定位置之间的距离,如果两个位置之间的其中一个不存在,那么命令返回空值。
// 指定单位的参数unit必须是以下单位的其中一个:
// m表示单位为米
// km表示单位为千米
// mi表示单位为英里
// ft表示单位为英尺
// 如果用户没有显式地指定单位参数,那么geodist默认使用米作为单位。
//geodist命令在计算距离时会假设地球为完美的球形,在极限情况下,这一假设最大会造成0.5%的误
差。
127.0.0.1:6379[1]>  geodist china:city beijing xian  //北京距离西安的距离,默认单位米
"927704.8160"
127.0.0.1:6379[1]>  geodist china:city beijing xian km  //北京距离西安的距离,单位千米
"927.7048"
    
// 语法
georadius key longitude latitude radius m|km|ft|mi [withcoord][withdist]
[withhash][asc|desc][count count]
//以给定的经纬度为中心, 找出某一半径内的元素
//在 china:city 中寻找坐标 100 30 半径为 1000km 的城市
127.0.0.1:6379[1]> georadius china:city 100 30 1000 km
1) "chongqing"
2) "xian"
    
//withdist 返回位置名称和中心距离
127.0.0.1:6379[1]> georadius china:city 100 30 1000 km withdist
1) 1) "chongqing"
   2) "635.2850"
2) 1) "xian"
   2) "963.3171"
 
// withcoord 返回位置名称和经纬度
127.0.0.1:6379[1]> georadius china:city 100 30 1000 km withcoord
1) 1) "chongqing"
   2) 1) "106.54000014066696167"
      2) "29.39999880018641676"
2) 1) "xian"
   2) 1) "108.92999857664108276"
      2) "34.23000121926852302"
    
 //withdist withcoord 返回位置名称 距离 和经纬度 count 限定寻找个数
127.0.0.1:6379[1]>  georadius china:city 100 30 1000 km withcoord withdist count 1
1) 1) "chongqing"
   2) "635.2850"
   3) 1) "106.54000014066696167"
      2) "29.39999880018641676"
127.0.0.1:6379[1]>  georadius china:city 100 30 1000 km withcoord withdist count 2
1) 1) "chongqing"
   2) "635.2850"
   3) 1) "106.54000014066696167"
      2) "29.39999880018641676"
2) 1) "xian"
   2) "963.3171"
   3) 1) "108.92999857664108276"
      2) "34.23000121926852302"
127.0.0.1:6379[1]>  georadius china:city 100 30 1000 km withcoord withdist count 3
1) 1) "chongqing"
   2) "635.2850"
   3) 1) "106.54000014066696167"
      2) "29.39999880018641676"
2) 1) "xian"
   2) "963.3171"
   3) 1) "108.92999857664108276"
      2) "34.23000121926852302"
    
//# 语法
georadiusbymember key member radius m|km|ft|mi [withcoord][withdist]
[withhash][asc|desc][count count]
//找出位于指定范围内的元素,中心点是由给定的位置元素决定
127.0.0.1:6379[1]> georadiusbymember  china:city beijing 1000 km  //以北京为中心,半径1000千米范围内的所有城市
1) "beijing"
2) "xian"

//语法
geohash key member [member...]
//Redis使用geohash将二维经纬度转换为一维字符串,字符串越长表示位置更精确,两个字符串越相似表示距离越近。
127.0.0.1:6379[1]>  geohash china:city beijing xian
1) "wx4sucu47r0"
2) "wqj6wz2khy0"

//GEO没有提供删除成员的命令,但是因为GEO的底层实现是zset,所以可以借用zrem命令实现对地理位置信息的删除.
127.0.0.1:6379[1]>  zrange china:city 0 -1
1) "chongqing"
2) "xian"
3) "wuhan"
4) "beijing"
127.0.0.1:6379[1]>  zrem china:city wuhan  //移除元素
(integer) 1

    
//应用场景:例如微信发送定位 共享位置 附近交友等等

HyperLogLog

简介:

Redis 在 2.8.9 版本添加了 HyperLogLog 结构。 Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积 非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。 在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。 HyperLogLog则是一种算法,它提供了不精确的去重计数方案。

什么是基数?

比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。

举个栗子:假如我要统计网页的UV(浏览用户数量,一天内同一个用户多次访问只能算一次),传统的 解决方案是使用Set来保存用户id,然后统计Set中的元素数量来获取页面UV。但这种方案只能承载少量 用户,一旦用户数量大起来就需要消耗大量的空间来存储用户id。我的目的是统计用户数量而不是保存 用户,这简直是个吃力不讨好的方案!而使用Redis的HyperLogLog最多需要12k就可以统计大量的用户 数,尽管它大概有0.81%的错误率,但对于统计UV这种不需要很精确的数据是可以忽略不计的。

基本命令

命令描述
[PFADD key element [element ...]添加指定元素到 HyperLogLog 中
[PFCOUNT key [key ...]返回给定 HyperLogLog 的基数估算值
[PFMERGE destkey sourcekey [sourcekey ...]将多个 HyperLogLog 合并为一个 HyperLogLog,并 集计算
127.0.0.1:6379[1]> pfadd mykey a b c d e f g h i j  //新增数据
(integer) 1 
127.0.0.1:6379[1]> pfcount mykey
(integer) 10
127.0.0.1:6379[1]>  PFADD mykey2 i j z x c v b n m  
(integer) 1
127.0.0.1:6379[1]> PFMERGE mykey3 mykey mykey2  //合并数据
OK
127.0.0.1:6379[1]> PFCOUNT mykey3 //查看基数
(integer) 15

BitMap

简介

在开发中,可能会遇到这种情况:需要统计用户的某些信息,如活跃或不活跃,登录或者不登录;又如 需要记录用户一年的打卡情况,打卡了是1, 没有打卡是0,如果使用普通的 key/value存储,则要记录 365条记录,如果用户量很大,需要的空间也会很大,所以 Redis 提供了 Bitmap 位图这中数据结构, Bitmap 就是通过操作二进制位来进行记录,即为 0 和 1;如果要记录 365 天的打卡情况,使用 Bitmap 表示的形式大概如下:0101000111000111...........................,这样有什么好处呢?当然就是节约内存 了,365 天相当于 365 bit,又 1 字节 = 8 bit , 所以相当于使用 46 个字节即可。

BitMap 就是通过一个 bit 位来表示某个元素对应的值或者状态, 其中的 key 就是对应元素本身,实际上 底层也是通过对字符串的操作来实现。Redis 从 2.2 版本之后新增了setbit, getbit, bitcount 等几个 bitmap 相关命令。

//setbit 设置操作
//SETBIT key offset value : 设置 key 的第 offset 位为value (1或0)
//使用 bitmap 来记录上述事例中一周的打卡记录如下所示:
//周一:1,周二:0,周三:0,周四:1,周五:1,周六:0,周天:0 (1 为打卡,0 为不打卡)
127.0.0.1:6379[1]> setbit sign 0 1
(integer) 0
127.0.0.1:6379[1]> setbit sign 1 0
(integer) 0
127.0.0.1:6379[1]>  setbit sign 2 0
(integer) 0
127.0.0.1:6379[1]> setbit sign 3 1
(integer) 0
127.0.0.1:6379[1]> setbit sign 4 1
(integer) 0
127.0.0.1:6379[1]> setbit sign 5 0
(integer) 0
127.0.0.1:6379[1]> setbit sign 6 0
(integer) 0

//getbit 获取操作
//GETBIT key offset 获取offset设置的值,未设置过默认返回0
127.0.0.1:6379[1]> getbit sign 3  //查看周四是否打卡
(integer) 1
127.0.0.1:6379[1]>  getbit sign 6  //查看周七是否打卡
(integer) 0
    
//bitcount 统计操作
//bitcount key [start, end] 统计 key 上位为1的个数
//统计这周打卡的记录,可以看到只有3天是打卡的状态:
127.0.0.1:6379[1]> bitcount sign  //统计 sign 上位为1的个数  也就是打卡的天数
(integer) 3

Redis的持久化

Redis 是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中 的数据库状态也会消失。所以 Redis 提供了持久化功能!

RDB(Redis DataBase)

uploading.4e448015.gif

正在上传…重新上传取消

什么是RDB 在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里 Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。

Fork

Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程

rdb 保存的是dump.rdb文件

uploading.4e448015.gif

正在上传…重新上传取消

配置位置及SNAPSHOTTING解析

uploading.4e448015.gif

正在上传…重新上传取消

RDB 是整合内存的压缩过的Snapshot,RDB 的数据结构,可以配置复合的快照触发条件。

默认:

1分钟内改了1万次

5分钟内改了10次

15分钟内改了1次

如果想禁用RDB持久化的策略,只要不设置任何save指令,或者给save传入一个空字符串参数也可以。 若要修改完毕需要立马生效,可以手动使用 save 命令!立马生效 !

如何触发RDB快照

自动触发

  1. save:用来配置触发Redis的RDB持久化条件,在间隔时间内有多少数据从内存中保存到硬盘。如:save m n,表示m秒内数据集存在n次变化时,自动触发bgsave。save 10 2,表示在20秒内至少有2个key发生改变。

  2. stop-writes-on-bgsave-error:默认值为yes。当RDB最后一次执行保存数据到硬盘中操作失败,直接关闭Redis的写操作。推荐为yes。

  3. rdbcompression:默认值为yes。对于存储到磁盘的快照文件,是否设置压缩存储。如果为yes,Redis会采用LZF算法压缩。若不想消耗CPU来进行压缩的话,可以设为no,但是存储在磁盘的快照文件会比较大。推荐为yes。

  4. rdbchecksum:默认值为yes。在存储快照文件后,还可以让Redis使用CRC64算法来进行数据校验,但开启此功能会增加大约10%的性能消耗,若想获得最大的性能提升,可以设为no。推荐为yes。

  5. dbfilename:设置快照文件的名称,默认是dump.db。 dir:设置快照文件的存放路径。默认是和当前配置文件保存在同一目录。 ————————————————

手动触发

  1. 命令save或者是bgsave

(1)save 该命令会阻塞Redis服务器。执行save命令期间,Redis不能处理其他命令,知道RDB过程完成为止。

(2)bgsave:执行该命令时,Redis会在后台异步完成快照操作,快照过程中话可以响应用户请求。具体操作是Redis进程执行fork操作创建子进程,RDB持久化过程由子进程完成,完成后会自动结束。阻塞只发生在fork阶段,时间很短。基本上 Redis 内部所有的RDB操作都是采用 bgsave 命令。

3、执行flushall命令,也会产生 dump.rdb 文件,但里面是空的,无意义 !

4、退出的时候也会产生 dump.rdb 文件!

如何恢复

1、将备份文件(dump.rdb)移动到redis安装目录并启动服务即可

2、CONFIG GET dir 获取目录

127.0.0.1:6379[1]> config get dir

127.0.0.1:6379[1]> config get dir
1) "dir"
2) "D:\\Redis\\redis\\bin"

优点和缺点

优点: 1、适合大规模的数据恢复

2、对数据完整性和一致性要求不高

缺点:

1、在一定间隔时间做一次备份,所以如果redis意外宕机的话,就会丢失最后一次快照后的所有修改

2、Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑。

小结

uploading.4e448015.gif

正在上传…重新上传取消

AOF(Append Only File)

AOF是什么

以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件 但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件 的内容将写指令从前到后执行一次以完成数据的恢复工作

Aof保存的是 appendonly.aof 文件

uploading.4e448015.gif

正在上传…重新上传取消

appendonly no   //是否以append only模式作为持久化方式,默认使用的是rdb方式持久化,这种方式在许多应用中已经足够用了
appendfilename "appendonly.aof" //appendfilename AOF 文件名称
appendfsync everysec //appendfsync aof持久化策略的配置
//no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。
// always表示每次写入都执行fsync,以保证数据同步到磁盘。
// everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。
No-appendfsync-on-rewrite //重写时是否可以运用Appendfsync,用默认no即可,保证数据安全性
Auto-aof-rewrite-min-size //设置重写的基准值
Auto-aof-rewrite-percentage //设置重写的基准值

持久化过程

1.客户端的请求写命令会被append追加到AOF缓冲区内。 2.AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中。 3.AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量。 4.Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的。 ————————————————

AOF 启动/修复/恢复

正常恢复:

启动:设置Yes,修改默认的appendonly no,改为yes

将有数据的aof文件复制一份保存到对应目录(config get dir)

恢复:重启redis然后重新加载

异常恢复:

启动:设置Yes 故意破坏 appendonly.aof 文件!

修复: redis-check-aof --fix appendonly.aof 进行修复

恢复:重启 redis 然后重新加载

Rewrite是什么:

AOF 采用文件追加方式,文件会越来越大,为避免出现此种情况,新增了重写机制,当AOF文件的大小 超过所设定的阈值时,Redis 就会启动AOF 文件的内容压缩,只保留可以恢复数据的最小指令集,可以 使用命令 bgrewriteaof !

重写原理:

AOF 文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再 rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧 的aof文件,这点和快照有点类似!

触发机制:

Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的已被且文件大 于64M的触发。

uploading.4e448015.gif

正在上传…重新上传取消

优点和缺点

优点:

1、每修改同步:appendfsync always 同步持久化,每次发生数据变更会被立即记录到磁盘,性能较差 但数据完整性比较好

2、每秒同步: appendfsync everysec 异步操作,每秒记录 ,如果一秒内宕机,有数据丢失

3、不同步: appendfsync no 从不同步

缺点:

1、相同数据集的数据而言,aof 文件要远大于 rdb文件,恢复速度慢于 rdb。

2、Aof 运行效率要慢于 rdb,每秒同步策略效率较好,不同步效率和rdb相同。

小结

uploading.4e448015.gif

正在上传…重新上传取消

总结

1、RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储

2、AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始 的数据,AOF命令以Redis 协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重 写,使得AOF文件的体积不至于过大。

3、只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化

4、同时开启两种持久化方式

(1)在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF 文件保 存的数据集要比RDB文件保存的数据集要完整。

(2)RDB 的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者 建议不 要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有 AOF可能潜在的 Bug,留着作为一个万一的手段。

5、性能建议

(1)因为RDB文件只用作后备用途,建议只在Slave(从机)上持久化RDB文件,而且只要15分钟备份一次就够 了,只 保留 save 900 1 这条规则。

(2)如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自 己的 AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite 的最后将 rewrite 过程中产 生的新数据写 到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite 的频率,AOF重写的基 础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重 写可以改到适当的数值。

(3)如果不Enable AOF ,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO,也 减少了 rewrite时带来的系统波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据, 启动脚本也要 比较两个 Master/Slave 中的 RDB文件,载入较新的那个,微博就是这种架构。

————————————————

Redis 事务

理论

Redis事务的概念:

Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列 化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事 务执行命令序列中。

总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。

Redis事务没有隔离级别的概念:

批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行!

Redis不保证原子性:

Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其 余的命令仍会被执行。

Redis事务的三个阶段:

(1)开始事务 (multi)

(2)命令入队 (...)

(3)执行事务 (exex)

Redis 事务命令

下表列出了 redis 事务的相关命令:

序号命令及描述
1DISCARD 取消事务,放弃执行事务块内的所有命令。
2EXEC 执行所有事务块内的命令。
3MULTI 标记一个事务块的开始。
4UNWATCH 取消 WATCH 命令对所有 key 的监视。
5[WATCH key key ...] 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

Redis事务相关命令:

watch key1 key2 ... //监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则事务被打断 ( 类似乐观锁 )
multi //标记一个事务块的开始( queued )
exec //执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 )
discard //取消事务,放弃事务块中的所有命令
unwatch //取消watch对所有key的监控

实践

正常执行

以下是一个事务的例子, 它先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令:

127.0.0.1:6379> multi  //开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED                         //命令输入
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set key3 v3
QUEUED
127.0.0.1:6379> exec  //执行
1) OK
2) OK                //输出结果
3) "v2"
4) OK

放弃事务

//放弃事务discard
127.0.0.1:6379> multi   //没执行完一次事务就得重新开启
OK
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> discard  //放弃事务
OK
127.0.0.1:6379> get k4  
(nil)                    //事务里的命令并没有执行
127.0.0.1:6379> get k5
(nil)

命令性错误

若在事务队列中存在命令性错误(代码性错误,类似于java编译性错误),则执行EXEC命令时,所有命令都不会 执行

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> getset k3  //错误的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> exec  //执行事务也报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5  //所有命令都不会执行
(nil)

语法性错误

若在事务队列中存在语法性错误(类似于java的1/0的运行时异常),则执行EXEC命令时,其他正确命令会被执行,错误命令抛出异常。

127.0.0.1:6379> set k1 "v1"  //设置k1值为字符v1
OK
127.0.0.1:6379> multi   //开启事务
OK
127.0.0.1:6379> incr k1  //让k1的value自增1,前面说过自增针对的是数字,所以代码没问题,是语法错误
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range //虽然一条命令错误但是依旧运行成功
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"

Watch 监控 (面试)

分布式:

分布式锁是控制分布式系统之间同步访问共享资源的一种方式,为了保证共享资源的数据一致性。

悲观锁:

悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在 拿数据的时候都会上锁,这样别人想拿到这个数据就会block直到它拿到锁。传统的关系型数据库里面就 用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在操作之前先上锁。

乐观锁:

乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会 上锁。但是在更新的时候会判断一下再此期间别人有没有去更新这个数据,可以使用版本号等机制,乐 观锁适用于多读的应用类型,这样可以提高吞吐量,乐观锁策略:提交版本必须大于记录当前版本才能 执行更新。

获取version,更新时比较version

应用场景1:秒杀问题,如何防止最后一件商品不会被多人同时购买。

解决方案

  • 使用setnx 设置一个公共锁。

    • setnx lock-key value
      

利用setnx命令的返回值特征,有值则返回设置失败,无值则返回设置成功。

  • 对于返回设置成功的,拥有控制权,进行下一步具体业务操作。

  • 对于返回值设置失败的,不具有控制权,排队或者等待。

操作完毕之后,通过del键值对来释放锁。

应用场景2

解决超卖问题

使用reids的 watch + multi

  1. 当用户购买时,通过 WATCH 监听用户库存,如果库存在watch监听后发生改变,就会捕获异常而放弃对库存减一操作

  2. 如果库存没有监听到变化并且数量大于1,则库存数量减一,并执行任务

Redis监视测试

正常执行成功!

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money  //监视money对象
OK
127.0.0.1:6379> multi   //事务正常结束,数据期间没有发送变动,这个时候正常执行成功
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20

测试多线程修改值,使用watch可以当做redis乐观锁操作!

127.0.0.1:6379> watch money  //监视 money  拿到money 100
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379>
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec //执行之前,另一个线程,修改了我们的值,这个时候,会导致事务执行失败
(nil)
//第二个线程的操作
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> set money 1000
OK
    
//解决办法
127.0.0.1:6379> unwatch   //如果发生事务执行失败,先解锁
OK
127.0.0.1:6379> watch money  //获取最新的值,再次监视 ,select version
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec   //比对监视值有没有发生变化,如果没有变化执行成功,发生变化执行失败
1) (integer) 990
2) (integer) 30

说明: 一但执行 EXEC 开启事务的执行后,无论事务使用执行成功, WARCH 对变量的监控都将被取消。 故当事务执行失败后,需重新执行WATCH命令对变量进行监控,并开启新的事务进行操作。

小结

watch指令类似于乐观锁,在事务提交时,如果watch监控的多个KEY中任何KEY的值已经被其他客户端 更改,则使用EXEC执行事务时,事务队列将不会被执行,同时返回Nullmulti-bulk应答以通知调用者事 务执行失败。

Redis 发布订阅

Redis发布订阅

Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。(微信微博,有关注系统的)

Redis 客户端可以订阅任意数量的频道。

订阅/发布消息图:

uploading.4e448015.gif

正在上传…重新上传取消

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

uploading.4e448015.gif

正在上传…重新上传取消

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

uploading.4e448015.gif

正在上传…重新上传取消

命令

这些命令被广泛用于构建即时通信应用,比如网络聊天室(chatroom)和实时广播、实时提醒等。

uploading.4e448015.gif

正在上传…重新上传取消

测试

两个端口: 订阅段 发送端

订阅端:

127.0.0.1:6379> SUBSCRIBE jiayu  //订阅一个频道 jiayu
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "jiayu"
3) (integer) 1
//等待发送信息
1) "message"  //消息
2) "jiayu"   //订阅的频道名
3) "hello,jiayu"  //消息的具体内容
1) "message"
2) "jiayu"
3) "hello,redis"

发送端:

127.0.0.1:6379> PUBLISH jiayu "hello,jiayu" //发布者发送消息到频道
(integer) 1
127.0.0.1:6379> PUBLISH jiayu "hello,redis" //发布者发送消息到频道
(integer) 1
127.0.0.1:6379>

原理

Redis是使用C实现的,通过分析 Redis 源码里的 pubsub.c 文件,了解发布和订阅机制的底层实现,籍 此加深对 Redis 的理解。

Redis 通过 PUBLISH 、SUBSCRIBE 和 PSUBSCRIBE 等命令实现发布和订阅功能。

通过 SUBSCRIBE 命令订阅某频道后,redis-server 里维护了一个字典,字典的键就是一个个 channel ,而字典的值则是一个链表,链表中保存了所有订阅这个 channel 的客户端。SUBSCRIBE 命令的关 键,就是将客户端添加到给定 channel 的订阅链表中。

uploading.4e448015.gif

正在上传…重新上传取消

通过 PUBLISH 命令向订阅者发送消息,redis-server 会使用给定的频道作为键,在它所维护的 channel 字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者。

Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以设定对某一个 key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应 的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。

使用场景

Pub/Sub构建实时消息系统

Redis的Pub/Sub系统可以构建实时的消息系统

比如很多用Pub/Sub构建的实时聊天系统的例子。(订阅一个频道,将信息回显给所有人)

稍微复杂的场景我们就会使用到消息中间件MQ

Redis主从复制

概念

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点 (master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。 Master以写为主,Slave 以读为主。

默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从 节点只能有一个主节点。

主从复制的作用主要包括

1、数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。

2、故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务 的冗余。

3、负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务 (即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写 少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。

4、高可用(集群)基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是 Redis高可用的基础。

一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的(宕机你就屎定了),原因如下:

1、从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较 大;

2、从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G,也不能将所有 内存用作Redis存储内存,一般来说,单台Redis最大使用内存不应该超过20G。 电商网站上的商品,一般都是一次上传,无数次浏览的,说专业点也就是"多读少写"。 对于这种场景,我们可以使如下这种架构:

uploading.4e448015.gif

正在上传…重新上传取消

主从复制,读写分离,80%的情况下都是在进行读操作,减缓服务器压力,架构中经常使用,最低配一主二从

环境配置

配从库不配主库,从库配置:

slaveof   //主库ip 主库端口 # 配置主从
Info replication //查看信息

[root@192 opt]# cd /usr/local/bin
[root@192 bin]# redis-server myredis/redis.conf 
[root@192 bin]# redis-cli -p 6379
127.0.0.1:6379> info replication  //查看当前库的信息
# Replication
role:master    //角色  master
connected_slaves:0  //没有从机
master_failover_state:no-failover
master_replid:6d420c0c72fd5a96dfb3d1d258e06bed3792a810
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379> 
  1. 每次与 master 断开之后,都需要重新连接,除非你配置进 redis.conf 文件!

修改配置文件!

准备工作:我们配置主从复制,至少需要三个,一主二从!配置三个客户端!

1、拷贝多个redis.conf 文件

uploading.4e448015.gif

正在上传…重新上传取消

2、指定端口 6379,依次类推

3、开启daemonize yes

4、Pid文件名字 pidfile /var/run/redis_6379.pid , 依次类推

uploading.4e448015.gif

正在上传…重新上传取消

5、Log文件名字 logfile "6379.log" , 依次类推

uploading.4e448015.gif

正在上传…重新上传取消

6、Dump.rdb 名字 dbfilename dump6379.rdb , 依次类推

uploading.4e448015.gif

正在上传…重新上传取消

上面都配置完毕后,3个服务通过3个不同的配置文件开启,我们的准备环境就OK 了!

//启动
redis-server myredis/redis79.conf

创建一个新的窗口测试

uploading.4e448015.gif

正在上传…重新上传取消

一主二从

一主二仆

1、环境初始化 开启master salve1 salve2

默认三个都是Master 主节点

//步骤
redis-cli -p 6381
127.0.0.1:6381> ping
PONG
127.0.0.1:6381> info replication

uploading.4e448015.gif

正在上传…重新上传取消

2、配置为一个Master 两个Slave

uploading.4e448015.gif

正在上传…重新上传取消

127.0.0.1:6380> slaveof 127.0.0.1 6379 //认本机下的6379为主机
OK
127.0.0.1:6380> info replication
    
//再去主机查看
127.0.0.1:6379> info replication
# Replication
role:master  
connected_slaves:1  //有了一个从机
slave0:ip=127.0.0.1,port=6380,state=online,offset=182,lag=0

 //真实的主从配置应该在配置文件中配置,这样的话是永久的,现在用的命令所以时暂时的

设置永远的从机一启动就是从机

uploading.4e448015.gif

正在上传…重新上传取消

3、在主机设置值,在从机都可以取到!从机不能写值!

uploading.4e448015.gif

正在上传…重新上传取消

层层链路

uploading.4e448015.gif

正在上传…重新上传取消

uploading.4e448015.gif

正在上传…重新上传取消

测试:6379 设置值以后 6380 和 6381 都可以获取到!OK!

谋朝篡位

一主二从的情况下,如果主机断了,从机可以使用命令 SLAVEOF NO ONE 将自己改为主机!这个时 候其余的从机链接到这个节点。对一个从属服务器执行命令 SLAVEOF NO ONE 将使得这个从属服务器 关闭复制功能,并从从属服务器转变回主服务器,原来同步所得的数据集不会被丢弃。

uploading.4e448015.gif

正在上传…重新上传取消

流程

主从复制过程大体可以分为三个阶段:

  • 建立连接阶段(准备阶段),slave主动去连master

  • 数据同步阶段,第一次连接时将master的数据同步到slave中

  • 命令传播阶段(反复同步),master的数据变化要同步到slave

    uploading.4e448015.gif

    正在上传…重新上传取消

指令有四种发送方式:

uploading.4e448015.gif

正在上传…重新上传取消

阶段一:建立连接

  • 建立slave到master的连接,使master能够识别slave,并保存slave端口号

  • 流程图如下所示

    uploading.4e448015.gif

    正在上传…重新上传取消

    此时状态:slave保存master的地址与端口,master保存slave的端口,二者可以通信

    阶段二:数据同步

    • 在slave初次连接master后,master会自动的复制所有数据到slave;将slave的数据库状态更新成master当前的数据库状态

    • 整个过程分为两大块:全量复制和增量复制(部分复制)

    • 流程图如下所示

      uploading.4e448015.gif

      正在上传…重新上传取消

此时状态:slave拥有master端全部数据,master保存slave当前数据同步的位置 (之后会讲);总体完成了数据克隆

阶段三:命令传播 当master数据库状态被修改后,导致主从服务器数据库状态不一致,此时需要让主从数据同步到一致的状态,同步的动作称为命令传播

master将接收到的数据变更命令发送给slave,slave接收命令后执行命令 增量复制的三个核心要素

服务器的运行id (run id)

① 概念:服务器运行ID是每一台服务器每次运行的身份识别码,一台服务器多次运行可以生成多个运行id

② 例如:fdc9ff13b9bbaab28db42b3d50f852bb5e3fcdce

③ 作用:运行id被用于在服务器间进行传输时识别身份;如果想两次操作均对同一台服务器进行,每次操作必须携带相同的运行id,用于对方识别。一旦master的运行id发生变化,会导致所有slave进行全量复制操作

④ 实现方式:运行id在每台服务器启动时自动生成的,master在首次连接slave时,会将自己的运行ID发送给slave;slave保存此ID,通过info server命令,可以查看节点的运行id

主服务器的复制积压缓冲区

注意:第一个slave连接时会创建复制缓冲区,之后的slave使用的是同一个复制缓冲区

① 概念:复制缓冲区,又名复制积压缓冲区,是一个先进先出的队列,用于存储服务器执行过的命令,每次传播命令,master都会将待传播的命令记录下来,并存储在复制缓冲区

② 特点:复制缓冲区默认数据存储空间大小是1M;当入队元素的数量大于队列长度时,最先入队的元素会被弹出,而新元素会被放入队列

③ 作用:用于保存master收到的所有指令(仅保存数据变更的指令,例如set,select)

④ 数据来源:当master接收到客户端的指令时,除了执行指令,还会将该指令存储到缓冲区中

主从服务器的复制偏移量

① 概念:一个数字,描述复制缓冲区中的指令字节位置

② 分类:

i. master复制偏移量:记录发送给所有slave的指令字节对应的位置

ii. slave复制偏移量:记录slave接收master发送过来的指令字节对应的位置

③ 作用:同步信息,比对master与slave的差异,当slave断线后,恢复数据使用

④ 数据来源:

master端:发送一次记录一次

slave端:接收一次记录一次

⑤ 工作原理:

i. 通过offset(复制偏移量)区分不同的slave当前数据传播的差异

ii. master记录已发送的信息对应的offset

iii. slave记录已接收的信息对应的offset

uploading.4e448015.gif

正在上传…重新上传取消

————————————————

复制原理

Slave 启动成功连接到 master 后会发送一个sync命令

Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行 完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。

全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。

增量复制:Master 继续将新的所有收集到的修改命令依次传给slave,完成同步

但是只要是重新连接master,一次完全同步(全量复制)将被自动执行

哨兵模式

概述:

主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工 干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑 哨兵模式。Redis从2.8开始正式提供了Sentinel(哨兵) 架构来解决这个问题。 谋朝篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。 哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独 立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例

uploading.4e448015.gif

正在上传…重新上传取消

这里的哨兵有两个作用

(1)通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。

(2)当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通 知其他的从服务器,修改配置文件,让它们切换主机。

然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。 各个哨兵之间还会进行监控,这样就形成了多哨兵模式。

uploading.4e448015.gif

正在上传…重新上传取消

假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认 为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一 定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。 切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为 客观下线

配置测试

1、调整结构,6379带着80、81

2、自定义的 /myredis 目录下新建 sentinel.conf 文件,名字千万不要错

3、配置哨兵,填写内容

sentinel monitor 被监控主机名字 127.0.0.1 6379 1

上面最后一个数字1,表示主机挂掉后slave投票看让谁接替成为主机,得票数多少 后成为主机

4、启动哨兵

Redis-sentinel /myredis/sentinel.conf

上述目录依照各自的实际情况配置,可能目录不同

5、正常主从演示

6、原有的Master 挂了

7、投票新选

8、重新主从继续开工,info replication 查查看

9、问题:如果之前的master 重启回来,会不会双master 冲突? 之前的回来只能做小弟了

哨兵模式的优缺点

优点:

  1. 哨兵集群模式是基于主从模式的,所有主从的优点,哨兵模式同样具有。

  2. 主从可以切换,故障可以转移,系统可用性更好。

  3. 哨兵模式是主从模式的升级,系统更健壮,可用性更高。

缺点:

1. Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。
1. 实现哨兵模式的配置也不简单,甚至可以说有些繁琐

缓存穿透和雪崩

Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一 些问题。其中,最要害的问题,就是数据的一致性问题,从严格意义上讲,这个问题无解。如果对数据 的一致性要求很高,那么就不能使用缓存。 另外的一些典型问题就是,缓存穿透、缓存雪崩和缓存击穿。目前,业界也都有比较流行的解决方案。

uploading.4e448015.gif

正在上传…重新上传取消

缓存穿透

(查不到导致)

概念:

缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于 是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是 都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。

解决方案:

布隆过滤器

布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;

uploading.4e448015.gif

正在上传…重新上传取消

缓存空对象

当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数 据将会从缓存中获取,保护了后端数据源;

uploading.4e448015.gif

正在上传…重新上传取消

但是这种方法会存在两个问题:

1、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多 的空值的键;

2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于 需要保持一致性的业务会有影响。

缓存击穿

(量太大,缓存过期,在空档期请求券砸在mysql服务器)

概述:

这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中 对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一 个屏障上凿开了一个洞。

当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访 问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。

例如微博宕机(集中在一个热点不停请求,假如设置60秒过期,60.1秒恢复了,但是在这0.1秒的时间内,巨大的冲击量的请求全部砸在mysql服务器,直接宕机)

解决方案

设置热点数据永不过期

从缓存层面来看,没有设置过期时间,所以不会出现热点 key 过期后产生的问题。

加互斥锁

分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布 式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。

uploading.4e448015.gif

正在上传…重新上传取消

缓存雪崩

概念:

缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis宕机!!!

产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商 品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都 过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波 峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。

uploading.4e448015.gif

正在上传…重新上传取消

其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然 形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就 是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知 的,很有可能瞬间就把数据库压垮。

举个栗子:双十一抢购,会停掉一些服务,保证主要服务可用,比如当天退款会告诉你繁忙明天再来

解决方案

redis高可用

这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续 工作,其实就是搭建的集群。(异地多活)

限流降级

这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对 某个key只允许一个线程查询数据和写缓存,其他线程等待。

数据预热

数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数 据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

Jedis

Jedis是Redis官方推荐的Java连接开发工具。要在Java开发中使用好Redis中间件,必须对Jedis熟悉才能 写成漂亮的代码

测试联通

1、新建一个普通的Maven项目

2、导入redis的依赖!

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>

3、编写测试代码

package com.jiayu;

import redis.clients.jedis.Jedis;

public class TestPing {
    public static void main(String[] args) {
        //1.  new Jedis 对象
        Jedis jedis = new Jedis("127.0.0.1",6379);
        //jedis 所有命令就是我们之前学的所有命令
        System.out.println(jedis.ping());  //测试是否联通
    }
}

4、启动redis服务

5、启动测试,结果

uploading.4e448015.gif

正在上传…重新上传取消

常用API

基本操作

对key操作的命令

package com.jiayu;

import redis.clients.jedis.Jedis;

import java.util.Set;

public class TestKey {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        System.out.println("清空数据:"+jedis.flushDB());
        System.out.println("判断某个键是否存在:"+jedis.exists("username"));
        System.out.println("新增<'username','jiayu'>的键值对:"+jedis.set("username", "jiayu"));
        System.out.println("新增<'password','123456'>的键值 对:"+jedis.set("password", "123456"));
        System.out.print("系统中所有的键如下:");
        Set<String> keys = jedis.keys("*");
        System.out.println(keys);
        System.out.println("删除键password:"+jedis.del("password"));
        System.out.println("判断键password是否存在:"+jedis.exists("password"));
        System.out.println("查看键username所存储的值的类型:"+jedis.type("username"));
        System.out.println("随机返回key空间的一个:"+jedis.randomKey());
        System.out.println("重命名key:"+jedis.rename("username","name"));
        System.out.println("取出改后的name:"+jedis.get("name"));
        System.out.println("按索引查询库:"+jedis.select(0));
        System.out.println("删除当前选择数据库中的所有key:"+jedis.flushDB());
        System.out.println("返回当前数据库中key的数目:"+jedis.dbSize());
        System.out.println("删除所有数据库中的所有key:"+jedis.flushAll());
    }
}

结果:

uploading.4e448015.gif

正在上传…重新上传取消

对String操作的命令

package com.jiayu;

import redis.clients.jedis.Jedis;

import java.util.concurrent.TimeUnit;

public class TestString {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();
        System.out.println("===========增加数据===========");
        System.out.println(jedis.set("key1","value1"));
        System.out.println(jedis.set("key2","value2"));
        System.out.println(jedis.set("key3", "value3"));
        System.out.println("删除键key2:"+jedis.del("key2"));
        System.out.println("获取键key2:"+jedis.get("key2"));
        System.out.println("修改key1:"+jedis.set("key1", "value1Changed"));
        System.out.println("获取key1的值:"+jedis.get("key1"));
        System.out.println("在key3后面加入值:"+jedis.append("key3", "End"));
        System.out.println("key3的值:"+jedis.get("key3"));
        System.out.println("增加多个键值对:"+jedis.mset("key01","value01","key02","value02","key03","value03"));
        System.out.println("获取多个键值对:"+jedis.mget("key01","key02","key03"));
        System.out.println("获取多个键值 对:"+jedis.mget("key01","key02","key03","key04"));
        System.out.println("删除多个键值对:"+jedis.del("key01","key02"));
        System.out.println("获取多个键值 对:"+jedis.mget("key01","key02","key03"));


        jedis.flushDB();
        System.out.println("===========新增键值对防止覆盖原先值==============");
        System.out.println(jedis.setnx("key1", "value1"));
        System.out.println(jedis.setnx("key2", "value2"));
        System.out.println(jedis.setnx("key2", "value2-new"));
        System.out.println(jedis.get("key1"));
        System.out.println(jedis.get("key2"));


        System.out.println("===========新增键值对并设置有效时间=============");
        System.out.println(jedis.setex("key3", 2, "value3"));
        System.out.println(jedis.get("key3"));
        try {
            //因为目前Java没有提供一种安全直接的方法来直接停止某个线程!!!没有任何java语言方面的需求要求一个被中断的程序应该终止。中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断
            //暂停线程的操作,对于简单的阻塞状态(可响应中断),通过抛出InterruptedException异常的方式
            TimeUnit.SECONDS.sleep(3);  //
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(jedis.get("key3"));

        System.out.println("===========获取原值,更新为新值==========");
        System.out.println(jedis.getSet("key2", "key2GetSet"));
        System.out.println(jedis.get("key2"));
        System.out.println("获得key2的值的字串:"+jedis.getrange("key2", 2, 4));

    }
}

结果:

uploading.4e448015.gif

正在上传…重新上传取消

对List操作命令

package com.jiayu;

import redis.clients.jedis.Jedis;

public class TestList {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();
        System.out.println("===========添加一个list===========");
        jedis.lpush("collections", "ArrayList", "Vector", "Stack",
                "HashMap", "WeakHashMap", "LinkedHashMap");
        jedis.lpush("collections", "HashSet");
        jedis.lpush("collections", "TreeSet");
        jedis.lpush("collections", "TreeMap");
        System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));//-1代表倒数第一个元素,-2代表倒数第二个元素,end为-1表示查询全部
        System.out.println("collections区间0-3的元 素:"+jedis.lrange("collections",0,3));
        System.out.println("===============================");
        // 删除列表指定的值 ,第二个参数为删除的个数(有重复时),后add进去的值先被删,类似于出栈
        System.out.println("删除指定元素个数:"+jedis.lrem("collections", 2, "HashMap"));
        System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));
        System.out.println("删除下表0-3区间之外的元 素:"+jedis.ltrim("collections", 0, 3));
        System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));
        System.out.println("collections列表出栈(左 端):"+jedis.lpop("collections"));
        System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));
        System.out.println("collections添加元素,从列表右端,与lpush相对 应:"+jedis.rpush("collections", "EnumMap"));
        System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));
        System.out.println("collections列表出栈(右 端):"+jedis.rpop("collections"));
        System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));
        System.out.println("修改collections指定下标1的内 容:"+jedis.lset("collections", 1, "LinkedArrayList"));
        System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));
        System.out.println("===============================");
        System.out.println("collections的长度:"+jedis.llen("collections"));
        System.out.println("获取collections下标为2的元 素:"+jedis.lindex("collections", 2));
        System.out.println("===============================");
        jedis.lpush("sortedList", "3","6","2","0","7","4");
        System.out.println("sortedList排序前:"+jedis.lrange("sortedList", 0, -1));
        System.out.println(jedis.sort("sortedList"));
        System.out.println("sortedList排序后:"+jedis.lrange("sortedList", 0, -1));


    }
}

结果:

uploading.4e448015.gif

正在上传…重新上传取消

对Set的操作命令

package com.jiayu;

import redis.clients.jedis.Jedis;

public class TestSet {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();
        System.out.println("============向集合中添加元素(不重复) ============");
        System.out.println(jedis.sadd("eleSet", "e1","e2","e4","e3","e0","e8","e7","e5"));
        System.out.println(jedis.sadd("eleSet", "e6"));
        System.out.println(jedis.sadd("eleSet", "e6"));
        System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
        System.out.println("删除一个元素e0:"+jedis.srem("eleSet", "e0"));
        System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
        System.out.println("删除两个元素e7和e6:"+jedis.srem("eleSet", "e7","e6"));
        System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
        System.out.println("随机的移除集合中的一个元素:"+jedis.spop("eleSet"));
        System.out.println("随机的移除集合中的一个元素:"+jedis.spop("eleSet"));
        System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
        System.out.println("eleSet中包含元素的个数:"+jedis.scard("eleSet"));
        System.out.println("e3是否在eleSet中:"+jedis.sismember("eleSet", "e3"));
        System.out.println("e1是否在eleSet中:"+jedis.sismember("eleSet", "e1"));
        System.out.println("e1是否在eleSet中:"+jedis.sismember("eleSet", "e5"));
        System.out.println("=================================");
        System.out.println(jedis.sadd("eleSet1", "e1","e2","e4","e3","e0","e8","e7","e5"));
        System.out.println(jedis.sadd("eleSet2", "e1","e2","e4","e3","e0","e8"));
        System.out.println("将eleSet1中删除e1并存入eleSet3 中:"+jedis.smove("eleSet1", "eleSet3", "e1"));//移到集合元素
        System.out.println("将eleSet1中删除e2并存入eleSet3 中:"+jedis.smove("eleSet1", "eleSet3", "e2"));
        System.out.println("eleSet1中的元素:"+jedis.smembers("eleSet1"));
        System.out.println("eleSet3中的元素:"+jedis.smembers("eleSet3"));
        System.out.println("============集合运算=================");
        System.out.println("eleSet1中的元素:"+jedis.smembers("eleSet1"));
        System.out.println("eleSet2中的元素:"+jedis.smembers("eleSet2"));
        System.out.println("eleSet1和eleSet2的交 集:"+jedis.sinter("eleSet1","eleSet2"));
        System.out.println("eleSet1和eleSet2的并 集:"+jedis.sunion("eleSet1","eleSet2"));
        System.out.println("eleSet1和eleSet2的差 集:"+jedis.sdiff("eleSet1","eleSet2"));//eleSet1中有,eleSet2中没有
        jedis.sinterstore("eleSet4","eleSet1","eleSet2");//求交集并将交集保存到dstkey的集合
        System.out.println("eleSet4中的元素:"+jedis.smembers("eleSet4"));
    }
}

结果:

uploading.4e448015.gif

正在上传…重新上传取消

对Hash的操作命令

package com.jiayu;

import redis.clients.jedis.Jedis;

import java.util.HashMap;
import java.util.Map;

public class TestHash {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();
        Map<String,String> map = new HashMap<>();
        map.put("key1","value1");
        map.put("key2","value2");
        map.put("key3","value3");
        map.put("key4","value4");
        //添加名称为hash(key)的hash元素
        jedis.hmset("hash",map);
        //向名称为hash的hash中添加key为key5,value为value5元素
        jedis.hset("hash", "key5", "value5");
        System.out.println("散列hash的所有键值对 为:"+jedis.hgetAll("hash"));//return Map<String,String>
        System.out.println("散列hash的所有键为:"+jedis.hkeys("hash"));//returnSet<String>
        System.out.println("散列hash的所有值为:"+jedis.hvals("hash"));//returnList<String>
        System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加 key6:"+jedis.hincrBy("hash", "key6", 6));
        System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
        System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加 key6:"+jedis.hincrBy("hash", "key6", 3));
        System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
        System.out.println("删除一个或者多个键值对:"+jedis.hdel("hash", "key2"));
        System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
        System.out.println("散列hash中键值对的个数:"+jedis.hlen("hash"));
        System.out.println("判断hash中是否存在 key2:"+jedis.hexists("hash","key2"));
        System.out.println("判断hash中是否存在 key3:"+jedis.hexists("hash","key3"));
        System.out.println("获取hash中的值:"+jedis.hmget("hash","key3"));
        System.out.println("获取hash中的 值:"+jedis.hmget("hash","key3","key4"));

    }
}

结果:

uploading.4e448015.gif

正在上传…重新上传取消

Spring Boot 整合 Redis

Spring Data Redis 操作 Redis。

1、创建 Maven 工程。

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.1.5.RELEASE</version>
</parent>

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <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>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
  </dependency>
</dependencies>

2、创建实体类,实现序列化接口(Serializable),否则无法存入 Redis 数据库。

package com.laotou.entity;

        import lombok.Data;

        import java.io.Serializable;
        import java.util.Date;
@Data
public class Student implements Serializable {
    private Integer id;
    private  String name;
    private Double score;
    private Date birthday;
}

3、创建控制器。

package com.laotou.controller;

import com.laotou.entity.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;


@RestController
public class StudentController {
    //不用配RedisTemplate实例,直接注入Autowiredx
    @Autowired
    private RedisTemplate redisTemplate;
    @PostMapping("/set")
    //json数据转java对象
    public void set(@RequestBody Student student){
        redisTemplate.opsForValue().set("student",student);
    }
    @GetMapping("/get/{key}")
    public Student get(@PathVariable("key") String key){
        return (Student) redisTemplate.opsForValue().get(key);
    }
}

4、创建配置文件 application.yml

输入spring.redis.databases,选择如图所示

uploading.4e448015.gif

正在上传…重新上传取消

spring:
  redis:
    database: 0  
    host: localhost
    port: 6379

5、创建启动类

package com.laotou;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}

用postman测试,传json格式数据

uploading.4e448015.gif

正在上传…重新上传取消

成功样图:

uploading.4e448015.gif

正在上传…重新上传取消

redis增删改查

1.可以去redis中找存入的数据

uploading.4e448015.gif

正在上传…重新上传取消

当输入get student会发现并没有,因为redis存入数据是key会自动添加前缀

keys *student找到key字段名再查找数据

uploading.4e448015.gif

正在上传…重新上传取消

get 查到的名字 ,查到数据会被序列化

uploading.4e448015.gif

正在上传…重新上传取消

2.redis中取数据,自动反序列化拿出数据

uploading.4e448015.gif

正在上传…重新上传取消

uploading.4e448015.gif

正在上传…重新上传取消

3.通过key删除redis中的数据

uploading.4e448015.gif

正在上传…重新上传取消

uploading.4e448015.gif

正在上传…重新上传取消

注意 false 是代表成功

Redis 5 种数据类型整合boot实操

字符串

@GetMapping("/string")
public String stringTest(){
    redisTemplate.opsForValue().set("str","Hello World");
    String str = (String) redisTemplate.opsForValue().get("str");
    return str;
}

列表

    @GetMapping("/list")
    public List<String> listTest(){
        ListOperations<String,String> listOperations =  redisTemplate.opsForList();
        //左存入数据   rightPush右存入数据
        listOperations.leftPush("list","Hello");
        listOperations.leftPush("list","World");
        listOperations.leftPush("list","Java");
        //起始下标数据和结尾下标数据
        List<String> list = listOperations.range("list",0,2);
        return list;

集合

    @GetMapping("/set")
    public Set<String> setTest(){
        SetOperations<String,String> setOperations =  redisTemplate.opsForSet();
        setOperations.add("set","Hello");
        //集合数据唯一,不会重复
        setOperations.add("set","Hello");
        setOperations.add("set","World");
        setOperations.add("set","World");
        setOperations.add("set","Java");
        setOperations.add("set","Java");
        //members查看存进去几个数据
        Set<String> set = setOperations.members("set");
        return set;
    }

有序集合

   @GetMapping("/zset")
    public Set<String> zsetTest(){
        //zset有序集合
        ZSetOperations<String,String> zSetOperations = redisTemplate.opsForZSet();
        //v1对数据进行排序
        zSetOperations.add("zset","Hello",1);
        zSetOperations.add("zset","World",2);
        zSetOperations.add("zset","Java",3);
        Set<String> set = zSetOperations.range("zset",0,2);
        return set;
    }

哈希

HashMap key value

HashOperations key hashkey value

key 是每一组数据的 ID,hashkey 和 value 是一组完整的 HashMap 数据,通过 key 来区分不同的 HashMap。

{

key:value //正常情况

key:{ //key里面是一个哈希

key:value //哈希里面又是一个key:value

}

}

 @GetMapping("/hash")
    public void hashTest(){
        HashMap hashMap1 = new HashMap();
        hashMap1.put(key1,value1);
        HashMap hashMap2 = new HashMap();
        hashMap2.put(key2,value2);
        HashMap hashMap3 = new HashMap();
        hashMap3.put(key3,value3);
        //三个泛型 key,hashkey,value
        HashOperations<String,String,String> hashOperations = redisTemplate.opsForHash();
        hashOperations.put(hashMap1,key1,value1);
        hashOperations.put(hashMap2,key2,value2);
        hashOperations.put(hashMap3,key3,value3);
  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot整合Redis,可以通过以下步骤: 步骤1:添加依赖 在pom.xml文件中添加Redis相关的依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> ``` 步骤2:配置Redis连接信息 在application.properties(或application.yml)文件中配置Redis连接信息,包括主机、端口、密码等。例如: ```properties spring.redis.host=localhost spring.redis.port=6379 spring.redis.password= ``` 步骤3:创建RedisTemplate Bean 在配置类中创建RedisTemplate Bean,用于操作Redis数据。可以使用默认的JedisConnectionFactory,也可以自定义配置。以下是一个简单的示例: ```java @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return template; } } ``` 步骤4:使用RedisTemplate操作Redis数据 在需要使用Redis的地方,注入RedisTemplate即可进行数据操作。以下是一个简单的示例: ```java @Autowired private RedisTemplate<String, Object> redisTemplate; public void set(String key, Object value) { redisTemplate.opsForValue().set(key, value); } public Object get(String key) { return redisTemplate.opsForValue().get(key); } ``` 这样,你就可以在Spring Boot中使用Redis了。当然,还有更多高级的用法,比如使用缓存注解、发布与订阅等,你可以根据自己的需求进行扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值