zoukankan      html  css  js  c++  java
  • Redis小结2

    一.Redis概念

    NoSQL

    Not Only SQL,泛指非关系型数据库

    Memcache:这是一个和Redis非常相似的数据库,但是它的数据类型没有Redis丰富。Memcache由LiveJournal的Brad Fitzpatrick开发,作为一套分布式的高速缓存系统,被许多网站使用以提升网站的访问速度,对于一些大型的、需要频繁访问数据库的网站访问速度的提升效果十分显著。

    Apache Cassandra:(社区内一般简称为C*)这是一套开源分布式NoSQL数据库系统。它最初由Facebook开发,用于储存收件箱等简单格式数据,集Google BigTable的数据模型与Amazon Dynamo的完全分布式架构于一身。Facebook于2008将 Cassandra 开源,由于其良好的可扩展性和性能,被 Apple、Comcast、Instagram、Spotify、eBay、Rackspace、Netflix等知名网站所采用,成为了一种流行的分布式结构化数据存储方案。

    MongoDB:是一个基于分布式文件存储、面向文档的NoSQL数据库,由C++编写,旨在为WEB应用提供可扩展的高性能数据存储解决方案。MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系型数据库的,它支持的数据结构非常松散,是一种类似json的BSON格式。

    redis

    Remote Dictionary Server,远程字典服务器
    redis是一个开源的、使用C语言编写的、支持网络交互的、可基于内存也可持久化的Key-Value数据库(非关系性数据库)

    二.Redis优点

    • 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
    • 支持丰富数据类型,支持string,list,set,sorted set,hash
    • 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
    • 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

    三.Redis数据类型

    1.字符串(string)

    常用命令

    set key value
    get key
    exists  key   //key是否存在
    

    redis会将字符串类型转换成数值;

    由于INCR等指令本身就具有原子操作的特性,所以我们完全可以利用redis的INCR、INCRBY、DECR、DECRBY等指令来实现原子计数的效果

    SDS结构

    //简单动态字符串(Simple Dynamic String)
    struct sdshdr{
        int len; //buf已使用的长度
        int free; //buf未使用的长度
        char buf[]; //存储字符串
    };
    //buf数组长度 = free + len + 1  (其中1表示字符串结尾空字符'')
    

    1604395118497

    C字符串对比

    C字符串 简单动态字符串SDS
    获取字符串长度复杂度O(N) 获取字符串长度复杂度O(1)
    API不安全,存在缓冲区溢出 API安全,不会造成缓冲区溢出
    修改字符串存在多次内存分配 修改字符串做多需要执行N次内存重分配
    只能保存文本数据 可以保存文本或者二进制数据(二进制安全)
    可以使用所有<string.h>库函数 只能使用一部分<string.h>库函数

    2.哈希(hash)

    常用命令

    hset hashKey  key1 value1 key2 value2
    hget hashkey  key1
    hgetall hashKey 
    

    3.集合(set)

    常用命令

    sadd mySet value  //向集合添加元素
    smembers mySet  //列出集合mySet中的所有元素
    scard mySet    //返回集合中元素数量
    sismember mySet value   //查看value是否在集合mySet中
    srem mySet value    //从集合mySet中删除value
    sunion mySet1 mySet2 //合并多个set,返回合并后的元素列表
    del mySet
    

    4.列表(list)

    常用命令

    lpush list  value  //在list左侧(开头)插入元素
    rpush list  value  //在list右侧(末尾)插入元素
    lpop list //删除并返回列表第一个元素
    rpop  list //删除并返回列表最后一个元素
    llen  list
    lrange myList 0 3  //列出mylist中从编号0到编号3的元素
    lrange myList 0 -1   //列出mylist中从编号0到最后一个元素
    del myList   
    

    其他

    Redis列表是简单的字符串列表,按照插入顺序排序,头部是左边,尾部是右边

    底层实现上就是链表,不是数组

    5.有序集合(sort set)

    常用命令

    zadd zset1 key1 value1 //key作为value的编号来用于排序
    zcard zset1   //统计zset1下key的个数
    zrank zset1 value2   //查看value2在zset1中排名位置
    zrange zset1 0 2 withscores   //查看0到2的所有值和分数按照排名
    zrange zset1 0 -1   //只查看zset中元素
    

    其他

    • key不要太长,尽量不要超过1024字节,这不仅消耗内存,而且会降低查找的效率;

    • 有序集合底层使用了 压缩链表和跳跃表:

    其中跳跃表基于有序单链表,在链表的基础上,每个结点不只包含一个指针,还可能包含多个指向后继结点的指针,这样就可以跳过一些不必要的结点,从而加快查找、删除等操作。如下图就是一个普通跳跃表(和redis跳跃表不完全一致):

    四.redis过期策略

    1.定期删除

    redis是每隔100ms随机抽取一些key来检查和删除的

    2.惰性删除

    在你获取某个key的时候,redis会检查是否过期,过期则删除并不返回结果

    3.内存淘汰

    当redis内存占用过多时,进行内存淘汰

    allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)(Least Recently Used,最近最久未使用)

    五.Redis常见问题

    1.击穿

    概念

    在Redis获取某一key时,由于key不存在,而必须向DB发起一次请求的行为

    原因

    第一次访问; 恶意访问不存在的key; key过期

    规避

    服务器启动时,提前写入;

    规范key的命名,通过中间件拦截;

    对某些高频访问的key,设置合理的TTL或永不过期

    2.雪崩:

    概念

    Redis缓存层由于某种原因宕机后,所有的请求会涌向存储层,短时间内高并发请求可能导致存储层挂机

    规避

    使用Redis集群;

    限流;

    六.Redis协议

    Redis客户端通讯协议:RESP(Redis Serialization Protocol),其特点是:

    • 简单
    • 解析速度快
    • 可读性好

    Redis集群内部通讯协议:RECP(Redis Cluster Protocol ) ,其特点是:

    • 每一个node两个tcp 连接
    • 一个负责client-server通讯(P: 6379)
    • 一个负责node之间通讯(P: 10000 + 6379)

    七.Redis面试题

    1.什么是缓存雪崩?解决方法?

    • redis挂了,请求全部从内存转为走数据库
    • 缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机

    解决:

    • 缓存数据的过期时间错开,防止同一时间大量数据过期现象发生
    • 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中
    • 设置热点数据永不过期或更长合理过期时间

    2.什么是缓存穿透/击穿?如何解决?

    缓存穿透:

    大量缓存中不存在的请求key访问直接落到数据库,一般是恶意攻击
    

    缓存击穿:

    热点key在请求高峰失效,瞬间大量请求落到数据库
    

    解决:

    ①可以使用布隆过滤器(BloomFilter)或者压缩filter拦截过滤不合法的请求
    
    ②查询为空的结果也写到缓存中去(但过期时间短一点)
    

    3.缓存与数据库双写一致

    读操作先去找缓存,有则直接返回;若没有就查询数据库,将该结果写到缓存中并返回给请求

    • 先删除缓存,再更新数据库

      在高并发下表现不如意,在原子性被破坏时表现优异

    • 先更新数据库,再删除缓存

      在高并发下表现优异,在原子性被破坏时表现不如意

    缓存同步的原理:如果后台数据库中内容修改了就需要将redis中的key进行删除,下次访问的时候,redis中没有该数据,则从DB进行查询,再次更新到redis中

    4.布隆过滤器

    判断一个元素是否存在一个集合中

    布隆过滤器的原理是,当一个元素被加入集合时,通过K个Hash函数将这个元素映射成一个一维的bool型的数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。

    优点:新增,查询速度足够快,内存小,代码简单

    缺点: 有一定误判率且随数据增加而增加; 不支持删除

    大白话布隆过滤器

    https://www.cnblogs.com/CodeBear/p/10911177.html

    5.Redis持久化

    持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失

    6.Redis是单进程单线程的

    redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销

    八.参考链接

    硬核!15张图解Redis为什么这么快

    https://www.cnblogs.com/caoyier/p/13896319.html

    linux安装redis一,超详细说明与图解!!

    https://blog.csdn.net/qq_30764991/article/details/81564652

    Redis中的跳跃表

    https://blog.csdn.net/universe_ant/article/details/51134020

    Redis源码解析:05跳跃表

    https://blog.csdn.net/gqtcgq/article/details/50613896

    布隆过滤器

    一.作用:

    判断一个元素是否存在一个集合中
    

    二.基本原理通俗:

    当一个元素被加入集合时,通过K个Hash函数将这个元素映射成一个一维的布尔型的数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。
    

    三.算法原理详细:

    1. n个要添加的元素
    2. k个hash函数
    3. m位的空的bitArray
    4. 添加一个元素key时,用k个hash函数计算出k个散列值,并把bitArray对应的比特位置为1
    5. 判断一个元素key是否存在时,用k个hash函数计算出k个散列值,并查询bitArray中对应的比特位;如果至少有一位不为1,则一定不在集合中;如果全部都为1,则认为在集合中(存在误判)

    四.优缺点

    优点:

    新增,查询速度足够快,内存小,代码简单

    缺点:

    有一定误判率且随数据增加而增加; 不支持删除

    五.简单代码实现

    package scala02
    
    import scala.collection.mutable.ArrayBuffer
    import scala.math.abs
    import scala.util.hashing.MurmurHash3
    
     object BloomFilter {
    
       private val BYTE_SIZE: Int = 8
       private var m: Int = _   //m 为要存的比特数组长度
       private var k: Int = _       //k 为哈希函数的个数
       private var bitmapCharArray: Array[Char] = _  
       private var seedArray: Array[Int] = _
    
    
       /**
         * 生成空的bitArray
         * @param m
         * @return
         */
      private def generateEmptyBitmap(m:Int): Array[Char] ={
        val charNum = (m.toDouble/BYTE_SIZE).ceil.toInt //ceil 不小于该浮点数的最小整数, (2.1).ceil则为3.0
        val charArrayBuffer = ArrayBuffer.empty[Char]
        val char=0x00.toChar
        for (elem <- 0 until charNum ) {//0 until len  或者 0 to len-1
          charArrayBuffer.append(char)
        }
        charArrayBuffer.toArray
    
      }
    
       /**
         * 判断字符串是否可能存在于过滤器中
         *
         * @param str
         * @return
         */
       def exists(str: String): Boolean = {
         var flag = true
         var s = 0
         while (s < k) {
           val pos = hash(str, seedArray(s))
           if (!getBit(pos)) {
             flag = false
             s = k
           }
           s = s + 1
         }
         flag
       }
    
    
       /**
         * 将字符串添加到过滤器中
         *
         * @param str
         */
       def put(str: String) = {
         seedArray.foreach(seed => {
           val pos = hash(str, seed)
           setBit(pos)
         })
       }
    
       /**
         * 将bitmap的第pos个bit置为1
         *
         * @param pos
         */
       private def setBit(pos: Int): Unit = {
         val charPos = getCharPos(pos)
         val char = bitmapCharArray(charPos)
         val bitPos = pos - charPos * BYTE_SIZE
         val byte = char.toByte
         val mask = 0x01 << bitPos
         val or = byte | mask
         bitmapCharArray(charPos) = or.toChar
       }
       
    
    
       /**
         * 基于MurmurHash3算法计算字符串的hash值
         *
         * @param str
         * @param seed hash种子
         * @return 取值范围 0 ~ m-1
         */
       private def hash(str: String, seed: Int): Int = {
         abs(MurmurHash3.stringHash(str, seed)) % m
       }
    
       /**
         * 读取bitmap的第pos个bit
         *
         * @param pos
         */
       private def getBit(pos: Int): Boolean = {
         val charPos = getCharPos(pos)
         val char = bitmapCharArray(charPos)
         val bitPos = pos - charPos * BYTE_SIZE
         val byte = char.toByte
         val mask = 0x01 << bitPos
         val and = byte & mask
         if (0 == and) false else true
       }
    
       /**
         * 获取第pos个bit对应的char的位置(从0开始编号)
         *
         * @param pos
         * @return 0 ~ m/BYTE_SIZE-1
         */
       private def getCharPos(pos: Int): Int = {
         (pos.toDouble / BYTE_SIZE).toInt
       }
    
       /**
         * 判断n是否为质数
         *
         * @param n
         * @return
         */
       private def isPrime(n: Int) = {
         var flag = true
         for (i <- 2 to n - 1) {
           if (n % i == 0) flag = false
         }
         flag
       }
       
    
    }
    
    

    六.参考链接

    布隆过滤器原理及数学推导

    https://www.cnblogs.com/allensun/archive/2011/02/16/1956532.html

  • 相关阅读:
    我的浏览器收藏夹分类
    我的浏览器收藏夹分类
    Java实现 LeetCode 318 最大单词长度乘积
    Java实现 LeetCode 318 最大单词长度乘积
    Java实现 LeetCode 318 最大单词长度乘积
    Java实现 LeetCode 316 去除重复字母
    Java实现 LeetCode 316 去除重复字母
    Java实现 LeetCode 316 去除重复字母
    Java实现 LeetCode 315 计算右侧小于当前元素的个数
    Java实现 LeetCode 315 计算右侧小于当前元素的个数
  • 原文地址:https://www.cnblogs.com/ShyPeanut/p/13995323.html
Copyright © 2011-2022 走看看