成长记录之redis

redis的基础数据结构

注意:redis中所有数据都是以key-value形式存在的,不同的数据类型在于key的value的结构不同

1、String  redis的最基本数据类型

redis的字符串类似java中的ArrayList,是可变的

字符串内部结构:带长度信息的字节数组

struct SDS<T> {
  T capacity; // 数组容量
  T len; // 数组长度
  byte flags; // 特殊标识位,不理睬它
  byte[] content; // 数组内容
}

 

2、List  列表

相当于java里的LinkedList,可以做为异步队列来使用:从右边添加,从左边拿(做栈也可以,从哪边放就从那边开始拿)

List内部结构:快速链表

元素较少的时候使用压缩列表,当元素增大,压缩列表无法满足的时候,则会将多个压缩列表用双向指针连接,形成一个快速链表

压缩列表结构:

struct ziplist<T> {
    int32 zlbytes; // 整个压缩列表占用字节数
    int32 zltail_offset; // 最后一个元素距离压缩列表起始位置的偏移量,用于快速定位到最后一个节点
    int16 zllength; // 元素个数
    T[] entries; // 元素内容列表,挨个挨个紧凑存储
    int8 zlend; // 标志压缩列表的结束,值恒为 0xFF
}

3、Hash  相当于java里的HashMap(数组+链表)

不同之处:

(1)redis的hash的value只能存字符串

(2)java中的hashmap在rehash的时候会一次性的完成;而redis的hash采用渐进式rehash,啥叫渐进式呢?就是在rehash的时候会保留新旧两个hash,在查询的时候也是查询这两个新旧hash,随后一点点的迁移数据。

4. Set  相当于java里的HashSet  内部元素是无序且唯一的。相当于一个value为null的hash

 

5.ZSet  有序集合

相当于java中SortedSet和HashMap的结合,元素唯一,并且可以给每个元素赋值一个得分,元素会根据得分排序

ZSet内部采用跳跃列表的数据结构,为啥不用链表呢?链表不支持快速插入,因为zset里的元素需要按照score排名,插入的时候需要找到一个合适的位置插入,通常我们会采用二分查找来找到这个合适的位置,但二分查找的对象必须是数组,链表不满足。

zset内部探究:

zset内部是一个跳跃列表和hash的符合结构,一方面需要用hash来保存value和score的关系,另一方面需要根据score的进行范围查找。

关于跳跃列表这篇文章写的很牛逼:https://zsr.github.io/2017/07/03/redis-zset内部实现/

 

redis应用

1.分布式锁

首先知道什么是分布式锁?synchronized是基于线程的锁,而分布式锁是基于进程的锁

具体使用需要根据业务场景,贴一段整理的redis分布式锁代码

@Component
public class DistributedLock {
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    //过期时间单位
    private static final String SET_WITH_EXPIRE_TIME = "EX";
    private static final Long DEFAULT_EXPIRE_TIME = 60L;
    private static final Long RELEASE_SUCCESS = 1L;
    private static Jedis jedis = new Jedis("127.0.0.1",6379,60000);

    /**
     * 尝试获取锁
     * @param lockKey 锁
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public boolean tryLock(String lockKey,Long expireTime) {
        expireTime = expireTime == null ? DEFAULT_EXPIRE_TIME : expireTime;
        //设置lockValue防止A删除B的锁
        String lockValue = UUID.randomUUID().toString();
        try {
            String result = jedis.set(lockKey, lockValue, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
            if (LOCK_SUCCESS.equals(result)) {
                return true;
            }
        } catch (Exception e){
            System.out.println(e);
        } finally {
            jedis.close();
        }
        return false;
    }

    /**
     * 释放锁
     * @param lockKey 锁
     * @param lockValue 请求标识
     * @return 是否释放成功
     */
    public boolean releaseLock(String lockKey, String lockValue) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(lockValue));
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

2.异步消息队列

redis作为消息队列并没有那么多的高级特性,如果业务场景要求不高的情况下可以使用它

原理就是使用redis的list类型

 

3.位图

比如我们要存每个用户每天的打卡情况信息,1代表打卡,0代表未打卡,那么每个人一年就需要365条数据,如果有上亿用户这个数据量是很惊人的。

redis提供了位图:它就是普通的字符串,也就是内部也用字节数组来存,这样每天的签到记录只占据一个位,365 天就是 365 个位,46 个字节 (一个稍长一点的字符串) 就可以完全容纳下,这就大大节约了存储空间。

 

4.Hyper Log Log  redis提供的统计解决方案

例如:求某个网站的uv(一个用户访问多少次都算一次这种需要去重的数据)

使用pfadd和pfcount指令

原理暂时先跳过

5.布隆过滤器

场景:新闻推荐,每一次的推荐需要将上一次推荐过的过滤掉,这个时候就排上用场了,为啥不用hashmap呢?简单一句话因为数据量过大时太占用空间

布隆过滤器其实就是一个位(bit)数组,比如我们存"祝君好运"这个字符串的时候,首先需要计算hashcode值,然后将其添加到位数组中。如下图(盗前辈的图?)

 

比如“百度”字符串存的位置是1、4、7   而“taobao”存的位置是2、5、8

而在我们查询“meituan”的时候,hash函数计算出来的位置是1、5、8  那么布隆过滤器就会认为“meituan”已经存在了(实际上不存在),在查询“meituan”的时候如果1、5、8三个位置上有一个是0那就说明这个值不存在。

 

问题:

(1)redis单线程为啥还这么快?

redis的数据都是存在内存,所有的操作都是基于内存的操作

(2)redis单线程如何处理那么多的并发客户端请求?

redis使用了多路复用的io模型,这里的单线程是指:redis使用一个线程来处理客户端的请求,并不是说它内部只有一个线程。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值