Redis

一 引言

1.数据库压力过大

由于用户量增大,请求数量也随之增大,数据压力过大。

2.数据不同步

多台服务器之间,数据不同步

3.传统锁失效

多台服务器之间的锁,已经不存在互斥性了

二 Redis介绍

1.NoSQL 介绍

NoSQL,泛指非关系型的数据库,NoSQL即Not-Only SQL,它可以作为关系型数据库的良好补充.
1.键值(Key-Value)存储数据库
相关产品:Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB
典型应用:内容缓存,主要用于处理大量数据的高访问负载;
数据模型:一系列键值对;
优势:快速查询;
劣势:存储的数据缺少结构化.
2.列存储数据库
相关产品:Cassandra, HBase, Riak;
典型应用:分布式的文件系统;
数据模型:以列簇式存储,将同一列数据存在一起;
优势:查找速度快,可扩展性强,更容易进行分布式扩展; 劣势:功能相对局限.
3.文档型数据库
相关产品:CouchDB、MongoDB;
典型应用:Web应用(与Key-Value类似,Value是结构化的);
数据模型:一系列键值对;
优势:数据结构要求不严格;
劣势:查询性能不高,而且缺乏统一的查询语法.
4.图形(Graph)数据库
相关数据库:Neo4J、lnfoGrid、lnHnite Graph;
典型应用:社交网络;
数据模型:图结构;
优势:利用图结构相关算法;
劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案.

2.数据库分类

1.关系型数据库(表)
MySQL,Oracle,SQLServer,DB2,SQLite....
注意:关系型数据库最大的不足,就是受限于磁盘读写能力(I/O)!

2.非关系型数据库(基于内存)
Redis,MongoDB,ElasticSearch,HBase....
非关系型数据库是为了弥补关系型数据库的不足的!

3.非关系型数据库的分类

①.键值对型:redis
②.列存储型:hbase
③.图形类型:neo4j
④.文档型.mongodb

4.Redis概念

Redis是一种用C语言实现的,开源的,键值对类型的非关系型数据库!

Redis:REmote Dictionary Server(远程字典服务器),它是一个完全开源免费且遵守BSD协议,用C语言开发的,高性能key-value型分布式内存数据库,它是基于内存运行并支持持久化的NoSQL数据库.可用 于缓存,事件发布或订阅,高速队列等场景.该数据库使用ANSI C语言编写,支持网络,提供字符串,哈希,列表,队列,集合结构直接存取,基于内存,可持久化.Redis是当前最热门的NoSql数据库之一,也被人们称为数据结构服务器.

5.Redis的使用场景

①.缓存(数据查询、短连接、新闻内容、商品内容等等,最常用);

②.消息队列,任务队列(秒杀、抢购、12306 等)等队列;

③.获取最新的N个数据(取最新文档、排行榜等);
 
④.计数器应用,网站访问统计等;

⑤.发布/订阅消息(消息通知);

⑥.数据过期处理(可以精确到毫秒),优惠券/红包等;

⑦.商品列表,评论列表,聊天室好友列表等实时性要求较高的场景;

⑧.分布式集群架构中的 session 分离和集中存储;

⑨.各种排行榜.

6.应用场景详解

1.会话缓存
最常用的一种使用Redis的情景是会话缓存(session cache).用Redis缓存会话比其他存储(如 Memcached)的优势在于:Redis提供持久化.
2.消息队列
Reids在内存存储引擎领域的一大优点是提供list和set操作,这使得Redis能作为一个很好的消息队
列平台来使用.Redis作为队列使用的操作,就类似于本地程序语言(如Python)对list的 push/pop 操作.
3.排行榜/计数器
Redis在内存中对数字进行递增或递减的操作实现的非常好.集合(Set)和有序集合(Sorted Set) 也使得我们在执行这些操作的时候变的非常简单,Redis正好提供了这两种数据结构.
4.发布/订阅
Redis的发布/订阅的使用场景非常多,经常在社交网络连接中使用,还可作为基于发布/订阅的脚本触
发器,甚至用Redis的发布/订阅功能来建立聊天系统!

7.Redis的数据类型

①.string字符串类型;

②.hash类型;

③.list列表类型;

④.set集合类型;

⑤.zset有序集合类型;

⑥.hyperloglog计算近似值的;

⑦.GEO地理坐标类型;

⑧.bit字节类型.

8.Reids的特点

Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库的加载都是在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存.
正因为是纯内存的操作,所以Redis的性能非常出色,每秒可以处理超过10万次读写操作,是已知性能最快的Key-Value DB.
Redis的出色之处不仅仅是性能,它的最大的魅力是支持保存多种数据结构,此外单个value的最大限制 是1GB,不像memcached只能保存1MB的数据,因此Redis可以用来实现很多有用的功能.
比方说可以用Redis的List数据类型来做FIFO双向链表,实现一个轻量级的高性能消息队列服务;用它的Set类型可以做高性能的tag系统等.另外Redis也可以对存入的Key-Value设置expire时间,因此也可以被当作一个功能加强版的memcached来用.

9.Redis优点

1 速度快:因为redis基于内存缓存,对数据可以进行高并发读写;
2 支持丰富数据类型:支持string,list,set,sorted set,hash;
3 支持事务:操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行;
4 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除;
5 扩展性和可用性高:支持垂直扩展,提升硬件性能;通过集群支持水平扩展.

10.Redis缺点

Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上.
另外Redis(ACID处理非常简单)也无法做到太复杂的关系数据库模型.

11.Redis与其他Key-Value缓存

• Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用.
• Redis不仅仅支持简单的key-valu e类型的数据,同时还提供list,set,zset,hash等数据结构的
存储.
• Redis支持数据的备份,即master-slave模式的数据备份.

四 安装 Redis

1.Docker-Compose安装

version: '3.1'
services:
  redis:
    image: daocloud.io/library/redis:5.0.7 restart: always
    container_name: redis

    environment:
      -TZ=Asia/Shanghai 
    ports:
      -6379:6379

2.redis-cli连接Redis

进去Redis容器的内部
docker exec -it 容器id bash
在容器内部,使用redis-cli连接

Redis_第1张图片

3.图形化界面连接Redis

Redis_第2张图片

4.redis的通讯机制

ping-pong机制

在这里插入图片描述

五 Redis常用命令

1.Redis存储数据的结构

常用的5种数据结构:
key-string:—个key对应一个值。
key-hash:—个key对应一个Map。
key-list:—个key对应一个列表。
key-set:—个key对应一个集合。
key-zset:-个key对应一个有序的集合。

另外三种数据结构:
HyperLogLog:计算近似值的。
GEO:地理位置。
BT:一般存储的也是一个字符串,存储的是一个byte[]。

2.五种常用的存储数据结构图

五种常用的存储数据结构图
•key-string:最常用的,一般用于存储一个值。
•key-hash:存储一个对象数据的。
•key-list :使用list结构实现栈和队列结构。
•key-set :交集,差集和并集的操作。
•key-zset:排行榜,积分存储等操作。

Redis_第3张图片

3.string常用命令

1.添加值
set	key value

在这里插入图片描述

2.取值
get key

在这里插入图片描述

3.批量操作
mset key value [key value...]
mget key [key...]

Redis_第4张图片

4.自增命令
incr key(自增1)

在这里插入图片描述

5.自减命令
decr key(自减1)

在这里插入图片描述

6.自增或自减指定数量
incrby key increment
decrby key increment

Redis_第5张图片

7.设置值的同时指定生存时间
设置值的同时,指定生存时间(每次向Redis中添加数据时,尽量都设置上生存时间)
setex key second value

Redis_第6张图片

8.设置值,当前key不存在
#8.设置值,如果当前key不存在的话(如果这个key存在,什么事都不做,如果这个key不存在,和set命令一样)
setnx key value

Redis_第7张图片

9.在key对应的value
在key对应的valu e后,追加内容
append key value

在这里插入图片描述

10.查看value字符串的长度
strlen key

Redis_第8张图片

4.hash常用命令

1.存储数据&获取数据
hset key field value
hget key field

在这里插入图片描述

2.批量操作
hmset key field value [field value .
hmget key field [field ...]

Redis_第9张图片

3.自增
hincrby key field increment

Redis_第10张图片

4.设置值
设置值(如果key-field不存在,那么就正常添加,如果存在,什么事都不做)
hsetnx key field value

Redis_第11张图片

6.检查field是否存在
hexists key field

在这里插入图片描述

7.删除key对应的field
可以删除多个
hdel key field [field ...]

Redis_第12张图片

8.获取当前hash(field和value)
获取当前hash结构中的全部field和value
hgetall key

Redis_第13张图片

9.获取当前hash(field)
获取当前hash结构中的全部field
hkeys key

Redis_第14张图片

10.获取当前hash(value)
获取当前hash结构中的全部value
hvals key

Redis_第15张图片

11.获取当前hash(数量)
获取当前hash结构中field的数量
hlen key

在这里插入图片描述

5.list常用命令

list的数据类型---->对应的java中的数据结构(栈/队列)

    lpush,lpop--->栈
    lpush:入栈,每次是从list集合的左侧入栈;
    lpop:出栈,每次取list集合的栈顶的数据;

    rpush,rpop--->队列
    rpush:入队,每次是从list集合的右侧入队;
    rpop:出队,每次从对尾取值.
1.存储数据(lpush&rpush)
存储数据
lpush key value [value ...] 从左侧插入数据
rpush key value [value ...] 从右侧插入数据

Redis_第16张图片

Redis_第17张图片

2.存储数据(lpushx&rpushx)
存储数据(如果key不存在,什么事都不做,如果key存在,但是不是list结构,什么都不做)
lpushx key value
rpushx key value

Redis_第18张图片

3.修改数据
修改数据(在存储数据时,指定好你的索引位置,覆盖之前索引位置的数据,index超出整个列表的 长度,也会失败)
lset key index value

Redis_第19张图片

4.弹栈方式获取数据
lpop key  左侧弹出数据
rpop key  从右侧弹出数据

在这里插入图片描述

5.获取指定索引范围的数据
获取指定索引范围的数据(start从0开始,stop输入-1,代表最后一个,-2代表倒数第二个)
lrange key start stop

Redis_第20张图片

6.获取指定索引位置的数据
lindex key index

Redis_第21张图片

7.获取整个列表的长度
llen key

在这里插入图片描述

8.删除列表中的数据
删除列表中的数据(他是删除当前列表中的count个value值,count > 0从左侧向右侧删除,count < 0从右侧向左侧删除,count == 0删除列表中全部的value)
lrem key count value

Redis_第22张图片

9.保留列表中的数据
保留列表中的数据(保留你指定索引范围内的数据,超过整个索引范围被移除掉)
ltrim key start stop

在这里插入图片描述

10.rpoplpush
将一个列表中最后的一个数据,插入到另外一个列表的头部位置 rpoplpush list1 list2

Redis_第23张图片

6.set常用命令

1.存储数据
sadd key member [member ...]

Redis_第24张图片

2.获取数据
获取数据(获取全部数据)
smembers key

Redis_第25张图片

3.随机获取一个数据
随机获取一个数据(获取的同时,移除数据,count默认为1,代表弹出数据的数量) spop key [count]

Redis_第26张图片

4.交集
交集(取多个set集合交集)
sinter set1 set2 ...

Redis_第27张图片

5.并集
并集(获取全部集合中的数据)
sunion set1 set2 ...

Redis_第28张图片

6.差集
差集(获取多个集合中不一样的数据)
sdiff set1 set2 ...

Redis_第29张图片

7.删除数据
srem key member [member ...]

在这里插入图片描述

8.查看当前的set集合
#查看当前的set集合中是否包含这个值
sismember key member

Redis_第30张图片

7.zset常用命令

1.添加数据
添加数据(score必须是数值。member不允许重复的。)
zadd key score member [score member ...]

Redis_第31张图片

Redis_第32张图片

2.修改member的分数
修改member的分数(如果member是存在于key中的,正常增加分数,如果memeber不存在,这个命令就相当zadd)
zincrby key increment member

Redis_第33张图片

3.查看指定的member的分数
zscore key member

Redis_第34张图片

4.获取zset中数据的数量
zcard key

Redis_第35张图片

5.根据score的范围查询member数量
根据score的范围查询member数量 
zcount key min max

Redis_第36张图片

6.删除zset中的成员
删除zset中的成员
zrem key member [member...]

在这里插入图片描述

7.根据分数从小到大排序
根据分数从小到大排序,获取指定范围内的数据(withscores如果添加这个参数,那么会返回member对应的分数)
zrange key start stop [withscores]

Redis_第37张图片

8.根据分数从大到小排序
根据分数从大到小排序,获取指定范围内的数据(withscores如果添加这个参数,那么会返回member对应的分数)
zrevrange key start stop [withscores]

在这里插入图片描述

8.key常用命令

1.查看Redis中的全部的key
查看Redis中的全部的key (pattern: * , xxx*, *xxx) keys pattern

Redis_第38张图片

2.查看某一个key是否存在
查看某一个key是否存在(1 - key存在,0 - key不存在)
exists key

在这里插入图片描述

3.删除key
del key [key ...]

在这里插入图片描述

4.设置key的生存时间
设置key的生存时间,单位为秒,单位为毫秒,设置还能活多久
expire key second
pexpire key milliseconds

设置key的生存时间,单位为秒,单位为毫秒,设置能活到什么时间点
expireat key timestamp
pexpireat key milliseconds

Redis_第39张图片

5.查看key的剩余生存时间
查看key的剩余生存时间,单位为秒,单位为毫秒(-2 -当前key不存在,-1 -当前key没有设置生存时间,具体剩余的生存时间)
ttl key
pttl key

Redis_第40张图片

6.移除key的生存时间
移除key的生存时间(1 -移除成功,0 - key不存在生存时间,key不存在) persist key

Redis_第41张图片

7.选择操作的库
select 0~15

Redis_第42张图片

8.移动key到另外一个库中
移动key到另外一个库中 move key db

Redis_第43张图片

9.库的常用命令

1.清空当前所在的数据库
flushdb

Redis_第44张图片

2.清空全部数据库
flushall

在这里插入图片描述

3.查看当前数据库中有多少个key
dbsize

Redis_第45张图片

4.查看最后一次操作的时间
lastsave

在这里插入图片描述

5.实时监控Redis服务接收到的命令
monitor

在这里插入图片描述

10.关于key的命令

如果你们项目中有  1亿 个key,如何过滤出想要的key?

      keys pattern通配符.---->该答案并不是面试官想要的答案!!!

      keys product*

      keys命令会造成redis线程阻塞,而scan不会!

      更好的答案是回答 scan命令:

      scan cursor match pattern

      SCAN 0 match product*

Redis_第46张图片

六 Java连接redis

1.Jedis 连接

1.导入需要的依赖

        <dependency>
            <groupId>redis.clientsgroupId>
            <artifactId>jedisartifactId>
            <version>2.9.0version>
        dependency>
<dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.46version>
        dependency>
<dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>
2.测试1
@Test
public void test1() {
      
    Jedis jedis = new Jedis("172.20.10.8", 6379);

    String result = jedis.ping();
    System.out.println("result=" + result);

    jedis.close();
}

在这里插入图片描述

2.测试2
//Jedis存储一个对象到Redis以byte[]的形式
@Test
public void test2() {
      
    //1.连接Redis服务
    Jedis jedis = new Jedis("172.20.10.8", 6379);
//2.1 准备 key(String)-value(User) String key = "user";
    User user = new User();
    user.setId(2);
    user.setUsername("特没谱");
    user.setPassword("sb");
//2.2 将 key 和 valu e 转换为 byte口
    byte[] key = SerializationUtils.serialize("user1");
    byte[] userByte = SerializationUtils.serialize(user);
    jedis.set(key, userByte);

    jedis.close();
}

Redis_第47张图片

3.测试 3
//获取对象-以byte口形式在Redis中获取
@Test
public void test3() {
      
    //1.连接Redis服务
    Jedis jedis = new Jedis("172.20.10.8", 6379);

    byte[] result = jedis.get(SerializationUtils.serialize("user1"));
    //反序列化
    User user = (User) SerializationUtils.deserialize(result);
    System.out.println("user=" + user.toString());

    jedis.close();
}

在这里插入图片描述

4.测试4
//存储对象-以String形式存储
@Test
public void test1() {
      
    //1.连接 Redis
    Jedis jedis = new Jedis("172.20.10.8", 6379);

    User user = new User();
    user.setId(1);
    user.setUsername("syc");
    user.setPassword("123");
//转化为json字符串 String string = JSON.toJSONString(user); 
    String json = JSON.toJSONString(user);
    jedis.set("user2", json);
	System.out.println(json);
    jedis.close();
}

Redis_第48张图片

5.测试5
//获取对象-以String形式获取
@Test
public void test2() {
      
    Jedis jedis = new Jedis("172.20.10.8", 6379);

    String json = jedis.get("user2");
//将value反序列化为User
    User user = JSON.parseObject(json, User.class);
    System.out.println("user=" + user.toString());

    jedis.close();
}

Redis_第49张图片

2.Jedis连接池的操作

使用连接池操作Redis,避免频繁创建和销毁链接对象消耗资源
@Test
public void test1() {
      
 //Jedis jedis = new Jedis("10.11.53.143", 6379);

 JedisPool pool=new JedisPool("172.20.10.8", 6379);
 //使用Jedis的连接池,来获取Jedis对象
 Jedis jedis = pool.getResource();

 User user = new User();
 user.setId(1);
 user.setUsername("syc");
 user.setPassword("123");
//转化为json字符串 String string = JSON.toJSONString(user); 
 String json = JSON.toJSONString(user);
 jedis.set("user3", json);

 jedis.close();
}

Redis_第50张图片

@Test
public void test2() {
      
    //Jedis jedis = new Jedis("10.11.53.143", 6379);
//1.创建连接池配置信息
    GenericObjectPoolConfig poolConfig=new GenericObjectPoolConfig();
    //设置连接池中的最大连接数
    poolConfig.setMaxTotal(100);
    //设置jedis对象的最大空闲时间
    poolConfig.setMaxIdle(10);
    poolConfig.setMinIdle(5);
    //当连接池中没有jedis对象,在3秒钟内如果获取不到jedis对象,就会产生超时
    poolConfig.setMaxWaitMillis(3000);

    JedisPool pool=new JedisPool(poolConfig,"172.20.10.8", 6379);
    //使用Jedis的连接池,来获取Jedis对象
    Jedis jedis = pool.getResource();

    User user = new User();
    user.setId(1);
    user.setUsername("yqq");
    user.setPassword("123");

    String json = JSON.toJSONString(user);
    jedis.set("user4", json);

    jedis.close();
}

Redis_第51张图片

3.Redis的管道操作

因为在操作Redis的时候,执行一个命令需要先发送请求到Redis服务器,这个过程需 要经历网络的延迟,Redis还需要给客户端一个响应。
如果我需要一次性执行很多个命令,上述的方式效率很低,可以通过Redis的管道,先 将命令放到客户端的一个Pipeline中,之后一次性的将全部命令都发送到Redis服务,
Redis服务一次性的将全部的返回结果响应给客户端。
// Redis管道的操作
@Test
public void pipeline(){
      
//1.创建连接池
JedisPool pool = new JedisPool("192.168.199.109,6379);
long l = System.currentTimeMillis。;
/*//2.获取一个连接对象
Jedis jedis = pool.getResource。;
//3.执行 incr - 100000次
for (int i = 0; i < 100000; i++) { jedis.incr("pp");
}
//================================
//2. 获取一个连接对象
Jedis jedis = pool.getResource();
//3.创建管道
Pipeline pipelined = jedis.pipelined(); //3.执行incr - 100000次放到管道中 for (int i = 0; i < 100000; i++) {
pipelined.incr("qq");
}
//4.执行命令 pipelined. syncAndReturnAll。;
//5. 释放资源 jedis.close();
System.out.println(System.currentTimeMillis() - l);

七 Redis其他配置及集群

1.Redis的事务

Redis的事务:一次事务操作,改成功的成功,该失败的失败。
先开启事务,执行一些列的命令,但是命令不会立即执行,会被放在一个队列中,如果 你执行事务,那么这个队列中的命令全部执行,如果取消了事务,一个队列中的命令全 部作废。
•开启事务:multi
•输入要执行的命令:被放入到一个队列中
•执行事务:exec
•取消事务:discard

Redis的事务向发挥功能,需要配置watch监听机制
在开启事务之前,先通过watch命令去监听一个或多个key,在开启事务之后,如果有 其他客户端修改了我监听的key,事务会自动取消。
如果执行了事务,或者取消了事务,watch监听自动消除,一般不需要手动执行 unwatch。

Redis_第52张图片

@Test
public void test1() {
      
    //Jedis jedis = new Jedis("10.11.53.143", 6379);

    JedisPool pool=new JedisPool("172.20.10.8", 6379);
    //使用Jedis的连接池,来获取Jedis对象
    Jedis jedis = pool.getResource();

    //开启事务
    Transaction transaction = jedis.multi();

    //redis.clients.jedis.exceptions.JedisDataException:
    // Cannot use Jedis when in Multi. Please use Transation or reset jedis state.
    //jedis.set("name","yyg");

    //注意:redis中事务的使用,要利用Transaction对象来执行!不能用Jedis对象!
    transaction.set("name","易青青");

    //提交事务
    transaction.exec();
    //取消事务
    //transaction.discard();

    jedis.close();
}

2.Redis持久化机制

1.RDB
RDB是Redis默认的持久化机制
・RDB持久化文件,速度比较快,而且存储的是一个二进制的文件,传输起来很方便。
・RDB持久化的时机:
save 900 1:在900秒内,有1个key改变了,就执行RDB持久化。
save 300 10:在300秒内,有10个key改变了,就执行RDB持久化。
save 60 10000:在60秒内,有10000个key改变了,就执行RDB持久化。
RDB无法保证数据的绝对安全

在这里插入图片描述

2. AOF
AOF持久化机制默认是关闭的,Redis官方推荐同时开启RDB和AOF持久化,更安全, 避免数据丢失。
•AOF持久化的速度,相对RDB较慢的,存储的是一个文本文件,到了后期文件会比较大,传输困难。
•AOF持久化时机。
appendfsync always:每执行一个写操作,立即持久化到AOF文件中,性能比较低。
appendfsync everysec :每秒执行一次持久化。
appendfsync no :会根据你的操作系统不同,环境的不同,在一定时间内执行一次持久化。
•AOF相对RDB更安全,推荐同时开启AOF和RDB。

注意事项:
同时开启RDB和AOF的注意事项:
如果同时开启了AOF和RDB持久化,那么在Redis宕机重启之后,需要加载一个持久化文件,优先选择AOF文件
如果先开启了RDB,再次开启AOF,如果RDB执行了持久化,那么RDB文件中的内容会被AOF覆盖掉。

Redis_第53张图片

加载自定义配置文件

在这里插入图片描述

1.redis.conf解读
实现跨机器访问redis服务器

Redis_第54张图片

保护模式,一般为yes ,在集群中建议开启

在这里插入图片描述

redis服务器端口:可以修改

在这里插入图片描述

内存设置

Redis_第55张图片

满足条件就自动持久化保存到磁盘》优化调优
如:save 900 1:在900秒内,有1个key改变了,就执行RDB持久化。

Redis_第56张图片

数据备份到dump.rdb文件

在这里插入图片描述

连接服务器需要密码

Redis_第57张图片

最多连接客户端数量

在这里插入图片描述

内存大小,一般根据机器性能默认分配

在这里插入图片描述

 redis的内存淘汰策略,

Redis_第58张图片

当为no时,只开启RDB持久化机制,
当为yes时,就开启了AOF持久化机制,这个时候相当于两种机制同时存在

Redis_第59张图片

在开启AOF持久化机制后,有自己的数据库文件

Redis_第60张图片

AOF的3种持久化规则

Redis_第61张图片

3.数据的备份与恢复命令

 save : 手动备份数据到磁盘中

 恢复: 只需要把dump.rdb文件复制到data目录下,只要redis一启动,就会自动恢复.

Redis_第62张图片

4.redis内存调优配置

redis的内存淘汰策略:

    XMEMORY POLICY: how Redis will select what to remove when maxmemory
	# is reached. You can select among five behaviors:
	# 
	# 如下是默认自带的几种(8种)淘汰策略(重点):
	# volatile-lru -> Evict using approximated LRU among the keys with an expire set.
	# allkeys-lru -> Evict any key using approximated LRU.
	# volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
	# allkeys-lfu -> Evict any key using approximated LFU.
	# volatile-random -> Remove a random key among the ones with an expire set.
	# allkeys-random -> Remove a random key, any key.
	# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
	# noeviction -> Don't evict anything, just return an error on write operations.
	#
	# LRU means Least Recently Used 最近最少使用算法
	# LFU means Least Frequently Used 最近最少频繁使用算法


	The default is:默认的内存淘汰策略:可以在下面修改策略
	 # maxmemory-policy noeviction 不驱除

5.redis的持久化方案及实现原理

 ①.rdb(默认)---->bgsave

       设置rdb的持久化规则:
        save 900 1
		save 300 10
		save 60 10000

       默认redis会使用 LZF 算法对dump.rdb文件进行压缩.

       Compress string objects using LZF when dump.rdb databases?	

       特点:
         默认开启;	
         性能好:不会每执行一次redis增删改操作就进行一次IO操作.
         生成的rdb备份文件较小,所以后来恢复速度就快;
         数据完整性不够好,存在数据丢失的风险.
         
②.aof---->bgrewriteof

       aof的3种持久化规则:
      
	    appendfsync always:只要做一次数据操作就做持久化
		appendfsync everysec:每秒做一次持久化
		appendfsync no

	   特点:
	     默认没有开启;
	     性能没有RDB好;
	     数据完整性好.
	     aof文件会很大,备份恢复速度就较慢.	

6.Redis的主从架构

单机版Redis存在读写瓶颈的问题

Redis_第63张图片

1.指定yml文件
version: "3.1"
services:
 redis1:
 image: daocloud.io/library/redis:5.0.7
 restart: always
 container_name: redis1
environment:
 - TZ=Asia/Shanghai
 ports:
 - 7001:6379
 volumes:
 - ./conf/redis1.conf:/usr/local/redis/redis.conf
command: ["redis-server","/usr/local/redis/redis.conf"]
 redis2:
 image: daocloud.io/library/redis:5.0.7
 restart: always
 container_name: redis2
 environment:
 - TZ=Asia/Shanghai
 ports:
 - 7002:6379
 volumes:
 - ./conf/redis2.conf:/usr/local/redis/redis.conf
links:
 - redis1:master
 command: ["redis-server","/usr/local/redis/redis.conf"]
 redis3:
 image: daocloud.io/library/redis:5.0.7
 restart: always
 container_name: redis3
 environment:
 - TZ=Asia/Shanghai
 ports:
 - 7003:6379
 volumes:
 - ./conf/redis3.conf:/usr/local/redis/redis.conf
links:
 - redis1:master
 command: ["redis-server","/usr/local/redis/redis.conf"]
2.conf

在这里插入图片描述

3.主机

Redis_第64张图片

4.从机1

Redis_第65张图片

5.从机2

Redis_第66张图片

6.总结
redis2和redis3从节点配置 replicaof master 6379
info replication :查看主从机信息

Redis_第67张图片

7.redis的高可用实现方案

	1.主从复制:一主多从,主机可写,从机备份.类似于MySQL的读写分离,存在的问题是一但主节点down掉,整个			Redis都不可用.

	2️.哨兵(2.x)机制:启用一个哨兵程序(节点),监控其余节点的状态,根据选举策略,进行主从切换.

	缺点:每个节点的数据依旧是一致的,仍无法实现分布式的数据库.

	3️.集群(3.x):结合上述两种模式,多主多从,实现高可用、分布数据存储.

8.哨兵

哨兵可以帮助我们解决主从架构中的单点故障问题

Redis_第68张图片

1.docker-compose.yml
version: "3.1"
services:
 redis1:
 image: daocloud.io/library/redis:5.0.7
 restart: always
 container_name: redis1
environment:
 - TZ=Asia/Shanghai
 ports:
 - 7001:6379
 volumes:
 - ./conf/redis1.conf:/usr/local/redis/redis.conf
 - ./conf/sentinel1.conf:/data/sentinel.conf
command: ["redis-server","/usr/local/redis/redis.conf"]
 redis2:
 image: daocloud.io/library/redis:5.0.7
 restart: always
 container_name: redis2
 environment:
 - TZ=Asia/Shanghai
 ports:
 - 7002:6379
 volumes:
 - ./conf/redis2.conf:/usr/local/redis/redis.conf
 - ./conf/sentinel2.conf:/data/sentinel.conf
links:
 - redis1:master
 command: ["redis-server","/usr/local/redis/redis.conf"]
 redis3:
 image: daocloud.io/library/redis:5.0.7
 restart: always
 container_name: redis3
 environment:
 - TZ=Asia/Shanghai
 ports:
 - 7003:6379
 volumes:
 - ./conf/redis3.conf:/usr/local/redis/redis.conf
 - ./conf/sentinel3.conf:/data/sentinel.conf
links:
 - redis1:master
 command: ["redis-server","/usr/local/redis/redis.conf"]

Redis_第69张图片

2.主机配置文件
daemonize yes
#指定Master节点的ip和端口(主)
sentinel monitor master 172.20.10.8 6379 2
#哨兵每隔多久监听一次redis架构 
sentinel down-after-milliseconds mymaster 10000
3.从机配置文件
daemonize yes
#指定Master节点的ip和端口(主)
sentinel monitor master 172.20.10.8 6379 2
#哨兵每隔多久监听一次redis架构 
sentinel down-after-milliseconds mymaster 10000
4.启动sentine
在Redis容器内部启动sentine即可
redis-sentinel sentinel.conf

在这里插入图片描述

5.查看sentinel运行状态
redis-cli -p 26379 info

Redis_第70张图片

Redis_第71张图片

Redis_第72张图片

9. Redis的集群

Redis_第73张图片

Redis集群在保证主从加哨兵的基本功能之外,还能够提升Redis存储数据的能力。
1.准备yml文件
version: "3.1"
services:
 redis1:
 image: daocloud.io/library/redis:5.0.7
 restart: always
 container_name: redis1
 environment:
 - TZ=Asia/Shanghai
 ports:
 - 7001:7001
 - 17001:17001
 volumes:
 - ./conf/redis1.conf:/usr/local/redis/redis.conf
 command: ["redis-server","/usr/local/redis/redis.conf"]
 redis2:
 image: daocloud.io/library/redis:5.0.7
 restart: always
 container_name: redis2
 environment:
 - TZ=Asia/Shanghai
 ports:
- 7002:7002
 - 17002:17002
 volumes:
 - ./conf/redis2.conf:/usr/local/redis/redis.conf
 command: ["redis-server","/usr/local/redis/redis.conf"] 
 redis3:
 image: daocloud.io/library/redis:5.0.7
 restart: always
 container_name: redis3
 environment:
 - TZ=Asia/Shanghai
 ports:
 - 7003:7003
 - 17003:17003
 volumes:
 - ./conf/redis3.conf:/usr/local/redis/redis.conf
 command: ["redis-server","/usr/local/redis/redis.conf"] 
 redis4:
 image: daocloud.io/library/redis:5.0.7
 restart: always
 container_name: redis4
 environment:
 - TZ=Asia/Shanghai
 ports:
 - 7004:7004
 - 17004:17004
 volumes:
 - ./conf/redis4.conf:/usr/local/redis/redis.conf
 command: ["redis-server","/usr/local/redis/redis.conf"] 
 redis5:
 image: daocloud.io/library/redis:5.0.7
 restart: always
 container_name: redis5
 environment:
 - TZ=Asia/Shanghai
 ports:
 - 7005:7005
 - 17005:17005
 volumes:
 - ./conf/redis5.conf:/usr/local/redis/redis.conf
 command: ["redis-server","/usr/local/redis/redis.conf"] 
 redis6:
 image: daocloud.io/library/redis:5.0.7
 restart: always
 container_name: redis6
 environment:
 - TZ=Asia/Shanghai
 ports:
 - 7006:7006
 - 17006:17006
 volumes:
 - ./conf/redis6.conf:/usr/local/redis/redis.conf
 command: ["redis-server","/usr/local/redis/redis.conf"]
2.conf配置文件
#指定redi s的端口号
port 7006
#开启Redis集群
cluster-enabled yes
#集群信息的文件
cluster-config-file nodes-7006.conf
#集群的对外ip地址
cluster-announce-ip 172.20.10.8
#集群的对外port
cluster-announce-port 7006
#集群的总线端口
cluster-announce-bus-port 17006

注意:需要配置6个 redis.conf 端口分别为1-6
3.启动6个节点
redis-cli --cluster create 172.20.10.8:7001 172.20.10.8:7002 172.20.10.8
:7003 172.20.10.8:7004 172.20.10.8:7005 172.20.10.8:7006 --cluster-rep
licas 1
注意:启动前先关闭Linux防火墙
	 6个节点,自动划分三个主机,三个从节点

Redis_第74张图片

Redis_第75张图片

4.集群内操作
redis-cli -c -p 7001|7002
-c 表示集群模式 cluster
注意:
	在集群内部任意任意主机或节点存储数据,都会根据 key 进行crc16算法,并且对16384取余,根据最终结果,将key-value存到指定redis(主机)节点上

在这里插入图片描述

Redis_第76张图片

Redis_第77张图片

5.查看主从机信息

Redis_第78张图片

10.Java连接Redis集群

1.测试1
@Test
public void test1() {
      
    //服务器节点集群
    Set<HostAndPort> nodes = new HashSet<>();
    nodes.add(new HostAndPort("172.20.10.8", 7001));
    nodes.add(new HostAndPort("172.20.10.8", 7002));
    nodes.add(new HostAndPort("172.20.10.8", 7003));
    nodes.add(new HostAndPort("172.20.10.8", 7004));
    nodes.add(new HostAndPort("172.20.10.8", 7005));
    nodes.add(new HostAndPort("172.20.10.8", 7006));

    JedisCluster cluster = new JedisCluster(nodes);
    cluster.hset("cluster", "name", "testCluster");
    cluster.hset("cluster", "info", "3333");

    //cluster.close();
}

Redis_第79张图片

2.测试2
@Test
public void test2() {
      
    //服务器节点集群
    Set<HostAndPort> nodes = new HashSet<>();
    nodes.add(new HostAndPort("172.20.10.8", 7001));
    nodes.add(new HostAndPort("172.20.10.8", 7002));
    nodes.add(new HostAndPort("172.20.10.8", 7003));
    nodes.add(new HostAndPort("172.20.10.8", 7004));
    nodes.add(new HostAndPort("172.20.10.8", 7005));
    nodes.add(new HostAndPort("172.20.10.8", 7006));

    JedisCluster cluster = new JedisCluster(nodes);
    String name = cluster.hget("cluster", "name");
    String info = cluster.hget("cluster", "info");
    System.out.println("name=" + name + "\n"+"info=" + info);

    //cluster.close();
}

在这里插入图片描述

八 Redis常见问题

1.key的生存时间

key的生存时间到了,Redis会立即删除吗?不会立即删除。
•定期删除:Redis每隔一段时间就去会去查看Redis设置了过期时间的key,会再 100ms的间隔中默认查看3个key。
•惰性删除:如果当你去查询一个已经过了生存时间的key时,Redis会先查看当前 key的生存时间,是否已经到了,直接删除当前key,并且给用户返回一个空值。

2.Redis的淘汰机制

在Redis内存已经满的时候,添加了一个新的数据,执行淘汰机制。

•volatile-lru:在内存不足时,Redis会在设置过了生存时间的key中干掉一个最近 最少使用的key。
•allkeys-lru:在内存不足时,Redis会在全部的key中干掉一个最近最少使用的 key。
•volatile-lfu:在内存不足时,Redis会在设置过了生存时间的key中干掉一个最近 最少频次使用的key。
•allkeys-lfu:在内存不足时,Redis会在全部的key中干掉一个最近最少频次使用 的key。
•volatile-random :在内存不足时,Redis会再设置过了生存时间的key中随机干掉 一个。
•allkeys-random:在内存不足时,Redis会在全部的key中随机干掉一个。
•volatile-ttl:在内存不足时,Redis会在设置过了生存时间的key中干掉一个剩余生存时间最少的key。
•noeviction :(默认)在内存不足时,直接报错。

3.缓存的常见问题

1.缓存穿透
问题出现的原因:查询的数据,Redis中没有,数据库中也没有。
1:根据id查询时,如果id是自增的,将id的最大值放到Redis中,在查询数据库之前,直接比较一下id。 2:如果id不是整形,可以将全部的id放到set中,在用户查询之前,去set中查看一下是否有一个id。
3:获取客户端的ip地址,可以将ip的访问添加限制。

Redis_第80张图片

2.缓存击穿
问题:缓存中的热点数据,突然到期了,造成了大量的请求都去访问数据库,造成数据库宕机?
1.在访问缓存中没有的时候,直接添加一个锁,让几个请求去访问数据库,避免数据库宕机。
2.热点数据的生存时间去掉。

Redis_第81张图片

3.缓存雪崩

Redis_第82张图片

4.缓存倾斜

Redis_第83张图片

九 SSM+Redis

1.web.xml

<servlet>
    <servlet-name>SpringMVCservlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>

    <init-param>
        <param-name>contextConfigLocationparam-name>
        <param-value>classpath:spring/spring-web.xmlparam-value>
    init-param>

    <load-on-startup>1load-on-startup>
servlet>

<servlet-mapping>
    <servlet-name>SpringMVCservlet-name>
    <url-pattern>/url-pattern>
servlet-mapping>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>

<context-param>
    <param-name>contextConfigLocationparam-name>
    <param-value>classpath:spring/application-*.xmlparam-value>
context-param>

<filter>
    <filter-name>Encodingfilter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>

    <init-param>
        <param-name>encodingparam-name>
        <param-value>UTF-8param-value>
    init-param>
filter>

<filter-mapping>
    <filter-name>Encodingfilter-name>
    <url-pattern>/*url-pattern>
filter-mapping>

2.spring-web.xml

<context:component-scan base-package="com.yyg.redis.web"/>

<mvc:annotation-driven/>

<mvc:resources mapping="/html/**" location="/html/"/>
<mvc:resources mapping="/js/**" location="/js/"/>

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/html/"/>
    <property name="suffix" value=".html"/>
bean>

3.application-service.xml

<context:component-scan base-package="com.yyg.redis.service"/>

4.application-redis.xml

<context:property-placeholder location="classpath:conf/redis.properties"/>


<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
    <property name="maxTotal" value="${redis.maxTotal}"/>
    <property name="maxIdle" value="${redis.maxIdle}"/>
    <property name="maxWaitMillis" value="${redis.maxWait}"/>
bean>


<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    <property name="hostName" value="${redis.host}"/>
    <property name="port" value="${redis.port}"/>
    <property name="password" value="${redis.password}"/>
    <property name="poolConfig" ref="poolConfig"/>
bean>


<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
    
    <property name="connectionFactory" ref="connectionFactory"/>

    
    <property name="enableTransactionSupport" value="true"/>

    
    <property name="keySerializer">
        <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
    property>
bean>

<bean id="redisUtil" class="com.yyg.redis.util.RedisUtil">
    <property name="redisTemplate" ref="redisTemplate"/>
bean>

5.application-dao.xml

<context:property-placeholder location="classpath:conf/*.properties"/>

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="url" value="${jdbc.url}"/>
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
bean>

<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>

    <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
bean>

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.yyg.redis.mapper"/>
bean>

6.mybatis-config.xml

<configuration>

    <settings>
        
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    settings>

    <typeAliases>
        <typeAlias type="com.yyg.redis.domain.Msg"/>
    typeAliases>

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <property name="helperDialect" value="mysql"/>
        plugin>
    plugins>

configuration>

7.db.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/yqq?useUnicode=true&characterEncoding=UTF8&useSSL=false
jdbc.username=root
jdbc.password=root

8.redis.properties

redis.host=172.20.10.8
redis.port=6379
redis.password=
redis.maxIdle=300
redis.maxTotal=600
redis.maxWait=1000

9.控制层接口

@Controller
public class MsgController {
      

    @Autowired
    private MsgService msgService;

    @GetMapping("/index")
    public String showIndex() {
      

        return "index";
    }

    @ResponseBody
    @PostMapping("/addMsg")
    public Map<String, Object> addMsg(Msg msg) {
      

        boolean result = msgService.addMsg(msg);
        Map<String, Object> map = new HashMap<>();
        if (result) {
      
            map.put("code", 200);
            map.put("msg", "success");
        } else {
      
            map.put("code", -1);
            map.put("msg", "error");
        }

        return map;
    }

    @ResponseBody
    @GetMapping("/queryMsg")
    public List<Msg> queryMsg(){
      

        return msgService.listMsg();
    }

}

10.业务处理

@Service
public class MsgServiceImpl implements MsgService {
      

    @Autowired
    private MsgMapper msgMapper;

    @Autowired
    private RedisUtil redisUtil;

    @Override
    public boolean addMsg(Msg msg) {
      
        //在进行数据库的添加,删除,修改等更新的时候,要考虑"双写一致性"问题!

        boolean result = msgMapper.addMsg(msg) > 0;

        if (result) {
      

            //第一种方案:删除某个key对应的缓存,把key-value
            //redisUtil.del("msg_list");
            //第二种方案:使得该key-value直接过期
            redisUtil.expire("msg_list", 0);

            //TODO:在分布式项目中,可以利用消息队列,在数据库更新成功后,发出一个消息(把缓存的key,id,内容等)
            //TODO:另外的一个服务中,就可以接收到该消息,从而做出redis缓存的更新!
        }
        return result;
    }

    @Override
    public List<Msg> listMsg() {
      
        //添加redis缓存,业务逻辑实现步骤:
        //1.先直接去redis缓存中查询是否有缓存

        //2.如果有缓存---->直接返回缓存结果

        //3.如果没有缓存--->进行数据库的查询--->把该结果存到redis缓存中

        List<Msg> msgs;

        List<Object> list = redisUtil.lGet("msg_list", 0, -1);

        if (list != null && list.size() > 0) {
      
            System.out.println("执行redis缓存...");

            msgs = JSON.parseArray(list.get(0).toString(), Msg.class);
        } else {
      

            System.out.println("执行数据库查询...");

            msgs = msgMapper.findAll();

            //查询mysql数据库
            String json = JSON.toJSONString(msgs);

            Random random = new Random();
            int time = random.nextInt(10000);
            //预防因为缓存过期时间同时失效,而造成的缓存雪崩问题
            redisUtil.lSet("msg_list", json, 3000 + time);
        }
        return msgs;
    }
}

11.RedisUtil

package com.yyg.redis.util;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;

public class RedisUtil {
      

   private RedisTemplate<String, Object> redisTemplate;

   public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
      
      this.redisTemplate = redisTemplate;
   }

   // =============================common============================
   /**
    * 指定缓存失效时间
    * 
    * @param key
    *            键
    * @param time
    *            时间(秒)
    */
   public boolean expire(String key, long time) {
      
      try {
      
         // if(time>0){
      
         redisTemplate.expire(key, time, TimeUnit.SECONDS);
         // }
         return true;
      } catch (Exception e) {
      
         e.printStackTrace();
         return false;
      }
   }

   /**
    * 根据key 获取过期时间
    * 
    * @param key
    *            键 不能为null
    * @return 时间(秒) 返回0代表为永久有效
    */
   public long getExpire(String key) {
      
      return redisTemplate.getExpire(key, TimeUnit.SECONDS);
   }

   /**
    * 判断key是否存在
    * 
    * @param key
    *            键
    * @return true 存在 false不存在
    */
   public boolean hasKey(String key) {
      
      try {
      
         return redisTemplate.hasKey(key);
      } catch (Exception e) {
      
         e.printStackTrace();
         return false;
      }
   }

   /**
    * 删除缓存
    * 
    * @param key
    *            可以传一个值 或多个
    */
   @SuppressWarnings("unchecked")
   public void del(String... key) {
      
      if (key != null && key.length > 0) {
      
         if (key.length == 1) {
      
            redisTemplate.delete(key[0]);
         } else {
      
            redisTemplate.delete(CollectionUtils.arrayToList(key));
         }
      }
   }

   // ============================String=============================
   /**
    * 普通缓存获取
    * 
    * @param key
    *            键
    * @return 值
    */
   public Object get(String key) {
      
      return key == null ? null : redisTemplate.opsForValue().get(key);
   }

   /**
    * 普通缓存放入
    * 
    * @param key
    *            键
    * @param value
    *            值
    * @return true成功 false失败
    */
   public boolean set(String key, Object value) {
      
      try {
      
         redisTemplate.opsForValue().set(key, value);
         //redisTemplate.opsForHash().put();
         return true;
      } catch (Exception e) {
      
         e.printStackTrace();
         return false;
      }

   }

   /**
    * 普通缓存放入并设置时间
    * 
    * @param key
    *            键
    * @param value
    *            值
    * @param time
    *            时间(秒) time要大于0 如果time小于等于0 将设置无限期
    * @return true成功 false 失败
    */
   public boolean set(String key, Object value, long time) {
      
      try {
      
         if (time > 0) {
      
            redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
         } else {
      
            set(key, value);
         }
         return true;
      } catch (Exception e) {
      
         e.printStackTrace();
         return false;
      }
   }

   /**
    * 递增
    * 
    * @param key
    *            键
    * @param delta
    *            要增加几(大于0)
    * @return
    */
   public long incr(String key, long delta) {
      
      if (delta < 0) {
      
         throw new RuntimeException("递增因子必须大于0");
      }
      return redisTemplate.opsForValue().increment(key, delta);
   }

   /**
    * 递减
    * 
    * @param key
    *            键
    * @param delta
    *            要减少几(小于0)
    * @return
    */
   public long decr(String key, long delta) {
      
      if (delta < 0) {
      
         throw new RuntimeException("递减因子必须大于0");
      }
      return redisTemplate.opsForValue().increment(key, -delta);
   }

   // ================================Map=================================
   /**
    * HashGet
    * 
    * @param key
    *            键 不能为null
    * @param item
    *            项 不能为null
    * @return 值
    */
   public Object hget(String key, String item) {
      
      return redisTemplate.opsForHash().get(key, item);
   }

   /**
    * 获取hashKey对应的所有键值
    * 
    * @param key
    *            键
    * @return 对应的多个键值
    */
   public Map<Object, Object> hmget(String key) {
      
      return redisTemplate.opsForHash().entries(key);
   }

   /**
    * HashSet
    * 
    * @param key
    *            键
    * @param map
    *            对应多个键值
    * @return true 成功 false 失败
    */
   public boolean hmset(String key, Map<String, Object> map) {
      
      try {
      
         redisTemplate.opsForHash().putAll(key, map);
         return true;
      } catch (Exception e) {
      
         e.printStackTrace();
         return false;
      }
   }

   /**
    * HashSet 并设置时间
    * 
    * @param key
    *            键
    * @param map
    *            对应多个键值
    * @param time
    *            时间(秒)
    * @return true成功 false失败
    */
   public boolean hmset(String key, Map<String, Object> map, long time) {
      
      try {
      
         redisTemplate.opsForHash().putAll(key, map);
         if (time > 0) {
      
            expire(key, time);
         }
         return true;
      } catch (Exception e) {
      
         e.printStackTrace();
         return false;
      }
   }

   /**
    * 向一张hash表中放入数据,如果不存在将创建
    * 
    * @param key
    *            键
    * @param item
    *            项
    * @param value
    *            值
    * @return true 成功 false失败
    */
   public boolean hset(String key, String item, Object value) {
      
      try {
      
         redisTemplate.opsForHash().put(key, item, value);
         return true;
      } catch (Exception e) {
      
         e.printStackTrace();
         return false;
      }
   }

   /**
    * 向一张hash表中放入数据,如果不存在将创建
    * 
    * @param key
    *            键
    * @param item
    *            项
    * @param value
    *            值
    * @param time
    *            时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
    * @return true 成功 false失败
    */
   public boolean hset(String key, String item, Object value, long time) {
      
      try {
      
         redisTemplate.opsForHash().put(key, item, value);
         if (time > 0) {
      
            expire(key, time);
         }
         return true;
      } catch (Exception e) {
      
         e.printStackTrace();
         return false;
      }
   }

   /**
    * 删除hash表中的值
    * 
    * @param key
    *            键 不能为null
    * @param item
    *            项 可以使多个 不能为null
    */
   public void hdel(String key, Object... item) {
      
      redisTemplate.opsForHash().delete(key, item);
   }

   /**
    * 判断hash表中是否有该项的值
    * 
    * @param key
    *            键 不能为null
    * @param item
    *            项 不能为null
    * @return true 存在 false不存在
    */
   public boolean hHasKey(String key, String item) {
      
      return redisTemplate.opsForHash().hasKey(key, item);
   }

   /**
    * hash递增 如果不存在,就会创建一个 并把新增后的值返回
    * 
    * @param key
    *            键
    * @param item
    *            项
    * @param by
    *            要增加几(大于0)
    * @return
    */
   public double hincr(String key, String item, double by) {
      
      return redisTemplate.opsForHash().increment(key, item, by);
   }

   /**
    * hash递减
    * 
    * @param key
    *            键
    * @param item
    *            项
    * @param by
    *            要减少记(小于0)
    * @return
    */
   public double hdecr(String key, String item, double by) {
      
      return redisTemplate.opsForHash().increment(key, item, -by);
   }

   // ============================set=============================
   /**
    * 根据key获取Set中的所有值
    * 
    * @param key
    *            键
    * @return
    */
   public Set<Object> sGet(String key) {
      
      try {
      
         return redisTemplate.opsForSet().members(key);
      } catch (Exception e) {
      
         e.printStackTrace();
         return null;
      }
   }

   /**
    * 根据value从一个set中查询,是否存在
    * 
    * @param key
    *            键
    * @param value
    *            值
    * @return true 存在 false不存在
    */
   public boolean sHasKey(String key, Object value) {
      
      try {
      
         return redisTemplate.opsForSet().isMember(key, value);
      } catch (Exception e) {
      
         e.printStackTrace();
         return false;
      }
   }

   /**
    * 将数据放入set缓存
    * 
    * @param key
    *            键
    * @param values
    *            值 可以是多个
    * @return 成功个数
    */
   public long sSet(String key, Object... values) {
      
      try {
      
         return redisTemplate.opsForSet().add(key, values);
      } catch (Exception e) {
      
         e.printStackTrace();
         return 0;
      }
   }

   /**
    * 将set数据放入缓存
    * 
    * @param key
    *            键
    * @param time
    *            时间(秒)
    * @param values
    *            值 可以是多个
    * @return 成功个数
    */
   public long sSetAndTime(String key, long time, Object... values) {
      
      try {
      
         Long count = redisTemplate.opsForSet().add(key, values);
         if (time > 0){
      
            expire(key, time);
         }
         return count;
      } catch (Exception e) {
      
         e.printStackTrace();
         return 0;
      }
   }

   /**
    * 获取set缓存的长度
    * 
    * @param key
    *            键
    * @return
    */
   public long sGetSetSize(String key) {
      
      try {
      
         return redisTemplate.opsForSet().size(key);
      } catch (Exception e) {
      
         e.printStackTrace();
         return 0;
      }
   }

   /**
    * 移除值为value的
    * 
    * @param key
    *            键
    * @param values
    *            值 可以是多个
    * @return 移除的个数
    */
   public long setRemove(String key, Object... values) {
      
      try {
      
         Long count = redisTemplate.opsForSet().remove(key, values);
         return count;
      } catch (Exception e) {
      
         e.printStackTrace();
         return 0;
      }
   }
   // ===============================list=================================

   /**
    * 获取list缓存的内容
    * 
    * @param key
    *            键
    * @param start
    *            开始
    * @param end
    *            结束 0 到 -1代表所有值
    */
   public List<Object> lGet(String key, long start, long end) {
      
      try {
      
         return redisTemplate.opsForList().range(key, start, end);
      } catch (Exception e) {
      
         e.printStackTrace();
         return null;
      }
   }

   /**
    * 获取list缓存的长度
    * 
    * @param key
    *            键
    * @return
    */
   public long lGetListSize(String key) {
      
      try {
      
         return redisTemplate.opsForList().size(key);
      } catch (Exception e) {
      
         e.printStackTrace();
         return 0;
      }
   }

   /**
    * 通过索引 获取list中的值
    * 
    * @param key
    *            键
    * @param index
    *            索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
    * @return
    */
   public Object lGetIndex(String key, long index) {
      
      try {
      
         return redisTemplate.opsForList().index(key, index);
      } catch (Exception e) {
      
         e.printStackTrace();
         return null;
      }
   }

   /**
    * 将list放入缓存
    * 
    * @param key
    *            键
    * @param value
    *            值
    * @return
    */
   public boolean lSet(String key, Object value) {
      
      try {
      
         redisTemplate.opsForList().rightPush(key, value);
         return true;
      } catch (Exception e) {
      
         e.printStackTrace();
         return false;
      }
   }

   /**
    * 将list放入缓存
    * 
    * @param key
    *            键
    * @param value
    *            值
    * @param time
    *            时间(秒)
    * @return
    */
   public boolean lSet(String key, Object value, long time) {
      
      try {
      
         redisTemplate.opsForList().rightPush(key, value);
         if (time > 0){
      
            expire(key, time);
         }
         return true;
      } catch (Exception e) {
      
         e.printStackTrace();
         return false;
      }
   }

   /**
    * 将list放入缓存
    * 
    * @param key
    *            键
    * @param value
    *            值
    * @return
    */
   public boolean lSet(String key, List<Object> value) {
      
      try {
      
         redisTemplate.opsForList().rightPushAll(key, value);
         return true;
      } catch (Exception e) {
      
         e.printStackTrace();
         return false;
      }
   }

   /**
    * 将list放入缓存
    * 
    * @param key
    *            键
    * @param value
    *            值
    * @param time
    *            时间(秒)
    * @return
    */
   public boolean lSet(String key, List<Object> value, long time) {
      
      try {
      
         redisTemplate.opsForList().rightPushAll(key, value);
         if (time > 0){
      
            expire(key, time);
         }
         return true;
      } catch (Exception e) {
      
         e.printStackTrace();
         return false;
      }
   }

   /**
    * 根据索引修改list中的某条数据
    * 
    * @param key
    *            键
    * @param index
    *            索引
    * @param value
    *            值
    * @return
    */
   public boolean lUpdateIndex(String key, long index, Object value) {
      
      try {
      
         redisTemplate.opsForList().set(key, index, value);
         return true;
      } catch (Exception e) {
      
         e.printStackTrace();
         return false;
      }
   }

   /**
    * 移除N个值为value
    * 
    * @param key
    *            键
    * @param count
    *            移除多少个
    * @param value
    *            值
    * @return 移除的个数
    */
   public long lRemove(String key, long count, Object value) {
      
      try {
      
         Long remove = redisTemplate.opsForList().remove(key, count, value);
         return remove;
      } catch (Exception e) {
      
         e.printStackTrace();
         return 0;
      }
   }
}

12.效果图

Redis_第84张图片

十 SpringBoot+Redis

1.依赖

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
dependency>

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-jpaartifactId>
dependency>


<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
dependency>

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-cacheartifactId>
dependency>

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-redisartifactId>
dependency>

<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>fastjsonartifactId>
    <version>1.2.39version>
dependency>

<dependency>
    <groupId>org.projectlombokgroupId>
    <artifactId>lombokartifactId>
dependency>

2.yml配置

cache:
  default-exp: 10000 #redis缓存总的过期时间,单位是秒
server:
  port: 8080
spring:
  application:
    name: redis-cache-demo
  datasource:
    url: jdbc:mysql://localhost:3306/db4?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&useSSL=false&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
  jpa:
    show-sql: true
    database: mysql
    hibernate:
      ddl-auto: update
  cache:
    #设置项目中使用的缓存类型
    type: redis
  redis:
    host: 172.20.10.8
    port: 6379
    database: 0
    #password:

3.控制层接口

@RestController
@RequestMapping("/user")
public class UserController {
      

    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public ResponseEntity<User> findById(@PathVariable Long id) {
      
        User user = userService.findById(id);
        HttpStatus status = user == null ? HttpStatus.NOT_FOUND : HttpStatus.OK;
        return new ResponseEntity<>(user, status);
    }

    @PostMapping
    public User addUser(@RequestBody User user) {
      

        return userService.addUser(user);
    }

    @PutMapping
    public User updateUser(@RequestBody User user) {
      

        return userService.updateUser(user);
    }

    @DeleteMapping("/{id}")
    public String deleteById(@PathVariable Long id) {
      

        userService.deleteById(id);

        return "success";
    }

}

4.业务处理

@Service
public class UserServiceImpl implements UserService {
      

    @Autowired
    private UserRepository userRepository;

    /**
     * @Cacheable:作用在查询方法上. "value"是redis缓存的key的前缀,"key"是缓存的具体的key的值;---->redis的key=user_02
     * 该注解会把当前方法的返回值缓存起来!
     * redis缓存的形式为:key-value
     * #: SpEL表达式,用来取参数的值或者取出系统中自带的对象的值!
     * unless = "#result eq null ":当result结果为null的时候不进行缓存!
     */
    @Cacheable(value = "user_", key = "#id", unless = "#result eq null")
    @Override
    public User findById(Long id) {
      
        //Optional optional = userRepository.findById(id);
        //User user = optional.get();
        //return optional.orElse(new User());

        //userRepository.findAll(Sort.by(Sort.Direction.DESC,"id","name"));

        return userRepository.findById(id).orElse(null);
    }

    /**
     * 添加方法:当往mysql数据库中添加新数据的时候,一般不需要往redis缓存中做任何操作!
     * 缓存预热:
     * 注意:value和key的值不能为空!
     */
    @CachePut(value = "user_", key = "#result.id")
    @Override
    public User addUser(User user) {
      

        return userRepository.save(user);
    }

    /**
     * 双写一致性:
     * 缓存更新.
     * CacheEvict:用在删除方法上面,用来清除某个redis的缓存
     */  
    @CacheEvict(value = "user_", key = "#id")
    @Override
    public void deleteById(Long id) {
      

        userRepository.deleteById(id);
    }

    /**
     * @CachePut:用在添加或者更新方法上面,用来更新redis缓存!
     */
    @CachePut(value = "user_", key = "#user.id", unless = "#result eq null")
    @Override
    public User updateUser(User user) {
      

        return userRepository.saveAndFlush(user);
    }
}

5.RedisCacheConfig

@Configuration
@EnableCaching
public class RedisCacheConfig {
      

    @Value("${cache.default-exp}")
    private long exps;

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    //@Value("${spring.redis.timeout}")
    //private int timeout;

    //@Value("${spring.redis.password}")
    //private String password;

    /**
     * 设置redis中key的生成规则
     */
    @Bean
    public KeyGenerator keyGenerator() {
      
        return new KeyGenerator() {
      
            @Override
            public Object generate(Object target, Method method, Object... params) {
      
                StringBuffer sb = new StringBuffer();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                for (Object obj : params) {
      
                    sb.append(obj.toString());
                }
                return sb.toString();
            }
        };
    }

    /**
     * RedisTemplate配置
     */
    @Bean
    public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
      
        RedisTemplate<Object, Object> template = new RedisTemplate<>();

        template.setConnectionFactory(redisConnectionFactory);

        // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
        Jackson2JsonRedisSerializer<JSON> serializer = new Jackson2JsonRedisSerializer<>(JSON.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);

        template.setValueSerializer(serializer);
        template.setHashValueSerializer(serializer);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());

        //使得上面的配置生效
        template.afterPropertiesSet();

        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
      
        // 生成一个默认配置,通过config对象即可对缓存进行自定义配置
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        // 使用Jackson2JsnRedisSerializer来序列化和反序列化redis的value值
        Jackson2JsonRedisSerializer<JSON> serializer = new Jackson2JsonRedisSerializer<>(JSON.class);
        // 配置序列化

        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer));
        config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer));
        // 设置缓存的默认过期时间
        config.entryTtl(Duration.ofSeconds(exps));
        // 不缓存空值
        config.disableCachingNullValues();

        return  RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).build();
    }

}

6.业务处理

@Service
public class UserServiceImpl implements UserService {
      

    @Autowired
    private UserRepository userRepository;

    /**
     * @Cacheable:作用在查询方法上. "value"是redis缓存的key的前缀,"key"是缓存的具体的key的值;---->redis的key=user_02
     * 该注解会把当前方法的返回值缓存起来!
     * redis缓存的形式为:key-value
     * #: SpEL表达式,用来取参数的值或者取出系统中自带的对象的值!
     * unless = "#result eq null ":当result结果为null的时候不进行缓存!
     */
    @Cacheable(value = "user_", key = "#id", unless = "#result eq null")
    @Override
    public User findById(Long id) {
      
        //Optional optional = userRepository.findById(id);
        //User user = optional.get();
        //return optional.orElse(new User());

        //userRepository.findAll(Sort.by(Sort.Direction.DESC,"id","name"));

        return userRepository.findById(id).orElse(null);
    }

    /**
     * 添加方法:当往mysql数据库中添加新数据的时候,一般不需要往redis缓存中做任何操作!
     * 缓存预热:
     * 注意:value和key的值不能为空!
     */
    @CachePut(value = "user_", key = "#result.id")
    @Override
    public User addUser(User user) {
      

        return userRepository.save(user);
    }

    /**
     * 双写一致性:
     * 缓存更新.
     * CacheEvict:用在删除方法上面,用来清除某个redis的缓存
     */  
    @CacheEvict(value = "user_", key = "#id")
    @Override
    public void deleteById(Long id) {
      

        userRepository.deleteById(id);
    }

    /**
     * @CachePut:用在添加或者更新方法上面,用来更新redis缓存!
     */
    @CachePut(value = "user_", key = "#user.id", unless = "#result eq null")
    @Override
    public User updateUser(User user) {
      

        return userRepository.saveAndFlush(user);
    }

}
    //使得上面的配置生效
    template.afterPropertiesSet();

    return template;
}

@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    // 生成一个默认配置,通过config对象即可对缓存进行自定义配置
    RedisSerializer redisSerializer = new StringRedisSerializer();
    // 使用Jackson2JsnRedisSerializer来序列化和反序列化redis的value值
    Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(JSON.class);
    // 配置序列化

    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
    config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer));
    config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer));
    // 设置缓存的默认过期时间
    config.entryTtl(Duration.ofSeconds(exps));
    // 不缓存空值
    config.disableCachingNullValues();

    return  RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).build();
}

}

6.业务处理

@Service
public class UserServiceImpl implements UserService {
      

    @Autowired
    private UserRepository userRepository;

    /**
     * @Cacheable:作用在查询方法上. "value"是redis缓存的key的前缀,"key"是缓存的具体的key的值;---->redis的key=user_02
     * 该注解会把当前方法的返回值缓存起来!
     * redis缓存的形式为:key-value
     * #: SpEL表达式,用来取参数的值或者取出系统中自带的对象的值!
     * unless = "#result eq null ":当result结果为null的时候不进行缓存!
     */
    @Cacheable(value = "user_", key = "#id", unless = "#result eq null")
    @Override
    public User findById(Long id) {
      
        //Optional optional = userRepository.findById(id);
        //User user = optional.get();
        //return optional.orElse(new User());

        //userRepository.findAll(Sort.by(Sort.Direction.DESC,"id","name"));

        return userRepository.findById(id).orElse(null);
    }

    /**
     * 添加方法:当往mysql数据库中添加新数据的时候,一般不需要往redis缓存中做任何操作!
     * 缓存预热:
     * 注意:value和key的值不能为空!
     */
    @CachePut(value = "user_", key = "#result.id")
    @Override
    public User addUser(User user) {
      

        return userRepository.save(user);
    }

    /**
     * 双写一致性:
     * 缓存更新.
     * CacheEvict:用在删除方法上面,用来清除某个redis的缓存
     */  
    @CacheEvict(value = "user_", key = "#id")
    @Override
    public void deleteById(Long id) {
      

        userRepository.deleteById(id);
    }

    /**
     * @CachePut:用在添加或者更新方法上面,用来更新redis缓存!
     */
    @CachePut(value = "user_", key = "#user.id", unless = "#result eq null")
    @Override
    public User updateUser(User user) {
      

        return userRepository.saveAndFlush(user);
    }

}

7.效果图

Redis_第85张图片

Redis_第86张图片

你可能感兴趣的:(Redis)