Redis

缓存(Cache)

​ Cache是高速缓冲存储器一种特殊的存储器子系统,就是将系统经常试用到的的数据或对象放入内存中,这样做的目的一是可以快速调用,不必在重复创建新的实例对象,减少系统开销,提高系统效率。

为什么使用缓存

​ 在项目中使用缓存主要是从性能并发两方面。

​ 性能:当我们在项目中碰到程序执行需要很久,并且结果变动不频繁的sql,我们可以将结果放入缓存。这样在后面的请求直接取缓存中的结果,使得系统响应速度提升。

​ 并发:在大并发的情况下,所有请求直接访问数据库,数据库会出现链接异常,这时就需要使用缓存做一个缓冲操作,当请求进来以后先查询缓存,如缓存中存在结果则直接返回,当缓存中 无结果再请求数据库,减小数据库压力。

Redis_第1张图片

什么是NoSQL数据库?

​ NoSQL是非关系型数据库,NoSQL = Not Only SQL。

​ 关系型数据库采用的结构化的数据,NoSQL大多采用的是键值对的方式存储数据。

​ 在处理非结构化/半结构化的大数据时;在水平方向上进行扩展时;随时应对动态增加的数据项时可以优先考虑使用NoSQL数据库。

​ 在考虑数据库的成熟度;支持;分析和商业智能;管理及专业性等问题时,应优先考虑关系型数据库。

常见的Nosql产品

Memcached

优势:Memcached可以利用多核优势,单实例吞吐量极高,可以达到几十万QPS(取决于key、value的字节大小以及服务器硬件性能,日常环境中QPS高峰大约在4-6w左右)。适用于最大程度扛量。
支持直接配置为session handle。

局限性:只支持简单的key-value数据结构,不像redis可以支持丰富的数据类型

​ 无法进行持久化,数据不能备份,只能用于缓存使用,重启后数据全部丢失

​ 无法进行数据同步,不能将mc中的数据转移到其他mc实例中

mongoDB :

​ mongoDB 是一种文档性的数据库。

mongoDB 存放json格式数据。

适合场景:事件记录、内容管理或者博客平台,比如评论系统。

Redis

优点:

​ 支持多种数据结构,如 string(字符串)、 list(双向链表)、dict(hash表)、set(集合)、zset(排序set)、hyperloglog(基数估算)

​ 支持持久化操作,可以进行aof及rdb数据持久化到磁盘,从而进行数据备份或数据恢复等操作,较好的防止数据丢失的手段。

​ 支持通过Replication进行数据复制,通过master-slave机制,可以实时进行数据的同步复制,支持多级复制和增量复制,master-slave机制是Redis进行HA的重要手段

​ 单线程请求,所有命令串行执行,并发情况下不需要考虑数据一致性问题

​ 支持pub/sub消息订阅机制,可以用来进行消息订阅与通知。

​ 支持简单的事务需求,但业界使用场景很少,并不成熟。

Redis为什么快?

​ 1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);

​ 2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;

​ 3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

​ 4、使用多路I/O复用模型,非阻塞IO(对id模型的理解?);

​ 5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;

redis介绍

​ redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

​ Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。 [1]

​ Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。

谁在使用redis

  • Twitter
  • GitHub
  • Weibo
  • Pinterest
  • Snapchat
  • Craigslist
  • Digg
  • StackOverflow
  • Flickr

Redis五大数据类型及操作命令

Redis key-value java
String String,String Map
List String,String… Map
Hash String,String Map>
Set String,String… Map
Zset String,String… Map

String

  1. String是Redis最基本的数据类型,你可以理解成Memcaheed一模一样的类型,一个key对应一个value.
  2. Sting类型是二进制安全的,Redis的String可以包含任意数据类型.比如jpg图片和序列化的对象
  3. String类型是Redis最基本的数据类型,一个Redis字符串的value最多可以是512M
//查询对应键值
get 

//添加键值对
set  

//将给定的value添加到原值的末尾(在员value后追加字符串)
append  

//获得指定key的length(长度)
strlen 

//当key不存在时添加键值对
setnx  

//将key中存储的数字+1
//只能对数字值操作,如果为空,则新增+1
incr 

//将key中存储的数字-1
//只能对数字值操作,如果为空,则新增-1
decr 

//将key中存储的数字值增减,自定义步长
incrby/decrby  <步长>

原子性:

所谓原子操作是指不会被线程调度机制 打断的操作;这种操作一旦开始,就一 直运行到结束,中间不会有任何 context switch (切换到另一个线程)。

(1) 在单线程中, 能够在单条指令中完成的操作都可以认为是" 原子操作", 因为中断只能发生于指令之间。

(2)在多线程中,不能被其它进程(线程)打断的操作就叫原子操作。

Redis单命令的原子性主要得益于Redis的单线程

//同时设置一个或多个key value
mset     ....

//同时获取一个或多个value
mget   ....

// 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。
msetnx         ..... 

// 获得值的范围,类似java中的substring
getrange   <起始位置(下标))>  <结束位置(下标))>

//用  覆写 所储存的字符串值,从<起 始位置>开始 (相当于replace替换)
setrange  <起始位置(下标))> 

//设置键值的同时设置过期时间
setex  <过期时间> 

//设置新值的同时获得旧值
getset   

List

➢ 单键多值

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

➢ 它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操 作中间的节点性能会较差。

Redis_第2张图片

//从左边/右边插入一个或多个值
lpush    ...

//从左边/右边吐出一个值
//值在键在,值关键亡
lpop/rpop 

//从key1列表右边吐出一个值,插入到key2左边
rpoplpush  

//按照索引下边获得元素(从左到右)
lindex  

//获得列表长度
llen 

//从列表左边删除n个value
lrem   

//在某个值之间/之后插入value
linsert  before/after  

//截取指定范围的值后再赋值给key
ltrim   

//修改(设置)指定下标上的值
lset   

Set

➢Redis set对外提供的功能与list类似是一个列表的功能, 特殊之处在于set是可以自动排重的,当你需要存储一个 列表数据,又不希望出现重复数据时,set是一个很好的 选择,并且set提供了判断某个成员是否在一个set集合内 的重要接口,这个也是list所不能提供的。

➢Redis的Set是string类型的无序集合。

它底层其实是一 个value为null的hash表,所以添加,删除,查找的复杂 度都是O(1)。

//将一个或多个元素添加到key
sadd  

//取出key中所有元素
smembers 

//判断key集合中是否包含 有返回1 没有返回0
sismember  

//返回该集合的元素个数
scard 

//删除集合中某一个元素
srem  

//随机从集合中突出一个或多个元素(不写count默认吐出一个元素)
spop  

//随机从集合中取出n个值,但是不会从集合中删除
srandmember  

//返回多个集合合并后元素p4
sunion  ...

//返回多个集合的并集元素
sinter  ...

//返回多个集合的差集元素
sdiff  ...

Hash

➢ Redis hash 是一个键值对集合。

➢ Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。

➢ 类似Java里面的Map

//给集合中的键赋值
hset   

//从集合中的取出
hget   

//批量设置hash的值
hmset     ...

//查看哈希表 key 中,给定域 field 是否存在。
hexists  

//取出哈希表中所有key的键
hkeys 

//取出哈希表中或有key的value
hvals 

//当哈希表中key的field不存在,在创建field键并赋值
hsetnx   

//为哈希表中的key的field的值设置增加increment
hincrby   

//取出哈希表中所有key的键值
hgetall 

//删除哈希表中key的一个或多个filed
hdel  

//获取哈希表中key的field的个数
hlen 

Zset(sorted set)

Redis有序集合zset与普通集合set非常相似,是 一个没有重复元素的字符串集合。不同之处是有序集合的成员都关联了一个评分(score) ,这个评分 (score)被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复的 。 因为元素是有序的, 所以你也可以很快的根据评分 (score)或者次序(position)来获取一个范围的元 素。访问有序集合的中间元素也是非常快的,因此你能够 使用有序集合作为一个没有重复成员的智能列表。

//向有序集合添加一个或多个成员
zadd   

//获取有序集合的成员数
zcard 

//获取有序集合的指定区间分数的成员数
zcount   

//有序集合中想指定成员的分数加increment
zincrby   

//通过索引获取有序集合指定区间的成员
zrange   

//通过索引获取有序集合指定区间的成员及分数
zrange    withscores

//通过分数返回有序集合指定区间的成员(如不包含min/max只需添加"(",如[zrangebyscores  ( (])
zrangebyscores   

//通过分数返回有序集合指定区间的成员并截取指定数据
zrangebyscores    limit  

//移除有序集合中的一个或多个成员
zrem    ...

//获取有序集合中指定成员的下标
zrank  

//获取有序集合中指定成员的分数
zscore  

//移除有序集合中给定的排名区间的所有成员
ZREMRANGEBYRANK zset 0 3

//移除有序集合中给定的分数区间的所有成员
zremrangeByScore key min max

//返回有序集中指定区间内的成员,通过索引,分数从高到低
ZREVRANGE key start stop [WITHSCORES]

//返回有序集中指定分数区间内的成员,分数从高到低排序
ZREVRANGEBYSCORE key max min [WITHSCORES]

//计算给定的一个或多个有序集的并集,并存储在新的 key 中
ZUNIONSTORE destination numkeys key [key ...]

Redis的三种特殊类型

地理空间(geospatial)

GEOADD:

将指定的地理空间位置(纬度、经度、名称)添加到指定的key中。这些数据将会存储到sorted set这样的目的是为了方便使用GEORADIUS或者GEORADIUSBYMEMBER命令对数据进行半径查询等操作。

该命令以采用标准格式的参数x,y,所以经度必须在纬度之前。这些坐标的限制是可以被编入索引的,区域面积可以很接近极点但是不能索引。具体的限制,由EPSG:900913 / EPSG:3785 / OSGEO:41001 规定如下:

  • 有效的经度从-180度到180度。
  • 有效的纬度从-85.05112878度到85.05112878度。

当坐标位置超出上述指定范围时,该命令将会返回一个错误。

//添加地理信息
geoadd key  纬度 经度  名称

 GEOADD city  116.408  39.904  beijing
 GEOADD city   121.445 31.213  shanghai
 GEOADD city   114.498 38.042  shijiazhuang

GEOPOS

key里返回所有给定位置元素的位置(经度和纬度)。

给定一个sorted set表示的空间索引,密集使用 geoadd 命令,它以获得指定成员的坐标往往是有益的。当空间索引填充通过 geoadd 的坐标转换成一个52位Geohash,所以返回的坐标可能不完全以添加元素的,但小的错误可能会出台。

因为 GEOPOS 命令接受可变数量的位置元素作为输入, 所以即使用户只给定了一个位置元素, 命令也会返回数组回复。

//获取指定城市的经纬度
 GEOPOS city beijing

GEODIST:

返回两个给定位置之间的距离。

如果两个位置之间的其中一个不存在, 那么命令返回空值。

指定单位的参数 unit 必须是以下单位的其中一个:

  • m 表示单位为米。
  • km 表示单位为千米。
  • mi 表示单位为英里。
  • ft 表示单位为英尺。

如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位。

GEODIST 命令在计算距离时会假设地球为完美的球形, 在极限情况下, 这一假设最大会造成 0.5% 的误差。

//获取两个位置之间的距离  单位默认米
GEODIST city beijing shijiazhuang  
// 单位km
GEODIST city beijing shijiazhuang km

GEORADIUS:

以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。

范围可以使用以下其中一个单位:

  • m 表示单位为米。
  • km 表示单位为千米。
  • mi 表示单位为英里。
  • ft 表示单位为英尺。

在给定以下可选项时, 命令会返回额外的信息:

  • WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。
  • WITHCOORD: 将位置元素的经度和维度也一并返回。
  • WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。

命令默认返回未排序的位置元素。 通过以下两个参数, 用户可以指定被返回位置元素的排序方式:

  • ASC: 根据中心的位置, 按照从近到远的方式返回位置元素。
  • DESC: 根据中心的位置, 按照从远到近的方式返回位置元素。

在默认情况下, GEORADIUS 命令会返回所有匹配的位置元素。 虽然用户可以使用 COUNT 选项去获取前 N 个匹配元素, 但是因为命令在内部可能会需要对所有被匹配的元素进行处理, 所以在对一个非常大的区域进行搜索时, 即使只使用 COUNT 选项去获取少量元素, 命令的执行速度也可能会非常慢。 但是从另一方面来说, 使用 COUNT 选项去减少需要返回的元素数量, 对于减少带宽来说仍然是非常有用的。

//查找半径300km范围内的城市   city->key   116->纬度前缀   39经度前缀  半径   单位
GEORADIUS city 116 39 300 km
//限制查询的数量
 GEORADIUS city 116 39 300 km  count 2
 //显示到中心的距离
 GEORADIUS city 116 39 300 km  withdist
 //显示中心半径范围内他人的定位信息
 GEORADIUS city 116 39 300 km  withcoord

GEORADIUSBYMEMBER

这个命令和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点

指定成员的位置被用作查询的中心。

//查找半径200km范围内的城市
GEORADIUSBYMEMBER city beijing 200 km

GEOHASH

返回一个或多个位置元素的 Geohash 表示。

通常使用表示位置的元素使用不同的技术,使用Geohash位置52点整数编码。由于编码和解码过程中所使用的初始最小和最大坐标不同,编码的编码也不同于标准。此命令返回一个标准的Geohash,在维基百科和geohash.org网站都有相关描述

Geohash字符串属性:

该命令将返回11个字符的Geohash字符串,所以没有精度Geohash,损失相比,使用内部52位表示。返回的geohashes具有以下特性:

  1. 他们可以缩短从右边的字符。它将失去精度,但仍将指向同一地区。

  2. 它可以在 geohash.org 网站使用,网址 http://geohash.org/。查询例子:http://geohash.org/sqdtr74hyu0.

  3. 与类似的前缀字符串是附近,但相反的是不正确的,这是可能的,用不同的前缀字符串附近。

    //将二位的经纬度转换为一维的字符串,两个字符串越接近则越近!
    GEOHASH city beijing shijiazhuang
    

Redis_第3张图片

GEO的底层实现其实就是Zset,我们可以使用Zset的命令来操作GEO

在这里插入图片描述

Redis_第4张图片

Hyperloglog

基数统计的算法:

什么是基数?

​ 基数(cardinal number)在数学上,是集合论中刻画任意集合大小的一个概念。两个能够建立元素间一一对应的集合称为互相对等集合。例如3个人的集合和3匹马的集合可以建立一一对应,是两个对等的集合。

简介

​ Redis 2.8.9 版本就更新了 Hyperloglog 数据结构!

使用场景:

网页的 UV (一个人访问一个网站多次,但是还是算作一个人!)

​ 传统的方式, set 保存用户的id,然后就可以统计 set 中的元素数量作为标准判断!

这个方式如果保存大量的用户id,就会比较麻烦!我们的目的是为了计数,而不是保存用户id;

0.81% 错误率! 统计UV任务,可以忽略不计的!

优点:占用的内存是固定,2^64 不同的元素的技术,只需要废 12KB内存!如果要从内存角度来比较的话 Hyperloglog 首选!

127 .0.0.1:6379> PFadd mykey a b c d e f g h i j # 创建第一组元素 mykey
(integer) 1
127 .0.0.1:6379> PFCOUNT mykey  # 统计 mykey 元素的基数数量
(integer) 10
127 .0.0.1:6379> PFadd mykey2 i j z x c v b n m # 创建第二组元素 mykey2
(integer) 1
127 .0.0.1:6379> PFCOUNT mykey2
(integer) 9
127 .0.0.1:6379> PFMERGE mykey3 mykey mykey2  # 合并两组 mykey mykey2 => mykey3 并集
OK
127 .0.0.1:6379> PFCOUNT mykey3  # 看并集的数量!
(integer) 15

Bitmaps(位存储):

Bitmap 位图,数据结构! 都是操作二进制位来进行记录,就只有 0 和 1 两个状态!

使用场景:

​ 统计用户信息,活跃,不活跃! 登录 、 未登录! 打卡, 365 打卡! 两个状态的,都可以使用

使用bitmap记录一周打卡情况:

Redis_第5张图片

查看某一天是否打卡:

在这里插入图片描述

1为打卡成功,0为未打卡!

统计打卡天数:

Redis_第6张图片

Redis发布订阅

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

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

​ 订阅/发布消息图:

Redis_第7张图片

​ 第一个:消息发送者, 第二个:频道 第三个:消息订阅者!

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

Redis_第8张图片

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

使用场景:

​ 构建即使通信应用,比如网络聊天室,实时广播和实时提醒等。

//订阅者订阅    redisChat为订阅的频道
SUBSCRIBE redisChat

Redis_第10张图片

打开一个新的窗口,连接redis

//发布者发布信息到频道  
PUBLISH redischat goodgoodstudydaydayip

Redis_第11张图片

Redis_第12张图片

redis发布订阅命令

//订阅一个或多个符合给定模式的频道。
PSUBSCRIBE pattern [pattern ...]

//查看订阅与发布系统状态。
PUBSUB subcommand [argument [argument ...]]

//将信息发送到指定的频道。
PUBLISH channel message

//退订所有给定模式的频道。
PUNSUBSCRIBE [pattern [pattern ...]]

//订阅给定的一个或多个频道的信息。
SUBSCRIBE channel [channel ...]

//指退订给定的频道
UNSUBSCRIBE [channel [channel ...]]

redis简单操作指令

//查看当前库的所有键
keys *

//判断某个键是否存在
 existe 
 
 //查看键的类型
 type 
 
 //删除键
 del 
 
 //为键值设置过期
 exprie  
 
 //查看键值还有多久过期  -1表示永不过期  -2表示已过期
 ttl 
 
 //查看当前库中key的数量
 DBSIZE

慎重使用命令

//清空当前库
flushdb

//清空所有库
flushall

keys *  

redis事务:

是什么?

​ 可以一次执行过个命令,本质是一组命令的集合,一个事务中的命令都会序列化,按顺序的串行化执行而不会被其他命令插入,不允许加塞

能干什么?

​ 一个队列中,一次性,顺序性,排他性的执行一系列命令

常用命令:

MULTI -->告诉 redis 服务器开启一个事务。注意,只是开启,而不是执行
EXEC -->告诉 redis 开始执行事务
DISCARD -->告诉 redis 取消事务
WATCH -->监视某一个键值对,它的作用是在事务执行之前如果监视的键值被修改,事务会被取消。

redis事务的几种状态

  • 正常执行 -->正常执行事务

Redis_第13张图片

  • 放弃事务 -->discard放弃事务

Redis_第14张图片

  • 全体连坐 -->编译时异常,要么全成功要么全失败

Redis_第15张图片

  • 冤头债主 —>运行时异常,出异常的失败,其他成功

    Redis_第16张图片

  • wacth监控 -->监控某个键值对,如果在事务执行之前被别人修改,事务会被打断

    • 正常执行

    Redis_第17张图片

    • 执行失败

    Redis_第18张图片

    • unwatch -->取消监控

事务的三阶段:

  • 开启事务:

    • 以MULTI开启一个事务
  • 入队

    • 将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务列队里面
  • 执行

    • 由EXEC命令触发事务

三个特性:

  • 单独的隔离操作:

    • 事务中的所有命令都会序列化,按顺序的执行,事务在执行过程中,不会被其他客户端发来的请求命令所打断
  • 没有隔离级别的概念:

    • 列队中的命令在没有被提交之前,都不会被实际的执行,因为事务在提交之前任何命令都不会被实际的执行,也就不存在"事务内的查询要看到事务里的更新,在事务外查询不能看到"这个让人万分头疼的问题
  • 不保证原子性

    • redis同一个事务中,如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

Redis持久化RDB和AOF

RDB(Redis DataBase):

RDB是将数据写入一个临时文件,持久化结束后,用这个临时文件替换上一个次持久化的文件,达到数据恢复.

Redis_第19张图片

优点:

  • RDB是一个非常紧凑的文件
  • RDB在RDB文件时父进程唯一需要做的就是fork一个子进程,接下来的工作全部由子进程来完成,父进程不在需要做其他IO操作,所以RDB持久化方式可以最大化Redis性能.
  • 与AOF相比,在恢复大的数据集的时候RDB方式会更快一些
  • 适合用来做数据备份,非常适用于灾难恢复

缺点:

  • 数据丢失风险大,一旦发生故障停机,可能会丢失数据
  • RDB需要经常fork子进程来保存数据集到硬盘上,当数据集比较大时,可能会导致Redis不能毫秒级响应客户端请求

在redis默认开启RDB存储方式.在redis.config配置如下:

#dbfilename:持久化数据存储在本地的文件
dbfilename dump.rdb (dump.rdb为存储的文件名,redis通过io流将内存中的数据,写入该文档)

#dir:持久化数据存储在本地的路径,如果是在/redis/redis-3.0.6/src下启动的redis-cli,则数据会存储在当前src目录下
dir ./  

#save时间,以下分别表示更改了1个key时间隔900s进行持久化存储;更改了10个key时间隔300s进行存储;更改10000个key时间隔60s进行存储。
save 900 1
save 300 10
save 60 10000

这里我们可以继续添加,也可以修改参数 比如 save 10 10000 10000个数据时间间隔10s就持久化

使用命令进行持久化存储:

save 在前台进行存储
bgsave  在后台进行存储

持久化过程:

​ 当满足save的条件时,比如更改了1个key,900s后会将数据写入临时文件,持久化完成后将临时文件替换旧的dump.rdb。(存储数据的节点是到触发时间时的的节点,也就是对Key有个计数)

使用RDB恢复数据:

​ 自动的持久化数据存储到dump.rdb后。实际只要重启redis服务即可完成(启动redis的server时会从dump.rdb中先同步数据),所以说当我们在关停redis服务的时候,只需要在/redis-3.2.12/src目录下查看是否存在dump.rdb,如果存在,则下次启动redis服务的时候,数据会由redis自动恢复。(如果不存在,那一定是配置文件 redis.conf出了错)

AOF(Append Only File):

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

Redis_第20张图片

优点:

  • AOF文件是一个只进行追加的日志文件
  • Redis可以在AOF文件体积过大时,自动在后台对AOF文件进行重写.
  • AOF文件有序的保证了所有对数据库执行的写入操作,这些写入的操作以Redis协议的格式保存,因此AOF文件的内容非常容易被人读懂.对文件进行分析也很轻松

缺点:

  • 对于相同的数据集来说,AOF文件的体积通常要大于RDB文件的体积
  • 根据所使用的的fsync策略,AOF的速度可能会慢于RDB

Redis修复AOF文件:

redis-check-aof --fix appendonly.aof

Redis默认关闭AOF, 开启方法,修改配置文件redis.conf:appendonly yes

其他相关配置项:

#AOF保存的文件名

appendfilename "appendonly.aof"  (将执行的指令记录在appendonly.aof文件中)

同步方式相关配置:

Appendfsync:
            Always:同步持久化,一旦插入数据立即同步到磁盘,保证了数据的完整性,但是速度慢,且浪费Redis性能,不推荐
            Everysec:出厂默认推荐,异步操作,每秒记录,如果一秒内宕机,有数据丢失
            no:不自动同步,性能最好,但是持久化没有保证

AOF重写:

概述:

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

原理:

​ AOF文件持续增长过大时,会fork出一条新的进程来将文件重写(也是先写临时文件最后在rename),遍历新进程的内存中数据,每条记录有一条set的语句.重写AOF文件的操作,没有读取旧的AOF文件,而是将内存中的数据库内容用命令的方式重新写了一个新的AOF文件,这点和快照有点类似.

命令:

命令:
 bgrewriteaof 

触发机制:

​ Redis会记录上一次重写时的AOF文件大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发,redis2.4以上版本,重写机制自动触发。

触发的相关redis.conf配置如下:

auto-aof-rewrite-percentage 100(当目前的AOF文件大小超过上一次重写文件大小的百分之几时进行重写,如果没有重启过,则以启动时的AOF文件大小为依据);
auto-aof-rewrite-min-size 64mb(允许重写的最小AOF文件大小);

AOF数据恢复:

重启redis服务,前提是配置文件必须设置了appendonly yes,然后会从appendfile的文件加载文件。反之是从RDB中加载数据的。

Redis内存维护策略

策略 描述
volatile-lru 从设置过期时间的数据集(server.db[i].expires)中挑选出最近最少使用的数据淘汰。没有设置过期时间的key不会被淘汰,这样就可以在增加内存空间的同时保证需要持久化的数据不会丢失。
volatile-ttl 除了淘汰机制采用LRU,策略基本上与volatile-lru相似,从设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰,ttl值越大越优先被淘汰。
volatile-random 从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。当内存达到限制无法写入非过期时间的数据集时,可以通过该淘汰策略在主键空间中随机移除某个key。
allkeys-lru 从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰,该策略要淘汰的key面向的是全体key集合,而非过期的key集合。
allkeys-random 从数据集(server.db[i].dict)中选择任意数据淘汰。
no-enviction 禁止驱逐数据,也就是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失,这也是系统默认的一种淘汰策略。

Redis集群配置

Redis主从复制同步的几种方式:

  • 一主二仆

    • 一个master两个slave
  • 薪火相传

    • A - B - C ,B既是C的 Master,又是A的 Slave 从节点,即上一个 Slave 是下一个 Slave 的 Master
  • 反客为主

    • master挂掉后,手动升级salve为master
  • 哨兵模式

    • master挂掉后,自动升级salve为master(反客为主)

是什么:

主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,slaver以读为主

能干什么:

  • 读写分离
  • 容灾恢复

怎么玩:

  • 配从(库)不配主(库)

  • 从库配置:slaveof 主库IP 主库端口号

    • 每次与master断开后,都需要重新连接,除非你配置进了redis.conf文件
    • info replication
  • 修改配置文件细节操作:(在一台电脑进行试验可如下配置)

    • 拷贝多个redis.conf
    • 开启 deamonize yes
    • Pid文件名字
    • 指定端口
    • Log文件名字
    • dump文件名字
  • 常用三招:

    • 一主二仆

Redis_第21张图片

      • 第一次从库切入点,是全量复制,之后是增量复制
      • 主库可以写,但是从库不可以写
      • 主机shutdown之后从库待命状态,主库回来后,主库新增记录从库可以继续复制
      • 从库shutdown之后,每次与master断开之后,都需要重新连接,除非你配置进了redis.conf文件
    • 薪火相传

Redis_第22张图片

      • 上一个的salve可以使下个salve的master,savle同样可以接受其他savles的连接和同步请求
      • 优点:该salve作为链条中下一个的master,可以有效的减轻master的写的压力.去中心化降低风险
      • 中途变更转向:会清除之前的数据,重新建立拷贝最新的
      • 缺点:一旦某个从机宕机,后边的从机都无法备份
    • 反客为主

      • 当一个master宕机后,后面的salve可以立即升为master(手动升级)

Redis 哨兵模式

Redis_第23张图片

哨兵模式概述:

  • 哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。

  • 作用:

    • 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
    • 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。

是什么:

反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库

怎么玩:

配置redis.conf文件

# 使得Redis服务器可以跨网络访问
bind 0.0.0.0
# 设置密码
requirepass "123456"
# 指定主服务器,注意:有关slaveof的配置只是配置从服务器,主服务器不需要配置(在从服务器中配置此项)
slaveof 192.168.11.128 6379
# 主服务器密码,注意:有关slaveof的配置只是配置从服务器,主服务器不需要配置(在从服务器中配置此项)
masterauth 123456

在Redis安装目录下又sentinel.conf,哨兵配置文件

Redis_第24张图片

复制进行修改:

linux 复制命令为 cp

此命令为在当前目录下复制一份,命名为sentinel1.conf

在这里插入图片描述

# 禁止保护模式
protected-mode no
# 配置监听的主服务器,这里sentinel monitor代表监控,mymaster代表服务器的名称,可以自定义,192.168.11.128代表监控的主服务器,6379代表端口,2代表只有两个或两个以上的哨兵认为主服务器不可用的时候,才会进行failover操作。
sentinel monitor mymaster 192.168.11.128 6379 2
# sentinel author-pass定义服务的密码,mymaster是服务名称,123456是Redis服务器密码
# sentinel auth-pass  
sentinel auth-pass mymaster 123456

在创建自己所需的哨兵数量后通过命令来启动哨兵:

此处因我是在安装时文件位置做了修改,执行文件为bin文件夹,配置文件为etc文件夹

./redis-sentinel ../etc/sentinel6379.conf 

Redis_第25张图片

在此处启动了两个从库,一个主库,出现此画面说明哨兵启动成功

Redis_第26张图片

当哨兵在检测到主库宕机后,会通过选举机制在从库中选择一个作为主库,其他从库将主库改为选举新产生的主库,当已经宕机的主库恢复上线后会自动变为从库

如何保证Redis中的数据都是热点数据

热点数据:redis热点数据指在redis数据库中访问量特别大的数据。

怎么保证都是rediis中热点数据?

​ 为键设置过期时间,过期,自动删除该键

redis缓存穿透

概念:

​ 请求一个不存在的key,缓存没有命中,于是向持久层查询数据库发现数据库也没有于是此次查询失败,当请求量特别大时,在缓存层都没有命中,都去请求数据库(相当于无缓存),这就给数据库造成很大压力,这就相当于缓存穿透。

解决方案:

  • 布隆过滤器:采用布隆过滤器,使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接被过滤;

Redis_第27张图片

  • 拦截器,id<=0的直接拦截。

  • 从cache和db都取不到,可以将key-value写为key-null,设置较短过期时间,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击。

redis缓存击穿

概念:

​ 请求一个存在的key,在缓存过期的一刻有大量请求,此时这些请求都会击穿到mysql,导致mysql请求量大,压力骤增。

解决方案:

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

redis缓存雪崩

概念:

​ 缓存雪崩值缓存集中过期,或缓存宕机,此时大量请求直接请求到数据库,数据库压力骤增无法承受宕机。

Redis_第28张图片

解决方案:

  • 设置缓存随机过期

  • 数据预热:

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

  • 限流降级:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对
    某个key只允许一个线程查询数据和写缓存,其他线程等待。

Redis_第29张图片

redis配置文件

# daemonize no 默认情况下,redis不是在后台运行的,如果需要在后台运行,把该项的值更改为yes
daemonize yes

# 当redis在后台运行的时候,Redis默认会把pid文件放在/var/run/redis.pid,你可以配置到其他地址。
# 当运行多个redis服务时,需要指定不同的pid文件和端口
pidfile /var/run/redis.pid

# 指定redis运行的端口,默认是6379
port 6379

# 指定redis只接收来自于该IP地址的请求,如果不进行设置,那么将处理所有请求,
# 在生产环境中最好设置该项
# bind 127.0.0.1

# 设置客户端连接时的超时时间,单位为秒。当客户端在这段时间内没有发出任何指令,那么关闭该连接
# 0是关闭此设置
timeout 0

# 指定日志记录级别
# Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
# debug 记录很多信息,用于开发和测试
# varbose 有用的信息,不像debug会记录那么多
# notice 普通的verbose,常用于生产环境
# warning 只有非常重要或者严重的信息会记录到日志
loglevel debug

# 配置log文件地址
# 默认值为stdout,标准输出,若后台模式会输出到/dev/null
#logfile stdout
logfile /var/log/redis/redis.log

# 可用数据库数
# 默认值为16,默认数据库为0,数据库范围在0-(database-1)之间
databases 16

# save 900 1 900秒内至少有1个key被改变
# save 300 10 300秒内至少有300个key被改变
# save 60 10000 60秒内至少有10000个key被改变

save 900 1
save 300 10
save 60 10000

# 存储至本地数据库时(持久化到rdb文件)是否压缩数据,默认为yes
rdbcompression yes

#跳过检查rdb文件
rdbchecksum yes

# 本地持久化数据库文件名,默认值为dump.rdb
dbfilename dump.rdb

# 工作目录
# 数据库镜像备份的文件放置的路径。
# 这里的路径跟文件名要分开配置是因为redis在进行备份时,先会将当前数据库的状态写入到一个临时文件中,等备份完成时,
# 再把该该临时文件替换为上面所指定的文件,而这里的临时文件和上面所配置的备份文件都会放在这个指定的路径当中。
# AOF文件也会存放在这个目录下面
# 注意这里必须制定一个目录而不是文件
dir ./

# 当master服务设置了密码保护时(用requirepass制定的密码)
# slav服务连接master的密码
# masterauth 

 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,
# 如果设置 maxclients 0,表示不作限制。
# 当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
# maxclients 10000


​ 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key
​ # Redis同时也会移除空的list对象
​ # 当此方法处理后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作
​ # 注意:Redis新的vm机制,会把Key存放内存,Value会存放在swap区
​ # maxmemory的设置比较适合于把redis当作于类似memcached的缓存来使用,而不适合当做一个真实的DB。
​ # 当把Redis当做一个真实的数据库使用的时候,内存使用将是一个很大的开销
​ # maxmemory

​ # 当内存达到最大值的时候Redis会选择删除哪些数据?有五种方式可供选择
​ # volatile-lru -> 利用LRU算法移除设置过过期时间的key (LRU:最近使用 Least Recently Used )
​ # allkeys-lru -> 利用LRU算法移除任何key
​ # volatile-random -> 移除设置过过期时间的随机key
​ # allkeys->random -> remove a random key, any key
​ # volatile-ttl -> 移除即将过期的key(minor TTL)
​ # noeviction -> 不移除任何可以,只是返回一个写错误
​ # 注意:对于上面的策略,如果没有合适的key可以移除,当写的时候Redis会返回一个错误
​ # maxmemory-policy volatile-lru

​ #设置密码
​ requirepass foobared

#redis持久化方式aof
# AOF文件名称 (默认: “appendonly.aof”)
# appendfilename appendonly.aof

no: 不进行同步,系统去操作 . Faster.
always: always表示每次有写操作都进行同步. Slow, Safest.
everysec: 表示对写操作进行累积,每秒同步一次. Compromise.
appendfsync everysec

jedis================================================================

使用Jedis操作redis

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

  • 创建maven项目

      
          redis.clients
          jedis
          3.3.0
      
    
  • 通过jedis链接redis

     //创建jedis客户端  jedis的命令就是之前学习的redis的命令
    Jedis jedis = new Jedis("192.168.138.128",6379);
    //如redis设置了密码,则需要设置链接密码
    jedis.auth("123456");
    //测试是否链接成功
    System.out.println(jedis.ping());
    

    出现PONG则表示链接成功

Redis_第30张图片

常用API

 //创建jedis客户端
Jedis jedis = new Jedis("192.168.138.128",6379);
//如redis设置了密码,则需要设置链接密码
jedis.auth("123456");
//测试是否链接成功
System.out.println(jedis.ping());
//设置键值对
jedis.set("曹培镇","曹培镇");
jedis.set("test","test");
jedis.sadd("set","set1","set2");
jedis.lpush("list","list1","list2");
HashMap<String, String> map = new HashMap<String, String>();
map.put("a","a");
jedis.hset("hash",map);
jedis.zadd("zset",2d,"zset2");
//判断某个键是否存在
System.out.println("判断某个键是否存在:"+jedis.exists("曹培镇"));
//查看当前库下的所有键值对
System.out.println("查看当前库下的所有键值对:"+jedis.keys("*"));
//删除键值对
System.out.println("删除键值对:"+jedis.del("崔蔚华"));
//查看键存储值的类型
System.out.println("查看键存储值的类型:"+jedis.type(jedis.randomKey()));
//随机返回一个key
System.out.println("随机返回key:"+jedis.randomKey());
//重命名key
jedis.rename("test","rename");
//取出重命名后的键值
System.out.println("取出重命名后的键的值:"+jedis.get("rename"));
//获取当前库下的键值数量
System.out.println("获取当前库下的键值数量:"+jedis.dbSize());
//清空当前库
System.out.println("清空当前库:"+jedis.flushDB());
//清空所有库
System.out.println("清空所有库:"+jedis.flushAll());

//关闭jedis链接
jedis.close();

String常用API:

Jedis jedis = new Jedis("192.168.138.128", 6379);
    jedis.auth("123456");
    jedis.flushDB();
    //增加数据
    jedis.set("cpz","曹培镇");
    jedis.set("zsc","周帅丞");
    jedis.set("ms","马帅");
    jedis.set("scq","索纯强");
    jedis.set("zxz","郑学镇");
    jedis.set("number","1");
    //删除键zxz
    System.out.println("删除键zxz:"+jedis.del("zxz"));
    //获取键cpz
    System.out.println("获取键cpz:"+jedis.get("cpz"));
    //修改键的值
    System.out.println("修改zsc的值:"+jedis.set("zsc", "周帅丞大帅比"));
    System.out.println("获取zsc的值:"+jedis.get("zsc"));
    //在ms值后追加字符串
    System.out.println("在ms值后追加字符串:"+jedis.append("ms","真的帅"));
    System.out.println("获取ms的值:"+jedis.get("ms"));
    //增加多个键值对
    System.out.println("增加多个键值对:"+jedis.mset("914","技术宅","915","大佬","916","神仙"));
    //获取多个键值对
    System.out.println("获取多个键值对:"+jedis.mget("914","915","916"));
    //删除多个键值对
    System.out.println("删除多个键值对:"+jedis.del("915","916"));

    //查看当前库下的所有键值对
    System.out.println("查看当前库下的所有键值对:"+jedis.keys("*"));
    System.out.println("新增键值防止对已有值的覆盖");
    System.out.println(jedis.setnx("scq","如无值则新增"));
    System.out.println("获取scq的值:"+jedis.get("scq"));
    //新增键值并设置过期时间
    jedis.setex("time",10,"10秒后过期");
    System.out.println("获取time:"+jedis.get("time"));
    try {
        Thread.sleep(10000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("获取time:"+jedis.get("time"));

    //获取旧值并更新为新值
    System.out.println("获取cpz的旧值并设置新值:"+jedis.getSet("cpz","奥里给"));
    System.out.println("获取cpz的值:"+jedis.get("cpz"));


    //设置key的值自增自减  只能对数字操作
    System.out.println();
    System.out.println("number的值自增1: "+jedis.incr("number"));
    System.out.println("获取number的值:"+jedis.get("number"));
    System.out.println("number的值自增5: "+jedis.incrBy("number",5));
    System.out.println("获取number的值:"+jedis.get("number"));
    System.out.println("number的值减1:"+jedis.decr("number"));
    System.out.println("获取number的值:"+jedis.get("number"));
    System.out.println("number的值减5:"+jedis.decrBy("number",5));
    System.out.println("获取number的值:"+jedis.get("number"));
    System.out.println();

    //截取字符串的值
    System.out.println("获取cpz的值长度:"+jedis.strlen("cpz"));
    System.out.println("截取cpz值:"+jedis.getrange("cpz",0,5));

    //替换字符串的值
    System.out.println("截取并替换cpz的值:"+jedis.setrange("cpz",6,"给给"));
    System.out.println("获取cpz的值:"+jedis.get("cpz"));

List常用API

 Jedis jedis = new Jedis("192.168.138.128", 6379);
        jedis.auth("123456");
        jedis.flushDB();

        //增加list  lpush代表在集合的左边插入  rpush在集合右边插入
        jedis.lpush("list1","cpz","zsc","zxz","ms","scq");
        jedis.rpush("list2","cpz","zsc","zxz","ms","scq");
        jedis.lpush("list3","cpz","zsc","zxz","ms","scq");
        jedis.lpush("list4","cpz","cpz","cpz","ms","scq");

        //查看list内容                开始下标   结束下标,-1代表到结尾
        System.out.println("list1内容:"+jedis.lrange("list1", 0, -1));
        System.out.println("list2内容:"+jedis.lrange("list2", 0, -1));

        //获取集合区间的元素
        System.out.println("获取list3集合区间的元素:"+jedis.lrange("list3",3,-1));
        System.out.println("list3内容:"+jedis.lrange("list3", 0, -1));

        //删除区间的内容
        System.out.println("删除list3集合中下标3到结尾外的所有元素:"+jedis.ltrim("list3", 3, -1));
        System.out.println("list3内容:"+jedis.lrange("list3", 0, -1));

        //删除指定元素个数
        System.out.println("删除list4集合中值为cpz的元素其中两个:"+jedis.lrem("list4",2,"cpz"));
        System.out.println("list4内容:"+jedis.lrange("list4", 0, -1));

        //从集合的左侧吐出一个元素  (出栈)
        System.out.println("list1内容:"+jedis.lrange("list1", 0, -1));
        System.out.println("从list1集合左侧吐出一个元素:"+jedis.lpop("list1"));
        //从集合右侧吐出一个元素
        System.out.println("从list1集合右侧吐出一个元素:"+jedis.rpop("list1"));
        System.out.println("list1内容:"+jedis.lrange("list1", 0, -1));

        //获取指定下标的值
        System.out.println("list2内容:"+jedis.lrange("list2", 0, -1));
        System.out.println("获取list2集合下标为2的值:"+jedis.lindex("list2",2));

        //修改指定下标的值
        System.out.println("修改list2集合下标为3的值:"+jedis.lset("list2",2,"zxzChange"));
        System.out.println("list2内容:"+jedis.lrange("list2", 0, -1));

        //获取集合的长度
        System.out.println("获取list1集合的长度:"+jedis.llen("list1"));

Set常用API

Jedis jedis = new Jedis("192.168.138.128", 6379);
jedis.auth("123456");
jedis.flushDB();

//新增
jedis.sadd("set","zsc","zsc","zsc","cpz","ms","zxz","scq");

//取出所有元素
System.out.println("取出集合中所有元素:"+jedis.smembers("set"));

//判断集合中是否保存指定的值
System.out.println("查看集合中是否存在指定的值:"+jedis.sismember("set","aa"));

//返回集合中的元素个数
System.out.println("返回集合中元素的个数:"+jedis.scard("set"));

//随机从集合中取出多个值,但不会删除
System.out.println("随机从集合中取出1个值:"+jedis.srandmember("set"));
System.out.println("随机从集合中取出多个值:"+jedis.srandmember("set",3));
System.out.println("取出集合中所有元素:"+jedis.smembers("set"));

//删除集合中指定的元素
System.out.println("删除集合中指定的元素:"+jedis.srem("set","zsc"));
System.out.println("取出集合中所有元素:"+jedis.smembers("set"));

//随机从集合中吐出一个 或元素
System.out.println("随机从集合中吐出一元素:"+jedis.spop("set"));
System.out.println("随机从集合中吐出一元素:"+jedis.spop("set",2));
System.out.println("取出集合中所有元素:"+jedis.smembers("set"));

//返回多个集合合并后元素
jedis.sadd("set2","set1","set2","set3");
System.out.println("set中元素:"+jedis.smembers("set"));
System.out.println("set2中元素:"+jedis.smembers("set2"));
System.out.println("返回多个集合合并后元素:"+jedis.sunion("set","set2"));

//返回多个集合的并集元素
System.out.println("返回多个集合的并集元素:"+jedis.sinter("set","set2"));

//返回多个集合的差集元素
System.out.println("返回多个集合的差集元素:"+jedis.sdiff("set","set2"));

Zset常用API

Jedis jedis = new Jedis("192.168.138.128", 6379);
jedis.auth("123456");
jedis.flushDB();

//新增
jedis.zadd("zset",1d,"1");
//多个
HashMap<String, Double> map = new HashMap<>();
map.put("2",2d);
map.put("3",3d);
jedis.zadd("zset",map);

//获取有序集合的成员数
System.out.println("zset集合的成员数:"+jedis.zcard("zset"));

//获取有序集合的指定分数区间的成员数
System.out.println("获取有序集合的指定分数区间的成员数:"+jedis.zcount("zset",2d,3d));

//集合中指定分数区间的值加1
System.out.println("获取集合的值及分数:"+jedis.zrangeByScoreWithScores("zset",0,3));
System.out.println("指定元素的分数+1:"+jedis.zincrby("zset",1,"2"));
System.out.println("获取集合的值及分数:"+jedis.zrangeByScoreWithScores("zset",0,3));

//通过分数区间获取值
System.out.println("根据分数区间获取值:"+jedis.zrangeByScore("zset",2d,3d));
System.out.println("根据分数区间获取值并截取:"+ jedis.zrangeByScoreWithScores("zset",2,3,0,1));

//通过索引区间获取值
System.out.println("通过索引区间获取值:"+jedis.zrange("zset",0,-1));

//获取集合中指定成员的下标
System.out.println("获取集合中指定成员的下标:"+jedis.zrank("zset","1"));

//获取集合中指定成员的分数:
System.out.println("获取集合中指定成员的分数:"+jedis.zscore("zset","1"));

//移除集合中的一个或多个成员
System.out.println("移除集合中的一个或多个成员:"+jedis.zrem("zset","1","2","3"));

//移除集合中指定区域的成员
System.out.println("移除集合中指定区域的成员:"+jedis.zremrangeByRank("zset",0,-1));

//移除集合中指定分数区域的成员
System.out.println("移除集合中指定分数区域的成员:"+jedis.zremrangeByScore("zset",2,3));

//将多个集合中的并集存储在新key中
jedis.zadd("zset2",1d,"zset222");
jedis.zadd("zset3",1d,"zset333");
System.out.println("将多个集合中的并集存储在新key中:"+jedis.zunionstore("zset4", "zset3", "zset2"));
System.out.println("获取新集合中的值:"+jedis.zrange("zset4",0,-1));

Hash常用API

 Jedis jedis = new Jedis("192.168.138.128", 6379);
    jedis.auth("123456");
    jedis.flushDB();
    //新增
    jedis.hset("hash","h1","哈哈");
    HashMap<String, String> map = new HashMap<String, String>();
    map.put("h2","呵呵");
    map.put("h3","嘿嘿");
    jedis.hset("hash",map);

    //批量新增
    HashMap<String, String> maps = new HashMap<String, String>();
    maps.put("h1","曹培镇");
    maps.put("h2","周帅丞");
    maps.put("h3","马帅");
    jedis.hmset("hash",maps);

    //取数据
    System.out.println(jedis.hget("hash", "h2"));
    System.out.println(jedis.hget("hash", "h1"));
    System.out.println(jedis.hget("hash", "h3"));

    //批量取出数据
    System.out.println("批量取出数据Hash:"+jedis.hmget("hash", "h1", "h2", "h3"));

    //查看哈希表中所有key
    System.out.println("取出hash集合中所有key:"+jedis.hkeys("hash"));

    //获取hash表中所有key的value
    System.out.println("获取hash表所有key值:"+jedis.hvals("hash"));

    //查看hash表中key是否存在
    System.out.println("查看hash中key是否存在:"+jedis.hexists("hash","h3"));
    System.out.println("查看hash中key是否存在:"+jedis.hexists("hash","h4"));

    //当哈希表中key的field不存在,在创建field键并赋值
    System.out.println("当哈希表中key的field不存在,在创建field键并赋值:"+jedis.hsetnx("hash","h4","1"));
    System.out.println("当哈希表中key的field不存在,在创建field键并赋值:"+jedis.hsetnx("hash","h5","当h5不存在时创建!"));

    //为哈希表中的key的field的值设置增加increment
    System.out.println("为哈希表中的key的field的值设置增加increment:"+jedis.hincrBy("hash","h4",1));

    //取出hash中所有key的键值
    System.out.println("取出hash中所有的键值:"+jedis.hgetAll("hash"));

    //查看hash表中key中值的个数
    System.out.println("hash表中值的个数:"+jedis.hlen("hash"));

    //删除hash表中一个或多个key
    System.out.println("删除hash表中一个或多个ke:"+jedis.hdel("hash","h1","h2","h3","h4","h5"));
    System.out.println("取出hash中所有的键值:"+jedis.hgetAll("hash"));

SpringBoot整合Redis

在springboot2.x之间spring-boot-starter-data-redis底层使用的Jedis,2.x之后底层使用lettuce


    org.springframework.boot
    spring-boot-starter-data-redis

spring:
  redis:
    host: 127.0.0.1
    database: 0 
    password: 123456

可通过文件RedisProperties查看我们可配置信息:

@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {

	/**
	 * Database index used by the connection factory.
	 */
	private int database = 0;

	/**
	 * Connection URL. Overrides host, port, and password. User is ignored. Example:
	 * redis://user:[email protected]:6379
	 */
	private String url;

	/**
	 * Redis server host.
	 */
	private String host = "localhost";

	/**
	 * Login password of the redis server.
	 */
	private String password;

	/**
	 * Redis server port.
	 */
	private int port = 6379;

	/**
	 * Whether to enable SSL support.
	 */
	private boolean ssl;

	/**
	 * Connection timeout.
	 */
	private Duration timeout;

	/**
	 * Client name to be set on connections with CLIENT SETNAME.
	 */
	private String clientName;

	private Sentinel sentinel;

	private Cluster cluster;

 @Autowired
RedisTemplate redisTemplate;

redisTemplate.opsForSet();//操作set
redisTemplate.opsForValue();//操作字符串
redisTemplate.opsForHash();//操作hash
redisTemplate.opsForZSet();//操作有序zset
redisTemplate.opsForList();//操作list

配置redis序列化方式

package com.cwh.config;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
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;

import java.net.UnknownHostException;

/**
 * @program: redisBoot
 * @description:
 * @author: cuiweihua
 * @create: 2020-08-23 16:45
 */
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        //设置redis默认序列化方式为json
        template.setDefaultSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
       	//key序列化方式为是String序列化方式
        template.setKeySerializer(stringRedisSerializer);
        //hashkey序列化方式是String序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        return template;
    }

}

Redis工具类

package com.kuang.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public final class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // =============================common============================
    /**
     * 指定缓存失效时间
     * @param key  键
     * @param time 时间(秒)
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }


    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }


    // ============================String=============================

    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */

    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 普通缓存放入并设置时间
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */

    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 递增
     * @param key   键
     * @param delta 要增加几(大于0)
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }


    /**
     * 递减
     * @param key   键
     * @param delta 要减少几(小于0)
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }


    // ================================Map=================================

    /**
     * HashGet
     * @param key  键 不能为null
     * @param item 项 不能为null
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * HashSet 并设置时间
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }


    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }


    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }


    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }


    // ============================set=============================

    /**
     * 根据key获取Set中的所有值
     * @param key 键
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0)
                expire(key, time);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 获取set缓存的长度
     *
     * @param key 键
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */

    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    // ===============================list=================================

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 获取list缓存的长度
     *
     * @param key 键
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 将list放入缓存
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }


    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return
     */

    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */

    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }

    }

}

整合到数据,(搜索页的规格查询)

  • 将list放入缓存
    • @param key 键
    • @param value 值
    • @param time 时间(秒)
      */
      public boolean lSet(String key, Object value, long time) {
      try {
      redisTemplate.opsForList().rightPush(key, value);
      if (time > 0)
      expire(key, time);
      return true;
      } catch (Exception e) {
      e.printStackTrace();
      return false;
      }
}


/**
 * 将list放入缓存
 *
 * @param key   键
 * @param value 值
 * @return
 */
public boolean lSet(String key, List value) {
    try {
        redisTemplate.opsForList().rightPushAll(key, value);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }

}


/**
 * 将list放入缓存
 *
 * @param key   键
 * @param value 值
 * @param time  时间(秒)
 * @return
 */
public boolean lSet(String key, List value, long time) {
    try {
        redisTemplate.opsForList().rightPushAll(key, value);
        if (time > 0)
            expire(key, time);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}


/**
 * 根据索引修改list中的某条数据
 *
 * @param key   键
 * @param index 索引
 * @param value 值
 * @return
 */

public boolean lUpdateIndex(String key, long index, Object value) {
    try {
        redisTemplate.opsForList().set(key, index, value);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}


/**
 * 移除N个值为value
 *
 * @param key   键
 * @param count 移除多少个
 * @param value 值
 * @return 移除的个数
 */

public long lRemove(String key, long count, Object value) {
    try {
        Long remove = redisTemplate.opsForList().remove(key, count, value);
        return remove;
    } catch (Exception e) {
        e.printStackTrace();
        return 0;
    }

}
 
  

                            
                        
                    
                    
                    

你可能感兴趣的:(Redis,redis)