6.28 redis详述

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分区容错奥!!因为分布式系统之间通信肯定会有延迟丢包,通信是分布式最基础的,所以必须有容错余地,
那数据一致表示在分布式系统中的所有数据备份,在同一时刻是否同样的值,这个可灵活;高可用是集群中有某节点故障,集群整体
能否继续为客户端提供服务,这个也很重要。
事务就是一个具体业务的最小单元,也就是一个复合操作为一个单元。

特点

  1. 支持数据的持久化,可将内存中的数据保持到磁盘中,重启时候可以再次加载进内存使用,不会造成数据的丢失。
  2. redis支持五种类型的数据结构:简单的string、list、set、zset、hash等数据结构的存储。注意:redis中数据都是以key/value的形式存储的,五大数据类型也是指value的数据类型。
  3. 简述一下五个类型:string——就是简单的字符串;hash类型——是一个映射对集合;list——是一个简单的字符串列表(集合),集合中可以重复字符串元素;set类型——也是字符串类型集合,但是集合中元素不可以重复字符串添加已存在的成员或者元素的话是无效的,且是无序的;zset(sorted set)——和set一样特点,是string类型元素的集合,且不允许重复成员不同的是每个元素关联一个double类型的分数,redis通过这个分数为集合中成员从小到大排序;即zset成员唯一但是分数可以重复。
  4. redis中数据都是以key-value形式存储,但是redis中数据类型是指value的类型,而key类似于java中的一个对象(实例)变量,即redis中的数据都是以key/value的形式存储的,五大数据类型主要是指value的数据类型如hash数据;key表示这个hash类型的变量名,而真正的hash数据是存储在value上的
  5. redis支持master-slave(主备方式)的数据备份
  6. 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来
  7. 每一个redis实例默认有16个数据库,切换不同数据库使用select index。且数据库名不支持自定义,都是以编号命名。
  8. 外部客户端连接redis服务注意点:redis.conf配置文件中属性bind;这个属性表示本redis实例绑定的ip号,也就是说外部如果想访问我本redis实例,必须连接这个bind绑定的ip才能连接到我本redis实例; reids中属性bind配置了什么ip,别人就得访问bind里面配置的ip才访问到redis服务。或者bind 0.0.0.0等价于 不配置 bind 即注释掉bind

功能

  1. 内存存储和持久化:redis支持异步将内存中数据写到磁盘上,在持久化同时不影响数据存储服务。
  2. 获取最新的N个数据的操作。如:可以将最新的10条评论的ID放在Redis的List集合里面。
  3. 数据可以设置过期时间
  4. 自带发布、订阅消息系统。
  5. 定时器、计数器。

五种数据类型介绍

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中,由于不同数据类型的数据结构本身有差异,因此对应的命令也会有所不同但是有些命令无论哪种数据类型都是存在的,如下一些特殊通用命令

  1. set k v ——插入键值对的命令
  2. del k ——删除存在的键对应的k/v数据
  3. exist k ——用来检测判断指定的k是否存在这条数据。返回0表示不存在,返回1表示存在。
  4. ttl k ——TTL命令(time to live存货时间)可以查看一个给定key的有效时间。返回-2表示该key不存在或者过期,-1表示key存在但是没有设置过期时间(永久有效);如果有其他整数,表示该键还可以存活时间。
  5. 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
  1. keys * —— KEYS命令可以获取满足给定模式的所有key。如keys * 获取所有键信息
  2. expire k 秒数 —— 表示设置指定的k存活时间。时间超时后就自动被毁灭了。如:EXPIRE k1 30 表示设置键k1 存活30秒,当然30秒后会被删错了。
  3. 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类型的——特有命令
  1. 追加命令——append key value; 表示在key对应的value后追加字符串,如果key存在就追加,如果key不存在,就相当于创建一个空字符串然后追加。
  2. 字符串最基础就是设值和获取——set key value;get key;有个特殊的set设置值命令:setnx key value;表示如果key存着,则设置的值无效,如果key不存在,设置的值有效。对比set和setnx,set会覆盖已存在的值,而setnx( SET if Not eXists)只设置不存在的key,安全点。
  3. 获取字符串指定范围的内容——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"
  1. 批量设置值和批量获取值—— 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"
  1. 对字符串的值进行增减操作——前提:这个字符串是数值,可以完成加一或者减一,或者加指定步长或者减指定步长,不是数组就会报错。decr key 表示将key对应的value值减一,incr key表示将key对应的value加一。decrby key 步长;incrby key 步长;incrbyfloat key 0.33 ;表示将key对应的value增加浮点数大小。
  2. 获取字符串的长度——strlen key;用来计算key的value的长度

上面是对string数据类型的一些基本命令,没有涉及到BIT的相关命令,了解这些命令前提——需要了解redis中字符串的存储方式,redis中字符串都是以二进制的方式进行存储的。

  1. 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
  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类型

就类似于队列的数据结构:有入队和出队;入队有从左往右元素入队,或者从右往左入队,出队有移除队尾,或者移除队头元素。

  1. lpush——将一个或多个值value插入到列表key的表头,如果有多个value值,那么各个value值按从左到右的顺序依次插入到表头(从表头处插入),如下:
//值按照从左到右顺序插入到表头,所以最后一个v3是在表头的。
127.0.0.1:6379> LPUSH k1 v1 v2 v3
(integer) 3
  1. 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"
  1. 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"
  1. 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"
  1. 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"
  1. lindex——可以返回列表中指定索引处的元素索引特点:0表示第一个元素,1表示第二个,以此类推…,-1表示倒数第一个,-2表示倒数第二个。
127.0.0.1:6379> LINDEX k2 0
"2"
127.0.0.1:6379> LINDEX k2 -1
"4"
  1. 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
  1. 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
  1. 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
  1. sismember——返回某成员元素是否是存储的集合key的成员。用于判断指定的元素是否存在于指定的集合key中,是否是集合key中的成员。
127.0.0.1:6379> SISMEMBER k1 v3
(integer) 1
  1. scard—— 返回指定key集合中存储的元素的基数(集合元素的数量),如下:
127.0.0.1:6379> SCARD k1
(integer) 3
  1. smembers key ——查询集合key中所有元素
127.0.0.1:6379> SMEMBERS k1
1) "v4"
2) "v1"
3) "v3"
  1. 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"
  1. spop——和上面srandmember用法类似,但是该命令除了返回元素还删除该元素。(SPOP命令的用法和SRANDMEMBER类似,不同的是,SPOP每次选择一个随机的元素之后,该元素会出栈,而SRANDMEMBER则不会出栈,只是将该元素展示出来。)
  2. 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"
  1. 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"
  1. 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"
  1. 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"
  1. sinterstore ——这个肯定就是将交集返回的结果保存到一个新的集合中。
127.0.0.1:6379> SINTERSTORE k3 k1 k2
(integer) 1
127.0.0.1:6379> SMEMBERS k3
1) "v3"
  1. sunion——集合之间的并集。
127.0.0.1:6379> SUNION k1 k2
1) "v4"
2) "v1"
3) "v3"
  1. 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)进行排序

  1. 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 ,所以他们排序顺序按照这个大小来排序

  1. zscore —— 查看指定有序集合中指定成员或者元素对应的score值。可以用来判断排序情况。
127.0.0.1:6379> ZSCORE keyset v2
"2"
  1. 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"
  1. 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"
  1. zcard—— 返回有序集合key中元素个数
127.0.0.1:6379> ZCARD keyset
(integer) 3
  1. 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
  1. 查询有序集合中元素——zrange(基于索引来查集合中元素)、zrangebyscore(按照score范围来查找对应的元素)
    在这里插入图片描述
  2. zrank key member ——用来查询有序集合key中指定成员的排名次序;其中有序集成员按score值递增(从小到大)顺序排列。排名第一以0为表示,即score值最小的成员排名为0,在第一位。
  3. zrevrank key member —— 这个是先将集合中元素顺序反正了,然后在查询指定成员的排名次序,这样排名次序和上面就是反过来了,大的边小,小的边大了
  4. 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"
  1. zlexcount key min max —— 返回指定有序集合指定成员之间的成员数量;注意包含指定的成员奥! 注意:可以用-和+表示得分最小值和最大值,如果使用成员名的话,一定要在成员名之前加上[。
127.0.0.1:6379> ZLEXCOUNT k2 - +
(integer) 2
127.0.0.1:6379> ZLEXCOUNT k2 [v2 [v4
(integer) 2
  1. 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的发布和订阅系统类似于我们生活中的电台,电台可以在某一个频率上发送广播,而我们可以接收任何一个频率的广播。简言之:就是你订阅了哪个电台,那么哪个电台发布信息的话,你就能接收到。

  1. 订阅消息的方式如下:表示接收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
  1. 上面订阅了某个信道后,那某个信道发布消息方式为:
127.0.0.1:6379> PUBLISH c1 "hello redis!"
(integer) 1
  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 事务的执行
并不是原子性的。事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失
败不会导致前面已做指令的回滚,也不会造成后续的指令不做。

五.其他命令

  1. info——获取redis服务器的统计信息。
  2. select index ——切换到不同数据库。
  3. dbsize —— 返回当前数据库的 key 的数量
  4. flushdb —— 清空当前数据库中所有key。
  5. flushall —— 删除所有数据库的所有key。这个太狠了,注意点
  6. role —— 返回本redis实例所属的主从角色。
  7. sync —— 同步主从服务器上数据。
  8. save —— 同步保存数据到硬盘;将当前 Redis 实例的所有数据快照(snapshot)以 RDB 文件的形式保存到硬盘,最终生成一个dump.rdb文件。 还有一个aof文件(Appendonly file);bgsave——异步保存数据中数据到磁盘。BGSAVE 命令执行之后立即返回 OK ,然后 Redis fork 出一个新子进程,原来的 Redis 进程(父进程)继续处理客户端请求,而子进程则负责将数据保存到磁盘,然后退出。
  9. 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
  1. appendfilename表示生成的AOF备份文件的文件名。
  2. appendfsync表示备份的时机,always表示每执行一个命令就备份一次,everysec表示每秒备份一次,no表示将备份时机交给操作系统。通常everysec选项是首选(最坏丢一秒数据),always会降低redis性能。
  3. no-appendfsync-on-rewrite表示在对aof文件进行压缩时,是否执行同步操作。
  4. 最后两行配置表示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的重写和压缩机制一定程度上可以缓解这个问题

  1. 当AOF的备份文件过大时,我们可以手动向redis发送bgrewriteaof命令进行文件的重写。
127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started
(0.71s)
  1. 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主从复制注意事项
  1. 从机复制主机数据是完整复制,和从机连接时间点无关;
  2. 主机可读可写,从机只可读不可写,但是可以修改配置文件的slave-read-only的值让从机也可读可写。
  3. 如果设置了指定的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实例(服务器),需要检查几点配置:

  1. 关闭linux防火墙,centos7之后用systemctl stopfirewalld.service。之前用service iptables stop;
  2. 关闭redis配置文件中protected-mode属性,这个属性表示redis实例是否有保护模式
  3. 配置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中数据操作,大体来分为两类:
  1. 针对key的操作,相关方法在redistemplate中;
  2. 针对具体数据类型的操作,相关的方法在redistemplate中定义的五种数据结构中;如下可见;
  3. 我们知道redis中数据存储就是key-value格式,所以整个数据操作也就是key和value的操作,java中对redis操作值得注意的是;序列化策略的不同,对key和value的序列化保存方式也就不同,保存的key-value的序列化策略的不同,那么访问时序列化策略不同的话就访问为空,
  4. 简单说你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
  1. 两者的关系是StringRedisTemplate继承RedisTemplate。
  2. 这两个bean操作的数据类型是不同的,redistemplate泛型是object,而stringRedistemplate泛型是string。
  3. 两的数据是不共同的,也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据;感觉是序列化方式的不同造成的
  4. 这两个bean中对key-value数据操作时的序列化策略是不同的,一种是string的序列化策略,一种是jdb的序列化策略。如redistemplate采用是jdk的序列化策略,保存的key和value都是采用jdk的序列化策略。注意:这两个bean的序列化方式不同而存储的key-value数据,相互之间不同的序列化来访问是不可见的,即访问不到的。
  5. 默认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);
	}
  1. java中操作redis中数据的保存或者获取操作过程中,注意保存key-value的序列化方式和获取key-value的序列化方式要保持一致。
  2. 如何使用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 核心几个缓存注解使用
  1. @CacheConfig——这个注解在类上,表示该类中所有方法使用的缓存名称。该注解中cacheNames属性可以指定缓存名。如: @CacheConfig(cacheNames = “c1”)
@Service
@CacheConfig(cacheNames = "c1")
public class UserService {
}
  1. @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);
}
  1. @cacheput——这个注解一般加载更新的方法上,这样当数据库中数据更新后,缓存中数据也会自动跟着更新,使用该注解,可以将方法的返回值自动更新到已经存在的key上。 特点——该方法每次都会执行,而且会将执行的结果以键值对的形式存入缓存中
@CachePut(key = "#user.id")             //@Cacheable(key="#root.methodName") 键名用类名保险点,很少重复
public User updateUserById(User user) {
    return user;
}
  1. @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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值