Redis基础
NOSQL
非关系型数据库的统称,Redis就是其中最常用的一种NOSQL型数据库
NOSQL数据库和SQL数据库的区别
- 不遵循sql标准
- 不支持ACID
- 具有远超关系型数据库的性能
什么时候使用NOSQL
- 对数据高并发的读写
- 海量数据的读写
- 需要高扩展性的数据
什么时候不使用NOSQL
- 当需要使用事务的情况下
- 当我们需要结构化查询数据的时候
常见的NOSQL数据库
- Redis
- MongoDB
- Hbase
Redis支持非常高速的读写,但是它是key-value型数据库,只能根据key来获取value,不能实现条件查询操作,所以Redis无法代替mysql,但是由于它的性能极强,所以可以用来当作关系型数据库的缓存层使用、分布式管理事务、分布式注册中心、消息队列
安装、配置Redis
#解压安装包
tar -zxvf redis-5.0.3.tar.gz
#安装Redis需要的依赖库
yum install gcc
#跳转到redis的目录
cd redis-5.0.3
#编译安装redis
make MALLOC=libc
#执行命令
cd src && make install
Redis启动的三种方式
- 前台启动:进入到src下,直接输入命令
./redis-server
,当启动之后需要一支开启窗口,不能执行其他操作,非常不方便 - 后台进程启动:
- 后台启动默认关闭,所以需要修改配置文件redis.conf,将
daemonize no
改成yes - 使用配置文件启动redis
src/redis-server redis.conf
- 后台启动默认关闭,所以需要修改配置文件redis.conf,将
- 设置开机自动启动
- 到etc下新建redis文件夹,将原来的配置文件复制一份过来
cp /usr/local/redis-5.0.3/redis.conf /etc/redis/redis.conf
- 在
/etc/init.d
下创建自启动脚本redis,文件内容如下程序块所示 - 给脚本添加权限
chmod 755 /etc/init.d/redis
- 将脚本添加到服务列表
chkconfig --add /etc/init.d/redis
- 设置开机自启
chkcinfig redis on
- 到etc下新建redis文件夹,将原来的配置文件复制一份过来
#!/bin/sh
# chkconfig: 2345 90 10
# description: Redis is a persistent key-value database
PATH=/usr/local/bin:/sbin:/usr/bin:/bin
REDISPORT=6379
EXEC=/usr/local/bin/redis-server
REDIS_CLI=/usr/local/bin/redis-cli
PIDFILE=/var/run/redis.pid
CONF="/usr/local/redis-5.0.3/redis.conf"
case "$1" in
start)
if [ -f $PIDFILE ]
then
echo "$PIDFILE exists, process is already running or crashed"
else
echo "Starting Redis server..."
$EXEC $CONF
fi
if [ "$?"="0" ]
then
echo "Redis is running..."
fi
;;
stop)
if [ ! -f $PIDFILE ]
then
echo "$PIDFILE does not exist, process is not running"
else
PID=$(cat $PIDFILE)
echo "Stopping ..."
$REDIS_CLI -p $REDISPORT SHUTDOWN
while [ -x ${PIDFILE} ]
do
echo "Waiting for Redis to shutdown ..."
sleep 1
done
echo "Redis stopped"
fi
;;
restart|force-reload)
${0} stop
${0} start
;;
*)
echo "Usage: /etc/init.d/redis {start|stop|restart|force-reload}" >&2
exit 1
esac
因为Redis默认不允许远程连接,需要使用远程连接通过Redis Desktop Manager看的话需要修改redis.conf配置文件,其中:
- 将
bind 127.0.0.1
改成0.0.0.0
- 将
protected-mode yes
改成protected-mode no
- 设置密码
requirepass 123456
(可以不设置) - 关闭防火墙
systemctl stop firewalld
、
使用Redis客户端
- 进入到Redis目录下的src中
- 输入
./redis-cli —raw -a 123456
进入
如何关闭redis
#查看redis的进程id
ps -aux | grep redis
#杀掉redis进程
kill 进程ID
Redis中常用的命令(key与五大数据类型)
使用select index
来切换数据库,例如:select 1
-
String (字符串)
在redis中,字符串的类型是安全的二进制,并且二进制的value最大值为512M,那就意味着String类型并不是只能存储一个简单的字符串,也可以存储图片或者文件的序列化对象set key value
,存储一个字符串到redis中get key
,从redis中取出一个字符串类型的数据append key value
,将内容追加到之前的key上,若没有这个key则会执行set操作,也就是说有key追加,没key创建一个再存储strlen name
,获得value占用的字节数(一个中文三个字节)incr key
,key存在则value自增,否则创建keyvalue为1(key对应的value必须是数字,否则会报错)incrby key increment
,指定自增多少,increment可正可负decr key
,与incr相反,为自减操作decrby key decrement
,指定自减多少,decrement可正可负mset key value [key value …]
,一次存多个key-value
一次存储多个key-value与多次存储key-value有什么区别?
一次存储多个key-value,命令不会被打断,而多条命令则可能被打断mget key [key ...]
,一次取出多个valuemsetnx key value [key value …]
,作用和mset
一致,区别是msetnx
若命令中有一个成功,就都成功;有一个失败,则全部失败(推荐使用)
Redis的原子性:
原子是不可分割的组织,在Redis中单条命令能完成的操作都是原子性的,因为Redis是单线程的,所以Redis在执行一条命令的途中不会被其他线程打断,也不会被切换到其他线程。getrange key start end
,截取字符串,从开始下标到结束下标,将值返回,没有改变原本的值。setrange key offset value
,字符串替换,从开始下标开始进行value的替换操作,value有多少就替换多少,例:setrange name 0 gggg
,从下标为0开始,替换成gggg,比如说原字符串是abcdefghigk
,替换之后就成了ggggefghigk
setex key seconds value
,在设置key的同时设置过期时间getset key value
,先获取原本的值,然后再设置新的值(value)psetex key milliseconds value
,设置key的时候设置过期时间(毫秒级)exists key [key ...]
,判断key是否存在,1存在,0不存在setnx key value
,判断key是否存在,若存在则返回0并且不做任何操作;若key不存在则返回1并同时新增key-value
-
List(列表)
底层是双向链表,数据可重复,对两端的数据操作效率高,如果根据索引操控中间的数据效率低。
如果链表中存在上下引用,并且只存储数字类型的数据,会造成空间的浪费lpush/rpush key value [value ...]
,从左/右侧插入值,key不存在会创建,并返回列表中数据个数lpop/rpop key
,从List左/右侧取出一个值(拿出后list中就没有这个值了,当值被取空后,list会被删除)rpoplpush source destination
,从source的右侧拿出一个值,放入到destination的左侧,例:rpoplpush list1 list2
lrange key start stop
,从左边根据下标取值,从开始下标到结束下标,值还是在key中,不会被删除,若需要取出全部值,则结束下标写-1,-1就代表最后一个下标,例:lrange key 0 -1
lindex key index
,根据下标取值llen key
,获取key的长度linsert key BEFORE|AFTER pivot value
,根据值插入,把value插入到pivot的前/后面lrem key count value
,从左侧开始删除count个value,并返回删除的个数lset key index value
,修改下标为index的值为value
-
set(集合)
有自动排重的功能,也就是指set中的值不能有重复。set底层是一个key-value形式的hash表,sadd key member [member ...]
,向集合中存放数据(member)无序,如果重复则会直接忽略smembers key
根据key取出所有的member(数据)无序,不会删除scard key
,获取元素个数sismember key member
,查找member,找到了返回1,找不到返回0spop key [count]
,随机从key中取值,可设置取值个数,取出之后key中删除srandmember key [count]
,从key中随机取值,可设置取值个数,取出后key中不删除smove source destination member
, 将source中的member移动到destinationsinter key [key ...]
,取出key的交集sunion key [key ...]
,取出key的并集sdiff key [key ...]
,取出key的差集srem key member [member ...]
,删除key中的member,可删除多个,并返回删除的个数
-
zset(有序)
zset有自己的排序规则,这个顺序并不是我们存储的顺序,而是按照存储数据时给的score排序zadd key [NX|XX] [CH] [INCR] score member [score member …]
,向key中添加数据,score是排序的时候用的,member为存放的数据zrange key start stop [WITHSCORES]
,取出key中start和stop之间的数据,需要score一起显示的话可以加上withscores
zrangebyscore key min max [WITHSCORES] [LIMIT offset count]
,可以==按照score的顺序(默认升序)==取出范围内的值zrevrangebyscore key max min [WITHSCORES] [LIMIT offset count]
,可以==按照score的顺序(降序)==取出范围内的值zincrby key increment member
,将key中member的score增加increment
例:zincrby zset 100 zhangsan
,意思就是给zset中的zhangsan的score值加上100zrank key member
,查询member在key中排第几,从零开始计算zcount key min max
,查询min到max之间有多少数据zrem key member [member ...]
,删除key中的member
-
hash
是一个键值对集合,类似于java中的Map。hash结构适合存储单个对象。
为什么不用String存储对象 如果用String来存储对象,则需要将对象序列化然后存入,如果有修改对象属性的需求,则需要将整个对象反序列化,修改后再次序列化然后存入,开销大
hset key field value
,根据键值对的形式向key中添加值,如果key已经存在则将其覆盖,field是属性名,value是属性值hget key field
,根据key以及field取出对应的valuehexists key field
,判断key中是否存在field,存在返回1,否则返回0hkeys key
,取出key中所有的属性名(field)hvals key
,取出key中所有的属性值(value)hincrby key field increment
,对integer类型的属性值进行加/减操作,increment可正可负,返回修改后的值hsetnx key field value
,当属性不存在的时候添加属性以及值,如果属性存在则不进行任何操作,成功返回1,否则返回0
-
key
redis中最关键的东西,所有的操作基本上都是通过key实现的keys pattern
,pattern为*的时候,可以获得所有的keyexists key [key ...]
,返回查询的key有多少个,存在返回存在的个数,否则返回0,不建议查多个type key
,查询key的类型是什么del key [key ...]
,删除key,可以删除多个,如果key中有特殊字符,需要将key写在双引号之内,否则不识别,返回成功删除的个数unlink key [key ...]
,异步删除keyexpire key seconds
,给key设置过期时间ttl key
,查询key的过期时间,-2代表已经删除,-1代表永不过期dbsize -
,查询当前数据库中key的数量flushdb [ASYNC]
,清空当前数据库flushall [ASYNC]
,清空所有数据库
三种特殊的数据类型
-
geospatial(地理位置)redis的Geo功能可以推算地理位置的信息,geo底层的原理就是Zset,我们可以使用Zset操作geo
#添加城市地理位置数据 #规则:两极无法添加,一般使用java程序等一次性导入 #有效的经度从-180度到180度。 #有效的纬度从-85.05112878度到85.05112878度。 #将指定的地理空间位置(纬度、经度、名称)添加到指定的key中 #参数: key value(纬度 精度 名称) 127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing (integer) 1 ---------------------------------------------------------------- #从key里返回所有给定位置元素的位置(经度和纬度) 127.0.0.1:6379> GEOPOS china:city beijing zhengzhou 1) 1) "116.39999896287918091" 2) "39.90000009167092543" 2) 1) "104.32000011205673218" 2) "29.41000080988116139" ---------------------------------------------------------------- #返回两个给定位置之间的距离。 #如果两个位置之间的其中一个不存在, 那么命令返回空值。 #指定单位的参数 unit 必须是以下单位的其中一个: #m 表示单位为米。 #km 表示单位为千米。 #mi 表示单位为英里。 #ft 表示单位为英尺。 #如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位。 #GEODIST 命令在计算距离时会假设地球为完美的球形, 在极限情况下, 这一假设最大会造成 0.5% 的误差。 #北京到郑州的直线距离 127.0.0.1:6379> geodist china:city beijing zhengzhou "1604092.3762" 127.0.0.1:6379> geodist china:city beijing zhengzhou km "1604.0924" #找附近的人?定位? #以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素 #参数 key value(经度 纬度 半径 单位) 127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km 1) "zhengzhou" #withdist 显示到中间 #withcoord 显示他人的定位信息 #count 1 先是返回结果为一个 127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withdist withcoord count 1 1) 1) "zhengzhou" 2) "552.5944" 3) 1) "104.32000011205673218" 2) "29.41000080988116139"
#这个命令和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的, #而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点 #指定成员的位置被用作查询的中心。 127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
-
“beijing”
#查看key中所有的value
127.0.0.1:6379> ZRANGE city 0 -1
-
“gg”
#删除 value
127.0.0.1:6379> ZREM city gg
(integer) 1
-
-
hyperloglog:这个数据结构用作基数统计的算法,比如说网页的UV(一个人访问一个网站多次,但是需要只算做一个人)
传统的方式:使用set保存用户id,然后就可以统计set中的元素数量作为标准判断。
基数:**数据集里面不重复的元素个数** hyperloglog的优点:占用的内存是固定的,只需要12kb的内存,但是**有0.81%的错误率**,具体使用需要看当时的需求
#创建一组元素 127.0.0.1:6379> pfadd key a b c d e f g h i j (integer) 1 #统计出key中的基数 127.0.0.1:6379> pfcount key (integer) 10 #合并两组数据 127.0.0.1:6379> PFMERGE key3 key key2 OK
-
bitmap:统计用户信息、用户是否活跃、是有登陆、打卡(两个状态的都可以使用bitmap)
传统使用一张表、一个字段保存
位图,是一种数据结构,都是操作二进制位来进行记录,只有0和1两种状态
#例如使用bitmap记录打卡: sign这个位图 前边的参数表示天数 0开始,表示周一 后边的参数表示是否打卡,1打卡 0未打卡 7.0.0.1:6379> setbit sign 0 1 (integer) 0 127.0.0.1:6379> setbit sign 1 1 (integer) 0 127.0.0.1:6379> setbit sign 2 1 (integer) 0 127.0.0.1:6379> setbit sign 3 0
#统计 为1的数据有多少 127.0.0.1:6379> BITCOUNT sign (integer) 3
基本知识
#选择数据库
127.0.0.1:6379> select 3
OK
#数据库内容
127.0.0.1:6379[3]> dbsize
(integer) 0
127.0.0.1:6379[3]>
#查看数据库所有的key
127.0.0.1:6379[3]> keys *
(empty array)
#清除当前数据度
127.0.0.1:6379[3]> flushdb
OK
#清除所有数据库
127.0.0.1:6379[3]> flushall
OK
redis是单线程的!
明白:redis是很快的。它是基于内存的,它的性能瓶颈是根据机器的内存大小和网络带宽,既然可以使用单线程来实现,就直接使用单线程了。
redus为什么是单线程还那么快?
- 误区1:高性能的服务器一定是多线程的
- 误区2: 多线程一定比单线程高
核心:redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的(多线程:cpu上下文切换,耗时),对于内存系统来说,如果没有上下文切换,那么效率就是最高的。
Redis基本事务
redus单条命令保证原子性,但是事务不保证原子性
redis事务特性:一次性、排它性、顺序性
redis事务没有隔离级别的概念
所有的命令在事务中并没有直接被执行,只有发起执行命令的时候才会执行 (Exec)
事务的本质就是一组命令的集合 。一个事务中的所有命令都会被序列化,在事务执行过程中会按照顺序执行
- 开启事务(MULTI)
- 命令入队(其他)
- 执行事务(exec)
正常执行事务
127.0.0.1:6379> MULTI #开启事务
OK
#命令入队
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> EXEC #执行事务
1) OK
2) OK
3) "v2"
4) OK
放弃事务
127.0.0.1:6379> MULTI #开启事务
OK
#事务队列中的命令都不会执行
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> DISCARD #取消事务
OK
127.0.0.1:6379> get k4
(nil)
编译型异常(代码有问题)事务中所有的命令都不会被执行
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
#错误的命令
127.0.0.1:6379(TX)> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> EXEC
#所有的命令都不执行
(error) EXECABORT Transaction discarded because of previous errors.
运行时异常(1/0 语法型错误)执行命令的时候没有错的命令是正常执行,错误命令会抛出异常
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k1 "v1"
QUEUED
#错误命令
127.0.0.1:6379(TX)> INCR k1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> get k3
QUEUED
127.0.0.1:6379(TX)> EXEC
1) OK
2) (error) ERR value is not an integer or out of range #只有这一个命令不能执行
3) OK
4) OK
5) "v3"
监控
悲观锁
- 认为什么时候都会出问题,无论干什么都会加锁
乐观锁
- 认为什么时候都不会出问题,不会加锁。更新数据的时候判断一下,在此期间是否有人修改过这个数据(version)
- 获取version
- 跟新的时候比较version
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(TX)> DECRBY money 20
QUEUED
127.0.0.1:6379(TX)> INCRBY out 20
QUEUED
127.0.0.1:6379(TX)> EXEC
1) (integer) 80
2) (integer) 20
测试多线程修改值,使用watch当作redis的乐观锁
27.0.0.1:6379> watch money #监视money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY money 10
QUEUED
127.0.0.1:6379(TX)> INCRBY out 10
QUEUED
127.0.0.1:6379(TX)> EXEC #执行之前另外一个线程修改了monry,就会导致事务失败
(nil)
监视失败,重新监视(重新获取最新的值就好)
127.0.0.1:6379> UNWATCH #事务失败之后,取消监视这个对象
OK
127.0.0.1:6379> watch money #再次监视这个对象
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY monry 100
QUEUED
127.0.0.1:6379(TX)> INCRBY out 100
QUEUED
127.0.0.1:6379(TX)> EXEC
1) (integer) -100
2) (integer) 120
Jedis控制redis
什么是jedis?官方推荐的java连接开发工具 使用java操作redis中间件
1.导入依赖
<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.62</version>
</dependency>
2.编码测试:
- 操作数据库
- 断开连接
测试连接redis
package com.yan;
import redis.clients.jedis.Jedis;
/**
* @author zhangruiyan
*/
public class TestPing {
public static void main(String[] args) {
//new jedis对象即可
Jedis jedis = new Jedis("127.0.0.1",6379);
//所有的命令都在这个对象里
System.out.println(jedis.ping());
}
}
jedis处理事务
package com.yan;
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
/**
* @author zhangruiyan
*/
public class TestTX {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
//开启事务
Transaction multi = jedis.multi();
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","word");
jsonObject.put("name","zhangsan");
String s = jsonObject.toJSONString();
//监视s这个对象
jedis.watch(s);
try {
multi.set("user1",s);
//代码有错,抛出异常,放弃事务
int i = 1/0;
multi.set("user2",s);
//执行事务
multi.exec();
}catch (Exception e){
//放弃事务
multi.discard();
}finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close();
}
}
}
SpringBoot整合Redis
说明:在Springboot2.x之后,jedis被替换成了lettuce
jedis:采用的是直连,多个线程操作的话是不安全的,如果想避免不安全,就需要使用jedis pool连接池,更像BIO
lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况,更像NIO
源码分析:
@Bean
//这个注解的意思就是如果不设置redisTemplet的话就直接使用这个现成的
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//redisTemplete 没有过多的设置 , redis对象都需要序列化
//两个泛型都是 object,object 的类型 我们使用需要转换成 string,object
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
//spring是redis中最常用的类型,所以说单独有一个bean
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
1.导入依赖
<!--操作redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.配置连接
#springboot所有的配置类 都有一个自动配置类 RedisAutoConfiguration
#自动配置类都会绑定一个properties配置文件 RedisProperties
spring:
#配置redis
redis:
host: 127.0.0.1
port: 6379
3.测试
package com.yan;
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.core.RedisTemplate;
@SpringBootTest
class Redis02SpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
/*
redisTemplate 操作不同的数据类型 api和redis的命令一样
redisTemplate.opsForValue(); 操作字符串 类似string
redisTemplate.opsForList(); 操作list集合
除了基本的操作,常用的方法都可以直接通过redisTemplate操作,例如事务、基本的crud
*/
redisTemplate.opsForValue().set("name","zhangSan");
System.out.println(redisTemplate.opsForValue().get("name"));
}
}
我们来编写自己的redisConfig
package com.yan.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.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;
/**
* @author zhangruiyan
*/
@SpringBootConfiguration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//为了开发方便一般直接使用 string object
RedisTemplate<String, Object> template = new RedisTemplate<String,Object>();
template.setConnectionFactory(redisConnectionFactory);
//json配置具体的序列化方式
Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//转译
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//已经废弃了(暂时不用,用下边的试试)
//om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
om.activateDefaultTyping(om.getPolymorphicTypeValidator());
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL);
objectJackson2JsonRedisSerializer.setObjectMapper(om);
//string的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key采用string的序列化方式
template.setKeySerializer(stringRedisSerializer);
//hash的key也采用string的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//value序列化方式采用jackson
template.setValueSerializer(objectJackson2JsonRedisSerializer);
//hash的value序列化采用jackson
template.setHashValueSerializer(objectJackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
自己写的RedisUtil
package com.yan.config.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.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @author zhangruiyan
*/
@Component
public class RedisUtil {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
//-----------------------------------------------------------------
/**
* 指定缓存失效时间
* @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 键 不能为空
* @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((Collection<String>) CollectionUtils.arrayToList(key));
}
}
}
/**
* 通过前缀模糊删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void delVague(String key) {
Set keys = redisTemplate.keys(key+"*");
if (keys!=null && keys.size()>0) {
for (Object o : keys) {
String curKey = (String) o;
redisTemplate.delete(curKey);
}
}
}
//============================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<Object, Object> hmget(String key){
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
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)
* @return
*/
public double hincr(String key, String item, double by){
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
public double hdecr(String key, String item, double by){
return redisTemplate.opsForHash().increment(key, item,-by);
}
//============================set=============================
/**
* 根据key获取Set中的所有值
* @param key 键
* @return
*/
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 键
* @return
*/
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代表所有值
* @return
*/
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 键
* @return
*/
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倒数第二个元素,依次类推
* @return
*/
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 值
* @return
*/
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 时间(秒)
* @return
*/
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;
}
}
}
redis.conf配置文件
单位
配置文件 unit单位对大小写不敏感
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
多个配置文件配置过来
################################## INCLUDES ###################################
# Include one or more other config files here. This is useful if you
# have a standard template that goes to all Redis servers but also need
# to customize a few per-server settings. Include files can include
# other files, so use this wisely.
#
# Note that option "include" won't be rewritten by command "CONFIG REWRITE"
# from admin or Redis Sentinel. Since Redis always uses the last processed
# line as value of a configuration directive, you'd better put includes
# at the beginning of this file to avoid overwriting config change at runtime.
#
# If instead you are interested in using includes to override configuration
# options, it is better to use include as the last line.
#
# include /path/to/local.conf
# include /path/to/other.conf
网络
bind 0.0.0.0 #绑定的ip
protected-mode no #保护模式
port 6379 #端口号
通用GENERAL
daemonize yes #以守护线程的方式运行,默认no 需要自己开启(就是后台启动)
pidfile /var/run/redis_6379.pid #如果以后台的方式运行 就需要执行一个pid文件
#日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) 生产环境使用
# warning (only very important / critical messages are logged)
loglevel notice
#日志的文件位置名
logfile ""
#数据库的数量 默认16个
databases 16
#是否显示redis的logo
always-show-logo no
快照SNAPSHOTTING
持久化,在规定的时间内执行了多少次操作,则会持久化到文件.rdb.aof
redis是内存数据库,如果没有持久化,那么断电的话数据就会丢失
#如果 3600s内 如果至少有1个key进行了操作,就会进行持久化操作
# save 3600 1
#如果 300s内 如果至少有100个key进行了操作,就会进行持久化操作
# save 300 100
#如果 60s内 如果至少有10000个key进行了操作,就会进行持久化操作
# save 60 10000
#持久化失败之后是否继续工作
stop-writes-on-bgsave-error yes
#是否压缩rdb文件 会消耗一些cpu资源
rdbcompression yes
#保存rdb文件的时候进行错误的检查校验
rdbchecksum yes
#rdb文件的保存位置
dir ./
安全 SECURITY
#设置密码
# requirepass foobared
redis持久化
RDB
在指定的时间间隔内将内存的数据集快照写进磁盘,即Snapshot快照,它恢复的时候是直接读到内存中。
Redis会单独创建一个子进程(fork)来进行持久化,它会将数据写入一个临时rdb文件,等持久化过程结束之后,再用这个临时文件替换上次持久化好的文件。
整个过程中,主进程不进行任何IO操作,这保证了极高的性能,如果需要进行大规模数据的恢复,并且对于数据恢复的完整性不是非常敏感,那么RDB方式比AOF更加的高效
RBD的缺点就是最后一次持久化后的数据可能丢失
Redis默认RDB,一般不进行修改
RDB保存的文件是 dump.rdb,在配置文件中的快照这一块配置
dbfilename dump.rdb
触发机制
- 满足save设置的规则
- 执行flushall命令
- 退出redis
备份就会自动生成一个REB文件
如何恢复RDB文件
只需要将RDB文件放在我们redis启动目录就可以,redis启动的时候会自动检查dump.rdb文件并恢复其中的数据
查看启动目录:
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/redis-6.2.5" #如果在这个目录下 redis启动就会自动恢复
RDB优点
- 适合大规模的数据恢复
- 如果对数据完整性要求不高
RDB缺点
- 需要一定的时间间隔进程操作
- fork进程的时候会占用内存空间
AOF(Append Only File)
将我们所有的命令都记录下来,恢复的时候就把这个文件全部执行一遍
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只允许追加文件,不可改写文件,Redis启动的时候会读取该文件重新构建数据,即:Redis重启的话就根据日志文件的内容将写指令从前到后执行一次,以完成数据的恢复工作。
AOF保存的是appendonly.aof文件
将appendonly改成yes即可开启AOF
############################## APPEND ONLY MODE ###############################
appendonly no #默认不开启
appendfilename "appendonly.aof" #AOF产生的文件
重启Redis即可生效
如果这个aof文件有错误,这时候redis无法启动,可以使用redis-check-aof –fix
命令来修复
优点
-
每次修改都同步,数据更完整
-
每秒同步一次,可能会丢失一秒的数据
缺点
- 相对于数据文件来说,AOF远远大于RDB,修复的速度慢
- AOF效率比较低