Redis 学习笔记
1,概述
Redis(Remote Dictionary Srvice),远程字典服务,c语言写的,支持网络,kv数据库,提供多种语言的api。
免费和开源,是当下最热门的nosql技术之一,也被人们称为结构化数据库
2,Redis能干嘛?
内存存储,持久化,内存中断电即失,所以持久化很重要(rdb,aof)
效率高,可以用于告诉缓存
发布订阅系统
地图信息分析
计时器,计数器(浏览量)
。。。。。。
3,特性
多样的数据类型
持久化
集群
事务
。。。。。。
4,windows安装(不建议)
下载安装包
解压
直接使用
5,linux下安装
6,作用
数据库
缓存
消息中间件MQ
7,Redis-key
查看所有key
设置key
判断当前key是否存在
移除当前key
设置key的过期时间
查看当前key的剩余时间
获得一个key
查看key的类型
8,数据结构
字符串(String)
append key 值 ---------- #向这个key中添加值,如果key不存在,就相当于set key strlen key ------------ #获取这个字符串的长度 incr key views --------- #相当于给这个key,i++ decr key views --------- #给这个key。i-- #步长 incrby key views -- #增这个key时,使用我们设置的步长 decrby key views -- #根据我们设置的步长来减少key getrange key start end - #截取字符串,(起始,结尾)当(0,-1)获取所有的字符串 #替换 setrange key v1 v2 ------#v1:第几位开始替换,v2:替换的值 setex key 过期时间 这个key中的值 --------------- #设置过期时间 setnx key 值 --------------- #当key不存在的时候,再创建key,如果当前key存在,那就创建失败,返回0 #批量获取设置key mset key1 v1 key2 v2 key3 v3 ------ #批量设置key mget k1 k2 k3 --------------------- #批量获取key msetnx key1 v1 key2 v2 key3 v3 ---- #如果不存在,再创建,是个原子性操作,要么一起成功,要么一起失败 #对象 set user:1{name:v,age:v} ------- #设置一个user:1的对象,值为json字符串对象来设置值 mset user:1:name v user:1:age v --- #这种操作和上面是一样的,但是我们使用了分开多次复制的方式 #组合命令 getset key v ----------- #当key不存在的时候,我们会返回一个空,并且赋值v,当key存在的时候,我们会先得到之前的值,然后使用v来赋值
String类似的使用场景:value除了是我们的字符串,还可以是我们的数字
计数器
统计多单位的数量
粉丝数
对象缓存存储
散列(hashes)
想象成一个map集合,key-<key,vlaue>
hset hash k v ------------ #往hash这个散列中设置一个键值对 hget hash k -------------- #在hash这个散列中通过key获得值 hmset hash k1 v1 k2 v2 --- #批量去赋值。如果遇到重复的,那就替换掉原来的 hmget hash k1 k2 --------- #批量得到hash中的键值对 hgetall hash ------------- #获得hash中所有的键值对 hdel hash key ------------ #在hash中根据key来删除对应的键值对 hlen hash ---------------- #查看hash中有多少对键值对 hexists hash key --------- #判断hash中指定的字段是否存在 #只获得所有的key hkeys hash --------------- #查看hash中所有的key #只获得所有的value hvals hash --------------- #查看hash中所有的值 hincrby hash key 步长 --------- #在hash中根据key来增加响应的步长 hsetnx hash k v ----------- #如果hash中不存在这个k,那就创建,否则返回0,设置失败
hash可以存一些变更的数据 user name age,经常变动的信息,都可以保存,hash更适合对象的存储,但是String更适合字符串的存储
列表(list)
在redis里面,我们可以把list完成栈,队列,阻塞队列(可以理解成阻塞队列,但是这两个是不同的概念)所有的list命令都是l开头的
lpush list v ------------ #向这个list中push值 lrange list 起始下标 末了下表 -- #当 0 -1 是取出所有的值,要注意想,我们取出的顺序是颠倒的,就和栈一样 rpush list 值 -------------#直接在尾部开始加,相当于可以在栈底进行添加 lpop list ----------------- #相当于从队列左边移除值,或者说相当于从栈顶移除值 rpop list ----------------- #从右边来移除了值,相当于从栈底来移除了一个值 lindex list index --------- #相当于从队列的左边开始根据索引取值 llen list ----------------- #返回list的长度 #移除指定的值 lrem list 1 v ------------- #移除list集合中值为v的元素,并且只移除一个(list中是可以放两个相同的值) ltrim list 起始下标,终止下标 -- #截取字符串,根据下下标来截取,我们需要的list中的元素,相当于是一个修剪的作用 rpoplpush list1 list2 ---------#我们从集合1中的最右边取出了一个数字,并且把这个数字从最左边放进了集合2中 lset list 0 v ----------------- #向这个列表中的下标为0的地方设置值为v(这里要注意,list必须要是存在的,否则就会失败,如果这个索引是不存在的,也会发生报错)相当于是一个更新,替换的操作 linsert list before/after v1 v2 ------ #在list集合中,在v1的前面来插入v2这个值(我们也可以在v1的后面来插入这个值)
集合(set)
set中的值是不能重复的
sadd set v --------- #向set中去加入值 smembers set ------- #查看这个set中所有的元素 sismember set v ----- #判断在set集合中,是否有v这个元素 scard set ----------- #获取这个集合中元素的个数 srem set v ---------- #移除set集合中值为v的元素 srandmember set 抽选的个数 ----- #随机抽选指定个数的元素,如果不写,默认是抽取一个 spop set ----------- #随机弹出一个元素 #将一个指定的值移动到另外一个集合中 smove 源 目标 值 -------- #把源集合中的值移动到目标集合中 #集合(比如,我们b站上可以看见共同关注的人有哪些) #数字集合类: #差集: sdiff set1 set2 ------ #以set1为源set ,看看set1中与set2的差集 #交集:(共同好友的实现) sinter set1 set2 ------ #获得这两个集合的交集 #并集: sunion set1 set2 ------ #两个集合的并集
微博,a用户将所有关注的人放在一个set集合中,将她的粉丝也放在一个集合中
共同关注,共同爱好,二度好友,推荐好友(六度分割理论)
有序集合(Zset)
在set的基础上增加了一个值,zset k1 score1 v1,在这里加了一个标志位,我们可以根据这个标记位置来得到我们需要的排序
zadd set 1 one ---------- #添加一个 asdd set 2 two 3 three -- #添加多个数据 zrange set 0 -1 --------- #根据起始位置获相应的值 zrangebyscore set min max #这样就根据标志位来进行排序,后面是获得的范围(负无穷是-inf,正无穷是+inf) zrangebyscore set min max withscores #可以在获得数据的同时,带上我们的标志位 zrevrangebyscores set max min #这个是降序排列,我们要注意,这里的min和max只是指一个范围,并不指排序方向 #移除元素 zrem set v -------------- #移除集合中值为v的元素 zcard set --------------- #返回set集合中有多少个元素 zcount set min max ------ #获取指定区间的成员数量
bitmaps
只有两个状态的,都可以使用这个来作为解决方案,所有的操作都是操作二进制位来进行记录,就只有0和1两个状态
setbit sign 0 1 setbit sign 1 1 setbit sign 2 1 setbit sign 3 0 setbit sign 4 1 setbit sign 5 0 setbit sign 6 1 #在上面的代码中,每一行的第一个数字都代表了星期几,后面的0和1代表了我们是否打卡,所以我们通过这个,就可以实现位存储 #在后续,如果我们想要统计这一周有多少天打卡,我们就可以只统计1的个数或者0的个数 getbit sign 3 ----------------- #通过这个就可以来查找星期4是否进行了考勤 #统计操作,统计打卡的天数 bitcount sigin ----------------- #这个会统计这个key中所有为1的元素的个数
hyperloglogs
优点:占用的空间时固定的,2^64不同的元素的基数,只需要12kb的内存,如果从内存角度来比较的话,这个是首选
缺点:官方宣称有0.81%的错误率,但是这对于我们网站计数来说是可以忽略不计的
什么是基数?
A{1,3,5,7,8,7}
B{1,3,5,7,8}
基数:不重复的元素
简介:他是做基数统计的算法
比如,一个人访问网站多次,但是还是只算一个人
传统的方式,set保存用户的id,然后就可以统计set中的元素数量作为标准判断,这种方式如果保存大量的id,就会很麻烦,我们的目的是为了计数,而不是保存用户id
pfadd key 元素(这个元素可以是多个,中间用空格分开) --------------- #加元素 pfcount key ---------------------------------------------- #统计这个key中的元素,统计的是基数 pfmerge key3 key1 key2 ----------------------------------- #把k1和k2中的元素进行合并,并且赋值给k3
geospatial 地理位置
朋友的定位,附近的人,打车距离计算
可以推算地理位置的信息,两地之间的距离,方圆几里的人
只有6个命令
geoadd
规则:两级没有办法直接添加,而且我们一般会下载城市数据,直接通过java程序一次性导入
geoadd china:city 经度 纬度 beijing -------- #这里的键是china:city ,并且设置了一个值来表述这个经纬度
geodist
这个命令会在计算距离时,假设地球是一个完美的球形,在极限情况下,这个最大会造成0.5%的误差
geodist china 城市1 城市2 ---------------------#查询两个城市之间的距离,如果其中一个为空,那就返回空值
geohash
返回一个或多个位置元素的哈希表示
geopso
geopso china:city beijing ------------- #通过最后的标注来查询china:city中对应的经纬度
georadius
以给定的经纬度为中心,找出某一半径内的元素
georadius china:city ----------------- #经度 纬度 半径 半径的单位(可选)也可以跟count 数字,来选择相对应的个数,还可以跟withdist来显示距离中心的极限距离,withcoord显示经纬度
georadiusbymeber
以一个元素为中心,找出该城市周围的城市,和上面是一样的,只是中心点由经纬度换成了元素
zrange
查看key中的所有元素
zrem
移除key中城市为v的元素
9,事务
mysql:acid
要么同时成功,要么同时失败,原子性
Redis单条命令是保证原子性的,但是redis的事务是不保证原子性的!Redis事务没有隔离级别的概念
所有的命令在事务中并没有被直接执行,只有发起执行命令的时候才会被执行
Redis事务本质:一组命令的集合,平常我们执行单条数据,但是Redis的事务就会把这一条一条的都放进队列中,然后来执行,一个事务中的所有命令都要被序列化,在事务执行过程中,按照先进先出的顺序来执行,谁先进谁就先被执行
一次性,顺序性,排他性执行一系列的命令
redis的事务:
开启事务(multi)
命令入队(。。。。。。)
执行命令(exec)
9.1,正常执行事务
先multi,然后正常写命令,最后使用exec来执行命令
9.2,放弃事务
dishcard----取消事务,当你在第二步骤的时候,如果想中断这个事务,就可以使用,使用之后,事务中的命令都不会被执行
9.3,编译型异常(代码有问题,命令有错)
事务中所有的命令都不会被执行
9.4,运行时异常
如果队列中存在语法性错误,那么执行命令的时候,其他命令是可以正常执行的
比如说在10条命令里面,有一条是错误的,这一条不执行,剩下的正常执行,这也就是说为什么redis的事务是没有原子性的,错误的命令抛出异常
10.监控(Watch)
10.1,悲观锁
很悲观,认为什么时候都会出问题,无论做什么都会加锁
10.2,乐观锁
很乐观,认为什么时候都不会出现问题,所以不会上锁,在更新数据的时候去判断一下,在此期间是否有人修改过这个数据,version!
获取version
更新的时候比较version
10.3,Redis监视测试
正常执行成功
使用watch key,来监视key对象
使用multi开启事务
命令入队
exec执行命令
事务正常结束,没有发生变动,这个时候就正常执行
没有正常执行
在线程一种watch key1 来监视这个对象
使用multi开启事务
incrby key1 10 给key1加了10
在这个时候,线程1还没有开始执行,线程2没有通过事务执行了incrby key 20
在线程2执行完之后,我们在线程1使用exec来执行命令,这个时候,由于我们的watch监视的key1发生了改变,这一次的事务就会执行失败
所以我们使用watch就可以当做redis的乐观锁来使用
那我们刚才出现问题现在应该怎么做?
接着上面的5开始:
6,使用unwatch 来解开监控
7,watch key1 重新开始监视,然后开始事务
11,Jedis
我们使用java来操作redis
11.1,什么是Jedis?
是Redis官方推荐的java连接开发工具,使用java操作Redis的中间件,如果你要使用java来操作redis,那么一定要对jedis十分熟悉
连接数据库
操作命令
断开连接
11.2,常用api
String
List
Set
Hash
Zset
11.3,事务
和之前的事务一样使用
12,Springboot整合
在springboot2.之后,原来使用的jedis被替换为了lettuce
为什么要换?jedis采用的是直连,多个线程同时操作的话,是不安全的,如果想要避免不安全,使用jedis pool连接池,更像BIO模式
lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全,可以减少我们的线程数量,更像NIO模式
导入依赖
配置连接
测试
12.1,对象的序列化
@Test
public void test01() throws JsonProcessingException {
Objects.requireNonNull(redisTemplate.getConnectionFactory()).getConnection().serverCommands().flushDb();
//真实的开发一般都使用json来传递数据
User lxl = new User("lxl", 20);
String jsonUser = new ObjectMapper().writeValueAsString(lxl);
redisTemplate.opsForValue().set("user", jsonUser);
System.out.println(redisTemplate.opsForValue().get("user"));
}
关于对象的保存:
在企业中,一般我们所有的Pojo都会进行序列化
12.2,Redis自定义序列化模板
//自己定义了一个RedisTemplate
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//我们为了自己开发方便,一般直接使用<String, Object>
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//Json序列化配置
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(objectMapper, Object.class);
//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;
}
13,Redis.config详解
启动的时候,就通过配置文件来启动
13.1,单位
配置文件unit单位对大小写不敏感
13.2,包含
就好比我们学习spring配置文件的时候,我们可以把其他的spring配置文件导入另一个spring配置文件中
# include /path/to/local.conf # include /path/to/other.conf
13.3,网络
# bind 127.0.0.1 --------------- *绑定的ip*
protected-mode no ----------------- *是否开启保护*
port 6379 --------------- *端口号,后期集群的时候使用*
13.4,通用配置
daemonize yes ------------- *以守护进程的方式运行。默认是no,我们要自己开启为yes*
pidfile /www/server/redis/redis.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 "/www/server/redis/redis.log" ---------------- *日志的文件名和地址*
databases 16 ------------------------- *数据库的数量,默认是16个*
always-show-logo yes --------------------- *是否显示logo,默认开启*
13.5,快照
持久化,在规定时间内,执行了多少次操作,则会持久化到文件.rdb .aof
redis是内存数据库,如果没持久化,那么就会断电即失
save 900 1 ------------------ *如果900秒内,至少有一个key进行了修改,我们就进行持久化操作*
save 300 10 ----------------- *300秒内,如果至少10个key发生了修改,那就进行持久化*
save 60 10000 ----------------- *60秒内,如果至少10000个key发生了修改,那就进行持久化*
/我们之后学习持久化,会自己定义这个测试/
stop-writes-on-bgsave-error yes ------ *如果持久化失败了,是否还需要继续工作*
rdbcompression yes ---------- *是否压缩rdb文件(会消耗cpu资源)*
rdbchecksum yes --------- *保存rdb文件时,是否进行错误校验*
dir /www/server/redis/ ------ *rdb文件的保存目录*
13.6,复制
后面讲解主从复制的时候再进行讲解
13.7,安全
# requirepass 自己的密码 ----------- *设置密码,默认是没有密码*
13.8,限制 CLIENTS(客户端)
# maxclients 10000 ---------- *设置能连接redis的最大客户端数量*
# maxmemory <bytes> ---------- *redis配置的最大内存*
# maxmemory-policy noeviction ---------- *内存到达上限的处理策略*
-noeviction(默认策略) :对于写请求不再提供服务,直接返回错误(DEL请求和部分特殊请求除外)
-allkeys-lru :从所有key中使用LRU算法进行淘汰
-volatile-lru :从设置了过期时间的key中使用LRU算法进行淘汰
-allkeys-random :从所有key中随机淘汰数据
-volatile-random :从设置了过期时间的key中随机淘汰
-volatile-ttl :在设置了过期时间的key中,根据key的过期时间进行淘汰,越早过期的越优先被淘汰
13.9,aof配置 APPEND ONLY MODE
appendonly no -------- *默认是不开启aof,默认是使用rdb方式持久化的*
/在一般情况下,rdb是完全够用的/
appendfilename "appendonly.aof" ---------- *持久化的文件的名字*
# appendfsync always *每次修改了值,都会sync(同步)*
appendfsync everysec *每秒执行一次, sync,可能会丢失这一秒的数据*
# appendfsync no *不同步,这个时候,操作系统自己同步数据,速度是最快的*
再具体的配置,我们在持久化中去
14,Redis持久化
是内存数据库,断电即失,所以需要持久化
14.1,RDB
在指定时间间隔内将内存中的数据集快照写入磁盘,也就是快照,恢复时,是将快照文件直接读到内存里。
redis会单独创建一个紫禁城来进行持久化,会先将数据写入到一个临时文件中,等到持久化都结束了,再用这个临时文件替换掉之前持久化好的文件,整个过程中,主进程是不进行任何io的,这就确保了很高额性能,如果要进行大规模的数据恢复,切对于数据的完整性并不是很敏感,RDB的方式要比AOF更加高效。
缺点:最后一次持久化后的数据可能丢失,我们默认就是RDB
RDB保存的文件是:dump.rdb
触发机制
save的规则满足的情况下,会自动触发rdb规则
执行了flushall,也会触发rdb规则,也会触发我们的rdb规则
退出redis,也会产生rdb文件
如何恢复rdb文件
只需要将我们的rdb文件放到我们的redis的启动目录下就可以了,redis会自己识别
优缺点
优点
适合大规模的数据
如果你对数据完整性要求不高,也是很好
缺点
需要一点时间间隔来进行操作,如果redis在这一段时间里宕机,那么数据就会丢失
fork进程的时候,会占用一定的内存空间
14.2,AOF
将我们所有的命令都记录下来,history,恢复的时候,就把这个文件全部执行一遍。
以日志的形式来记录每一个写操作,将redis个的所有指令记录下来,(读操作不记录。只能追加文件,但是不能改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次来完成数据恢复的工作。
要在config中去进行配置,之后重启生效。
如果这个aof文件有错误,这个时候redis是启动不起来的, 需要修复这个配置文件
redis给我们提供了一个工具 redis-check-aof --fix
这个工具的作用不是恢复,而是把错误的地方上修改,有可能会导致数据丢失
重写规则说明
aof默认的就是文件无线追加,文件会越来越大,这和rdb的清理方式是不一样的
如果aof的文件大于我们config中配置的大小,fork一个新的进程来将我们的文件进行重写
优缺点
rbd是全丢,但是aof只丢失错误的
优点
每一次修改都同步,文件的完整性会更好
默认每秒同步一次,可能会丢失一秒的数据
如果配置文件中修改从不同步,那么效率是最高的
缺点
相对于来说,aof远远大于rdb,修复速度比rdb慢
aof运行效率也是比rdb慢,所以 redis默认的配置就是rdb
15,Redis发布订阅
16, 复制
先要有不同的配置文件,注意改里面的 ,日志文件,等,然后redis-server开启多个服务
之后我们只改变从机的配置
slaveof 地址 端口号