一 Redis数据结构-Redis的数据存储及String类型的实现( 二 )

  • 某个时间点上,ht[0]的所有键值对都会被rehash至ht[1] ,这时程序将rehashidx属性的值设为-1 ,  表示rehash操作已完成
  • 在渐进式 rehash 进行期间,字典的删除(delete)、查找(find)、更新(update)等操作会在两个哈希表上进行;在字典里面查找一个键的话,程序会先在 ht[0] 里面进行查找,如果没找到的话 , 就会继续到ht[1]里面进行查找;新添加到字典的键值对一律会被保存到 ht[1] 里面,而ht[0]则不再进行任何添加操作:这一措施保证了ht[0]包含的键值对数量会只减不增(如果长时间不进行操作时,事件轮询进行这种操作),并随着rehash操作的执行而最终变成空表 。
    dict.h/redisObject
    Typedef struct redisObject {unsigned type:4;unsigned encoding:4;unsigned lru:LRU_BITS;int refcount;void *ptr;}
    • type:4:约束客户端操作时存储的数据类型,已存在的数据无法修改类型,4bit
    • encoding:4:值在redis底层的编码模式,4bit
    • lru:LRU_BITS:内存淘汰策略
    • refcount:通过引用计数法管理内存,4byte
    • ptr:指向真实存储值的地址,8byte
    完整结构图如下:
    一 Redis数据结构-Redis的数据存储及String类型的实现

    文章插图
    3 String类型3.1 String类型使用场景String 字符串存在有三种类型:字符串 , 整数,浮点 。主要有以下使用场景
    1)页面动态缓存比如生成一个动态页面,首次可以将后台数据生成页面,并且存储到redis字符串中 。再次访问,不再进行数据库请求,直接从redis中读取该页面 。特点是:首次访问比较慢,后续访问快速 。
    2)数据缓存在前后分离式开发中,有些数据虽然存储在数据库,但是更改特别少 。比如有个全国地区表 。当前端发起请求后,后台如果每次都从关系型数据库读取 , 会影响网站整体性能 。我们可以在第一次访问的时候 , 将所有地区信息存储到redis字符串中,再次请求,直接从数据库中读取地区的json字符串,返回给前端 。
    3)数据统计redis整型可以用来记录网站访问量,某个文件的下载量 。(原子自增自减)
    4)时间内限制请求次数比如已登录用户请求短信验证码,验证码在5分钟内有效的场景 。当用户首次请求了短信接口,将用户id存储到redis 已经发送短信的字符串中 , 并且设置过期时间为5分钟 。当该用户再次请求短信接口,发现已经存在该用户发送短信记录 , 则不再发送短信 。
    5)分布式session当我们用nginx做负载均衡的时候,如果我们每个从服务器上都各自存储自己的session,那么当切换了服务器后 , session信息会由于不共享而会丢失,我们不得不考虑第三应用来存储session 。通过我们用关系型数据库或者redis等非关系型数据库 。关系型数据库存储和读取性能远远无法跟redis等非关系型数据库 。
    3.2 String类型的实现——SDS结构Redis并没有直接使用C字符串实现String类型,在Redis3.2版本之前通过SDS实现
    Typedef struct sdshdr {int len;int free;char buf[];};
    • len:分配内存空间
    • free:剩余可用分配空间
    • char[]:value值实际数据
    3.3 SDS与C字符串之间的区别3.3.1 查询时间复杂度
    C获取字符串长度的复杂度为O(N) 。而SDS通过len记录长度,从C的O(n)变为O(1) 。
    3.3.2 缓冲区溢出
    C字符串不记录自身长度容易造成缓冲区溢出(buffer overflow) 。SDS的空间分配策略完全杜绝了发生缓冲区溢出的可能性,当需要对SDS进行修改时,会先检查SDS的空间是否满足修改所需的要求,如果不满足的话SDS的空间扩展至执行修改所需的大?。?然后才执行实际的修改操作 , 所以使用SDS既不需要手动修改SDS的空间大小,也不会出现缓冲区溢出问题 。
    在SDS中,buf数组的长度不一定就是字符数量加一,数组里面可以包含未使用的字节,而这些字节的数量就由SDS的free属性记录 。通过未使用空间,SDS实现了空间预分配和惰性空间释放两种优化策略: