Redis实战-数据安全和性能保障

1 持久化选项

redis提供了两种方式进行持久化:

1 快照:某一时刻将所有数据写入硬盘。
2 只追加文件:在执行写命令时,将被执行的命令复制到硬盘。

在这里插入图片描述
与持久化相关的选项。

1.1 快照持久化

根据redis.conf配置项,创建快照,几种创建快照方法:

  • bgsave命令:创建子进程进行快照创建。
  • save命令:直接主进程执行快照创建,完成之前阻塞其它命令。
  • 配置文件选项:如save 60 1,从上次创建开始,60s有一次改动就进行快照创建。
  • shutdown命令:当收到该命令,会自动执行save命令。
  • sync命令:如从服务器复制主服务器,主服务器将执行bgsave命令。

在只使用快照存储数据,如果系统发生故障,将导致最后一次创建快照到系统崩溃时刻的所有数据变化情况。

1.2 AOF持久化

将被执行的写命令写到aof文件的末尾。需要配置选项:appendonly yes
同步频率:
在这里插入图片描述

always:每次写都要同步写入硬盘,严重降低redis速度;
everysec:每秒写入一次,常用;
no:操作系统决定何时写入。

aof文件体积较大,可以通过配置对应的压缩选项,进行重写。

2 复制

2.1 复制选项配置

redis采用了与关系数据库相同的方法实现复制特性,由主服务器进行写操作,从服务器进行读请求。

可以通过命令 slaveof host port让服务器连接主服务器,执行复制。

2.2 Redis复制启动过程

slave和master交互过程如下:
在这里插入图片描述

注意:redis在进行同步时,会清空自己的所有数据。
redis不支持主主同步。

2.3 主从链

redis支持主从链模式类似下图,也就是一个从服务器也可以有它的从服务器。在这里插入图片描述

从而避免都从主服务器进行复制。

3 处理系统故障

如果发送系统故障,需要采取有效措施,应对各种问题

3.1 验证快照和AOF文件

redis提供了对应的命令redis-check-aof和redis-check-rdb验证aof文件和快照文件,是否有效。
在这里插入图片描述

可以对aof使用–fix进行修复,但是快照目前无法修复。

3.2 更换故障主服务器

假设A、B两天机器运行Redis服务,A为B的主服务器,如果A因为各种故障导致无法提供服务。可以使用另一台服务器C作为主服务器。
更换步骤如下:主要是copy对应的备份文件,这里以AOF文件为例(记得更改配置(appendonly yes):

1. B服务器,创建演示数据:
在这里插入图片描述
2 将Windows文件appendonly.aof传输到linux下

在这里插入图片描述
查看linux文件存储地址:
在这里插入图片描述
将对应的aof文件复制到目标文件夹下。
3 然后重启linux文件服务,查看数据是否恢复:
在这里插入图片描述
4 测试同步:
在这里插入图片描述
可见已经完成主服务器更换。

4 Redis事务

事务是为了保障数据的正确性。

Redis主要是使用multi和exec命令实现。

4.1 事务演示例子:游戏物品出售

数据结构分析:

  • 定义hash存储用户信息。
  • 定义用户包裹集合存储用户游戏物品。
  • 定义market存储用户需要出售的物品和价格。

4.2 Java代码演示

物品放到市场销售

/**
     * 将指定物品添加到market
     * @param itemid
     * @param sellerid
     * @param price
     */
    public void listItem(String itemid, String sellerid, double price) {
        String inventory = "inventory:" + sellerid;
        String item = itemid + "." + sellerid;
        long endTime = System.currentTimeMillis() + 5000;
        while (System.currentTimeMillis() < endTime) {
            redisTemplate.watch(inventory);
            BoundSetOperations<String, Object> userSet = redisTemplate.boundSetOps(inventory);
            if (!userSet.isMember(itemid)) {
                redisTemplate.unwatch();
                return;
            }
            //开启事务执行
            redisTemplate.multi();
            BoundZSetOperations<String, Object> market = redisTemplate.boundZSetOps("market:");
            market.add(item, price);
            userSet.remove(itemid);
            redisTemplate.exec();
        }
    }
  1. 这里首先使用了watch命令监视用户包裹集合,以防止出售过程用户更改,提供失败重试机会。
  2. 之后将开启事务,将出售物品放入市场,并移除用户包裹的对应物品。
  3. 执行事务。

购买商品:

/**
     * 购买物品
     * @param buyerid
     * @param sellerid
     * @param itemid
     * @param price
     */
    public void purchaseItem(String buyerid, String sellerid, String itemid, double price) {
        String buyer = "users:" + buyerid;
        String seller = "user:" + sellerid;
        String item = itemid + "." + sellerid;
        String inventory = "inventory:" + buyerid;
        long endTime = System.currentTimeMillis() + 10000;
        while (System.currentTimeMillis() < endTime) {
            redisTemplate.watch("market:");
            BoundZSetOperations<String, Object> market = redisTemplate.boundZSetOps("market");
            Double score = market.score(item);
            BoundHashOperations<String, Object, Object> buyerInfo = redisTemplate.boundHashOps(buyer);
            BoundHashOperations<String, Object, Object> sellerInfo = redisTemplate.boundHashOps(seller);
            Object funds = buyerInfo.get("funds");
            if (score != price || price > (Double) funds) {
                redisTemplate.unwatch();
                return;
            }
            //开启、执行事务
            redisTemplate.multi();
            sellerInfo.increment("funds", price);
            buyerInfo.increment("funds", -price);
            redisTemplate.opsForSet().add(inventory, itemid);
            market.remove(item);
            redisTemplate.exec();
        }
    }
  1. 这里首先watch市场,防止出售过程被改动。
  2. 判断买家是否有足够金额购买。
  3. 如果满足则开启事务
  4. 添加对应修改命令,执行事务。

Redis没有提供数据库那种加锁机制,主要是防止加锁客户端等待时间过长。但是提供了失败重试的乐观锁机制。

4.5 非事务性流水线

事务性流水线可以加快执行速度,但是multi和exec命令会消耗资源。不过Redis提供了不使用事务命令来执行流水线命令的机制-pipeline。
这里以Java为例,演示非事务性流水线:

/**
     * 使用pipeline
     */
    @Test
    void pipeLineTest() {
        List<Object> objects = redisTemplate.executePipelined(new SessionCallback<String>() {
            @Override
            public String execute(RedisOperations operations) throws DataAccessException {
                redisTemplate.opsForList().leftPush("pipe", "k1");
                redisTemplate.opsForList().rightPush("pipe", "k2");
                redisTemplate.opsForList().leftPop("pipe");
                redisTemplate.opsForList().rightPop("pipe");
                return null;
            }
        });
        objects.stream().forEach(System.out::println);
    }

主要是使用executePipelined打包执行命令,一次发送给redis服务器执行。
返回值为列表对应每条命令执行结果,上述代码测试执行结果:
在这里插入图片描述

下面示例是对同样非事务性流水线和普通执行的性能测试demo:

/**
     * 使用pipeline
     */
    @Test
    void pipeLineTest() {
        redisTemplate.executePipelined(new SessionCallback<String>() {
            @Override
            public String execute(RedisOperations operations) throws DataAccessException {
                redisTemplate.opsForList().leftPush("pipe", "k1");
                redisTemplate.opsForList().rightPush("pipe", "k2");
                redisTemplate.opsForList().leftPop("pipe");
                redisTemplate.opsForList().rightPop("pipe");
                return null;
            }
        });
    }

    @Test
    void notusePipeTest() {
        redisTemplate.opsForList().leftPush("pipe", "k1");
        redisTemplate.opsForList().rightPush("pipe", "k2");
        redisTemplate.opsForList().leftPop("pipe");
        redisTemplate.opsForList().rightPop("pipe");
    }

    @Test
    void performanceTest() {
        long pipelineEndTime = System.currentTimeMillis() + 60000;
        int count = 0;
        while (System.currentTimeMillis() < pipelineEndTime) {
            pipeLineTest();
            count++;
        }
        System.out.println(count);

        pipelineEndTime = System.currentTimeMillis() + 60000;
        count = 0;
        while (System.currentTimeMillis() < pipelineEndTime) {
            notusePipeTest();
            count++;
        }
        System.out.println(count);
    }

下面是执行10s和执行一分钟的结果比较:

表 1 结果对比
时间普通命令执行流水线命令执行
10s69次210次
1分钟403次1548次

可以看出通过流水线执行命令,可以将原来通信时间缩短到原来的1/4到1/3,从而提升Redis的性能。

我们可以通过redis-benchmark命令来查看redis的执行性能,如下演示:
在这里插入图片描述
对于上述可以看出redis对于基本的命令tps可以达到3-5万/s,执行非常快。

参考文献

[1] 《Redis实战》.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LamaxiyaFc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值