0
点赞
收藏
分享

微信扫一扫

Redis学习记录

罗蓁蓁 2022-05-02 阅读 65

一、Redis五大数据类型

1. 字符串String
setnx为redis分布式锁底层
2. 列表List
3. 集合Set
4. 哈希Hash
value为K-V形式
5. 有序集合Zset
真实需求:
充10元可享vip1;
充20元可享vip2;
充30元可享vip3;


二、持久化

1. RDB
在指定的时间间隔内,将内存中的数据集的快照写入磁盘;默认保存在/usr/local/bin中,文件名dump.rdb;(自动备份,也可以执行命令 save手动备份)

优势and劣势

  • 优:适合大规模数据恢复,对数据完整性和一致行要求不高;
  • 劣:一定间隔备份一次,意外down掉,就失去最后一次快照的所有修改

2. AOF

  • 以日志的形式记录每个写操作;
  • 将redis执行过的写指令全部记录下来(读操作不记录);
  • 只许追加文件,不可以改写文件;
  • redis在启动之初会读取该文件从头到尾执行一遍,这样来重新构建数据;

3. 总结

  • RDB:只用作后备用途,建议15分钟备份一次就好
  • AOF:
    • 在最恶劣的情况下,也只丢失不超过2秒的数据,数据完整 性比较高,但代价太大,会带来持
      续的IO
    • 对硬盘的大小要求也高,默认64mb太小了,企业级最少都是5G以上;
    • master/slave才是新浪微博的选择

三、事务

  • 可以一次执行多个命令,是一个命令组,一个事务中,所有命令都会序列化(排队),不会被插
    队;
  • 一个队列中,一次性,顺序性,排他性的执行一系列命令
    reids-check-aof --fix appendonly.aof
  • 三特性
    • 隔离性:所有命令都会按照顺序执行,事务在执行的过程中,不会被其他客户端送来的命令
      打断
    • 没有隔离级别:队列中的命令没有提交之前都不会被实际的执行,不存在“事务中查询要看到
      事务里的更新,事务外查询不能看到”这个头疼的问题
    • 不保证原子性:冤有头债有主,如果一个命令失败,但是别的命令可能会执行成功,没有回
  • 三步走
    开启multi
    入队queued
    执行exec

四、Redis的发布订阅

进程间的一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。例如:微信订阅号;订阅一个或多个频道


五、主从复制

  • 就是 redis集群的策略
  • 配从(库)不配主(库):小弟可以选择谁是大哥,但大哥没有权利去选择小弟
  • 读写分离:主机写,从机读

1. 血脉相传

  • 一个主机理论上可以多个从机,但是这样的话,这个主机会很累
  • 我们可以使用java面向对象继承中的传递性来解决这个问题,减轻主机的负担,形成祖孙三代

2. 谋权篡位

  • 1个主机,2个从机,当1个主机挂掉了,只能从2个从机中再次选1个主机
  • 国不可一日无君,军不可一日无帅
  • 手动选老大
  • 模拟测试:1为master,2和3为slave,当1挂掉后,2篡权为master,3跟2
  • 当1再次回归,2和3已经形成新的集群,和1没有任何的关系了。所以1成为了光杆司令

3. 复制原理
在这里插入图片描述

  • 全量复制:Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份slave接收到数据
    文件后,存盘,并加载到内存中;(步骤1234)
  • 增量复制:Slave初始化后,开始正常工作时主服务器发生的写操作同步到从服务器的过程;(步
    骤56)
    • 但,只要是重新连接master,一次性(全量复制)同步将自动执行;
  • Redis主从同步策略:主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。
  • 当然,如果有需要,slave 在任何时候都可以发起全量同步。
  • redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

4. 哨兵模式

  • 自动版的谋权篡位
  • 有个哨兵一直在巡逻,突然发现!!!!!老大挂了,小弟们会自动投票,从众小弟中选出新的老
  • Sentinel是Redis的高可用性解决方案(因为主机负责写入,不能没有主机):
    • 由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器,以及所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级,为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求

六、Jedis(Java操作Redis)

java和redis打交道的API客户端

<dependency>
	<groupId>redis.clients</groupId>
	<artifactId>jedis</artifactId>
	<version>3.1.0</version>
</dependency>

1. 连接redis

public static void main(String[] args) {

	Jedis jedis = new Jedis("192.168.204.141",6379);
	String pong = jedis.ping();
	System.out.println("pong = " + pong);
}

// 运行前:
// 1.关闭防火墙 systemctl stop firewalld.service
// 2.修改redis.conf [ bind 0.0.0.0 ] 允许任何ip访问,以这个redis.conf启动redis服务
(重启redis)
// redis-server /opt/redis5.0.4/redis.conf

2. 事务

在这里插入图片描述


七、JedisPool

redis的连接池技术

<dependency>
	<groupId>commons-pool</groupId>
	<artifactId>commons-pool</artifactId>
	<version>1.6</version>
</dependency>

“一个池”使用单例模式进行优化

public class JedisPoolUtil {

    private JedisPoolUtil(){}

    private volatile static JedisPool jedisPool = null;
    private volatile static Jedis jedis = null;

    // 返回一个连接池
    private static JedisPool getInstance(){
        // 双层检测锁(企业中用的非常频繁)
        if(jedisPool == null){ // 第一层:检测体温
            synchronized (JedisPoolUtil.class){  // 排队进站
                if(jedisPool == null) { //第二层:查看健康码
                    JedisPoolConfig config = new JedisPoolConfig();
                    config.setMaxTotal(1000);
                    config.setMaxIdle(30);
                    config.setMaxWaitMillis(60*1000);
                    config.setTestOnBorrow(true);
                    jedisPool = new JedisPool( config, "192.168.204.141",6379 );
                }
            }
        }
        return jedisPool;
    }

    // 返回jedis对象
    public static Jedis getJedis(){
        if(jedis == null){
            jedis = getInstance().getResource();
        }
        return jedis;
    }
}

测试类

public class Test_JedisPool {
    public static void main(String[] args) {
        Jedis jedis1 = JedisPoolUtil.getJedis();
        Jedis jedis2 = JedisPoolUtil.getJedis();

        System.out.println(jedis1==jedis2);
    }
}

八、高并发下的分布式锁

  • 经典案例:秒杀,抢购优惠券等

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.7.RELEASE</version>
        </dependency>
        <!--实现分布式锁的工具类-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.6.1</version>
        </dependency>
        <!--spring操作redis的工具类-->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>2.3.2.RELEASE</version>
        </dependency>
        <!--redis客户端-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.1.0</version>
        </dependency>
        <!--json解析工具-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.8</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <port>8002</port>
                    <path>/</path>
                </configuration>
                <executions>
                    <execution>
                        <!-- 打包完成后,运行服务 -->
                        <phase>package</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

synchronized锁的一个进程下的线程并发,如果分布式环
境,多个进程并发,这种方案就失效了!只能解决一个tomcat的并发问题

1. 实现分布式锁的思路

  • . 因为redis是单线程的,所以命令也就具备原子性,使用setnx命令实现锁,保存k-v
    • 如果k不存在,保存(当前线程加锁),执行完成后,删除k表示释放锁
    • 如果k已存在,阻塞线程执行,表示有锁
  • 如果加锁成功,在执行业务代码的过程中出现异常,导致没有删除k(释放锁失败),那么就会造
    成死锁(后面的所有线程都无法执行)!
    • 设置过期时间,例如10秒后,redis自动删除
  • 高并发下,由于时间段等因素导致服务器压力过大或过小,每个线程执行的时间不同
    • 第一个线程,执行需要13秒,执行到第10秒时,redis自动过期了k(释放锁)
    • 第二个线程,执行需要7秒,加锁,执行第3秒(锁 被释放了,为什么,是被第一个线程的
      finally主动deleteKey释放掉了)
    • 。。。连锁反应,当前线程刚加的锁,就被其他线程释放掉了,周而复始,导致锁会永久失
  • 给每个线程加上唯一的标识UUID随机生成,释放的时候判断是否是当前的标识即可
  • 问题又来了,过期时间如果设定?
    • 如果10秒太短不够用怎么办?
    • 设置60秒,太长又浪费时间
    • 可以开启一个定时器线程,当过期时间小于总过期时间的1/3时,增长总过期时间(吃仙丹续
      命!)

九、Redisson

  • Redis 是最流行的 NoSQL 数据库解决方案之一,而 Java 是世界上最流行(注意,我没有说“最
    好”)的编程语言之一。
  • 虽然两者看起来很自然地在一起“工作”,但是要知道,Redis 其实并没有对 Java 提供原生支持。
  • 相反,作为 Java 开发人员,我们若想在程序中集成 Redis,必须使用 Redis 的第三方库。
  • 而 Redisson 就是用于在 Java 程序中操作 Redis 的库,它使得我们可以在程序中轻松地使用
    Redis。
  • Redisson 在 java.util 中常用接口的基础上,为我们提供了一系列具有分布式特性的工具类。
@Controller
public class TestKill {

    @Autowired
    private Redisson redisson;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @RequestMapping("kill")
    // 只能解决一个tomcat的并发问题:synchronized锁的一个进程下的线程并发,如果分布式环境,多个进程并发,这种方案就失效了!
    public @ResponseBody synchronized String kill() {

        // 定义商品id
        String productKey = "HUAWEI-P40";
        // 通过redisson获取锁
        RLock rLock = redisson.getLock(productKey); // 底层源码就是集成了setnx,过期时间等操作
        // 上锁(过期时间为30秒)
        rLock.lock(30, TimeUnit.SECONDS);

        try{
        // 1.从redis中获取 手机的库存数量
        int phoneCount = Integer.parseInt(stringRedisTemplate.opsForValue().get("phone"));
        // 2.判断手机的数量是否够秒杀的
        if (phoneCount > 0) {
            phoneCount--;
            // 库存减少后,再将库存的值保存回redis
            stringRedisTemplate.opsForValue().set("phone", phoneCount + "");
            System.out.println("库存-1,剩余:" + phoneCount);
        } else {
            System.out.println("库存不足!");
        }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 释放锁
            rLock.unlock();
        }
        return "over!";
    }


    @Bean
    public Redisson redisson(){
        Config config = new Config();
        // 使用单个redis服务器
        config.useSingleServer().setAddress("redis://192.168.204.141:6379").setDatabase(0);
        // 使用集群redis
        // config.useClusterServers().setScanInterval(2000).addNodeAddress("redis://192.168.204.141:6379","redis://192.168.204.142:6379","redis://192.168.204.143:6379");
        return (Redisson)Redisson.create(config);
    }
}
  • 实现分布式锁的方案其实有很多,我们之前用过的zookeeper的特点就是高可靠性,现在我们用的
    redis特点就是高性能。
  • 目前分布式锁,应用最多的仍然是“Redis”
举报

相关推荐

0 条评论