redis
注:内容整理自【狂神说】
Redis是一种开放源代码(BSD许可)的内存中数据结构存储,用作数据库,缓存和消息中间件。
一、快速上手
1、基层配置
官网下载安装
https://redis.io/download
上传到/opt/redis-6.0.7
解压后
$ wget http://download.redis.io/releases/redis-6.0.7.tar.gz
$ tar xzf redis-6.0.7.tar.gz
$ cd redis-6.0.7
$ make
启动测试
./redis-server /opt/redis-6.0.7/redis.conf
$ src/redis-cli
redis> set foo bar
OK
redis> get foo
"bar"
如果要实现redis从外部连接的目的需要满足的条件:
2、修改默认配置文件
redis默认是不支持后台启动,将配置文件里的不支持改为支持
/opt/redis-6.0.7/redis.conf
daemonize yes
关闭redis服务指令
shutdown
exit
3、通过自带的benchmark进行压测
./redis-benchmark -h localhost -p 6379 -c 100 -n 110000
====== PING_INLINE ======
110000 requests completed in 1.75 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
0.00% <= 0.6 milliseconds
1.16% <= 0.7 milliseconds
4.89% <= 0.8 milliseconds
12.22% <= 0.9 milliseconds
25.79% <= 1.0 milliseconds
40.54% <= 1.1 milliseconds
55.96% <= 1.2 milliseconds
71.00% <= 1.3 milliseconds
83.33% <= 1.4 milliseconds
90.59% <= 1.5 milliseconds
94.05% <= 1.6 milliseconds
96.15% <= 1.7 milliseconds
97.37% <= 1.8 milliseconds
98.22% <= 1.9 milliseconds
98.76% <= 2 milliseconds
100.00% <= 2 milliseconds
63037.25 requests per second
====== PING_BULK ======
110000 requests completed in 1.70 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
31.65% <= 1 milliseconds
98.98% <= 2 milliseconds
99.74% <= 3 milliseconds
99.81% <= 4 milliseconds
99.91% <= 5 milliseconds
99.95% <= 6 milliseconds
100.00% <= 6 milliseconds
64743.97 requests per second
====== SET ======
#110000条写入请求耗时1.72秒
110000 requests completed in 1.72 seconds
#此次测试采用了100个并发客户端进行测试
100 parallel clients
#每次写入3个字节
3 bytes payload
#单机执行的该项测试
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
27.39% <= 1 milliseconds
99.12% <= 2 milliseconds
99.99% <= 3 milliseconds
100.00% <= 3 milliseconds
64139.94 requests per second
二、redis的基础知识
1、redis默认的16个数据库
默认使用的是第0个
127.0.0.1:6379> select 3 #查看第四个数据库
OK
127.0.0.1:6379[3]> DBSIZE #查看当前数据库大小
(integer) 0
127.0.0.1:6379[3]>
127.0.0.1:6379[3]> set name beijing #设置一条数据,key 是name ,值是beijing
OK
127.0.0.1:6379[3]> keys * #查看当前库里的所有Key的值
1) "name"
127.0.0.1:6379[3]> flushdb #清除当前数据库
OK
127.0.0.1:6379[3]> FLUSHALL #清除所有数据库
OK
2、Redis为什么这么快
1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。
2、数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的;
3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、使用多路 I/O 复用模型,非阻塞 IO;
5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
三、redis的五大数据类型
注: 指令忘记了去官网查看。
1、(Redis-Key)String
127.0.0.1:6379> EXISTS name #判断名字为name的数据是否存在
(integer) 1
127.0.0.1:6379> EXISTS names
(integer) 0
127.0.0.1:6379> move name 1 #移除当前库里key为name的数据
(integer) 1
127.0.0.1:6379> set name spring
OK
127.0.0.1:6379> EXPIRE name 10 #设置key为name的数据的过期时间是10秒
(integer) 1
127.0.0.1:6379> ttl name #查看key为name数据的过期时间还剩多少秒
(integer) 0
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379>
127.0.0.1:6379> type name #查看当前key为name数据的key的数据类型
string
127.0.0.1:6379> APPEND name "boot" #追加字符串,如果不存在就是新建key
(integer) 10
127.0.0.1:6379> STRLEN name #获取字符串长度
(integer) 10
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> incr views #自增1
(integer) 1
127.0.0.1:6379> decr views #自减1
(integer) 0
127.0.0.1:6379> INCRBY views 10 #用一次指令增10
(integer) 10
127.0.0.1:6379> DECRBY views 5 #用一次指令减5
(integer) 5
127.0.0.1:6379> set key1 helloworld
OK
127.0.0.1:6379> GETRANGE key1 0 3 #截取0~3号元素字符
"hell"
127.0.0.1:6379> GETRANGE key1 0 -1 #查询所有字符串
"helloworld"
127.0.0.1:6379>
#替换
127.0.0.1:6379> SETRANGE key1 5 W #将第5号元素替换成W
(integer) 10
127.0.0.1:6379> get key1
"helloWorld"
127.0.0.1:6379>
# setex(set with expire) 设置过期时间
# setnx(set if not exist) 不存在时再设置(分布式锁里常用到)
127.0.0.1:6379> setex key1 30 "hello" #设置key1的值为hello,30秒后过期
OK
127.0.0.1:6379> ttl key1
(integer) 26
127.0.0.1:6379> setnx key2 "redis" #如果key2不存在,创建key2
(integer) 1
127.0.0.1:6379> setnx key2 "redis" #如果key2存在,创建key2失败
(integer) 0
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 #批量设置多个值
OK
127.0.0.1:6379> keys *
1) "k2"
2) "k1"
3) "k3"
127.0.0.1:6379> mget k1 k2 k3 #批量获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 #msetnx是一个原子性的操作,要么一起成功,要么一起失败
(integer) 0
127.0.0.1:6379>
#对象
#设置一个user对象,属性为name和age ,存放在redis里的key就是user:1:name 和user:1:age,值为一个json字符串,1代表第一号对象
127.0.0.1:6379> mset user:1:name spring user:1:age 3
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "spring"
2) "3"
#getset 先get再set
127.0.0.1:6379> getset db redis #如果存在db这个key,则改变原来的值,设置成redis
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongdb
"redis"
127.0.0.1:6379> get db
"mongdb"
2、List
redis里,list可以当成栈、队列、阻塞队列使用
所有的list的命令都是l开头的,且不区分大小写
127.0.0.1:6379> lpush list one #将一个或多个值插入列表头部【左】
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1 #查询结果和插入顺序是颠倒的
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LRANGE list 0 1
1) "three"
2) "two"
127.0.0.1:6379> RPUSH list four #将一个或多个值插入列表尾部【右】
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "four"
#LPOP RPOP 弹出列表元素
127.0.0.1:6379> lpop list #移除list的第一个元素
"three"
127.0.0.1:6379> rpop list #移除list的最后一个元素
"four"
#lindex
127.0.0.1:6379> lindex list 1 #通过下标获得list中的某个值
"one"
127.0.0.1:6379> llen list #获取列表长度
(integer) 2
#lrem 移除指定的值
127.0.0.1:6379> lpush list one two two tree
(integer) 4
127.0.0.1:6379> lrem list 1 one #移除列表中指定个数的value,精确匹配
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "tree"
2) "two"
3) "two"
127.0.0.1:6379> lrem list 2 two
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "tree"
127.0.0.1:6379>
#trim修剪 list被截断
127.0.0.1:6379> rpush list va1 va2 va3 va4
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "va1"
2) "va2"
3) "va3"
4) "va4"
127.0.0.1:6379> ltrim list 1 2 #通过下标截取指定长度的元素,当前的list被截取只剩下被截取的元素
OK
127.0.0.1:6379> lrange list 0 -1
1) "va2"
2) "va3"
#rpoplpush 移除列表的最后一个元素并且将其移动到新的列表中
127.0.0.1:6379> rpoplpush list myList
"va3"
127.0.0.1:6379> lrange list 0 -1
1) "va2"
127.0.0.1:6379> lrange myList 0 -1
1) "va3"
# lset 将列表中指定下标的值替换为另一个值,更新操作
127.0.0.1:6379> exists list
(integer) 0
127.0.0.1:6379> lset list 0 value1
(error) ERR no such key
127.0.0.1:6379> lpush list value1
(integer) 1
127.0.0.1:6379> lrange list 0 0
1) "value1"
127.0.0.1:6379> lset list 0 item
OK
127.0.0.1:6379> lrange list 0 0
1) "item"
#linsert 在某个元素前面或者后面添加一个指定的值
127.0.0.1:6379> rpush list world other
(integer) 2
127.0.0.1:6379> linsert list before "world" "hello" #往world前面插入hello
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "world"
3) "other"
127.0.0.1:6379> linsert list after "other" "man" #往other后面插入man
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "world"
3) "other"
4) "man"
小结:
消息队列!(Lpush Rpop),栈(Lpush Lpop)
3、Set
127.0.0.1:6379> sadd mySet hello spring cloud # set集合中添加元素
(integer) 3
127.0.0.1:6379> smembers mySet # 查看set集合中所有元素
1) "cloud"
2) "spring"
3) "hello"
127.0.0.1:6379> sismember mySet hello # 判断某个值在不在set集合里
(integer) 1
# 获取set集合中的内容元素个数
127.0.0.1:6379> scard mySet
(integer) 3
# srem 移除set集合中的指定元素
127.0.0.1:6379> srem mySet hello
(integer) 1
127.0.0.1:6379> smembers mySet
1) "cloud"
2) "spring"
# SRANDMEMBER 随机抽选出指定个数的元素
127.0.0.1:6379> SRANDMEMBER mySet
"cloud"
127.0.0.1:6379> SRANDMEMBER mySet
"spring"
127.0.0.1:6379> SRANDMEMBER mySet 2
1) "cloud"
2) "spring"
# spop 随机弹出一个元素
127.0.0.1:6379> spop mySet
"spring"
127.0.0.1:6379> smembers mySet
1) "cloud"
# 将一个指定的值移动到另一个set集合
127.0.0.1:6379> sadd mySet hello spring boot
(integer) 3
127.0.0.1:6379> sadd mySet1 cloud
(integer) 1
127.0.0.1:6379> smove mySet mySet1 "boot"
(integer) 1
127.0.0.1:6379> smembers mySet
1) "spring"
2) "hello"
127.0.0.1:6379> smembers mySet1
1) "cloud"
2) "boot"
# 数字集合类 已set1为参照
127.0.0.1:6379> sadd set1 a b c
(integer) 3
127.0.0.1:6379> sadd set2 c d e
(integer) 3
127.0.0.1:6379> SDIFF set1 set2 #差集
1) "a"
2) "b"
127.0.0.1:6379> SINTER set1 set2 #交集 共同好友
1) "c"
127.0.0.1:6379> SUNION set1 set2 #并集
1) "c"
2) "d"
3) "a"
4) "b"
5) "e"
4、Hash
和string类型没有本质区别,还是简单的key-value
127.0.0.1:6379> hset myHash field1 spring field2 boot field3 cloud
(integer) 3
127.0.0.1:6379> hget myHash field1 # 获取单个字段值
"spring"
127.0.0.1:6379> hmget myHash field1 field2 # 获取多个字段值
1) "spring"
2) "boot"
127.0.0.1:6379> hgetall myHash # 一次获取全部的值
1) "field1"
2) "spring"
3) "field2"
4) "boot"
5) "field3"
6) "cloud"
# hdel 删除hash指定的key字段,对应的value也就被删除了
127.0.0.1:6379> hdel myHash field1
(integer) 1
127.0.0.1:6379> hgetall myHash
1) "field2"
2) "boot"
3) "field3"
4) "cloud"
# hlen 获取字段长度
127.0.0.1:6379> hlen myHash
(integer) 2
# HEXISTS 判断hash中指定字段是否存在
127.0.0.1:6379> HEXISTS myHash field1
(integer) 0
127.0.0.1:6379> HKEYS myHash # 获取所有的field
1) "field2"
2) "field3"
127.0.0.1:6379> HVALS myHash # 获取所有的value
1) "boot"
2) "cloud"
127.0.0.1:6379> hset myHash field3 5 #指定增量
(integer) 1
127.0.0.1:6379> HINCRBY myHash field3 1
(integer) 6
127.0.0.1:6379> HINCRBY myHash field3 -1
(integer) 5
127.0.0.1:6379> HSETNX myHash field4 hello # 如果不存在就可以设置
(integer) 1
127.0.0.1:6379> HSETNX myHash field4 hello # 如果存在则不能设置
(integer) 0
小结:
hash 更适合对象的存储,string 更适合字符串的存储
5、Zset
127.0.0.1:6379> zadd salary 2500 zhangsan 5000 lisi 6000 xiaohong
(integer) 3
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf #从小到大显示全部的用户
1) "zhangsan"
2) "lisi"
3) "xiaohong"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores #获取全部的用户并附带成绩
1) "zhangsan"
2) "2500"
3) "lisi"
4) "5000"
5) "xiaohong"
6) "6000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 5000 withscores #升序获取工资小于5000的用户附带成绩
1) "zhangsan"
2) "2500"
3) "lisi"
4) "5000"
# zrem 移除集合中指定的元素
127.0.0.1:6379> zrem salary xiaohong
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "zhangsan"
2) "lisi"
127.0.0.1:6379> zcard salary # 获取集合的元素个数
(integer) 2
# ZCOUNT 获取指定区间的成员数量
127.0.0.1:6379> zadd myset 1 hello 2 world 3 spring
(integer) 3
127.0.0.1:6379> ZCOUNT myset 1 3
(integer) 3
127.0.0.1:6379> ZCOUNT myset 1 2
应用场景:
set 排序 、存储班级成绩表、工资表排序
普通消息:1、重要消息 2、带权重进行判断
排行版应用实现,获取Top N测试
四、redis的三种特殊数据类型(不全)
1、geospatial 地理位置
使用场景:朋友圈定位、附近的人、打车距离计算?
只有6个命令
1、geopos
注意:
(纬度,经度,名称)添加到指定键。
有效经度为-180至180度。
有效纬度为-85.05112878至85.05112878度。
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 shenzhen
(integer) 1
127.0.0.1:6379> geopos china:city beijing #获取指定城市的经度和纬度
2、GEODIST
两人之间的距离
单位:
m :米
km :千米
mi :英里
ft :英尺
#查看北京到上海的直线距离
127.0.0.1:6379> GEODIST china:city beijing shanghai km
"1067.3788"
3、GEORADIUS
以给定的经纬度为中心,找出某一半径的元素
我附近的人?获得指定数量的人:200
2、Hyperloglog
3、Bitmaps
五、redis的基本事务操作
Redis事务本质:一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行。
一次性、顺序性、排他性!执行一些列的命令。
Redis事务没有隔离级别的概念
所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行:exec
Redis单条命令是保持原子性的,但是事务不保证原子性!
redis的事务:
开启事务
命令入队列(…)
执行事务(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> exec #执行事务
注意:一旦执行事务,事务就没有了
中途放弃事务指令:事务中队列里 的命令都将不会被执行
127.0.0.1:6379> DISCARD
OK
小结:
事务队列总的一组命令中如果存在一条命令或者代码错误,执行命令时,所有指令都不会生效。
事务队列总的一组命令中如果存在一条运行时错误,执行命令时,除了这条错误命令,其他的都会正常执行,错误会被抛出。
六、Redis实现乐观锁
悲观锁:
很悲观,认为什么时候都会出问题,做什么操作前都加锁!费性能。
乐观锁:
很乐观,认为什么时候都不会出问题,所以不上锁!在更新数据的时候才去判断一下,在此期间是否有人修改过这个数据;获取version,更新的时候比较下version
redis通过watch监控实现乐观锁
正常监控状态
127.0.0.1:6379> set money 1000
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money #开启监控模式
OK
127.0.0.1:6379> MULTI #开启事务
OK
127.0.0.1:6379> DECRBY money 100 #减少100块钱
QUEUED
127.0.0.1:6379> incrby out 100 #增加100块钱
QUEUED
127.0.0.1:6379> exec
1) (integer) 900
2) (integer) 100
另起一个线程,在正常监控状态执行事务之前,修改掉money,会导致正常态的事务执行失败。需要重新开始监视、开启事务。
解锁命令: unwatch 放弃监视。
七、通过Jedis操作Redis
引入依赖
redis.clients
jedis
3.3.0
com.alibaba
fastjson
1.2.73
new一个最简单的maven项目,创建个测试类
package com.demo;
import redis.clients.jedis.Jedis;
public class TestString {
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.85.130",6379);
//方法都是三四节点的指令
System.out.println(jedis.ping());
jedis.close();
}
}
通过jedis操作事务
package com.demo;
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class TestString {
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.85.130",6379);
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","spring");
// 开启事务
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
// 开启监视模式
// jedis.watch("result");
try {
multi.set("user1",result);
multi.set("user2",result);
// 代码抛出异常事务,执行失败!
// int i = 1/0;
multi.exec();
}catch (Exception e){
// 放弃事务
multi.discard();
}finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close();
}
}
}
八、SpringBoot集成Redis
SpringBoot 操作数据:Spring-data jpa jdbc mongdb redis
注意:Springboot 2.x之后,原来使用jedis被替换成了lettuce
jedis:采用直连,多线程操作时不安全,用jedis pool连接池提升安全性。像BIO模式
lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全。减少线程数量,像NIO模式
1、导入依赖
org.springframework.boot
spring-boot-starter-data-redis
2、配置连接
spring:
redis:
host: 192.168.85.130
port: 6379
3、测试
package redis01.demo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class DemoApplicationTests {
@Autowired
RedisTemplate redisTemplate;
@Test
void contextLoads() {
/**
* opsForValue :操作字符串类似String
* opsForList :操作List
*opsForSet :操作set
*/
// 获取redis的连接对象
// RedisConnection redisconnection = (RedisConnection) redisTemplate.getConnectionFactory();
// redisconnection.flushDb();
redisTemplate.opsForValue().set("mykey","学习springboot整合redis");
System.out.println(redisTemplate.opsForValue().get("mykey"));
}
}
注意:redis对象都需要序列化!
默认的序列化方式是JDK序列化,我们平常都是使用Json来序列化。
九、定制redisTemplate和封装redis工具类
package redis01.demo.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.net.UnknownHostException;
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
// 修改成常用泛型
RedisTemplate template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// Json序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置具体的序列化方式
// String 的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value采用jackson的序列化方式
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value采用jackson的序列化方式
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
封装redis工具类
package com.oal.microservice.util;
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;
/**
* Redis工具类
*
* @author zhangzhixiang
* @date 2019年06月19日
*/
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @return
*/
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)
* @return
*/
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)
* @return
*/
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
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public Map
十、redis的持久化操作
1、持久化之RDB操作
概念:指定时间内将内存中的数据集快照写入磁盘。
rdb保存文件的是dump.rdb
(1)、配置相关
RDB 触发机制分为使用指令手动触发和 redis.conf 配置自动触发。
手动触发 Redis 进行 RDB 持久化的指令的为:
(2)、触发生成dump.rdb的条件
(3)如何恢复rdb文件数据
将生成的rdb文件放在我们的redis启动目录(默认)下就能在redis启动的时候自动检测到并恢复。
优点:
缺点:
2、持久化之AOF操作
AOF是将执行过的指令记录下来,数据恢复时按照从前到后的顺序再将指令执行一遍,实现数据恢复。
aof保存文件的是appendonly.aof文件。
默认是不开启此模式的,需要修改redis.conf里的appendonly no 改为appendonly yes
如果这个aof文件有错误(恶意修改),redis就启动不了,可以启动redis-check-aof.sh来修复。
优点:
缺点:
十一、redis的发布订阅
1 发布
publish [key] [something]
127.0.0.1:6379> publish learn "hello"
(integer) 1
2 订阅
subscribe [key]
psubscribe [pattern] 使用正则表达式订阅多个频道
127.0.0.1:6379> PSUBSCRIBE learn
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "learn"
3) (integer) 1
1) "pmessage"
2) "learn"
3) "learn"
4) "hello"
十二、redis主从复制(了解)
(1)主从复制的作用:
数据冗余:实现数据的热备份。
故障恢复:主节点挂了,从节点提供服务。
负载均衡:主节点负责写,从节点负责读。
高可用(集群): 主从复制是哨兵和集群能够实施的基础。
单机redis使用内存超过20G应该考虑加机器。
(2)一主二从配置
暂时性的:
特点:每个redis默认自己是主节点。
因此配置连个从节点认主就行了,在客户端里输入指令:slaveof 【host】 【prot】
客户端里查看本机redis的节点信息指令:info replication
永久配置(推荐):
从机配置文件里replication:
relicaof 主机ip 主机port
masterauth 主机密码
特点:
从机不能写
主机挂了,随时连接回来,依然可用。反之也可以。
原理:新建的连接主从会进行一次全量连接,之后进行增量连接。
十三、哨兵模式
https://blog.csdn.net/macro_g/article/details/82593996
十四、缓存穿透和击穿和雪崩
1、缓存穿透
概念:用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
解决方案
(1)布隆过滤器
对所有可能查询的参数以hash形式存储,当用户想要查询的时候,使用布隆过滤器发现不在集合中,就直接丢弃,不再对持久层查询。
(2)缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;
但是这种方法会存在两个问题:
2、缓存击穿
概念:“爆款”。缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
解决方案:
(1)redis设置缓存永不过期
(2)mutex key互斥锁
3、缓存雪崩
概念:通俗讲:redis挂了。缓存雪崩是指,缓存层出现了错误,不能正常工作了。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。
解决方案:
(1)redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。
(2)限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
(3)数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。