redis概念
redis是一个由c编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。
- 在java中,redis的使用场景:spring cache(数据缓存)、 springsession(会话共享);这两个核心就是redis的start依赖和自身cache或者session依赖。
- spring cache的简单整合——spring cache在redis环境存在的情况下,只要自己的spring-boot-starter-cache依赖,然后cache这个接口的所有API的实现,就都自动化配置和封装好了。spring cache只要设置一些自身cache的属性+主配置类上@@EnableCaching就完成了cache的整合;springboot就自动注册了RedisCacheManager这个Bean。有这个bean我们可以直接使用cache的相关注解和接口了。缓存数据会自动和redis关联上了,自动存储到Redis了。
- spring session的简单整合——也是在redis环境存在的情况下,只要导入自己的spring-session-data-redis依赖,然后springboot的自动化配置就完成了session和redis的整合。会自动将项目中的session过滤转为redisSession,并且会话自动存到redis,获取也自动到redis上获取。当然Session 的配置还是自己在配置文件中自定义设置的——即在快速整合的介绍中 我们使用都是默认的配置。如果你想自定义定义 Session 过期时间 、session的刷新模式 、存储Session的命名空间。可以通过在 application.properties 中进行配置。
redis和数据库双写一致性问题
分析:一致性问题是分布式常见问题,还可以再分为最终一致性和强一致性。数据库和缓存双写,就必然会存在不一致
的问题。答这个问题,先明白一个前提。就是如果对数据有强一致性要求,不能放缓存。我们所做的一切,只能保证
最终一致性。另外,我们所做的方案其实从根本上来说,只能说降低不一致发生的概率,无法完全避免。因此,有强
一致性要求的数据,不能放缓存。
回答:首先,采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个
补偿措施即可,例如利用消息队列。
redis文档
redis菜鸟教程
redis基础分析
redis中存储中文数据而显示乱码解决方式
传统数据库的事务特性:acid,原子性、一致性、隔离性、持久性;其中隔离性是对于并发事务情形而言的,有四种隔离级别
(读未提交、读已提交、可重复读、串行化)
分布式数据库的事务特性:CAP(强一致性consistency、可用性availability、分区容错性partition tolerance);
任何一个分布式系统中都只能满足cap中两个条件。如单点集群满足ca(一致性和高可用特点);
而分布式中必须有P分区容错奥!!因为分布式系统之间通信肯定会有延迟丢包,通信是分布式最基础的,所以必须有容错余地,
那数据一致表示在分布式系统中的所有数据备份,在同一时刻是否同样的值,这个可灵活;高可用是集群中有某节点故障,集群整体
能否继续为客户端提供服务,这个也很重要。
事务就是一个具体业务的最小单元,也就是一个复合操作为一个单元。
特点
- 支持数据的持久化,可将内存中的数据保持到磁盘中,重启时候可以再次加载进内存使用,不会造成数据的丢失。
- redis支持五种类型的数据结构:简单的string、list、set、zset、hash等数据结构的存储。注意:redis中数据都是以key/value的形式存储的,五大数据类型也是指value的数据类型。
- 简述一下五个类型:string——就是简单的字符串;hash类型——是一个映射对集合;list——是一个简单的字符串列表(集合),集合中可以重复字符串元素;set类型——也是字符串类型集合,但是集合中元素不可以重复字符串,添加已存在的成员或者元素的话是无效的,且是无序的;zset(sorted set)——和set一样特点,是string类型元素的集合,且不允许重复成员,不同的是每个元素关联一个double类型的分数,redis通过这个分数为集合中成员从小到大排序;即zset成员唯一但是分数可以重复。
- redis中数据都是以key-value形式存储,但是redis中数据类型是指value的类型,而key类似于java中的一个对象(实例)变量,即redis中的数据都是以key/value的形式存储的,五大数据类型主要是指value的数据类型。如hash数据;key表示这个hash类型的变量名,而真正的hash数据是存储在value上的。
- redis支持master-slave(主备方式)的数据备份。
- 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
- 每一个redis实例默认有16个数据库,切换不同数据库使用select index。且数据库名不支持自定义,都是以编号命名。
- 外部客户端连接redis服务注意点:redis.conf配置文件中属性bind;这个属性表示本redis实例绑定的ip号,也就是说外部如果想访问我本redis实例,必须连接这个bind绑定的ip才能连接到我本redis实例; reids中属性bind配置了什么ip,别人就得访问bind里面配置的ip才访问到redis服务。或者bind 0.0.0.0等价于 不配置 bind 即注释掉bind
功能
- 内存存储和持久化:redis支持异步将内存中数据写到磁盘上,在持久化同时不影响数据存储服务。
- 获取最新的N个数据的操作。如:可以将最新的10条评论的ID放在Redis的List集合里面。
- 数据可以设置过期时间。
- 自带发布、订阅消息系统。
- 定时器、计数器。
五种数据类型介绍
redis中的数据都是以key/value的形式存储的,五大数据类型主要是指value的数据类型,包含如下五种:
- string——string字符串是redis中最基本的数据类型,redis中的STRING类型是二进制安全的,即它可以包含任何数据,比如一个序列化的对象、甚至一个jpg图片,要注意的是redis中的字符串大小上限是512M。即——我们得先了解下redis中字符串的存储方式,redis中的字符串都是以二进制的方式进行存储的。
- list——是一个简单的字符串列表,特点是按照插入顺序进行排序、元素可以重复。 命令以l开头。
- hash——hash类似于java中的map,是一个键值对集合,在redis中用来存储对象。命令以h开头。
- set——是string类型的无序集合(不按照插入顺序排列),不同于list,set中元素不可重复。命令以s开头。
- Zset (sorted set) ——ZSET和SET一样,元素不可重复,也是STRING类型的元素的集合,不同的是ZSET中的每个元素都会关联一个double类型的分数,且以分散来从小到大进行排序,ZSET中的成员都是唯一的,但是所关联的分数可以重复。命令以z开头。
相关命令
前言
首先通过redis-server redis.conf命令启动redis,再通过redis-cli -p xxx端号命令进入到对应端口号的redis实例的控制台中(默认是6379端口是不需要-p的)。
./bin/redis-cli -h 127.0.0.1 -p 6379 //如果不指定ip和端口的话,默认就是本机和6379端口。
redis中各类型通用命令
redis中,由于不同数据类型的数据结构本身有差异,因此对应的命令也会有所不同。但是有些命令无论哪种数据类型都是存在的,如下一些特殊通用命令:
- set k v ——插入键值对的命令
- del k ——删除存在的键对应的k/v数据
- exist k ——用来检测判断指定的k是否存在这条数据。返回0表示不存在,返回1表示存在。
- ttl k ——TTL命令(time to live存货时间)可以查看一个给定key的有效时间。返回-2表示该key不存在或者过期,-1表示key存在但是没有设置过期时间(永久有效);如果有其他整数,表示该键还可以存活时间。
- rename key newkey—— 为key重命名。
127.0.0.1:6379> EXPIRE k1 30 //表示k1这个键有效时间为30秒
(integer) 1
127.0.0.1:6379> TTL k1 //返回值表示k1键还有25秒的有效时间
(integer) 25
- keys * —— KEYS命令可以获取满足给定模式的所有key。如keys * 获取所有键信息。
- expire k 秒数 —— 表示设置指定的k存活时间。时间超时后就自动被毁灭了。如:EXPIRE k1 30 表示设置键k1 存活30秒,当然30秒后会被删错了。
- persist k ——表示键有生命周期的key给持久化,就是设置为永久存活
127.0.0.1:6379> EXPIRE k1 60
(integer) 1
127.0.0.1:6379> ttl k1
(integer) 57
127.0.0.1:6379> PERSIST k1
(integer) 1
127.0.0.1:6379> ttl k1 //time to live 存活时间,如果返回为-1,表示这个k1存在,且没有设置存活时间
(integer) -1
小结:
实际上虽然不同类型的数据结构的特性通过各自特有命令体现:但基本操作还是:增、删、查、判断。
一.string类型的——特有命令
- 追加命令——append key value; 表示在key对应的value后追加字符串,如果key存在就追加,如果key不存在,就相当于创建一个空字符串然后追加。
- 字符串最基础就是设值和获取——set key value;get key;有个特殊的set设置值命令:setnx key value;表示如果key存着,则设置的值无效,如果key不存在,设置的值有效。对比set和setnx,set会覆盖已存在的值,而setnx( SET if Not eXists)只设置不存在的key,安全点。
- 获取字符串指定范围的内容——getrange key start end;start是左边,end是右边。如果下标是负数,则从往左计算,如-1表示最后一个字符,-2表示倒数第二个。
127.0.0.1:6379> SET k1 helloworld
OK
127.0.0.1:6379> GETRANGE k1 0 2
"hel"
127.0.0.1:6379> GETRANGE k1 -3 -1
"rld"
- 批量设置值和批量获取值—— mset、mget;
127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> MGET k1 k2 k3
1) "v1"
2) "v2"
3) "v3"
- 对字符串的值进行增减操作——前提:这个字符串是数值,可以完成加一或者减一,或者加指定步长或者减指定步长,不是数组就会报错。decr key 表示将key对应的value值减一,incr key表示将key对应的value加一。decrby key 步长;incrby key 步长;incrbyfloat key 0.33 ;表示将key对应的value增加浮点数大小。
- 获取字符串的长度——strlen key;用来计算key的value的长度。
上面是对string数据类型的一些基本命令,没有涉及到BIT的相关命令,了解这些命令前提——需要了解redis中字符串的存储方式,redis中字符串都是以二进制的方式进行存储的。
- getbit——返回key对应的值的二进制的bit值。如set k a;a对应的ascii是97,转为二进制是01100001,bit相关命令都是对二进制数据进行操作的,类似于位操作命令。
set k a ;//保存一个键为k,值为字符串a的数据,a在内存中以01100001存储 a>01100001
getbit k 0 ;//表示获取k键对应值的第一个位的二进制值;从左往右方向,所以第一个是0
getbit k 1 ;——>1
getbit k 2 ;——>1
getbit k 3 ;——>0
getbit k 4 ;——>0
getbit k 5 ;——>0
getbit k 6 ;——>0
getbit k 7 ;——>1
- setbit——就是设置key对应的bit的二进制值。如 a>01100001;setbit k 6 1;表示将key对应的 a>01100001的第七位二进制设置为1,变成了01100011——>字符串c,该命令有返回值返回的是本位上原本的bit值;
二.列表和集合类型的——特有命令
列表是redis中另一种数据类型,前面知道redis数据都是以key/value形式存储,而不同数据类型是指value的数据类型。所以列表类型,就是指某个key,对应的值是一个(多数据组成的)列表。
2.1 列表命令——list类型
就类似于队列的数据结构:有入队和出队;入队有从左往右元素入队,或者从右往左入队,出队有移除队尾,或者移除队头元素。
- lpush——将一个或多个值value插入到列表key的表头,如果有多个value值,那么各个value值按从左到右的顺序依次插入到表头(从表头处插入),如下:
//值按照从左到右顺序插入到表头,所以最后一个v3是在表头的。
127.0.0.1:6379> LPUSH k1 v1 v2 v3
(integer) 3
- lrange——返回列表key中指定区间内的元素;lrange k start(0) stop(-1);0表示第一个元素,1表示第二个元素,-1表示最后一个(倒数第一个),-2表示倒数第二个元素。
lrange k1 0 -1 //这个返回的就是按照第一个到最后一个元素顺序返回的列表信息。
//返回列表中数据,从第一个到最后一个元素顺序返回,说明第一个元素是v3
127.0.0.1:6379> LRANGE k1 0 -1 //这个返回的就是按照第一个到最后一个元素顺序返回的列表信息。
1) "v3"
2) "v2"
3) "v1"
- rpush——和lpush功能基本一致,都是插入列表数据,不同是元素是从右往左的顺序插入到表头(即从表头位置插入数据,从右往左,一个一个从表头插入,所以最后一个正好在表头处。)
127.0.0.1:6379> RPUSH k2 1 2 3 4 5
(integer) 5
127.0.0.1:6379> LRANGE k2 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
- rpop——表示移除列表中尾元素并返回列表中该尾元素 ; 如果不确定列表中哪个元素是尾元素,可以通过标准的lrange k1 0 -1 //这个返回的就是按照第一个到最后一个元素顺序返回的列表信息。获取列表信息来判断。
127.0.0.1:6379> RPOP k2
"5"
127.0.0.1:6379> LRANGE k2 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
- lpop——和上面rpop类似,都是移除元素,和上面不同是lpop是移除列表的头元素并返回列表的该头元素。
127.0.0.1:6379> LPOP k2
"1"
127.0.0.1:6379> LRANGE k2 0 -1
1) "2"
2) "3"
3) "4"
- lindex——可以返回列表中指定索引处的元素。索引特点:0表示第一个元素,1表示第二个,以此类推…,-1表示倒数第一个,-2表示倒数第二个。
127.0.0.1:6379> LINDEX k2 0
"2"
127.0.0.1:6379> LINDEX k2 -1
"4"
- ltrim——对列表进行修剪,即让列表中只保留指定区间内的元素。不在指定范围内的元素将被删除。
127.0.0.1:6379> LRANGE k1 0 -1
1) "v3"
2) "v2"
3) "v1"
127.0.0.1:6379> LTRIM k1 0 1
OK
127.0.0.1:6379> LRANGE k1 0 -1
1) "v3"
2) "v2"
2.2 集合常见操作命令
和下面有序集合区分开,有序集合是有一个标准来判断排序,这边是无序集合,因为没有某个标准来判断排序。
存储set类型——set
- sadd key value…——可以添加一个或者多个指定的member元素到集合key对应的value中;一些情况:指定的元素如果在集合key对应的value中存在了,就忽略;当然如果集合key都不存在,则新建集合key,并添加member元素到集合key的value中。
127.0.0.1:6379> SADD k1 v1 v2 v3 v4
(integer) 4
- srem key value ——SREM命令可以在key集合中移除指定的元素,如果指定的元素不是key集合中的元素则忽略。如果key集合不存在则被视为一个空的集合,该命令返回0。如下:
127.0.0.1:6379> SREM k1 v2 // 表示移除集合k1中,元素为v2那个数据
(integer) 1
127.0.0.1:6379> SREM k1 v10 //表示移除元素v10,但是v10不存在这个元素,所以返回为0
(integer) 0
- sismember——返回某成员元素是否是存储的集合key的成员。用于判断指定的元素是否存在于指定的集合key中,是否是集合key中的成员。
127.0.0.1:6379> SISMEMBER k1 v3
(integer) 1
- scard—— 返回指定key集合中存储的元素的基数(集合元素的数量),如下:
127.0.0.1:6379> SCARD k1
(integer) 3
- smembers key ——查询集合key中所有元素。
127.0.0.1:6379> SMEMBERS k1
1) "v4"
2) "v1"
3) "v3"
- srandmember key 个数 —— 表示随机返回集合中指定个数的元素。如果个数大于集合中实际存在的总个数,则返回集合中所有元素。如果个数是负数,还是会返回个数绝对值的个数元素。但是负数特殊情况是:如果负数的绝对值大于集合中总个数,则返回的结果中会出现某个元素多次的情况。
127.0.0.1:6379> SRANDMEMBER k1
"v4"
127.0.0.1:6379> SRANDMEMBER k1 2
1) "v4"
2) "v1"
127.0.0.1:6379> SRANDMEMBER k1 5
1) "v4"
2) "v1"
3) "v3"
127.0.0.1:6379> SRANDMEMBER k1 -1
1) "v4"
127.0.0.1:6379> SRANDMEMBER k1 -5
1) "v3"
2) "v1"
3) "v1"
4) "v3"
5) "v3"
- spop——和上面srandmember用法类似,但是该命令除了返回元素还删除该元素。(SPOP命令的用法和SRANDMEMBER类似,不同的是,SPOP每次选择一个随机的元素之后,该元素会出栈,而SRANDMEMBER则不会出栈,只是将该元素展示出来。)
- smove (smove source destination member)—— SMOVE命令可以将member从source集合移动到destination集合中,如下:
127.0.0.1:6379> SMOVE k1 k2 v1
(integer) 1
127.0.0.1:6379> SMEMBERS k1
1) "v4"
2) "v3"
127.0.0.1:6379> SMEMBERS k2
1) "v1"
- sdiff key1 key2 ——用来返回key1集合对应key2集合中差异的元素(key1对比key2中不同的元素返回)
k1中的元素是v3、v4, k2中的元素是v1,差集就是v3、v4.
127.0.0.1:6379> SDIFF k1 k2
1) "v4"
2) "v3"
- sdiffstore—— SDIFFSTORE命令与SDIFF命令基本一致,不同的是SDIFFSTORE命令会将结果保存在一个集合中,如下:
127.0.0.1:6379> SDIFFSTORE key k1 k2
(integer) 2
127.0.0.1:6379> SMEMBERS key
1) "v4"
2) "v3"
- sinter —— 上面是集合之间的差集,那么肯定有交集呀!
127.0.0.1:6379> SMEMBERS k1
1) "v4"
2) "v3"
127.0.0.1:6379> SMEMBERS k2
1) "v1"
2) "v3"
127.0.0.1:6379> SINTER k1 k2
1) "v3"
- sinterstore ——这个肯定就是将交集返回的结果保存到一个新的集合中。
127.0.0.1:6379> SINTERSTORE k3 k1 k2
(integer) 1
127.0.0.1:6379> SMEMBERS k3
1) "v3"
- sunion——集合之间的并集。
127.0.0.1:6379> SUNION k1 k2
1) "v4"
2) "v1"
3) "v3"
- sunionstore——集合间并集的存储到一个新集合;
127.0.0.1:6379> SUNIONSTORE k4 k1 k2
(integer) 3
127.0.0.1:6379> SMEMBERS k4
1) "v4"
2) "v1"
3) "v3"
存储hash类型——这里value存放的是结构化的对象
就是键值对中的值是一个映射对——field/value对;
1.赋值
hset key field value : 为指定的key设定field/value对
hmset key field1 value1 field2 value2 field3 value3 为指定的key设定多个field/value对
2.取值
hget key field : 返回指定的key中的field的值
hmget key field1 field2 field3 : 获取key中的多个field值
hkeys key : 获取所有的key
hvals key :获取所有的value
hgetall key : 获取key中的所有field 中的所有field-value,上面是field,接着就是对应的value。
3.删除
hdel key field[field…] : 可以删除一个或多个字段,返回是被删除的字段个数
del key : 删除整个list
4.增加数字
hincrby key field increment :设置key中field的值增加increment,如: age增加20
hincrby myhash age 5
自学命令:
hexists key field : 判断指定的key中的field是否存在
hlen key : 获取key所包含的field的数量
hkeys key :获得所有的key
hkeys myhash
hvals key :获得所有的value
hvals myhash
127.0.0.1:6379> HMSET runoobkey name "redis tutorial" description "redis basic commands for
caching" likes 20 visitors 23000
OK
127.0.0.1:6379> HGETALL runoobkey
1) "name"
2) "redis tutorial"
3) "description"
4) "redis basic commands for caching"
5) "likes"
6) "20"
7) "visitors"
8) "23000"
三. sortedset类型的存储结构——特有命令
首先,Redis 有序集合和集合一样也是string类型元素的集合,都不允许重复的成员。
有序集合存储特点:首先redis中所有数据都是key/value形式存储的,但是有序集合是指value是一个多元素组成的集合,而且可以按照元素关联的某个标准(value处额外包含一个新字段score)进行排序。
- zadd—— 向集合中添加元素(或者成员),同时可以设置元素排序的标准【每个元素都关联一个叫score浮动数值,从而通过score从小到大进行排序】
语法:
zadd key score member [score member ...]
案例:
127.0.0.1:6379> zadd keyset 2 v2 5 v5 1 v1 //表示向有序集合keyset中添加了三个元素,
(integer) 3 //对应的score分别为2,5,1 ,所以他们排序顺序按照这个大小来排序
- zscore —— 查看指定有序集合中指定成员或者元素对应的score值。可以用来判断排序情况。
127.0.0.1:6379> ZSCORE keyset v2
"2"
- zrange—— 通过指定索引index来查看有序集合中各元素或者成员。该命令在执行时加上withscores参数可以连同score一起返回:
127.0.0.1:6379> zrange keyset 0 -1
1) "v1"
2) "v2"
3) "v5"
================================
127.0.0.1:6379> zrange keyset 0 -1 withscores
1) "v1"
2) "1"
3) "v2"
4) "2"
5) "v5"
6) "5"
- zrevrange—— 和上面一样,是返回有序集合中指定索引范围的元素。但是是反着来的。
127.0.0.1:6379> zrevrange keyset 0 -1
1) "v5"
2) "v2"
3) "v1"
================================
127.0.0.1:6379> zrevrange keyset 0 -1 withscores
1) "v5"
2) "5"
3) "v2"
4) "2"
5) "v1"
6) "1"
- zcard—— 返回有序集合key中元素个数
127.0.0.1:6379> ZCARD keyset
(integer) 3
- zcount key min max—— 返回集合key中元素个数,但是是基于score的值在min和max之间的元素个数。默认边是包含的,如果不包含需要加一个(
127.0.0.1:6379> ZCOUNT k1 60 90
(integer) 4
=======================
//如果在统计时,不需要包含60或者90,则添加一个 ( 即可,如下:
127.0.0.1:6379> ZCOUNT k1 60 (90
(integer) 3
- 查询有序集合中元素——zrange(基于索引来查集合中元素)、zrangebyscore(按照score范围来查找对应的元素)
- zrank key member ——用来查询有序集合key中指定成员的排名次序;其中有序集成员按score值递增(从小到大)顺序排列。排名第一以0为表示,即score值最小的成员排名为0,在第一位。
- zrevrank key member —— 这个是先将集合中元素顺序反正了,然后在查询指定成员的排名次序,这样排名次序和上面就是反过来了,大的边小,小的边大了。
- zrem key member —— 删除集合key中指定成员。
127.0.0.1:6379> ZRANGE k2 0 -1 withscores
1) "v1"
2) "2"
3) "v2"
4) "3"
5) "v3"
6) "4"
127.0.0.1:6379> ZREM k2 v1
(integer) 1
127.0.0.1:6379> ZRANGE k2 0 -1 withscores
1) "v2"
2) "3"
3) "v3"
4) "4"
- zlexcount key min max —— 返回指定有序集合指定成员之间的成员数量;注意包含指定的成员奥! 注意:可以用-和+表示得分最小值和最大值,如果使用成员名的话,一定要在成员名之前加上[。
127.0.0.1:6379> ZLEXCOUNT k2 - +
(integer) 2
127.0.0.1:6379> ZLEXCOUNT k2 [v2 [v4
(integer) 2
- zrangebylex key min max —— 返回指定集合 指定成员之间的成员;上面是成员数量,这个是成员名称。
127.0.0.1:6379> zrangebylex k2 [v2 [v4
1) "v2"
2) "v3"
127.0.0.1:6379> zrangebylex k2 - +
1) "v2"
2) "v3"
127.0.0.1:6379>
四.redis中的发布订阅和事务
4.1 发布订阅
redis的发布和订阅系统类似于我们生活中的电台,电台可以在某一个频率上发送广播,而我们可以接收任何一个频率的广播。简言之:就是你订阅了哪个电台,那么哪个电台发布信息的话,你就能接收到。
- 订阅消息的方式如下:表示接收c1 、 c2 、 c3三个频道传来的消息,
127.0.0.1:6379> SUBSCRIBE c1 c2 c3
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "c1"
3) (integer) 1
1) "subscribe"
2) "c2"
3) (integer) 2
1) "subscribe"
2) "c3"
3) (integer) 3
- 上面订阅了某个信道后,那某个信道发布消息方式为:
127.0.0.1:6379> PUBLISH c1 "hello redis!"
(integer) 1
- 当c1频道上有消息发出时,此时在消息订阅控制台上,它一直在监控这订阅的那些信道,所以会收到:
1) "message"
2) "c1"
3) "hello redis!"
4.2 事务
redis也是一种nosql数据库,所以也会有事务的功能,只不过和关系型数据库中事务有些差异。redis中事务用法很简单。简述为:multi开启事务,然后后面所有命令都是入队到事务中,注意并没有执行,最后由exec命令触发事务,一并执行事务中所有命令。
特点:(1)事务开启后,但是exec执行前,批量操作被放入队列中缓存、(2)执行命令后,事务中任意命令执行失败,其余命令依然执行、(3)事务执行过程中,其他客户端提交的命令不会插入到事务命令序列中。
- 首先通过multi 命令开启一个事务;
127.0.0.1:6379> MULTI
OK
- 开启事务后,下面就可以指定redis相关数据操作命令,但是这些命令不会被立即执行,而是放在一个队列中;
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
- 最后,之前的命令并没有被执行,需要通过exec命令发起执行,也可以通过discard命令清空队列,如下:
127.0.0.1:6379> EXEC
1) OK
2) OK
3) OK
- 上面事务中异常情况总的分为两类:一种是没遵守命令语法,进入队列之前就能发现错误(如:命令输出【set k3 v3 3 3】)、还有一种是执行exec后才能发现的错误(如:给非数字元素加减操作。)
- redis对于异常处理策略:第一种是在事务执行exec命令时,拒绝执行并自动放弃那一个错误的命令;第二种redis并没有对这种情况有特殊处理,直接显示执行错误的原因,对其他命令不影响,其他命令任然会继续执行。
- redis单个命令是原子性的,但是redis事务的执行并不是原子性的,即redis事务可以理解为打包的批处理执行脚本,内部某个命令执行失败不会导致前面已经做过指令的回滚,也不会造成后续指令不做。
单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行
并不是原子性的。事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失
败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
五.其他命令
- info——获取redis服务器的统计信息。
- select index ——切换到不同数据库。
- dbsize —— 返回当前数据库的 key 的数量。
- flushdb —— 清空当前数据库中所有key。
- flushall —— 删除所有数据库的所有key。这个太狠了,注意点
- role —— 返回本redis实例所属的主从角色。
- sync —— 同步主从服务器上数据。
- save —— 同步保存数据到硬盘;将当前 Redis 实例的所有数据快照(snapshot)以 RDB 文件的形式保存到硬盘,最终生成一个dump.rdb文件。 还有一个aof文件(Appendonly file);bgsave——异步保存数据中数据到磁盘。BGSAVE 命令执行之后立即返回 OK ,然后 Redis fork 出一个新子进程,原来的 Redis 进程(父进程)继续处理客户端请求,而子进程则负责将数据保存到磁盘,然后退出。
- config set parameter value—— 修改redis配置参数,无需重启。
redis 127.0.0.1:6379> CONFIG GET slowlog-max-len
1) "slowlog-max-len"
2) "1024"
redis 127.0.0.1:6379> CONFIG SET slowlog-max-len 10086
OK
redis 127.0.0.1:6379> CONFIG GET slowlog-max-len
1) "slowlog-max-len"
2) "10086"
- config get parameter —— 获取指定配置参数的值。
- config rewrite —— 对启动 Redis 服务器时所指定的 redis.conf 配置文件进行改写。或者认为将新修改的配置写入服务器的当前配置中。
- config get dir —— 获取redis安装目录路径或者快照文件生成的位置;
六.redis持久化
redis持久化有两种方式:快照持久化(RDB)和AOF;
aof存储是将redis执行过程中的所有写指令以追加的方式写到一个文件,这个文件通常是appendonly.aof。这个写指令包括
string类型的set,delete,incr,incrby,decrby,append等,list类型的rpush,rpop,lpush,lpop,lset等,set类型的
sadd,srem等,有序set类型的zadd,zrem,zincrby等,哈希类型的hset,hdel,hmset,hincrby等。
rdb存储和aof存储可以同时开启,在redis重启后会优先采用aof存储进行恢复,因为aof存储的数据更加完整。
1.快照持久化——内存数据的备份
顾名思义,通过拍摄快照的方式实现数据的持久化(数据的备份)在某个时间点对内存中的数据创建一个副本文件,副本文件中的数据在redis重启是会被自动加载,我们也可以将副本文件拷贝到其他地方也可以使用。** 简单说:快照就是内存中数据的备份,重启会自动将这些数据加载进内存(如果需要恢复数据,只需将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可)。
如何配置快照
redis中的快照持久化默认是开启的。但是有几个配置项可以需要了解:
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
dbfilename dump.rdb
dir ./
- 其中save相关选项表示备份的频率:900秒内至少一个键被修改则进行快照、300秒内至少10个键被更改则进行快照、如果有10000个键被修改则60内就会进行快照。
- stop-writes-on-bgsave-error表示在快照创建出错后,是否继续执行写命令。
- rdbcompression则表示是否对快照文件进行压缩。
- dbfilename表示生成的快照文件的名字,默认快照名为:dump.rdb;dir则表示生成的快照文件的位置。
注意:每次redis退出前都会默认执行save来生成一个快照来保存内存中数据的。
快照持久化操作流程
实际上就是redis发送save或者bgsave命令,来讲数据备份持久化到硬盘保存。但是save是一个阻塞操作,即在备份数据时其他请求不能操作会被挂起,所以用的不多,bgsave创建一个子进程来负责将快照写入硬盘,这样不影响父进程处理客户端请求,不会有阻塞情况。(常用,配置文件中save属性也是自动触发bgsave命令进行备份的)
- shutdown命令关闭redis是,会执行save命令进行数据备份的。
- 主从同步数据时;即当从机连接上主机后,会发生一条sync命令来开始数据同步,此时主机会开始bgsave操作备份此刻最新数据,并在bgsave操作后向从机发生备份的快照文件实现数据同步;上面知道dump.rdb文件拷贝到其他地方一样可以使用。
- 快照持久化备份数据特点:save命令会发生阻塞,bgsave虽然不会发生阻塞,但是fork一个子进程会耗费资源,甚至极端情况,fork子进程的时间超过备份数据时间,定期持久化存在数据丢失风险,最坏可能丢失最近一次备份到此刻的数据,可以根据项目的承受能力,来配置save参数。
2.AOF久化——执行命令的追加(记录事务操作行为的备份)
与快照持久化备份数据不同,AOF持久化是将创建数据而执行的命令写入到aof文件末尾,在恢复时需要从头到尾执行一遍写可以恢复所有数据,AOF在redis中默认是没有开启的。
开启AOF持久化机制:
appendonly yes
其他和AOF相关的配置:
appendfilename “appendonly.aof”
# appendfsync always
appendfsync everysec
# appendfsync no
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
- appendfilename表示生成的AOF备份文件的文件名。
- appendfsync表示备份的时机,always表示每执行一个命令就备份一次,everysec表示每秒备份一次,no表示将备份时机交给操作系统。通常everysec选项是首选(最坏丢一秒数据),always会降低redis性能。
- no-appendfsync-on-rewrite表示在对aof文件进行压缩时,是否执行同步操作。
- 最后两行配置表示AOF文件的压缩时机,这个我们一会再细说。
演示aof持久化
(1)为了避免快照备份的影响,关闭快照备份,关闭方式如下:
save ""
# save 900 1
# save 300 10
# save 60 10000
这个save属性设置为“”后,之后shutdown退出redis后,就不会有备份数据文件dump.rdb文件了。
(2)但是可以在客户端中设置aof持久化的方式:由于默认aof没有开启,所以需要设置属性:
config set appendonly yes //表示开启aof持久化方式。其他可以使用默认配置。当然也可以自定义;
(3)下面shutdown关闭redis后,就会有appendonly.aof文件的生成,这个里面就保存所有数据创建时的执行性命令操作;以后新的数据创建的命令都会追加到这个文件末尾中;
aof的重写和压缩
AOF备份有很多明显的优势,但是也有劣势,就是文件的大小,AOF是追加的,所以文件会越来越大,所以AOF的重写和压缩机制一定程度上可以缓解这个问题。
- 当AOF的备份文件过大时,我们可以手动向redis发送bgrewriteaof命令进行文件的重写。
127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started
(0.71s)
- bgrewriteaof的执行原理和上文bgsave的原理一致,当然aof重写机制也会自动执行,就像save备份数据命令也会在关闭时自动执行,当然手动人为控制也很重要,自动执行时间是依赖于auto-aof-rewrite-percentage和auto-aof-rewrite-min-size配置,auto-aof-rewrite-percentage 100表示目前aof文件大小藏上次重写的aof文件大小的百分比时会再次进行重写,如果之前没有重写过,则以启动是aof文件的大小为依据,同时要求AOF文件的大小至少要大于64M(auto-aof-rewrite-min-size 64mb这是属性设置的)。
3.redis的持久化方式选择——快照(RDB文件)持久化和AOF持久化
(1)如果redis只是做缓存服务器,那么可以不使用任何持久化方式
(2)如果两种持久化方式都开启了,那么当redis重启时,会优先载入AOF文件来恢复数据,因为通常情况下AOF中保存的数据比RDB文件保存的数据要完整,那么rdb备份还需要吗?当然需要,RDB更适合于备份数据库(AOF在不断的变化不好备份),快速重启。也可以适当缓解AOF存在的劣势,作为多一个后备的用途。
(3)因为RDB文件只用作后备用途,建议只在slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。
(4)启动AOF持久化方式,好处是最差情况也只会丢失不到2秒的数据,启动脚本较简单只load自己的aof文件就可以了,但是劣势是:带来持久化的io,还有是数据满了时候,重写过程会造成阻塞,这是不可避免的。最好减少重写的频率,aof文件大小设置64M有点小,可以设置5G以上。
(5)如果不Enable AOF ,仅靠Master-Slave Replication 实现高可用性也可以。能省掉一大笔IO也减少了rewrite时带来的系统波动。代价是如果Master/Slave同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个
七.redis的主从复制
主从复制可以一定程度上扩展redis性能,从机能精确的复制主机上的内容,实现了主从复制之后,一方面能够实现数据的读写分离,降低master的压力;redis中主从机制核心就是在从机上配置slaveof 127.0.0.1 6379(主机ip+端口)来完成的,另一方面也能实现数据的备份。【集群可以实现系统的性能的高可用和高伸缩,因为集群中各节点都对等的提供服务(且有对多服务器的负载均衡),所以总的性能肯定提高很多;难点是各节点如何完全对等且高效连接】
1.配置方式
- 首先主从就是多个独立的配置文件,配置文件中配置整个集群中所有redis实例的信息——主机:端口号;
192.168.248.128:6379
192.168.248.128:6380
192.168.248.128:6381
- 各redis实例的socket信息配置后,接着配置各自redis实例需要的配置文件中的其他相关属性。
port 6379
pidfile /var/run/redis_6379.pid
logfile "6379.log"
dbfilename dump6379.rdb
appendfilename "appendonly6379.aof"
- 不同redis实例的配置文件配置后,基于自己的配置文件打开各自redis实例服务器。
[root@localhost redis-4.0.8]# redis-server redis6379.conf
[root@localhost redis-4.0.8]# redis-server redis6380.conf
[root@localhost redis-4.0.8]# redis-server redis6381.conf
- 服务器启动后,接着打开各自服务器对应的客户端,就完成了不同redis实例组合主从集群的过程了。
[root@localhost redis-4.0.8]# redis-cli -p 6379
[root@localhost redis-4.0.8]# redis-cli -p 6380
[root@localhost redis-4.0.8]# redis-cli -p 6381
- 主从集群完成了,但是如果想要设置集群中redis实例指定的主从关系,可以通过:假设在三个实例中,想要6379是主机,即master,6380和6381是从机,即slave。那么可以在6380和6381从实例上执行如下命令:
这一步也可以通过在两个从机的redis.conf中添加如下配置来解决。
127.0.0.1:6381> SLAVEOF 127.0.0.1 6379//表示本redis实例是隶属于6379的redis实例。或者表示从6379实例上复制数据
OK
- 主从关系搭建好后,可以查看每个实例当前的状态:如在主机6379客户端上通过——INFO replication 可以看到6379是一个主机,上面挂了两个从机,两个从机的地址、端口等信息都展现出来了、从机6380客户端上通过——INFO replication 可以看到看到6380是一个从机,从机的信息以及它的主机的信息都展示出来了
127.0.0.1:6379> INFO replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=56,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=56,lag=0
master_replid:26ca818360d6510b717e471f3f0a6f5985b6225d
master_replid2:0000000000000000000000000000000000000000
2.redis主从复制注意事项
- 从机复制主机数据是完整复制,和从机连接时间点无关;
- 主机可读可写,从机只可读不可写,但是可以修改配置文件的slave-read-only的值让从机也可读可写。
- 如果设置了指定的slaveof xxx:端口。那么该主机不幸挂掉,重启之后,它依然是主机。
3.redis主从复制原理
- redis集群中master会有一个replication ID,这是一个伪随机字符串,用来标识某个给定的数据集的。
- 同时master还持有一个偏移量(offset),这个偏移量表示将自己产生的复制流发送给slave从机时,发送多少个字节数据,自身的偏移量就会增加多少,这样如果自身有新操作修改自己的数据集时,可以基于这两个状态量来更新slave状态。
- 复制偏移量即使在没有一个slave连接到master是,也会自增的,所以基本上每一对给定的replication ID、offset都会标识一个master数据集的版本。
- 从机同步主机数据方式——当slave连接到master时,它们使用PSYNC命令来发送它们记录的旧的master replication ID和它们至今为止处理的偏移量。通过这种方式,master能够仅发送slave所需的增量部分,这样发送同步的io就少一点
- 但是如果master不知道从机的数据集版本对信息,会进行一个全量重同步:在这种情况下,slave会得到一个完整的数据集副本,从头开始。
4.redis的哨兵模式
4.1前言
实际上,之前发现,主从模式都是在配置文件中设置好的,如设置6379为主,其他6380、6381为从,所以就算6379挂了,然后重启,它依然是主机,但是就会出现群龙无首的局面。个人对zk那边的集群内主备选举有些了解可知,一个集群中配置好集群相关信息后,内部进行选举完成主的产生,即使主挂了,只要集群中主机不低于选举模式需要的个数,还是会继续内部选举一个新主。但是redis这边内部选举就是通过哨兵模式手动配置的,不是默认内嵌的。
4.2 哨兵原理和配置过程
所谓的哨兵模式,其实并不复杂,我们还是在我们前面的基础上来搭建哨兵模式。假设现在我的master是6379,两个从机分别是6380和6381,两个从机都是从6379上复制数据;开始配置哨兵机制。
在sentinel.conf文件中配置:
sentinel monitor mymaster 127.0.0.1 6379 1
mymaster是给监控的主机取的别名,名字随便取,最后1表示有多少个哨兵任务主机挂掉,才进行切换(这边表示有一个哨兵感知到被监控的主机挂掉,就进行新主机的切换)。
启动哨兵
redis-sentinel sentinel.conf
之后不用管哨兵了,正常启动主从模式,只要该模式中主挂了,redis内部就会重新选举,就和zk中内部选举一样,就主启动也不再是扛把子奥!
细节
由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,
当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。因此我们还需要集群
来进一步提升redis性能,这个问题我们将在后面说到。
八.redis集群
集群的目的是:如果中心化的服务器,那么访问量大时必定亚历山大或者挂掉,所以出现了集群;集群配置可以用于提高系统可用性和可缩放性;
redis集群中所有的redis节点彼此互联,而且内部使用二进制协议优化传输速度和带宽。
(1)首先明确集群是去中心化的,即集群中每个节点都是平等、对等的。集群中每个节点都只保存各自的数据和集群的整体状态,但是每个节点都和其他所有节点相连,且同时这些连接是活跃的,所有才能保证我们只要连接集群中任意一个节点,就可以获取到其他节点的数据。
(2)由于集群中各节点是对等、平等的,那么提供服务也是对等的(负载均衡的),即传统集群使用一致性哈希来提供服务或者数据;但是redis中使用哈希槽的方式来提供服务(进行写操作)。
(3)哈希槽——redis集群中默认分配了16384个slot(槽),然后基于集群中对等节点数进行均衡分配这些槽;每当redis集群中有键值对数据写入,则redis会用CRC16算法对key算出一个值【CRC16(key)%16384】(Redis先对key使用crc16算法算出一个结果,然后把结果对 16384 求余数,这样每个key都会对应一个编号在 0-16383 之间的哈希槽),最后根据这个哈希槽值redis会自动决定分配到那个节点上从而进行节点的切换来保存数据。
(4)上面集群会进行类似负载均衡一样,分配写服务保存数据,redis集群明确将数据存在指定哈希槽的节点上,然后这个节点和其对应的主从模式会进行数据的同步和备份;
(5)上面是集群的写过程,当然读也是这样,也根据一致性哈希算法到对应的master节点获取数据,只有当节点中master挂掉后,这个节点内部的主从会进行选举新主继续为一个独立对等的节点。
目前理解的主从复制模式和集群之间的关系:
主从复制:是多个服务器实例之间,有层级关系组成的一个独立的对外服务。 有时候主从复制会理解为小集群(一个分片)
集群:是多个独立的上面主从复制模块(这边称为一个节点);即多个独立、对等节点共同合作对外提供服务,由于各个节点是对等
、独立的,所以一般是共同合作对外提供服务,集群中每个节点保存这个集群对外提供服务的一个子集,所以集群
会有负载均衡、分区(就是各节点负责某一子集数据;redis的分区:分区是分割数据到多个Redis实例的处理过程,
因此每个实例只保存key的一个子集);集群挂掉——如果集群任意master挂掉,且当前master没有slave.集群进入
fail状态,
****************也可以理解成集群的slot映射[0-16383]不完整时进入fail状态,所以说集群是大家共同服务**************
九.redis实例的java客户端连接——jedis或者spring data redis
重点:redis服务器中配置文件里一个属性bind要特别注意,否则连接不上redis服务器。
bind 127.0.0.1 //bind配置了什么ip,别人就得访问bind里面配置的ip才访问到redis服务。
或者配置:
bind 0.0.0.0 //表示所有ip都可以访问到;0.0.0.0,最特殊的一个IP地址,代表的是本机所有ip地址,
不管你有多少个网口,多少个ip,如果监听本机的0.0.0.0上的端口,就等于监听机器上的所有IP端口。
bind 0.0.0.0等价于 不配置 bind 即注释掉bind
这样配置的意思是,要访问到我的redis服务就只能通过127.0.0.1这个ip来访问,额。。。那这样不管是哪台机都不可能访问到啦,一输入这个127.0.0.1,就连到自己本地了。这样恰好又起到了只能本地访问的效果,所以网上的误会也就这样产生了。以为bind配置了哪个IP,就得对应的IP才能连接和访问。
9.1 java 操作redis ——jedis
首先外部想要连接linux上redis实例(服务器),需要检查几点配置:
- 关闭linux防火墙,centos7之后用systemctl stopfirewalld.service。之前用service iptables stop;
- 关闭redis配置文件中protected-mode属性,这个属性表示redis实例是否有保护模式
- 配置redis配置文件中bind属性;这个表示该redis的端口能够监控指定的ip,这个ip才可以连接到本redis服务器上。或者理解为本redis服务绑定的ip,这个ip对外暴露本redis服务或者理解为redis端口监控这个ip,其他ip没有关联和监控,没有反应。
上面三步之后,就可以远程连接redis了。
原生jedis的依赖。
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
最简单的连接测试;
@Test
public void first() {
// reids中属性bind配置了什么ip,别人就得访问bind里面配置的ip才访问到redis服务端;
//就是bind的ip就是该redis服务端的ip,所以连接这个ip才能访问这个redis服务。
Jedis jedis = new Jedis("10.10.16.192", 6379);
String ping = jedis.ping();
System.out.println(ping);
}
上面是jedis框架连接redis的方式;通过jedis对象来操作redis就OK了,Jedis类中方法名称和redis中的命令基本是一致的,
看到方法名小伙伴就知道是干什么的。但是频繁的创建和销毁连接影响性能,通常采用连接池来解决:
public static void main(String[] args) {
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
config.setMaxTotal(100);
config.setMaxIdle(20);
JedisPool jedisPool = new JedisPool(config, "192.168.248.128", 6379);
Jedis jedis = jedisPool.getResource();
System.out.println(jedis.ping());
}
上面是创建redis客户端的jedis对象,和自己手动来配置连接池而完成实例对象的频繁创建和销毁的方式;
下面是springboot集成且自动配置好所有的方式。
************************ 使用springboot的redis自动配置方式的 spring data redis技术 ******************
@Test
public void test_set_get() {
//springboot自动配置注册好redistemplate进入容器,然后在需要的类中自动注入获取。类似于jdbctemplate。
ValueOperations opsForValue = redistemplate.opsForValue();
opsForValue.set("key_java", "javatest");
Object object = opsForValue.get("key_java");
System.out.println(object);
}
9.2 分析springboot内嵌的spring data redis(SDR)——细节注意
springboot中,默认集成的redis就是spring data redis,SDR它封装出一个redistemplate对象来对redis进行各种操作,它支持所有的redis原生的api;
经过Spring Boot的整合封装与自动化配置,在Spring Boot中整合Redis已经变得非常容易了,开发者只需要引入
Spring Data Redis依赖,然后简单配下redis的基本信息(连接redis服务器信息和资源池信息),系统就会提供一个
RedisTemplate供开发者使用
注意:客户端和redis组件服务端交互过程中注意序列化问题
对比redis中存和取 key-value的编码问题,核心注意:key和value这两个都要注意编码或者序列化问题,有时候key是string编码或者jdk编码,而value是jdk或者string编码。
===== 特别注意好key和value 这两部分的序列化问题。不同序列化存储时,用key取的时候value容易取空==========
- 首先导入相关的依赖
下面redis依赖实际上为了引入redis连接池的。当然spring data redis默认lettuce连接池。
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<!-- <version>2.1.0</version> -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
- 由于是spring-boot-starter-data-redis——所以是springboot封装的自动化配置,自动完成操作是连接redis服务器、连接池、序列化方案等过程;但是需要提供redis连接信息和连接池基本配置信息,如下,使用的是redis连接池奥!!!!。
# Redis数据库索引(默认为0)
spring.redis.database=0
## Redis服务器连接密码(默认为空)
#spring.redis.password=
spring.redis.port=6379
## Redis服务器地址
spring.redis.host=10.10.16.192
## 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=5
## 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=10
## 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=8
## 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=1000ms
## 连接超时时间(毫秒)
spring.redis.lettuce.shutdown-timeout=10000ms
- 上面配置好后,如果想进一步理解自动化配置类的原理:就看RedisAutoConfiguration里面有整个自动配置的原理,如果不想看就直接使用redistemplate操作redis就OK了。自动注册相关组件进入容器,相关配置属性对应的属性类也自动生效。
- 最后由于自动配置类中已经自动注册了redistemplate组件进入了容器,所以可以直接在service层注入redistemplate来使用了。
(1)redis中数据操作,大体来分为两类:
- 针对key的操作,相关方法在redistemplate中;
- 针对具体数据类型的操作,相关的方法在redistemplate中定义的五种数据结构中;如下可见;
- 我们知道redis中数据存储就是key-value格式,所以整个数据操作也就是key和value的操作,java中对redis操作值得注意的是;序列化策略的不同,对key和value的序列化保存方式也就不同,保存的key-value的序列化策略的不同,那么访问时序列化策略不同的话就访问为空,
- 简单说你key-value存储用x编码方式,你取时候用y编码的key取,那么肯定对应的value要么为null,要么不存在呀。还有redis中key和value的编码可以分开单独设置的。
redisTemplate.opsForValue();//操作字符串
redisTemplate.opsForHash();//操作hash
redisTemplate.opsForList();//操作list
redisTemplate.opsForSet();//操作set
redisTemplate.opsForZSet();//操作有序set
(2)springboot中自动配置注入的redis操作对象——StringRedisTemplate与RedisTemplate
- 两者的关系是StringRedisTemplate继承RedisTemplate。
- 这两个bean操作的数据类型是不同的,redistemplate泛型是object,而stringRedistemplate泛型是string。
- 这两的数据是不共同的,也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据;感觉是序列化方式的不同造成的。
- 这两个bean中对key-value数据操作时的序列化策略是不同的,一种是string的序列化策略,一种是jdb的序列化策略。如redistemplate采用是jdk的序列化策略,保存的key和value都是采用jdk的序列化策略。注意:这两个bean的序列化方式不同而存储的key-value数据,相互之间不同的序列化来访问是不可见的,即访问不到的。
- 默认redis中存储的数据都是string序列化的,所以如果用redistemplate这个jdb序列化的对象去访问那些string序列化的key,那么访问的value都是为null的。 如下更改不同序列化的方式访问:
@Test
public void test_other_type() {
RedisSerializer<String> stringSerializer = new StringRedisSerializer();
//将原本对key的jdb序列化更改为string序列化
redistemplate.setKeySerializer(stringSerializer );
//将原本对value的jdb序列化方式改为string序列化方式
redistemplate.setValueSerializer(stringSerializer );
ValueOperations<String, String> opsForValue = redistemplate.opsForValue();
//sun这个key是用string策略序列化的,如果直接用redistemplate访问不到value的,但是可以改变序列化策略去访问
Object object = opsForValue.get("sun");
System.out.println(object);
}
- java中操作redis中数据的保存或者获取操作过程中,注意保存key-value的序列化方式和获取key-value的序列化方式要保持一致。
- 如何使用RedisTemplate访问Redis不同的数据结构的案例
9.3 springboot中引入cach接口
springboot 3.1中开始引入了令人激动的cache,在springboot中,可以非常方便的使用redis来作为cache的实现,进而实现数据的缓存。 注意:spring cache是一个接口,而redis正好是这个接口的一个实现方式;
- springboot中首先就是引入pom依赖;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 依赖导入后,就是一些配置属性类的属性配置;redis这边就是连接redis服务端的信息,和缓存基本信息;
spring.redis.database=0
#spring.redis.password=
spring.redis.port=6379
spring.redis.host=10.10.16.192
#表示给缓存起一个名字
spring.cache.cache-names=c1
spring.redis.jedis.pool.min-idle=5
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-wait=1000ms
spring.redis.lettuce.shutdown-timeout=10000ms
- springboot的自动化配置的好处就是,导入依赖和配置好属性后,就会自动完成封装和自动化配置类实现整合过程,之后直接可以简单使用,spring cache这边还需要在主类上加一个注解@EnableCaching,表示开启cache缓存。
@SpringBootApplication
@EnableCaching //表示开启缓存。
public class RediscacheApplication {
public static void main(String[] args) {
SpringApplication.run(RediscacheApplication.class, args);
}
}
- 完成上面这些配置后,springboot实际上已经自动帮我们在项目启动的时候,会自动注册一个RedisCacheManager类进入容器,详情可看RedisCacheConfiguration这个配置类。
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisTemplate.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {
private final CacheProperties cacheProperties;
private final CacheManagerCustomizers customizerInvoker;
RedisCacheConfiguration(CacheProperties cacheProperties,
CacheManagerCustomizers customizerInvoker) {
this.cacheProperties = cacheProperties;
this.customizerInvoker = customizerInvoker;
}
@Bean
public RedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setUsePrefix(true);
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return this.customizerInvoker.customize(cacheManager);
}
}
//分析
这个一个配置类,项目启动时会被解析,然后一些注解表示整个解析这个配置类的逻辑,值得注意这个配置类中注入一个
RedisCacheManager的bean,这个RedisCacheManager间接实现了spring的cache接口,
- 上面自动配置类中发现注入一个RedisCacheManager的bean,这个bean实现了spring的cache接口,所以获取这个cache接口的实现类RedisCacheManager,就可以直接使用spring中缓存cache相关的注解和接口了,而缓存数据会被自动存储到Redis上。
9.4 核心几个缓存注解使用
- @CacheConfig——这个注解在类上,表示该类中所有方法使用的缓存名称。该注解中cacheNames属性可以指定缓存名。如: @CacheConfig(cacheNames = “c1”)
@Service
@CacheConfig(cacheNames = "c1")
public class UserService {
}
- @cacheable——这个注解一般加在查询方法上,表示将该方法的返回值缓存起来,默认情况下,缓存的key就是方法的参数(多参数时可以指定某个参数为key),缓存的value就是方法的返回值。当然也可以通过属性key来指定;如:@Cacheable(key = “#id”); 特点——该方法每次执行前会先判断缓存,缓存中有就直接返回结果、 @Cacheable(key="#root.methodName") 键名用类名保险点,很少重复。
//@Cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0");后两个属性都支持springEl表达式。
//即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);key表示需要清除的是哪个key,如未指定则会使用
//默认策略生成的key;condition表示清除操作发生的条件
@Cacheable(key = "#id") //@Cacheable(key="#root.methodName") 键名用类名保险点,很少重复
public User getUserById(Integer id,String username) {
System.out.println("getUserById");
return getUserFromDBById(id);
}
- @cacheput——这个注解一般加载更新的方法上,这样当数据库中数据更新后,缓存中数据也会自动跟着更新,使用该注解,可以将方法的返回值自动更新到已经存在的key上。 特点——该方法每次都会执行,而且会将执行的结果以键值对的形式存入缓存中
@CachePut(key = "#user.id") //@Cacheable(key="#root.methodName") 键名用类名保险点,很少重复
public User updateUserById(User user) {
return user;
}
- @CacheEvict——该注解一般加载删除方法上,当数据库中数据删除后,相关的缓存数据也自动清除了;该注解在使用的时候也可以配置按照某种条件删除(condition属性)或者或者配置清除所有缓存(allEntries属性),示例代码如下:
@CacheEvict()
public void deleteUserById(Integer id) {
//在这里执行删除操作, 删除是去数据库中删除
}
==========================================
@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存
的清除操作。@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和
condition的语义与@Cacheable对应的属性类似。即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);
key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;condition表示清除操作发生的条件。
总结:
(1)spring cache缓存方式中,@cacheable修饰的方法就是表示要缓存的内容;默认情况下,缓存的key就是方法的参数,缓存的value就是方法的返回值,当有多个参数时,默认就使用多个参数来做key。这是默认情况下的情形;
(2)如果不想要默认的方式;当然可以自定义key名;通过@cacheable注解内的属性key设置,这个属性支持springEL表达式,如使用第一个参数名为key:@Cacheable(key = “#参数名”);该注解还有一个属性condition:该属性默认为null,表示缓存所以的情形,该属性也支持springel表达式来指定true、false,表示那些情形缓存,那些情形不缓存(@Cacheable(value={“users”}, key="#user.id", condition="#user.id%2==0"))。
(3)@cacheable该注解修饰的方法结果被缓存,缓存的特点:对于使用@cacheable标注的方法,spring每次调用前都会检查cache中是否有相同key的缓存元素,存在的话,该方法就不会执行,而是直接从缓存中获取该方法结果返回,不存在该缓存key,才会执行该方法; 区别于@CachePut标注的方法,该注解用于更新,所以每次该方法执行前是不会检查缓存中是否存在之前的key的,而是每次执行该方法,会将方法结果以键值对形式存入指定的缓存中。
(4)在springboot中,使用redis缓存,既可以使用RedisTemplate自己来实现,也可以使用这种自动配置类封装redis+注解的方式来实现,这种方式是spring cache提供的统一接口,实现这个接口可以用redis,也可以是其他支出这种规范的缓存框架。从这个角度来说,spring cache和redis的关系就像JDBC和各自数据库驱动的关系一样。
(5)spring中缓存是一个接口标准:默认接口的实现是ConCurrentMapCacheManager缓存管理器,然后缓存组件是ConCurrentMapCache,将数据保存在ConCurrentMap中。springboot中自动配置会基于上面的实例来自动配置实现缓存功能,自动配置类是——SimpleCacheConfiguration但是通常会使用缓存中间件——redis;同样道理,redis的缓存自动化配置如何完成?也是有配置类RedisCacheConfiguration;这个配置类上会有一些条件注解,满足redis依赖相关的组件时,该自动配置类会生效代理默认的缓存配置类奥!
(6)xxxCacheManager【管理缓存组件】——> xxxcache【可以直接操作缓存的:增删改查将数据给缓存的】;但是redis缓存服务——真正底层操作的执行者是redistemplate,即redistemplate是比rediscachemanager还要底层:redistemplate——> xxxCacheManager【管理缓存组件】——> xxxcache【可以直接操作缓存的:增删改查缓存的】,越往后就是封装的越简单,如:cache组件直接put(key,value)就完成将key-value缓存到redis中了。。
(7)简单说,缓存的原理,传统缓存是存储到map中,spring默认的缓存原理是:使用ConCurrentMapCacheManager缓存管理器来创建一个cache组件,然后这个cache组件将数据保存到缓存——map中。而实际开发中,缓存不一定是map,而是一些中间缓存件,如redis缓存内存服务器。单纯redis服务器的操作是redistemplate客户端来实现的。但是spring的cache缓存接口是可以直接操作缓存的,即RedisCacheManager 获得的cache组件是可以直接操作redis缓存的?综上可知——操作redis缓存服务器可以使用两种方式:1.使用redistemplate客户端来操作redis服务端,2.使用redis的cache接口实现类来操作redis完成数据的缓存。
json方式序列化redis的缓存
spring中缓存接口的原理:传统缓存是存储到map中,spring默认的缓存原理是:使用ConCurrentMapCacheManager缓存管理器来
创建一个cache组件,然后这个cache组件将数据保存到缓存——map中;而redis缓存中间件也是符合spring的缓存接口的,所以redis
缓存中也可以由rediscache组件来完成。即redis的starter的自动配置类中注入了rediscachemanager的redis缓存管理器,然后redis缓存
管理器创建rediscache组件,这个redis组件就是操作redis来缓存数据的。所以操作redis缓存数据——可以两种方式:
(1)redistemplate
(2)就是使用rediscachemanager缓存管理器,创建初rediscache组件,然后这个组件就可以操作redis来缓存数据了。
@Configuration
public class MyRedisConfig {
//自定义redistemplate的序列化方式
@Bean
public RedisTemplate<dbject,Employee> empRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object,Employee> template = new RedisTemplate<Object,Employee>();
template.setConnectionFactory(redisConnectionFactory);
Jackson23sonRedisSerializer<Employee> ser = new Jackson2JsonRedisSerializer<Employee>(Employee.class)
template.setDefaultSerializer(ser);
return template;
}
//CacheManagerCustomizers可以来定制缓存的一些规则
@Bean
public RedisCacheManager employeeCacheManager(RedisTemplate<Object,Employee> empRedisTemplate){
RedisCacheManager cacheManager = new RedisCachewanager(empRedisTemplate);
// key多了一个前缀
//使用前缀,默认会将cacheName作为key的前缀
cacheManager.setUsePrefix(true);
return cacheManager;
}