Redis

一、Redis介绍

我们是可以通过Tomcat进行优化、集群部署等策略提高用户请求的并发处理能力,随着应用服务器并发性能的提升,数据库服务器就逐渐成为整个软件系统的瓶颈:

  • 随着用户并发请求的提升,数据库的访问压力也越来越大
  • 集群中的多个节点访问数据库的同一数据时,事务锁就不存在互斥性
  • 在多台服务器之间、数据同步也存在问题
1.1 产生背景
  • 2008年—萨尔瓦多—网站实时统计系统(LLOOGG)==>需求:每秒数千次的写操作;MySQL不能满足需求,萨尔瓦多就用C语言写了一个数据存储系统–Redis
  • 2010年VMware开始赞助Redis的开发

概念:redis就是一个用C语言开发的、基于键值对存储的、高性能的、非关系型数据库。

1.2 Redis支持的数据类型(值类型)
  • 字符串
  • hash
  • 列表
  • 集合
  • 无序集合
1.3 Redis的应用场景(常用)
  • 数据库缓存
    • 我们将应用系统中经常使用且不会频繁修改数据缓存在redis中,一则可以提升网站的访问速度,二则可以大大降低数据库的访问压力。
  • 排行榜
    • 在很多的网站系统中排行榜功能(电商系统中商品检索之后按销量、价格等排序)
  • 点赞
    • 例如抖音等短视频等点赞频繁的修改操作,使用redis来缓存点赞操作
  • 分布式锁
    • 使用setNx构建多个应用服务器之间的分布式锁
  • 消息中间件
    • 使用redis作为消息中间价可以实现不同应用服务器之间的通信
1.4 Redis的优缺点
  • 优点:
    • redis是基于内存的,性能极高,读取速度110000次/s,写速度81000次/s
    • redis虽然是基于键值对存储,但是值可以支持多种数据类型
    • redis中所有的操作都是原子性的,同时也支持将多个操作合并到一个原子操作(redis事务)
  • 缺点:
    • 缓存与数据库的数据必须通过两次写操作保持一致性
    • 使用缓存可能导致缓存穿透、缓存击穿及缓存雪崩等问题

二、Redis的安装及配置

2.1 redis安装
2.2.1 下载

https://redis.io/ 【redis-5.0.5.tar.gz】

下载之后通过xftp工具上传到linux的usr/local目录

2.2.2 安装
  • 先安装gcc工具

    yum install -y gcc
    
  • 解压

    tar -zxvf redis-5.0.5.tar.gz
    
  • 进入到解压后的目录,进行编译

    make MALLOC=libc
    
  • 安装

    make install
    
  • 启动redis

    ## 进入到redis的src目录执行
    redis-server
    
    ## 启动redis,并后台运行redis服务
    redis-server &
    
  • 启动redis客户端

    ## 进入到redis的src目录执行
    redis-cli
    
2.2 redis配置

在redis的根目录中提供了一个redis.conf文件,此文件是redis配置文件的一个模板,默认启动的时候时不加载的,如果对配置文件进行了修改之后,启动时需要指定配置文件

  • 如果对配置文件进行修改,建议拷贝一份模板进行修改,尽量保留一份原始模板
2.2.1 修改port
  • 先查询要修改的内容在配置文件的行号:cat -n redis.conf| grep port
[root@theo redis-5.0.5]# cat -n redis.conf| grep port
    90	# Accept connections on the specified port, default is 6379 (IANA #815344).
    91	# If port 0 is specified Redis will not listen on a TCP socket.
    92	port 6379
    ....
  • 直接编辑指定行:vim +92 redis.conf
[root@theo redis-5.0.5]# vim +92 redis.conf
  • 启动redis指定配置文件
[root@theo redis-5.0.5]# ./src/redis-server redis.conf &
2.2.2 常用配置
## 设置redis是否以守护程序运行
daemonize no|yes

## 指定保存redis进程ID的文件路径
pidfile /var/run/redis_6379.pid

## 指定redis端口
port 6379

## 设置是否开启保护模式
protected-mode yes

## 在保护模式下设置允许访问redis的主机的ip(此ip一定时redis主机可见)
bind 127.0.0.1

## 设置访问密码
requirepass 123456

## 设置redis中数据库的个数
databases 16

三、Redis基本使用

3.1 Redis支持的数据结构
  • Redis中的数据是以key-value对的形式进行存储的

    • key–字符串
    • value–string(字符串)、Hash(键值对)、list(列表)、zset(有序集合)、set(集合)
  • Value的数据类型

在这里插入图片描述

3.2 string常用指令
## 添加值
set key value

## 取值
get key

## 批量添加
mset k1 v1 [k2 v2 ...]
## 批量取值
mget k1 [k2 ...]

## 自增和自减【计数器】
incr key   # 将key对应的值自增1
decr key   # 将key对应的值自减1
incrby key v #自增指定的值
decrby key v #自减指定的值

## 指定添加的键值对的TTL
setex key seconds value

## 设置值,如果key不存在则添加成功;如果key已经存在则不做任何操作--->【分布式事务锁】
setnx key value

## 在指定的key对应的值拼接内容
append key value

## 查看指定的key对应值的长度
strlen key
3.3 hash常用指令
## 存储键值对
hset key field value

## 根据field取value
hget key field

## 批量添加
hmset key f1 v1 [f2 v2...]
## 批量获取
hmget key f1 f2 ...

##给指定的字段的值自增v
hincrby key f v

##取出key对应的map的所有field和value
hgetall key

## 取出key对应的map的所有field
hkeys key

## 取出key对应的map的所有value
hvals key

## 检查key对应的map中是否存在指定的field
hexists key field

## 获取key对应的map中的键值对个数
hlen key

## 在key对应的map中新增键值对,只有当field不存在时才执行成功
hsetnx key field value
3.4 list常用指令

list表示的是列表,并不是咱们Java的List集合

## 存储数据
lpush key value   ## 对key对应的列表左边添加数据(如果key对应的列表不存在,则创建此列表)
rpush key value   ## 对key对应的列表右边添加数据(如果key对应的列表不存在,则创建此列表)

## 存储数据
lpushx key value   ##对key对应的列表左边添加数据(如果key对应的列表不存在,则不执行任何操作)
rpushx key value   ##对key对应的列表右边添加数据(如果key对应的列表不存在,则不执行任何操作)

## 从列表中取值(出栈)
lpop key   ##  从key对应的列表中左边获取一个值
rpop key   ##  从key对应的列表中右边获取一个值

## 修改key对应的列表 index索引处的值(索引从左往右,从0开始)
lset key index value

## 查看key对应的列表索引在start到stop范围的值(列表数据不会改变)
lrange key start stop

## 查看key对应的列表中指定索引的值(列表数据不会改变)
lindex key index

## 获取key对应的列表中值的个数
llen key

## 截取key对应列表中索引在[start,stop]范围的数据,删除超过此范围的数据
ltrim key start stop   
示例: key1  :   aaa   bbb    ccc   ddd    eee 
      ltrim key1 1 3   ==>    key1  :   bbb    ccc   ddd

## 从key1列表的右侧出栈一个数据入栈到key2列表的左侧
rpoplpush key1 key2
3.5 set常用指令

set 表示的是无序集合 s

## 存储数据:将值存储在key对应的集合中
sadd key value [value ...]

## 获取数据(获取key对应的集合的全部数据)
smembers key

## 随机获取key对应的集合中的一个数据(出栈)
spop key

## 交集
sinter key1 key2

## 并集
sunion key1 key2

## 差集
sdiff key1 key2

## 从key对应的集合中移除指定元素
srem key value

## 检查key对应的集合中是否存在指定的值
sismember key value
3.6 zset常用指令

zset 有序不可重复集合 z

## 存储数据(score存储位置必须是数值,member元素不允许重复)
zadd key score member [score member...]

## 查看key对应的有序集合中索引[start,stop]数据——按照score值由小到大
zrange key start top

##查看member元素在key对应的有序集合中的索引
zscore key member

## 获取key对应的zset中的元素个数
zcard key

## 获取key对应的zset中,score在[min,max]范围内的member个数
zcount key min max

## 从key对应的zset中移除指定的member
zrem key6 member

## 查看key对应的有序集合中索引[start,stop]数据——按照score值由大到小
zrevrange key start stop
3.7 key相关指令
## 查看redis中满足pattern规则的所有的key(keys *)
keys pattern

## 查看指定的key谁否存在
exists key

## 删除指定的key-value对
del key

## 获取当前key的存活时间(如果没有设置过期返回-1,设置过期并且已经过期返回-2)
ttl key

## 设置键值对过期时间
expire key seconds
pexpire key milliseconds

## 取消键值对过期时间
persist key
3. 8 db常用指令

redis的键值对是存储在数据库中的——db

redis中默认有16个db,编号 0-15

## 切换数据库
select index

## 将键值对从当前db移动到目标db
move key index

## 清空当前数据库数据
flushdb

## 清所有数据库的k-v
flushall

## 查看当前db中k-v个数
dbsize

## 获取最后一次操作时间
lastsave

四、Java应用连接Redis

Java应用连接Redis — Jedis

  • 普通的Java应用操作redis
  • SSM应用整合Jedis
4.1 普通的Java应用连接Redis
  • 导入Java代码连接redis的依赖 — jedis

    <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
    </dependency>
    
  • 创建Java代码与redis的连接

    //1.连接
    Jedis jedis = new Jedis("47.96.11.185",6380);
    //2.授权
    jedis.auth("123456");
    
  • 通过连接实现对redis数据的访问

    //3.操作
    String s = jedis.set("key1", "value1");
    
4.2 Spring整合redis
4.2.1 整合
  • 创建项目完成SSM整合

  • 添加依赖(注意版本的匹配)

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-redis</artifactId>
        <version>2.0.2.RELEASE</version>
    </dependency>
    
  • 在resources目录创建redis.properties文件,并配置数据源信息

    redis.userPool=true
    redis.hostName=47.96.11.185
    redis.port=6380
    redis.password=123456
    
  • 在resources目录创建spring-redis.xml,配置redis客户端

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
        <context:property-placeholder location="classpath:redis.properties"/>
    
        <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
            <property name="usePool" value="${redis.userPool}"/>
            <property name="hostName" value="${redis.hostName}"/>
            <property name="port" value="${redis.port}"/>
            <property name="password" value="${redis.password}"/>
        </bean>
    
        <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
            <property name="connectionFactory" ref="jedisConnectionFactory"/>
        </bean>
    
    </beans>
    
4.2.2 测试
  • string

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration({"classpath:spring-context.xml",
            "classpath:spring-redis.xml",
            "classpath:spring-mybatis.xml",
            "classpath:spring-servlet.xml"})
    public class RedisStringTest {
    
        @Resource
        private StringRedisTemplate stringRedisTemplate;
    
        @Test
        public void testSet(){
            stringRedisTemplate.boundValueOps("key1").set("value1");
        }
        
        @Test
        public void testSetNx(){
            Boolean b = stringRedisTemplate.boundValueOps("key2").setIfAbsent("value2");
            System.out.println(b);
        }
    
        @Test
        public void testGet(){
            String v = stringRedisTemplate.boundValueOps("key1").get();
            System.out.println(v);
        }
    
    }
    
  • hash

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration({"classpath:spring-context.xml",
            "classpath:spring-redis.xml",
            "classpath:spring-mybatis.xml",
            "classpath:spring-servlet.xml"})
    public class RedisHashTest {
    
        @Resource
        private StringRedisTemplate stringRedisTemplate;
    
        @Test
        public void test1(){
            stringRedisTemplate.boundHashOps("key3").put("1001","{userId:1001,userName:'zhangsan'}");
            stringRedisTemplate.boundHashOps("key3").put("1002","{userId:1002,userName:'lisi'}");
        }
    
        @Test
        public void test2(){
            String v1 = (String) stringRedisTemplate.boundHashOps("key3").get("1001");
            String v2 = (String) stringRedisTemplate.boundHashOps("key3").get("1002");
            System.out.println(v1);
            System.out.println(v2);
        }
    
    }
    
  • list

    @Test
    public void test3(){
        stringRedisTemplate.boundListOps("key4").leftPush("v4");
    }
    @Test
    public void test4(){
        String v = stringRedisTemplate.boundListOps("key4").leftPop();
    }
    
4.3 redis使用案例

在微分销平台管理系统中,使用redis缓存权限数据(菜单数据)

@Service
public class ModuleServiceImpl implements ModuleService {

    @Resource
    private ModuleDAO moduleDAO;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    private ObjectMapper mapper = new ObjectMapper();	

    public List<Module> listModulesByPage(int page, int limit){
        List<Module> modules = null;
        try{
            //1.查询redis
           String s = (String) stringRedisTemplate.boundHashOps("modules").get("page-" + page);
            //2.如果redis中没有数据,则查询数据库
            if(s == null ){
                int start = (page-1)*limit;
                modules = moduleDAO.listModulesByPage(start, limit);
                System.out.println("--------------访问数据库");
                //将List集合转成JSON字符串
                String jsonStr = mapper.writeValueAsString(modules);
                stringRedisTemplate.boundHashOps("modules").put("page-" + page,jsonStr);
            }else{
                //将json字符串转换成集List合
                modules = mapper.readValue(s, new TypeReference<List<Module>>(){});
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return modules;
    }
    
}

五、redis持久化策略

Redis虽然是基于内存的存储,但它本质上还是一个数据库,因此是支持持久化。

Redis提供了两种持久化策略:

  • RDB
  • AOF
5.1 RDB(Redis DataBase)

在满足特定条件时,将redis内存中的数据以数据快照的形式存储在后缀名为rdb的文件中

  • 是redis默认的持久化策略,在满足特定的条件是会触发持久化操作

  • 900s 1次 ---------------- 操作次数大于等于1 ,小于10次, 则15分钟才会进行持久化

  • 300s 10次---------------- 操作次数大于等于10次,小于10000次,则5分钟会进行持久化

  • 60s 10000次----------- 才做次数大于等于10000次,则1分钟进行持久化

  • 修改RDB策略的条件:

    ## 设置是否开启RDB持久化策略
    rdbcompression yes
    
    ## 配置rdb的持久化条件
    save 900 1
    save 300 10
    save 60 10000
    
    ## 指定rdb数据文件的路径
    dbfilename dump.rdb
    
  • RDB策略细节:

    • 如果redis服务突然异常终止了,存在数据丢失的风险,丢失上一次RDB持久化之后的数据
    • RDB是使用数据快照的形式进行持久化的,不适合实时性的持久化;当数据量不大的情况下,执行速度还是比较快的
    • 如果数据量巨大,会导致RDB的持久化过程时间比较长,从而导致Redis会出现卡顿,因此RDB的触发条件不宜设置过短
    • 因为RDB是基于快照进行持久化,因此在数据恢复、移植等方面比较方便
5.2 AOF (Appen Only File)

AOF,就是将redis的写操作的指令保存到一个aof文件,当redis遇到故障并恢复的时候执行此aof文件以恢复数据。AOF实际上持久化的并不是数据,而是数据的写指令。

  • AOF保存的是写操作,每次持久化只做增量保存

  • AOF 默认是未开启的

  • AOF配置:

    ## 设置是否开启aof持久化策略 (默认是关闭的)
    appendonly no|yes
    
    ## 设置aof持久化触发条件
    appendfsync always
    # appendfsync everysec
    # appendfsync no
    
    ## 设置aof文件路径
    appendfilename "appendonly.aof"
    
  • AOF细节:

    • aof文件也是支持移植的
    • redis官方建议同时开启rdb和aof
    • rdb和aof同时开启时,aof优先

六、Redis高级应用

当我们web应用中的并发访问持续提升时,一台redis服务器难以满足我们的请求,我们可以通过读写分离、集群等方式来分流redis的访问压力

6.1 主从配置(读写分离)

如果要实现Java应用redis访问的读写分离,为了保证读库与写库的数据的一致性,我们可以通过配置读库和写库至今的主从关系来实现

  • 将写库定义为主库 master
  • 将读库定义为从库slave
  • 在主从配置中可以一主一从,也可以一主多从
6.1.1 主从介绍

在这里插入图片描述

6.1.2 主从配置
  • 配置文件结构

在这里插入图片描述

  • 创建配置文件

    [root@theo ~]# cd /usr/local/redis-5.0.5
    [root@theo redis-5.0.5]# mkdir msconf
    [root@theo redis-5.0.5]# cat redis.conf|grep -v "#"|grep -v "^$" > msconf/redis-6381.conf
    [root@theo redis-5.0.5]# cd msconf/
    [root@theo msconf]# ls
    redis-6381.conf
    ## 将redis-6381.conf端口设置为6381
    
    [root@theo msconf]# sed 's/6381/6382/g' redis-6381.conf > redis-6382.conf 
    [root@theo msconf]# sed 's/6381/6383/g' redis-6381.conf > redis-6383.conf 
    

在这里插入图片描述

  • 进行主从配置:为从库指定主库

    • redis-6382.conf
    • redis-6383.conf
    slaveof 127.0.0.1 6381
    masterauth 123456
    
6.2 哨兵模式

为了保证redis服务器的可用性,我们可以进行主备配置(主从);当主库出现故障时,为了保证redis的可用性,我们需要从备库中选举一个来替代主库(转备为主),redis提供了哨兵模式:对主库状态进行监控,当主库宕机时负责从多个备库中选举一个替代主库。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WUTbVpyN-1598004191916)(imgs/1597895142163.png)]

  • 哨兵模式配置:

    • 先完成主从配置

      • msconf/redis-6381.conf 主库 (建议取消密码测试)
      • msconf/redis-6382.conf 备1 (建议取消密码测试)
      • msconf/redis-6383.conf 备2 (建议取消密码测试)
    • 哨兵配置(哨兵也是一个redis实例,此实例不负责数据存取,只负责对主库进行监视)

      • 创建哨兵配置文件

        [root@theo redis-5.0.5]# mkdir sentinelconf
        [root@theo redis-5.0.5]# cat sentinel.conf | grep -v "#"| grep -v "^$" > sentinelconf/sentinel1.conf
        [root@theo redis-5.0.5]# cd sentinelconf/
        [root@theo sentinelconf]# ls
        sentinel1.conf
        [root@theo sentinelconf]# 
        ## 对哨兵配置文件进行修改
        
      • 对哨兵配置文件进行修改

在这里插入图片描述

- 拷贝其他哨兵的配置文件

  ```shell
  [root@theo sentinelconf]# sed 's/26381/26382/g' sentinel1.conf > sentinel2.conf 
  [root@theo sentinelconf]# sed 's/26381/26383/g' sentinel1.conf > sentinel3.conf 
  ```
6.3 集群配置
6.3.1 集群介绍
6.3.2 集群配置
  • 创建clusterconf目录,并拷贝redis.conf文件

    [root@theo redis-5.0.5]# mkdir clusterconf
    [root@theo redis-5.0.5]# cat redis.conf |grep -v "#"|grep -v "^$" > clusterconf/redis-7001.conf
    [root@theo redis-5.0.5]# cd clusterconf/
    [root@theo clusterconf]# ls
    redis-7001.conf
    [root@theo clusterconf]# vim redis-7001.conf
    
  • 修改redis-7001.conf
    在这里插入图片描述

  • 复制redis-7001.conf 到 2-6

    [root@theo clusterconf]# sed 's/7001/7002/g' redis-7001.conf > redis-7002.conf 
    [root@theo clusterconf]# sed 's/7001/7003/g' redis-7001.conf > redis-7003.conf 
    [root@theo clusterconf]# sed 's/7001/7004/g' redis-7001.conf > redis-7004.conf 
    [root@theo clusterconf]# sed 's/7001/7005/g' redis-7001.conf > redis-7005.conf 
    [root@theo clusterconf]# sed 's/7001/7006/g' redis-7001.conf > redis-7006.conf 
    [root@theo clusterconf]# ls
    redis-7001.conf  redis-7002.conf  redis-7003.conf  redis-7004.conf  redis-7005.conf  redis-7006.conf
    
  • 启动6个实例

    [root@theo clusterconf]# redis-server redis-7001.conf
    [root@theo clusterconf]# redis-server redis-7002.conf
    [root@theo clusterconf]# redis-server redis-7003.conf
    [root@theo clusterconf]# redis-server redis-7004.conf
    [root@theo clusterconf]# redis-server redis-7005.conf
    [root@theo clusterconf]# redis-server redis-7006.conf
    
  • 查看启动的6个redis实例

    [root@theo clusterconf]# ps -ef| grep redis
    root     20977     1  0 14:53 ?        00:00:00 redis-server *:7001 [cluster]
    root     21020     1  0 14:54 ?        00:00:00 redis-server *:7002 [cluster]
    root     21031     1  0 14:54 ?        00:00:00 redis-server *:7003 [cluster]
    root     21041     1  0 14:54 ?        00:00:00 redis-server *:7004 [cluster]
    root     21050     1  0 14:54 ?        00:00:00 redis-server *:7005 [cluster]
    root     21060     1  0 14:54 ?        00:00:00 redis-server *:7006 [cluster]
    
  • 启动集群

    [root@theo clusterconf]# redis-cli --cluster create 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 --cluster-replicas 1
    >>> Performing hash slots allocation on 6 nodes...
    Master[0] -> Slots 0 - 5460
    Master[1] -> Slots 5461 - 10922
    Master[2] -> Slots 10923 - 16383
    Adding replica 127.0.0.1:7005 to 127.0.0.1:7001
    Adding replica 127.0.0.1:7006 to 127.0.0.1:7002
    Adding replica 127.0.0.1:7004 to 127.0.0.1:7003
    >>> Trying to optimize slaves allocation for anti-affinity
    [WARNING] Some slaves are in the same host as their master
    M: eb8362578376c9dec323165798c0c421f422fc2f 127.0.0.1:7001
       slots:[0-5460] (5461 slots) master
    M: f89ba59b7ce398a67ca0f43d966a5c0f26e3dacc 127.0.0.1:7002
       slots:[5461-10922] (5462 slots) master
    M: 6918a91d223388ad2c195c39e7a4b327606d9160 127.0.0.1:7003
       slots:[10923-16383] (5461 slots) master
    S: c5e3ae630e5e3c023d7a9a1f6d9ea13fc3674805 127.0.0.1:7004
       replicates 6918a91d223388ad2c195c39e7a4b327606d9160
    S: f1162cb4edd6a754c8593c602803c5fefd1a9212 127.0.0.1:7005
       replicates eb8362578376c9dec323165798c0c421f422fc2f
    S: 33a3aa6f8fa1fce7a1aebc8b4527871c07469dd8 127.0.0.1:7006
       replicates f89ba59b7ce398a67ca0f43d966a5c0f26e3dacc
    Can I set the above configuration? (type 'yes' to accept): yes
    >>> Nodes configuration updated
    >>> Assign a different config epoch to each node
    >>> Sending CLUSTER MEET messages to join the cluster
    Waiting for the cluster to join
    ..........
    >>> Performing Cluster Check (using node 127.0.0.1:7001)
    M: eb8362578376c9dec323165798c0c421f422fc2f 127.0.0.1:7001
       slots:[0-5460] (5461 slots) master
       1 additional replica(s)
    M: f89ba59b7ce398a67ca0f43d966a5c0f26e3dacc 127.0.0.1:7002
       slots:[5461-10922] (5462 slots) master
       1 additional replica(s)
    S: c5e3ae630e5e3c023d7a9a1f6d9ea13fc3674805 127.0.0.1:7004
       slots: (0 slots) slave
       replicates 6918a91d223388ad2c195c39e7a4b327606d9160
    M: 6918a91d223388ad2c195c39e7a4b327606d9160 127.0.0.1:7003
       slots:[10923-16383] (5461 slots) master
       1 additional replica(s)
    S: f1162cb4edd6a754c8593c602803c5fefd1a9212 127.0.0.1:7005
       slots: (0 slots) slave
       replicates eb8362578376c9dec323165798c0c421f422fc2f
    S: 33a3aa6f8fa1fce7a1aebc8b4527871c07469dd8 127.0.0.1:7006
       slots: (0 slots) slave
       replicates f89ba59b7ce398a67ca0f43d966a5c0f26e3dacc
    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.
    
6.4 Spring整合redis集群
  • 添加依赖
    <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis -->
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-redis</artifactId>
        <version>2.0.12.RELEASE</version>
    </dependency>
    
  • 创建redis-cluster.properties
    redis.host1=127.0.0.1
    redis.port1=6379
    redis.host2=127.0.0.1
    redis.port2=6380 
    redis.host3=127.0.0.1
    redis.port3=6381  
    redis.host4=127.0.0.1
    redis.port4=6382  
    redis.host5=127.0.0.1
    redis.port5=6383
    redis.host6=127.0.0.1
    redis.port6=6384
    
    redis.expiration=3000  
    #最大空闲数
    redis.maxIdle=300 
    #连接池的最大数据库连接数。设为0表示无限制,如果是jedis 2.4以后用redis.maxTotal 
    redis.maxActive=600  
    #最大建立连接等待时间
    redis.maxWait=1000  
    #是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
    redis.testOnBorrow=true
    #客户端超时时间单位是毫秒 默认是2000
    redis.timeout=10000  
    #控制一个pool可分配多少个jedis实例,用来替换上面的redis.maxActive,如果是jedis 2.4以后用该属性
    redis.maxTotal=1000  
    #最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
    redis.maxWaitMillis=1000  
    #连接的最小空闲时间 默认1800000毫秒(30分钟)
    redis.minEvictableIdleTimeMillis=300000  
    #每次释放连接的最大数目,默认3
    redis.numTestsPerEvictionRun=1024  
    #逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
    redis.timeBetweenEvictionRunsMillis=30000  
    #在空闲时检查有效性, 默认false
    redis.testWhileIdle=true
    
  • spring-redis.xml配置
    <!-- 配置JedisPoolConfig实例 -->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <!--最大空闲数-->
        <property name="maxIdle" value="${redis.maxIdle}" />
        <!--连接池的最大数据库连接数  -->
        <property name="maxTotal" value="${redis.maxActive}" />
        <!--最大建立连接等待时间-->
        <property name="maxWaitMillis" value="${redis.maxWait}" />
        <!--是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个-->
        <property name="testOnBorrow" value="${redis.testOnBorrow}" />
        <!--逐出连接的最小空闲时间 默认1800000毫秒(30分钟)-->
        <property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}" />
        <!--每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3-->
        <property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}" />
        <!--逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1-->
        <property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}" />
    </bean>
    
    <!-- Redis集群配置 -->
    <bean id="redisClusterConfig" class="org.springframework.data.redis.connection.RedisClusterConfiguration">
        <property name="maxRedirects" value="6"></property>
        <property name="clusterNodes">
            <set>
                <bean class="org.springframework.data.redis.connection.RedisNode">
                    <constructor-arg name="host" value="${redis.host1}"></constructor-arg>
                    <constructor-arg name="port" value="${redis.port1}"></constructor-arg>
                </bean>
    
                <bean class="org.springframework.data.redis.connection.RedisNode">
                    <constructor-arg name="host" value="${redis.host2}"></constructor-arg>
                    <constructor-arg name="port" value="${redis.port2}"></constructor-arg>
                </bean>
                <bean class="org.springframework.data.redis.connection.RedisNode">
                    <constructor-arg name="host" value="${redis.host3}"></constructor-arg>
                    <constructor-arg name="port" value="${redis.port3}"></constructor-arg>
                </bean>
                <bean class="org.springframework.data.redis.connection.RedisNode">
                    <constructor-arg name="host" value="${redis.host4}"></constructor-arg>
                    <constructor-arg name="port" value="${redis.port4}"></constructor-arg>
                </bean>
                <bean class="org.springframework.data.redis.connection.RedisNode">
                    <constructor-arg name="host" value="${redis.host5}"></constructor-arg>
                    <constructor-arg name="port" value="${redis.port5}"></constructor-arg>
                </bean>
                <bean class="org.springframework.data.redis.connection.RedisNode">
                    <constructor-arg name="host" value="${redis.host6}"></constructor-arg>
                    <constructor-arg name="port" value="${redis.port6}"></constructor-arg>
                </bean>
            </set>
        </property>
    </bean>
    
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <constructor-arg name="clusterConfig" ref="redisClusterConfig"/>
        <!-- <constructor-arg name="poolConfig" ref="poolConfig"/>-->
        <!-- <property name="password" value="123456"></property>-->
    </bean>
    
    <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory"/>
    </bean>
    

七、使用Redis作为缓存存在的问题

Redis作为缓存极大的提升了数据的访问速度,降低了数据库的访问压力,但同时也存在一些问题:

一是数据的一致性问题,只要使用了缓存都会存在数据的一致性问题,这是无法避免的;我们可以通过两次写操作以及设置缓存数据过期时间来尽量保证数据的一致性。

二是在使用缓存的过程中也可能出现缓存击穿、缓存穿透以及缓存雪崩等问题。

7.1 缓存击穿

缓存击穿:大量的并发请求同时请求一个缓存中不存在的数据,因为并发请求达到时缓存中还不存在这个数据,则大量的请求都同时转向请求数据库,会造成数据库的并发访问压力。

解决方案:使用双重检测锁机制

@Service
public class BookServiceImpl {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    private ObjectMapper mapper = new ObjectMapper();

    public Book get(int bookId){
        Book book = null;
        try{
            String s = (String)stringRedisTemplate.boundHashOps("books").get("book-" + bookId);
            if(s == null ){
                // 500个请求都进入if了
                synchronized (this) {
                   s = (String)stringRedisTemplate.boundHashOps("books").get("book-" + bookId);
                    if(s == null) {
                        System.out.println("------------查询数据库");
                        book = new Book(bookId, "Java" + bookId);
                        //存到redis
                        String jsonstr = mapper.writeValueAsString(book);
                      stringRedisTemplate.boundHashOps("books").put("book-" + bookId, jsonstr);
                    }
                }
            }else{
                book = mapper.readValue(s,Book.class);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return book;
    }

}
7.2 缓存穿透

缓存穿透:大量的并发请求访问一个数据库中不存在的数据,在redis中无法命中,最终请求都会到达数据库,导致数据库需要处理大量的请求。

解决方式:即使用户请求的数据在数据库中不存在,也想缓存中写入一个非空的值(要设置过期时间)

7.3 缓存雪崩

缓存雪崩:缓存中大量的数据集中过期,会导致用户的数据请求都会去请求数据库

解决方案:将数据设置不同的过期时间

7.4 Jmeter使用

Jmeter是一个apache提供的压测工具

  • 下载

  • 解压

  • 运行C:\apache-jmeter-5.3\bin\jmeter.bat

  • 创建测试计划
    在这里插入图片描述

  • 创建线程组

在这里插入图片描述

在这里插入图片描述

  • 为线程组设置请求信息
    在这里插入图片描述
    在这里插入图片描述

  • 设置请求响应的结果的监听

在这里插入图片描述

  • 发送请求

八、Redis的淘汰策略

Redis是基于内存结构实现数据存储的,当内存资源比较紧张、耗尽的情况我们要保证新的数据进入内存,也必然需要从内存释放一些数据,Redis支持以下淘汰策略:

# volatile-lru -> 在设置了过期时间的数据集中根据‘最近最久未使用’原则淘汰
# allkeys-lru -> 在所有的数据中根据‘最近最久未使用’原则淘汰
# volatile-lfu -> 在设置了过期时间的数据集中根据'最近最少使用'原则淘汰
# allkeys-lfu -> 在所有的数据集中根据'最近最少使用'原则淘汰
# volatile-random -> 在设置了过期时间的数据集中随机淘汰数据
# allkeys-random -> 在所有数据中随机淘汰数据
# volatile-ttl -> 在设置了过期时间的数据集中淘汰剩余存活时间最短的数据
# noeviction -> 不淘汰任何数据
maxmemory-policy noeviction
  • 算法理解:
    • LRU(Least Recently Used) 最近最久未使用
    • LFU(least frequently used)最近最少使用
      过期时间)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值