本文共 34073 字,大约阅读时间需要 113 分钟。
redis是一个由c编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。
redis和数据库双写一致性问题分析:一致性问题是分布式常见问题,还可以再分为最终一致性和强一致性。数据库和缓存双写,就必然会存在不一致的问题。答这个问题,先明白一个前提。就是如果对数据有强一致性要求,不能放缓存。我们所做的一切,只能保证最终一致性。另外,我们所做的方案其实从根本上来说,只能说降低不一致发生的概率,无法完全避免。因此,有强一致性要求的数据,不能放缓存。回答:首先,采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。
传统数据库的事务特性:acid,原子性、一致性、隔离性、持久性;其中隔离性是对于并发事务情形而言的,有四种隔离级别(读未提交、读已提交、可重复读、串行化)分布式数据库的事务特性:CAP(强一致性consistency、可用性availability、分区容错性partition tolerance);任何一个分布式系统中都只能满足cap中两个条件。如单点集群满足ca(一致性和高可用特点);而分布式中必须有P分区容错奥!!因为分布式系统之间通信肯定会有延迟丢包,通信是分布式最基础的,所以必须有容错余地,那数据一致表示在分布式系统中的所有数据备份,在同一时刻是否同样的值,这个可灵活;高可用是集群中有某节点故障,集群整体能否继续为客户端提供服务,这个也很重要。事务就是一个具体业务的最小单元,也就是一个复合操作为一个单元。
redis中的数据都是以key/value的形式存储的,五大数据类型主要是指value的数据类型,包含如下五种:
首先通过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中,由于不同数据类型的数据结构本身有差异,因此对应的命令也会有所不同。但是有些命令无论哪种数据类型都是存在的,如下一些特殊通用命令:
127.0.0.1:6379> EXPIRE k1 30 //表示k1这个键有效时间为30秒(integer) 1127.0.0.1:6379> TTL k1 //返回值表示k1键还有25秒的有效时间(integer) 25
127.0.0.1:6379> EXPIRE k1 60(integer) 1127.0.0.1:6379> ttl k1(integer) 57127.0.0.1:6379> PERSIST k1(integer) 1127.0.0.1:6379> ttl k1 //time to live 存活时间,如果返回为-1,表示这个k1存在,且没有设置存活时间(integer) -1
实际上虽然不同类型的数据结构的特性通过各自特有命令体现:但基本操作还是:增、删、查、判断。
127.0.0.1:6379> SET k1 helloworldOK127.0.0.1:6379> GETRANGE k1 0 2 "hel"127.0.0.1:6379> GETRANGE k1 -3 -1"rld"
127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3OK127.0.0.1:6379> MGET k1 k2 k31) "v1"2) "v2"3) "v3"
上面是对string数据类型的一些基本命令,没有涉及到BIT的相关命令,了解这些命令前提——需要了解redis中字符串的存储方式,redis中字符串都是以二进制的方式进行存储的。
set k a ;//保存一个键为k,值为字符串a的数据,a在内存中以01100001存储 a>01100001getbit k 0 ;//表示获取k键对应值的第一个位的二进制值;从左往右方向,所以第一个是0getbit k 1 ;——>1getbit k 2 ;——>1getbit k 3 ;——>0getbit k 4 ;——>0getbit k 5 ;——>0getbit k 6 ;——>0getbit k 7 ;——>1
列表是redis中另一种数据类型,前面知道redis数据都是以key/value形式存储,而不同数据类型是指value的数据类型。所以列表类型,就是指某个key,对应的值是一个(多数据组成的)列表。
就类似于队列的数据结构:有入队和出队;入队有从左往右元素入队,或者从右往左入队,出队有移除队尾,或者移除队头元素。
//值按照从左到右顺序插入到表头,所以最后一个v3是在表头的。127.0.0.1:6379> LPUSH k1 v1 v2 v3(integer) 3
//返回列表中数据,从第一个到最后一个元素顺序返回,说明第一个元素是v3127.0.0.1:6379> LRANGE k1 0 -1 //这个返回的就是按照第一个到最后一个元素顺序返回的列表信息。1) "v3"2) "v2"3) "v1"
127.0.0.1:6379> RPUSH k2 1 2 3 4 5(integer) 5127.0.0.1:6379> LRANGE k2 0 -11) "1"2) "2"3) "3"4) "4"5) "5"
127.0.0.1:6379> RPOP k2"5"127.0.0.1:6379> LRANGE k2 0 -11) "1"2) "2"3) "3"4) "4"
127.0.0.1:6379> LPOP k2"1"127.0.0.1:6379> LRANGE k2 0 -11) "2"2) "3"3) "4"
127.0.0.1:6379> LINDEX k2 0"2"127.0.0.1:6379> LINDEX k2 -1"4"
127.0.0.1:6379> LRANGE k1 0 -11) "v3"2) "v2"3) "v1"127.0.0.1:6379> LTRIM k1 0 1OK127.0.0.1:6379> LRANGE k1 0 -11) "v3"2) "v2"
和下面有序集合区分开,有序集合是有一个标准来判断排序,这边是无序集合,因为没有某个标准来判断排序。
127.0.0.1:6379> SADD k1 v1 v2 v3 v4(integer) 4
127.0.0.1:6379> SREM k1 v2 // 表示移除集合k1中,元素为v2那个数据(integer) 1127.0.0.1:6379> SREM k1 v10 //表示移除元素v10,但是v10不存在这个元素,所以返回为0(integer) 0
127.0.0.1:6379> SISMEMBER k1 v3(integer) 1
127.0.0.1:6379> SCARD k1(integer) 3
127.0.0.1:6379> SMEMBERS k11) "v4"2) "v1"3) "v3"
127.0.0.1:6379> SRANDMEMBER k1"v4"127.0.0.1:6379> SRANDMEMBER k1 21) "v4"2) "v1"127.0.0.1:6379> SRANDMEMBER k1 51) "v4"2) "v1"3) "v3"127.0.0.1:6379> SRANDMEMBER k1 -11) "v4"127.0.0.1:6379> SRANDMEMBER k1 -51) "v3"2) "v1"3) "v1"4) "v3"5) "v3"
127.0.0.1:6379> SMOVE k1 k2 v1(integer) 1127.0.0.1:6379> SMEMBERS k11) "v4"2) "v3"127.0.0.1:6379> SMEMBERS k21) "v1"
k1中的元素是v3、v4, k2中的元素是v1,差集就是v3、v4.127.0.0.1:6379> SDIFF k1 k21) "v4"2) "v3"
127.0.0.1:6379> SDIFFSTORE key k1 k2(integer) 2127.0.0.1:6379> SMEMBERS key1) "v4"2) "v3"
127.0.0.1:6379> SMEMBERS k11) "v4"2) "v3"127.0.0.1:6379> SMEMBERS k21) "v1"2) "v3"127.0.0.1:6379> SINTER k1 k21) "v3"
127.0.0.1:6379> SINTERSTORE k3 k1 k2(integer) 1127.0.0.1:6379> SMEMBERS k31) "v3"
127.0.0.1:6379> SUNION k1 k21) "v4"2) "v1"3) "v3"
127.0.0.1:6379> SUNIONSTORE k4 k1 k2(integer) 3127.0.0.1:6379> SMEMBERS k41) "v4"2) "v1"3) "v3"
就是键值对中的值是一个映射对——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 : 获取所有的keyhvals key :获取所有的valuehgetall key : 获取key中的所有field 中的所有field-value,上面是field,接着就是对应的value。3.删除hdel key field[field…] : 可以删除一个或多个字段,返回是被删除的字段个数del key : 删除整个list4.增加数字hincrby key field increment :设置key中field的值增加increment,如: age增加20hincrby myhash age 5自学命令:hexists key field : 判断指定的key中的field是否存在hlen key : 获取key所包含的field的数量hkeys key :获得所有的key hkeys myhashhvals key :获得所有的valuehvals myhash
127.0.0.1:6379> HMSET runoobkey name "redis tutorial" description "redis basic commands for caching" likes 20 visitors 23000 OK127.0.0.1:6379> HGETALL runoobkey1) "name"2) "redis tutorial"3) "description"4) "redis basic commands for caching"5) "likes"6) "20"7) "visitors"8) "23000"
首先,Redis 有序集合和集合一样也是string类型元素的集合,都不允许重复的成员。
有序集合存储特点:首先redis中所有数据都是key/value形式存储的,但是有序集合是指value是一个多元素组成的集合,而且可以按照元素关联的某个标准(value处额外包含一个新字段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 ,所以他们排序顺序按照这个大小来排序
127.0.0.1:6379> ZSCORE keyset v2"2"
127.0.0.1:6379> zrange keyset 0 -11) "v1"2) "v2"3) "v5"================================127.0.0.1:6379> zrange keyset 0 -1 withscores1) "v1"2) "1"3) "v2"4) "2"5) "v5"6) "5"
127.0.0.1:6379> zrevrange keyset 0 -11) "v5"2) "v2"3) "v1"================================127.0.0.1:6379> zrevrange keyset 0 -1 withscores1) "v5"2) "5"3) "v2"4) "2"5) "v1"6) "1"
127.0.0.1:6379> ZCARD keyset(integer) 3
127.0.0.1:6379> ZCOUNT k1 60 90(integer) 4=======================//如果在统计时,不需要包含60或者90,则添加一个 ( 即可,如下:127.0.0.1:6379> ZCOUNT k1 60 (90(integer) 3
127.0.0.1:6379> ZRANGE k2 0 -1 withscores1) "v1"2) "2"3) "v2"4) "3"5) "v3"6) "4"127.0.0.1:6379> ZREM k2 v1(integer) 1127.0.0.1:6379> ZRANGE k2 0 -1 withscores1) "v2"2) "3"3) "v3"4) "4"
127.0.0.1:6379> ZLEXCOUNT k2 - +(integer) 2127.0.0.1:6379> ZLEXCOUNT k2 [v2 [v4(integer) 2
127.0.0.1:6379> zrangebylex k2 [v2 [v41) "v2"2) "v3"127.0.0.1:6379> zrangebylex k2 - +1) "v2"2) "v3"127.0.0.1:6379>
redis的发布和订阅系统类似于我们生活中的电台,电台可以在某一个频率上发送广播,而我们可以接收任何一个频率的广播。简言之:就是你订阅了哪个电台,那么哪个电台发布信息的话,你就能接收到。
127.0.0.1:6379> SUBSCRIBE c1 c2 c3Reading messages... (press Ctrl-C to quit)1) "subscribe"2) "c1"3) (integer) 11) "subscribe"2) "c2"3) (integer) 21) "subscribe"2) "c3"3) (integer) 3
127.0.0.1:6379> PUBLISH c1 "hello redis!"(integer) 1
1) "message"2) "c1"3) "hello redis!"
redis也是一种nosql数据库,所以也会有事务的功能,只不过和关系型数据库中事务有些差异。redis中事务用法很简单。简述为:multi开启事务,然后后面所有命令都是入队到事务中,注意并没有执行,最后由exec命令触发事务,一并执行事务中所有命令。
特点:(1)事务开启后,但是exec执行前,批量操作被放入队列中缓存、(2)执行命令后,事务中任意命令执行失败,其余命令依然执行、(3)事务执行过程中,其他客户端提交的命令不会插入到事务命令序列中。127.0.0.1:6379> MULTIOK
127.0.0.1:6379> set k1 v1QUEUED127.0.0.1:6379> set k2 v2QUEUED127.0.0.1:6379> set k3 v3QUEUED
127.0.0.1:6379> EXEC1) OK2) OK3) OK
单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
redis 127.0.0.1:6379> CONFIG GET slowlog-max-len1) "slowlog-max-len"2) "1024"redis 127.0.0.1:6379> CONFIG SET slowlog-max-len 10086OKredis 127.0.0.1:6379> CONFIG GET slowlog-max-len1) "slowlog-max-len"2) "10086"
redis持久化有两种方式:;
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存储的数据更加完整。
顾名思义,通过拍摄快照的方式实现数据的持久化(数据的备份)在某个时间点对内存中的数据创建一个副本文件,副本文件中的数据在redis重启是会被自动加载,我们也可以将副本文件拷贝到其他地方也可以使用。** 简单说:快照就是内存中数据的备份,重启会自动将这些数据加载进内存(如果需要恢复数据,只需将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可)。
redis中的快照持久化默认是开启的。但是有几个配置项可以需要了解:
save 900 1save 300 10save 60 10000stop-writes-on-bgsave-error yesrdbcompression yesdbfilename dump.rdbdir ./
实际上就是redis发送save或者bgsave命令,来讲数据备份持久化到硬盘保存。但是save是一个阻塞操作,即在备份数据时其他请求不能操作会被挂起,所以用的不多,bgsave创建一个子进程来负责将快照写入硬盘,这样不影响父进程处理客户端请求,不会有阻塞情况。(常用,配置文件中save属性也是自动触发bgsave命令进行备份的)
与快照持久化备份数据不同,AOF持久化是将创建数据而执行的命令写入到aof文件末尾,在恢复时需要从头到尾执行一遍写可以恢复所有数据,AOF在redis中默认是没有开启的。
开启AOF持久化机制:appendonly yes其他和AOF相关的配置:appendfilename “appendonly.aof”# appendfsync alwaysappendfsync everysec# appendfsync nono-appendfsync-on-rewrite noauto-aof-rewrite-percentage 100auto-aof-rewrite-min-size 64mb
(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的重写和压缩机制一定程度上可以缓解这个问题。
127.0.0.1:6379> BGREWRITEAOFBackground append only file rewriting started(0.71s)
(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性能,从机能精确的复制主机上的内容,实现了主从复制之后,一方面能够实现数据的读写分离,降低master的压力;redis中主从机制核心就是在从机上配置slaveof 127.0.0.1 6379(主机ip+端口)来完成的,另一方面也能实现数据的备份。【集群可以实现系统的性能的高可用和高伸缩,因为集群中各节点都对等的提供服务(且有对多服务器的负载均衡),所以总的性能肯定提高很多;难点是各节点如何完全对等且高效连接】
192.168.248.128:6379 192.168.248.128:6380 192.168.248.128:6381
port 6379pidfile /var/run/redis_6379.pidlogfile "6379.log"dbfilename dump6379.rdbappendfilename "appendonly6379.aof"
[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
[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
127.0.0.1:6381> SLAVEOF 127.0.0.1 6379//表示本redis实例是隶属于6379的redis实例。或者表示从6379实例上复制数据OK
127.0.0.1:6379> INFO replication# Replicationrole:masterconnected_slaves:2slave0:ip=127.0.0.1,port=6380,state=online,offset=56,lag=1slave1:ip=127.0.0.1,port=6381,state=online,offset=56,lag=0master_replid:26ca818360d6510b717e471f3f0a6f5985b6225dmaster_replid2:0000000000000000000000000000000000000000
实际上,之前发现,主从模式都是在配置文件中设置好的,如设置6379为主,其他6380、6381为从,所以就算6379挂了,然后重启,它依然是主机,但是就会出现群龙无首的局面。个人对zk那边的集群内主备选举有些了解可知,一个集群中配置好集群相关信息后,内部进行选举完成主的产生,即使主挂了,只要集群中主机不低于选举模式需要的个数,还是会继续内部选举一个新主。但是redis这边内部选举就是通过哨兵模式手动配置的,不是默认内嵌的。
所谓的哨兵模式,其实并不复杂,我们还是在我们前面的基础上来搭建哨兵模式。假设现在我的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节点彼此互联,而且内部使用二进制协议优化传输速度和带宽。 (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服务器中配置文件里一个属性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才能连接和访问。
首先外部想要连接linux上redis实例(服务器),需要检查几点配置:
上面三步之后,就可以远程连接redis了。
原生jedis的依赖。redis.clients jedis 2.9.0
最简单的连接测试; @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); }
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连接池。redis.clients jedis org.springframework.boot spring-boot-starter-data-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
redisTemplate.opsForValue();//操作字符串redisTemplate.opsForHash();//操作hashredisTemplate.opsForList();//操作listredisTemplate.opsForSet();//操作setredisTemplate.opsForZSet();//操作有序set
@Test public void test_other_type() { RedisSerializerstringSerializer = new StringRedisSerializer(); //将原本对key的jdb序列化更改为string序列化 redistemplate.setKeySerializer(stringSerializer ); //将原本对value的jdb序列化方式改为string序列化方式 redistemplate.setValueSerializer(stringSerializer ); ValueOperations opsForValue = redistemplate.opsForValue();//sun这个key是用string策略序列化的,如果直接用redistemplate访问不到value的,但是可以改变序列化策略去访问 Object object = opsForValue.get("sun"); System.out.println(object); }
springboot 3.1中开始引入了令人激动的cache,在springboot中,可以非常方便的使用redis来作为cache的实现,进而实现数据的缓存。 注意:spring cache是一个接口,而redis正好是这个接口的一个实现方式;
org.springframework.boot spring-boot-starter-cache org.springframework.boot spring-boot-starter-data-redis
spring.redis.database=0#spring.redis.password=spring.redis.port=6379spring.redis.host=10.10.16.192#表示给缓存起一个名字spring.cache.cache-names=c1 spring.redis.jedis.pool.min-idle=5spring.redis.jedis.pool.max-idle=10spring.redis.jedis.pool.max-active=8spring.redis.jedis.pool.max-wait=1000msspring.redis.lettuce.shutdown-timeout=10000ms
@SpringBootApplication@EnableCaching //表示开启缓存。public class RediscacheApplication { public static void main(String[] args) { SpringApplication.run(RediscacheApplication.class, args); }}
@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
@Service@CacheConfig(cacheNames = "c1")public class UserService {}
//@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 = "#user.id") //@Cacheable(key="#root.methodName") 键名用类名保险点,很少重复public User updateUserById(User user) { return user;}
@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完成数据的缓存。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来缓存数据了。@Configurationpublic class MyRedisConfig {//自定义redistemplate的序列化方式@Beanpublic RedisTemplateempRedisTemplate(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException {RedisTemplate
转载地址:http://rqdxi.baihongyu.com/