Redis特性及使用场景

Redis支持的几种类型?

String、hash(类似java的map)、list(链表)、set(集合)、zset(无需集合)

Redis有哪几种持久化技术?

1.RDB

RDB包括两种持久化命令:save、bgsave

save命令:在主线程中工作,因此有可能阻塞主线程其他操作redis的请求,尽量避免使用此命令

bgsave命令:从主线程中fork(分叉)出一个子进程,父进程继续处理操作请求。

子进程将数据写入临时文件,并在写完后替换原有的.rdb文件。

在持久化写入临时文件的过程中处理的redis数据也会被复制一份,而不会共享内存。

所以RDB持久化的数据是,fork一个子进程时redis中保存的数据(不含持久化时的数据)

如果redis在RDB持久化过程中宕机,那么可能导致持久化这段时间的数据丢失

2.AOF

需在配置文件中修改appendonly 值为yes

开启AOF持久化技术之后,所执行的指令会根据配置文件的设定记录到appendonly.aof文件中

事实上并不会立即将命令写入到硬盘文件中,而是写入到磁盘缓存中

配置文件:

appendsync everysec(每一秒都AOF持久化到磁盘缓存)

appendsync always(每一次操作数据都持久化)

appendsync no(30秒持久化一次)

RDB和AOF持久化技术的好坏?

RDB由于是在主进程中fork一个子进程来持久化到磁盘文件中,所以不会影响到父进程的处理数据

可是持久化过程中宕机可能会导致数据丢失。

若数据比较不重要可以选择RDB高效率持久化技术,可是安全性较差

 

AOF由于是每一次数据操作或每一秒都要持久化一次数据到磁盘缓存中。

且AOF的持久化技术是用续写的方式在原有持久化文件的基础上续写,不会因为宕机破坏日志原有内容

所以操作数据的性能效率一定有所影响,可是安全性较强

Redis的特点?

内存数据库,存取速度快,也支持数据的持久化

就算没有持久化数据,也可以将内存中的数据保存在磁盘中,重启的时候可以再次加载使用

Redis支持实时数据备份,就算宕机也能把数据恢复过来

Redis支持事务,操作都是原子性

Redis具有丰富特性:缓存、消息、key-value的自动过期

Redis拓展性强:随时可以加一台新的服务器集群,成本较低

Redis的使用场景?

1.配合关系型数据库做高速缓存

2.社区中的评论、点赞、恢复等等。(使用传统数据库的select语句很耗费性能)

3.网页的排行榜、计数器

4.webMagic爬虫框架,使用RedisSchedule作去重,支持分布式

Redis事务?

Redis的事务不支持回滚,只会跳过错误命令,继续执行队列中剩下的命令

Jedis jedis = pool.getResource();
// 创建一个redis事务对象
Transaction transaction = jedis.multi();
transaction.setnx("haha","aaa");
// 提交事务中的所有操作
transaction.exec();
// 放弃队列中的所有命令
transaction.discard();

通过redis事务实现乐观锁(check-and-set检查再设置)

/*
watch("key")监听这个键值对,是否被外界修改过
如果被修改过则watch()-unwatch()之间操作队列全部放弃
 */
jedis.watch("haha");
// 创建一个redis事务对象
Transaction transaction = jedis.multi();
transaction.del("haha");
// 提交事务中的所有操作
transaction.exec();
// 结束监听
jedis.unwatch();
// 放弃队列中的所有命令
transaction.discard();

Redis集群?

一台master一定要有至少一台slave,所以redis集群至少要有6台服务器

1.所有redis节点都是彼此互联(ping-pong机制),内部使用二进制协议优化传输速度和带宽

2.节点的fail是通过集群中节点超过半数的节点检测失效时生效的

3.客户端与redis节点直连,不需要要proxy层,客户端连接集群整体即会分配节点

4.redis集群把所有物理节点映射到【0-16383】slot槽上,集群维护node<->slot<->value

 

集群维护16384个哈希槽,redis会根据集群的节点数,平均分配给每个节点相同的哈希槽。

当客户端往集群中插入一个key-value数据时,会先对key使用CRC16算法求出结果并取16384的余

最终决定此key-value数据存放的位置

 

投票:

所有的master参与投票,若一个master被超过半数的master节点通信超时,则会被认为此master节点挂掉

如果集群中任意master挂掉,且没有slave可用,则整个集群进入fail状态,(因为【0-16383】不完整)

可用打开cluster-require-full-coverage参数,兼容集群部分节点挂掉

集群超过半数以上的master挂掉,无论是否拥有slave,整个集群进入fail状态

Redis作分布式锁?


public class RedisLock {

    private final JedisPool jedisPool;

//    在ioc容器中已经配置了jedisPool连接池对象
//    private String host;

//    private String port;

    public RedisLock(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    /**
     * 加锁
     *
     * @param lockName       锁的key
     * @param acquireTimeout 获取超时时间
     * @param timeout        锁的超时时间
     * @return 锁标识
     */
    public String lockWithTimeout(String lockName, long acquireTimeout, long timeout) {
        Jedis jedis = null;
        String retIdentifier = null;
        try {
            // 获取连接
            jedis = jedisPool.getResource();
            // 随机生成一个value
            String identifier = UUID.randomUUID().toString();
            // 锁名,即key值
            String lockKey = "lock:" + lockName;
            // 超时时间,上锁后超过此时间则自动释放锁
            // 预防死锁!!
            int lockExpire = (int) (timeout / 1000);

            // 获取锁的超时时间,超过这个时间则放弃获取锁
            long end = System.currentTimeMillis() + acquireTimeout;

            // 判断当前系统时间毫秒值 小于 超时时间
            while (System.currentTimeMillis() < end) {
                if (jedis.setnx(lockKey, identifier) == 1) {
                    // 为这个key一个超时时间,超过时间后自动删除key
                    jedis.expire(lockKey, lockExpire);
                    // 返回value值,用于释放锁时间确认
                    retIdentifier = identifier;
                    return retIdentifier;
                }
                // 返回-1代表key没有设置超时时间,为key设置一个超时时间
                if (jedis.ttl(lockKey) == -1) {
                    jedis.expire(lockKey, lockExpire);
                }

                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        } catch (JedisException e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return retIdentifier;
    }

    /**
     * 释放锁
     *
     * @param lockName   锁的key
     * @param identifier 释放锁的标识
     * @return
     */
    public boolean releaseLock(String lockName, String identifier) {
        Jedis jedis = null;
        String lockKey = "lock:" + lockName;
        boolean retFlag = false;
        try {
            jedis = jedisPool.getResource();
            while (true) {
                /*
                 监视lock,准备开始事务
                 WATCH 命令用于在事务开始之前监视任意数量的键:当调用 EXEC 命令执行事务时
                 如果任意一个被监视的键已经被其他客户端修改了,那么整个事务不再执行,直接返回失败。
                  */

                jedis.watch(lockKey);
                // 通过前面返回的value值判断是不是该锁,若是该锁,则删除,释放锁
                if (identifier.equals(jedis.get(lockKey))) {
                    /*
                     redis的multi开始一个事务
                     这个命令唯一做的就是,将客户端的 REDIS_MULTI 选项打开,让客户端从非事务状态切换到事务状态。
                     将多个命令打包, 然后一次性、按顺序地执行的机制并且事务在执行的期间不会主动中断
                     服务器在执行完事务中的所有命令之后,才会继续处理其他客户端的其他命令。
                     */
                    Transaction transaction = jedis.multi();
                    transaction.del(lockKey);
                    //  最后由 redis的exec 命令触发事务
                    List<Object> results = transaction.exec();
                    if (results == null) {
                        continue;
                    }
                    retFlag = true;
                }
                jedis.unwatch();
                break;
            }
        } catch (JedisException e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return retFlag;
    }
}

在用户请求进入高并发业务的方法时,调用获取锁方法,并在用户处理完业务逻辑后释放锁。

保证方法仅能被一个进程所访问,方法仅处理一个用户的请求。

获取锁:

调用此方法时,传入当前方法名作为key、锁自动释放时间和方法超时时间。创建一个uuid随机字符串作锁的value值。

在一个循环中(循环结束条件是系统时间>方法超时时间),调用redis的setnx(key,value)方法往redis中存入锁对象,若返回1则说明存入成功,否则存入失败。存入成功再调用redis的expire(key,timeout)方法为key设置一个自动删除时间。最终返回锁的value随机字符串结束方法。

释放锁:

调用此方法时,传入当前方法名、获取锁时返回的随机字符串。

在死循环中while(true){}开启redis事务,判断随机字符串与redis中存放锁value是否相同,若相同则进入方法体,将删除锁的del()操作放入事务的队列中,调用exec()方法执行事务中的所有操作。若返回的result集合不为空,那么说明释放锁成功。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值