简介:
Redis是基于内存可持续化的分布式非关系型数据库,结构化数据: 将一批数据集合抽取共有特点形成结构–学生信息,人体信息.mysql中每一张表格都是结构化数据,redis不仅支持像mysql结构化查询,也支持非结构化数据:无法公用一批共有特点的数据集合–如日志数据.网页
redis是采用key-value存储数据::redis支持的基本结构,对key值是字符串,value有五种不同的内部数据结构:string,hash,list,set,zset;
可持久化: redis是主要将数据存储在内存中(特点:快,可靠性不高),为了防止内存数据宕机丢失,创建的持久化功能,可以根据需求,将内存数据保存在磁盘中,一旦故障,可以实现数据的恢复功能.
支持分布式:redis服务端是单例单线程的,利用线程非阻塞原理实现高吞吐量(1秒钟万条数据的读写操作).单个的redis节点(启动的一个server进程)不足以实现某个服务器的cpu性能,redis都是以集群节点的结构存在的(分布式的,高可用的结构)
雪崩/缓存击穿:当某个一个功能,运转流程中包含了缓存数据库的技术(有大量的缓存数据),一旦缓存数据丢失,海量请求涌入访问,造成缓存数据未命中,海量请求涌入数据库,造成数据库压力过大–宕机–重启–海量请求未消失–宕机;
redis应用场景::
• 系统的缓存
• 内存的数据锁结构解决各种复杂的逻辑判断
• 队列(list)
• 标签(某个用户访问网站收集的兴趣爱好,社交网站共同好友) • 排行系统(热搜的排行,点击量排行)
数据操作
Redis基础命令+五种类型的命令
操作数据:redis具备五种数据类型,每种数据类型都是针对key-value结构中的value而言,对于系统生成value各种数据的不同实现场景做对应
String
Hash
List
Set
Zset
String类型(包含基础命令)
**keys *** :查看当前redis节点(启动的每一个redis服务都叫redis节点。)所有已存在的key值。
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> set name hanlaoshi
OK
127.0.0.1:6379> set location beijing
OK
127.0.0.1:6379> keys *
1) "location"
2) "name"
**set key value:**存储一个key-value结构的数据
get key:获取当前key对应的value数据
127.0.0.1:6379> get age
"22"
127.0.0.1:6379> set age 33
OK
127.0.0.1:6379> get age
"33"
127.0.0.1:6379>
select 整数:redis中默认存在16个数据分库(database),index号0-15,在一个服务器节点上,可以区分多种类型,多种功能的数据库仓库,默认登录的是0号分库(现在使用不多)
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> keys *
(empty list or set)
127.0.0.1:6379[1]> select 0
OK
127.0.0.1:6379> keys *
1) "location"
2) "age"
3) "name"
127.0.0.1:6379>
exists key:判断当前节点是否包含key的数据。get也可以完成这个任务。redis在key-value结构的value存储容量是512M。使用get判断存在会先读在判断数据是否为空,浪费资源
127.0.0.1:6379> exists haha kaka age
(integer) 1
127.0.0.1:6379> exists age
(integer) 1
127.0.0.1:6379> exists haha
(integer) 0
127.0.0.1:6379>
del key:删除key值对应的key-value数据;
127.0.0.1:6379> del age
(integer) 1
127.0.0.1:6379> del haha
(integer) 0
127.0.0.1:6379> keys *
1) "location"
2) "name"
127.0.0.1:6379>
type key:查看当前key的类型
127.0.0.1:6379> type name
string
save:将当前的内存数据保存到磁盘文件(根目录dump.rdb)
127.0.0.1:6379> keys *
1) "location"
2) "name"
127.0.0.1:6379> save
OK
save前,save后观察redis根目录的dump.rdb大小可以发现已经存储到持久化文件
flushall:将当前redis所有数据清空,包括持久化文件,内存数据(创建集群的时候使用)在这里插入代码片
127.0.0.1:6379> keys *
1) "location"
2) "name"
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> set name wanglaoshi
OK
127.0.0.1:6379[1]> set age 11
OK
127.0.0.1:6379[1]> keys *
1) "age"
分区 day06 的第 6 页
1) "age"
2) "name"
127.0.0.1:6379[1]> save
OK
127.0.0.1:6379[1]> flushall
OK
127.0.0.1:6379[1]> keys *
(empty list or set)
127.0.0.1:6379[1]> select 0
OK
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379>
flushdb:清空一个分库的数据,不删除持久化文件内容(进行测试使用)
127.0.0.1:6379[1]> flushdb
OK
127.0.0.1:6379[1]> keys *
(empty list or set)
127.0.0.1:6379[1]> select 0
OK
127.0.0.1:6379> keys (
(empty list or set)
127.0.0.1:6379> keys *
1) "name"
redis中string字符串如果是纯数字,也有一些简单的数字操作命令
incr decr key:自增1,自减1;计步器
127.0.0.1:6379> set num 100
OK
127.0.0.1:6379> incr num
(integer) 101
127.0.0.1:6379> decr num
(integer) 100
多步计步器:incrby decrby key 整数:对key的value数字自增自减去整数步数
127.0.0.1:6379> incrby num 10
(integer) 110
127.0.0.1:6379> decrby num 10
(integer) 100
mset mget:单节点批量操作(不支持多节点分布式,只能对本节点数据进行处理)(测试时确定当前节点拥有的数据)
127.0.0.1:6379> mset num1 200 num2 300
num3 400
OK
127.0.0.1:6379> keys *
1) "num3"
2) "name"
3) "num"
4) "100"
5) "num1"
6) "num2"
127.0.0.1:6379> mget num1 num2 num3
1) "200"
2) "300"
3) "400"
redis作为内存缓存数据库虽然速度快,还是有缺点的,内存容量,不可能比硬盘容量大的.redis最常见的保护内存的机制(热点数据保存,冷点数据超时清除
expire key:对key添加超时过期的设定配合ttl key可以查看 key值剩余时间,已经超时的内容删除,ttl剩余时间是-2 -1表示永久数据
127.0.0.1:6379> ttl num1
(integer) -2
127.0.0.1:6379> keys *
1) "num3"
2) "name"
3) "num"
4) "100"
5) "num2"
127.0.0.1:6379> ttl name
(integer) -1
append key value :对key对应的value进行追加数据操作
Hash数据类型
对应value为hash结构的数据都是面向对象的数据结构
hset key field value:key 是 相当于变量名,filed是value中的key-value对中的key,但是面向对象时表示一个对象的属性名称, value表示属性的值
127.0.0.1:6379> hset user username hanlaoshi
(integer) 1
127.0.0.1:6379> hset user age 18
(integer) 1
127.0.0.1:6379> hset user location beijing
(integer) 1
127.0.0.1:6379> type user
hash
hget key field
127.0.0.1:6379> hget user username
"hanlaoshi"
hexists key field:判断hash类型中的属性值是否存在
127.0.0.1:6379> hexists user haha
(integer) 0
127.0.0.1:6379> hexists user age
(integer) 1
(integer) 1
hmset和hmget:批量设置,不支持分布式
127.0.0.1:6379> hmset student age 19 name
wangxiaoxiao gender male
OK
127.0.0.1:6379> hmget student age name gender
1) "19"
2) "wangxiaoxiao"
3) "male
Hdel key field:删除属性和值
127.0.0.1:6379> hdel student age
(integer) 1
127.0.0.1:6379> hget student age
(nil)
``
**Hkeys Hvals**:单独获取对象的属性名,或者属性的值`
![在这里插入图片描述](https://img-blog.csdnimg.cn/2020040311034097.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0ODAwOTg2,size_16,color_FFFFFF,t_70)
```bash
127.0.0.1:6379> hkeys user
1) "name"
2) "username"
3) "age"
4) "location"
127.0.0.1:6379> hvals user
1) "haha"
2) "hanlaoshi"
3) "18"
4) "beijing"
Hlen key:获取属性个数(长度)
127.0.0.1:6379> hlen user
(integer) 4
127.0.0.1:6379> hlen student
(integer) 2
List链表数据结构
查看链表list和设置链表数据(左表示上,右表示下)
lpush key value [vlaues]:对于一个链表,从左(上)开始插入数据;
lrange key 起始下标 结束下标(展示范围,结束下
标是-1表示到尾部)
127.0.0.1:6379> lpush mylist01 100 200 300
(integer) 3
127.0.0.1:6379> lrange mylist 0 1
(empty list or set)
127.0.0.1:6379> lrange mylist01 0 1
1) "300"
2) "200
Rpush key value:从下向上插入数据
127.0.0.1:6379> rpush mylist01 400
(integer) 4
127.0.0.1:6379> lrange mylist01 0 -1
1) "300"
2) "200"
3) "100"
4) "400
Linsert:从链表中间操作数据,从左侧找到第一个
相同元素插入数据(before after)
127.0.0.1:6379> linsert mylist01 before 100
one
(integer) 5
127.0.0.1:6379> lrange mylist01 0 -1
1) "300"
2) "200"
3) "one"
4) "100"
5) "400"
127.0.0.1:6379> linsert mylist01 after 100 two
(integer) 7
127.0.0.1:6379> lrange mylist01 0 -1
1) "100"
2) "two"
3) "300"
4) "200"
5) "one"
5) "one"
6) "100"
7) "400"
lset key index value将下标对应的数据修改
127.0.0.1:6379> lset mylist01 1 2
OK
127.0.0.1:6379> lrange mylist01 0 -1
1) "100"
2) "2"
3) "300"
4) "200"
5) "one"
6) "100"
7) "400"
**Lrem:**这个没有rrem,内部逻辑涉及到了从下往上删除
lrem key count value:从左向右寻找count个在这里插入代码片
value相同值元素删除。
count>0从左往右删
count=0 全删
count<0从右往左删
127.0.0.1:6379> lpush mylist01 200 200 200
(integer) 9
127.0.0.1:6379> lrange mylist01 0 -1
1) "200"
2) "200"
3) "200"
4) "2"
5) "300"
5) "300"
6) "200"
7) "one"
8) "100"
9) "400"
127.0.0.1:6379> lrem mylist01 -2 200
(integer) 2
127.0.0.1:6379> lrange mylist01 0 -1
1) "200"
2) "200"
3) "2"
4) "300"
5) "one"
6) "100"
7) "400"
127.0.0.1:6379> lrem mylist01 0 200
(integer) 2
ltrim 保留链表的范围内的数据元素,ltrim key 起始位置 结束位置(-1表示到尾部)
127.0.0.1:6379> ltrim mylist01 4 6
OK
127.0.0.1:6379> lrange mylist01 0 -1
1) "400
``lpop 从list头部删除元素,返回删除结果(类似remove) 秒杀逻辑,可以利用list对象实现阻止超卖现象`
```bash
127.0.0.1:6379> lrange mylist01 0 -1
1) "six"
2) "five"
2) "five"
3) "four"
4) "three"
5) "two"
6) "one"
7) "400"
127.0.0.1:6379> lpop mylist01
"six"
127.0.0.1:6379> lrange mylist01 0 -1
1) "five"
2) "four"
3) "three"
4) "two"
5) "one"
6) "400"
两个链表的数据交互
rpoplpush:从第一个list的尾部删除数据,移动到第二个list的头部添加
127.0.0.1:6379> rpoplpush mylist01 mylist02
"400"
127.0.0.1:6379> lrange mylist02 0 -1
1) "400"
2) "5"
3) "4"
4) "3"
5) "2"
6) "1
Set结构
集合用来保存多个字符串元素,和列表不同的是不允许有重复元素,并且集合中元素是无序的
最常见的应用场景,比如爱好,兴趣,社交平台中关注人存在一个集合中,将其所有粉丝存在一个集合
单集合操作:
sadd key member添加
127.0.0.1:6380> sadd favor histroy english
math
(integer) 3
srem key member删除
127.0.0.1:6380> srem favor histroy
(integer) 1
scard key 返回元素个数
127.0.0.1:6380> scard favor
(integer) 2
sismember key element判断元素是否在集合中
127.0.0.1:6380> sismember favor histroy
(integer) 0
127.0.0.1:6380> sismember favor math
(integer) 1
srandmember key 随机抽取几个元素
127.0.0.1:6380> sismember favor math
(integer) 1
127.0.0.1:6380> srandmember favor 1
1) "math"
127.0.0.1:6380> srandmember favor 1
1) "english
smembers key获取所有元素
127.0.0.1:6380> sadd favor english histry
(integer) 2
127.0.0.1:6380> smembers favor
1) "english"
2) "histry"
3) "math"
集合间的操作:
sinter set1 set2 交集
在这里插入代码片127.0.0.1:6380> sadd favor1 english histroy
math
(integer) 3
127.0.0.1:6380> sadd favor2 english math
chinese
(integer) 3
127.0.0.1:6380> sinter favor1 favor2
1) "english
2) "math"
suinon set1 set2 并集
127.0.0.1:6380> sunion favor1 favor2
1) "english"
2) "chinese"
3) "histroy"
4) "math
sdiff set1 set2 差集属于set1不属于set2的元素
为差集
127.0.0.1:6380> sdiff favor1 favor2
1) "histroy"
127.0.0.1:6380> sdiff favor2 favor1
1) "chinese"
127.0.0.1:6380>
ZSet结构(sorted set)
在集合的基础上绑定一个score作为排序的依据比较典型的使用场景是排行榜系统。例如视频网站需要对用户上传的视频做排行榜
单集合操作:
zadd key score memeber添加成员
127.0.0.1:6380> zadd result 10 xiaolaoshi
(integer) 1
127.0.0.1:6380> zadd result 20 wanglaoshi
(integer) 1
127.0.0.1:6380> zadd result 30 chenlaoshi
(integer) 1
zscore key member计算某个成员的分数
127.0.0.1:6380> zadd result 30 chenlaoshi
(integer) 1
127.0.0.1:6380> zscore result chenlaoshi
"30"
127.0.0.1:6380> zadd result 40 chenlaoshi
(integer) 0
127.0.0.1:6380> zscore result chenlaoshi
"40"
计算成员的排名 zrank key member
127.0.0.1:6380> zrank result xiaolaoshi
(integer) 0
127.0.0.1:6380> zrank result wanglaoshi
(integer) 1
删除成员 zrem key member
127.0.0.1:6380> zrem result wanglaoshi
(integer) 1
127.0.0.1:6380> zrank result wanglaoshi
(nil)
增加成员的 分数zincrby key increment member
127.0.0.1:6380> zincrby result 50 xiaolaoshi
"60
返回指定排名范围的成员 zrange key start end
返回指定排名范围的成员 zrange key start end
127.0.0.1:6380> zrange result 0 1
1) "chenlaoshi"
2) "xiaolaoshi"
返回指定分数范围的成员 zrangebysore key min max
返回指定分数范围成员个数 zcount key min max
删除指定排名内的升序元素 zremrangebyrank key start end
127.0.0.1:6380> zremrangebyrank result 0 1
(integer) 2
127.0.0.1:6380> zrange result 0 1
(empty list or set)
删除指定分数范围的成员 zremrangebyscore key min max key min max
集合间的操作
交集 zinterstore destination numkeys key 制定一个交集并集查询结果的输出集合desitination指定查询交集并集的总zset个数numkeys ,给定符合数量的多个zset集合
127.0.0.1:6380> zadd result1 10 xiao
(integer) 1
127.0.0.1:6380> zadd result1 20 wang
(integer) 1
127.0.0.1:6380> zadd result2 30 xiao
(integer) 1
127.0.0.1:6380> zadd result2 40 wang
(integer) 1
127.0.0.1:6380> zinterstore out1 2 result1
result2
(integer) 2
127.0.0.1:6380> zrange out1 0 -1 withscores
1) "xiao"
2) "40"
3) "wang"
4) "60
并集 zunionstore destionation numkeys key
redis多实例部署
单实例单线程的redis进程不足以高效率使用cpu和内存资源,所以一般来讲redis在同一台机器上要启动多个进程完成多实例部署;默认占用6379的情况下无法完成直接的3个实例启动,这里我们需要了解如何通过指定配置文件,将多实例部署在linux上
启动redis服务的命令redis-server 没有加载任何配置文件指定各种各样的配置信息(端口指定,ip绑定,后台运行)
例如在根目录存在一个配置文件的模板(大部分与默认启动的配置相同)redis.conf
启动默认配置结点
#redis-server 配置文件的名称
/redis根目录/redis.conf
redis中的数据单位解释
配置文件配置
bind 用#注释掉
如果需要绑定监听的ip(客户端只有通过被绑定的ip才可以利用redis-cli -h ip地址链接服务器)
bing 127.0.0.1 106.75.101.219(外网可访问当前服务器的ip)
一旦用#注释bind,没有任何限制,只要可以链接服务器,都允许使用
保护模式不启动
保护模式开启,需要登录密码,改成no
6379是默认端口(要启动其他的redis实例需要修改端口)
当客户端空闲时间达到一小时,就会自动断开连接,0秒表示不启用超时配置
daemonize 设置成yes让redis服务器启动有守护进程管理
对应不同的redis实例,pid的文件名称需要和端口同名每个进程在linux或者其他操作系统中都会占用pid号,当系统中的进程过多时,需要查找redis进程号可能比较麻烦,直接打开pid文件查看即可
3logfile 需要指定,利用端口号命名,放到redis根目录
save 900(秒) 1(变动的数据条数) 当900以内,至少有1条数据变动,flush保存数据到文件
save 300 10
300秒以内至少10条数据变动,保存文件
save 60 10000
指定dump的持久化文件,每个服务单独指向一个文件,重启时,数据不会错乱
启动第二和第三个redis实例,redis-server redis.conf(指定启动文件)
拷贝redis.conf,用redis6380.conf,redis6381.conf
将拷贝的文件中只修改与端口有关内容
用vim打开,命令:%s/6379/6380/g,将所有6379改为6380
6381结点类似
启动另外两个节点
#redis-server redis6380.conf
#redis-server redis6381.conf
#ps -ef|grep redis
指定端口登录客户端redis-cli -p [端口号] -h [ip]
redis的java客户端–jedis
SpringBoot整合redis
- List item
导入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.4.7.RELEASE</version>
</dependency
- 连接java客户端到redis服务
- 创建一个jedis的对象,提供ip地址,和端口等信息
@Test
//创建Jedis对象
Jedis jedis=new Jedis("10.9.151.60", 6380);
//jedis具备了操作6380的能力,
jedis.set("name","yuzb");
System.out.println(jedis.get("name"));
public void connectRedis(){
}
利用redis实现缓存
需求:查询某个商品数据,使用缓存数据返回响应;请求发送之后,到达系统中,利用jedis对象连接redis实现缓存功能
-
生成商品对象的缓存key-value中调用数据的key
key值设计=“业务逻辑”+“业务id” -
判断缓存是否存在这个key值,如果有key值:说明曾经至少访问过一次,已经从数据库中将数据查询并且存放到缓存,如果没有key值:需要调用数据库持久层封装product对象,并且存放到缓存供后续访问使用
public Product queryById(String productId) {
//创建一个连接对象使用来操作redis缓存技术
Jedis jedis=new Jedis("10.9.151.60",6381);
//生成当前业务逻辑的key值,业务信息+productId
String productKey="product_"+productId;
//想办法将product序列化成String的json字符串,使用jackson的api
ObjectMapper mapper=new ObjectMapper();
try{
//判断缓存数据是否存在
if(jedis.exists(productKey)){//表示可以使用缓存
//Map<String, String> productMap = jedis.hgetAll(productKey);
//JSON字符串解析反序列化
String json=jedis.get(productKey);
Product product=mapper.readValue(json, Product.class);
return product;
}else{//商品key不存在,需要访问数据库,将数据存放redis
Product product=productMapper.queryById(productId);
//cong product对象映射到json字符串 writeValueAsString"":""
String json=mapper.writeValueAsString(product);
System.out.println(json);//{"":"","":"","":""}
//将json存储到redis中
jedis.set(productKey, json);
return product;
}
}catch(Exception e){
e.printStackTrace();
return null;
}
}
上面缓存方案存在问题:
1.不应该直接new一个jedis,而是交给框架去处理
2.只有一个嗯结点,如果宕机就不可用,造成缓存失效,并发下容易击穿数据库
分片的自定义计算(三个节点)
数据分片:数据层的分布式体系中,有多个节点维护的是整体数据,每一个节点维护一部分数据–分片数据
自定义的分片计算逻辑—hash取余
key.hashCode():key值不变,hashCode结果不变并且是整数
key.hashCode()&Integer.MAX_VALUE:31保真运算,得到正整数
(key.hashCode()&Integer.MAX_VALUE)%N:N是节点个数,分片个数,取余结果时[0,1,2,…N-1]
可以通过这样的一个公式,将任意的key值,只要key值不变永远对应[0,1,2,…N-1]中一个整数
public void set(String key,String value){
//根据hash取余结果的逻辑实现存储在不同的结点0,6379,1,6380,6381
Jedis jedis6379=new Jedis("10.9.151.60", 6379);
Jedis jedis6380=new Jedis("10.9.151.60", 6380);
Jedis jedis6381=new Jedis("10.9.151.60", 6381);
//hash取余得到3个结点的取余结果,
int result=(key.hashCode()&Integer.MAX_VALUE)%3;
if(result==0){//6379
jedis6379.set(key, value);
System.out.println("数据绑定在6379存储");
}
if(result==1){//6380
jedis6380.set(key, value);
System.out.println("数据绑定在6380存储");
}
if(result==2){//6381
jedis6381.set(key, value);
System.out.println("数据绑定在6381存储");
}
}
hash取余的算法存在的问题:在扩容,缩容,数据迁移特别大,导致数据缓存未命中的概率极高;
hash一致性分片对象ShardedJedis
jedis的客户端如果连接的是redis的集群,可以在内部直接生成一个hash一致性的算法,解决key值与结点的对应关系.ShardedJedis的分片对象就封装了对多个结点集群操作时的hash一致性计算
@Test
public void shardeJedisTest(){
//收集结点信息
List<JedisShardInfo> infoList=new ArrayList<JedisShardInfo>();
//收集6379,6380,6381
infoList.add(new JedisShardInfo("10.9.151.60", 6379, 500, 500, 5));
infoList.add(new JedisShardInfo("10.9.151.60", 6380, 500, 500, 1));
infoList.add(new JedisShardInfo("10.9.151.60", 6381, 500, 500, 1));
//创建分片对象shardedJedis
ShardedJedis sJedis=new ShardedJedis(infoList);
//sJedis.set("name", "yuzb");
//System.out.println(sJedis.get("name"));
for(int i=0;i<10000;i++){
String key="key_"+i;
sJedis.set(key, "");
}
}
分片连接池
单个分片对象无法满足系统中操作集群使用hash一致性分片计算的要求;引入jedis提供的分片连接池,初始化一些分片对象,每次调用需要获取resource资源,用完了还回池子
生成代码步骤
1 收集结点信息;
2 连接池的各种属性设置:最大连接数,最大空闲,最小空闲等的的配置对象
生成
3 创建连接池对象
4 从连接池获取资源调用代码
5 返回资源到连接池
//连接池使用
@Test
public void connectPool(){
//收集节点信息
List<JedisShardInfo> infoList=new ArrayList<JedisShardInfo>();
//收集6379,6380,6381
infoList.add(new JedisShardInfo("10.9.151.60", 6379, 500, 500, 5));
infoList.add(new JedisShardInfo("10.9.151.60", 6380, 500, 500, 1));
infoList.add(new JedisShardInfo("10.9.151.60", 6381, 500, 500, 1));
//连接池配置对象
GenericObjectPoolConfig config=new GenericObjectPoolConfig();
config.setMaxTotal(200);
config.setMaxIdle(8);
config.setMinIdle(3);
//连接池对象
ShardedJedisPool pool=new ShardedJedisPool(config, infoList);
//从连接池获取分片对象操作集群
ShardedJedis sJedis = pool.getResource();
sJedis.set("name", "ghaha");
pool.returnResource(sJedis);
}
以上存在硬编码问题,如将redis结点直接写在代码中,连接池不是交给框架维护
SpringBoot整合redis改进版:
- 引入的技术准备配置文件application.properties
- 在配置文件写上redis配置
#redis-customize
redis.nodes=10.9.151.60:6379,10.9.151.60:6380,10.9.151.60:6381
redis.maxTotal=200
redis.maxIdle=8
redis.minIdle=3
- 读取properties属性:
@ConfigurationProperties(prefix="")对属性赋值,调用getter/setter将读取的properties数据存放到生成bean中
/**
* 在当前工程扫描范围之内的configuration配置类
* 读取前缀是redis的所有key值,根据私有属性名称的getter/stter
* 赋值给属性
* @author yuzb
*
*/
@Configuration
@ConfigurationProperties(prefix="redis")
public class RedisCumConfiguration {
//根据前缀读取数据,私有属性名称,必须和
//properties中的值相同
private String nodes;
private Integer maxTotal;
private Integer maxIdle;
private Integer minIdle;
//编写初始化JedisShardPool对象的方法,@Bean将返回对象
//作为框架管理的bean
@Bean
public ShardedJedisPool initJedisPool(){
//利用本类中读取的属性,创建连接池对象
//先做一个config对象
GenericObjectPoolConfig config=new GenericObjectPoolConfig();
config.setMaxIdle(maxIdle);
config.setMaxTotal(maxTotal);
//config.setMinIdle(minIdle);
//解析nodes,生成一个list对象
//准备一个空内容
List<JedisShardInfo> infoList=new ArrayList<JedisShardInfo>();
String[] node = nodes.split(",");//{"10.9.9.9:6379","10l.9.9.9:6380",""}
for (String hostAndPort : node) {
String host=hostAndPort.split(":")[0];
int port=Integer.parseInt(hostAndPort.split(":")[1]);
infoList.add(new JedisShardInfo(host, port));
}
//list,config,构造连接池对象返回
return new ShardedJedisPool(config,infoList);
}
public String getNodes() {
return nodes;
}
public void setNodes(String nodes) {
this.nodes = nodes;
}
public Integer getMaxTotal() {
return maxTotal;
}
public void setMaxTotal(Integer maxTotal) {
this.maxTotal = maxTotal;
}
public Integer getMaxIdle() {
return maxIdle;
}
public void setMaxIdle(Integer maxIdle) {
this.maxIdle = maxIdle;
}
public Integer getMinIdle() {
return minIdle;
}
public void setMinIdle(Integer minIdle) {
this.minIdle = minIdle;
}
- 利用读取的属性生成框架维护的对象JedisShardPool连接池.
/**
* jedis工具类,用于生成连接池和Jedis对象,并提供redis的常用操作
* @author yuzb
*
*/
@Component
public class RedisCumUtils {
@Autowired
private ShardedJedisPool pool;
//query,addOrUpdate,delete,isExists
public String query(String key){
ShardedJedis jedis = pool.getResource();
try{
return jedis.get(key);
}catch(Exception e){
//异常处理逻辑
return null;
}finally{
pool.returnBrokenResource(jedis);
}
}
public void addOrUpdate(String key,String value){
ShardedJedis jedis = pool.getResource();
try{
jedis.set(key, value);
}catch(Exception e){
//异常处理逻辑
}finally{
pool.returnBrokenResource(jedis);
}
}
public void delete(String key){
ShardedJedis jedis = pool.getResource();
try{
jedis.del(key);
}catch(Exception e){
//异常处理逻辑
}finally{
pool.returnBrokenResource(jedis);
}
}
public Boolean isExist(String key){
ShardedJedis jedis = pool.getResource();
try{
return jedis.exists(key);
}catch(Exception e){
//异常处理逻辑
return false;
}finally{
pool.returnBrokenResource(jedis);
}
}
}
@Autowired
private RedisCumUtils jedis;
public Product queryById(String productId) {
//创建一个连接对象使用来操作redis缓存技术
//Jedis jedis=new Jedis("10.9.151.60",6381);
//通过注入进来的pool获取数据连接资源ShardedJedis
//ShardedJedis jedis=pool.getResource();
//生成当前业务逻辑的key值,业务信息+productId
String productKey="product_"+productId;
//想办法将product序列化成String的json字符串,使用jackson的api
ObjectMapper mapper=new ObjectMapper();
try{
//判断缓存数据是否存在
if(jedis.isExist(productKey)){//表示可以使用缓存
//Map<String, String> productMap = jedis.hgetAll(productKey);
//JSON字符串解析反序列化
String json=jedis.query(productKey);
Product product=mapper.readValue(json, Product.class);
return product;
}else{//商品key不存在,需要访问数据库,将数据存放redis
Product product=productMapper.queryById(productId);
//cong product对象映射到json字符串 writeValueAsString"":""
String json=mapper.writeValueAsString(product);
System.out.println(json);//{"":"","":"","":""}
//将json存储到redis中
jedis.addOrUpdate(productKey, json);
return product;
}
}catch(Exception e){
e.printStackTrace();
return null;
}
}
hash一致性:集群中节点的数量越多,使用hash取余算法经过扩容缩容时,需要迁移的数据量就越大,否则,会造成大量的数据缓存未命中–雪崩/缓存击穿,hash一致性恰好与hash取余相反:节点越多,扩容缩容迁移越少;