Redis简单介绍

Posted tuacy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis简单介绍相关的知识,希望对你有一定的参考价值。

一 Redis简介

Redis是一个开源(BSD许可)的,用C语言编写的基于内存的数据结构存储系统(是一个高性能的 key-value存储系统)。而且会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,实现数据的持久化。Redis可以用在数据库,缓存和消息中间件。

Redis官网:https://redis.io/

Redis中文官网:http://www.redis.cn/

1.1 Redis的特点

  • 性能极高,Redis读取的速度是110000次/s,写的速度是81000次/s。
  • 丰富的数据类型,支持多种数据结构:string(字符串);list(列表);hash(哈希),set(集合);zset(有序集合)。
  • 原子性,Redis的所有操作都是原子性的,要么成功,要么失败完全不执行。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
  • 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。
  • 支持持久化,主从复制,集群。

1.2 Redis的应用场景

  • 数据缓存(热数据):将一些在短时间内不会发生变化的数据,并且还要被频繁访问的数据保存在Redis中。从而提高我们的请求速度,降低数据库的读写次数。

  • 会话缓存:seccsion cache,保存web的会话信息。

  • 位操作:用于数据量上亿的场景下,例如几亿用户系统的签到,去重登录次数统计,某用户是否在线状态等等。

  • 消息队列:相当于消息系统,ActiveMQ,RocketMQ等工具类似,但是个人觉得简单用一下还行,如果对于数据一致性要求高的话还是用RocketMQ等专业系统。

  • 分布式锁:秒杀系统,全局增量ID的生产。

  • 最新列表:例如新闻列表页面最新的新闻列表,如果总数量很大的情况下,尽量不要使用select a from A limit 10这种low货,尝试redis的 LPUSH命令构建List,一个个顺序都塞进去就可以啦。不过万一内存清掉了咋办?也简单,查询不到存储key的话,用mysql查询并且初始化一个List到redis中就好了。

二 Redis安装

  • 官网下载redis安装包,这里我下载的是我目前最高的版本 redis-6.2.4.tar.gz

  • 解压(将redis解压到/usr/local/目录下)

    # 将redis解压到/usr/local/目录下
    tar -zxf redis-6.2.4.tar.gz -C /usr/local/
    
  • 安装

    # 进入到解压后的目录/usr/local/redis-6.2.4/
    cd /usr/local/redis-6.2.4/
    # 编译安装
    make
    make install
    # 修改配置文件redis.conf,当然你也可以直接手动去修改,为了方便一点这里直接用命令修改了
    # 把bind 127.0.0.1 改为 bind 0.0.0.0
    # daemonize no 改为 daemonize yes
    # logfile “” 改为logfile “/var/log/redis.log”
    sed -i "s/^bind 127.0.0.1/bind 0.0.0.0/g" redis.conf
    sed -i "s/^daemonize no/daemonize yes/g" redis.conf
    sed -i 's/^logfile \\"\\"/logfile \\"\\/var\\/log\\/redis.log\\"/g' redis.conf
    
  • 把redis做成服务,方便开机启动

    添加/etc/init.d/redis文件并且输入相应的内容,这里为了方便大家,我提供两种方式

    • 第一种,新建/etc/init.d/redis文件添加如下内容

      # /etc/init.d/redis 输入如下内容,保存退出(注意,EXEC,CLIEXEC几个参数的修改)
      vi /etc/init.d/redis 
      
      #!/bin/sh
      # chkconfig: 2345 10 90
      # description:redis
      # processname:redis
      # Simple Redis init.d script conceived to work on Linux systems
      # as it does use of the /proc filesystem.
      REDISPORT=6379
      EXEC=/usr/local/redis-6.2.4/src/redis-server
      CLIEXEC=/usr/local/redis-6.2.4/src/redis-cli
      PIDFILE=/var/run/redis_${REDISPORT}.pid
      CONF="/usr/local/redis-6.2.4/redis.conf"
      case "$1" in
          start)
              if [ -f $PIDFILE ]
              then
                      echo "$PIDFILE exists, process is already running or crashed"
              else
                      echo "Starting Redis server..."
                      $EXEC $CONF &
              fi
              ;;
          stop)
              if [ ! -f $PIDFILE ]
              then
                      echo "$PIDFILE does not exist, process is not running"
              else
                      PID=$(cat $PIDFILE)
                      echo "Stopping ..."
                      $CLIEXEC -p $REDISPORT shutdown
                      while [ -x /proc/${PID} ]
                      do
                          echo "Waiting for Redis to shutdown ..."
                          sleep 1
                      done
                      echo "Redis stopped"
              fi
              ;;
          status)
              if [ -f $PIDFILE ]
              then
                      echo "Redis running"
              else
                      echo "Redis stopped"
              fi
              ;;
          restart)
              if [ -f $PIDFILE ]
              then
                      PID=$(cat $PIDFILE)
                      echo "Stopping ..."
                      $CLIEXEC -p $REDISPORT shutdown
                      while [ -x /proc/${PID} ]
                      do
                          echo "Waiting for Redis to shutdown ..."
                          sleep 1
                      done
                      echo "Redis stopped"
                      echo "Starting Redis server..."
                      $EXEC $CONF &
              else
                      echo "Redis stopped"
                      echo "Starting Redis server..."
                      $EXEC $CONF &
              fi
              ;;
          *)
              echo "require start|stop|status|restart"
              ;;
      esac
      
    • 第二种,直接在命令行里面完成

      # 直接用命令行创建/etc/init.d/redis文件,注意我这里是redis-6.2.4,如果你的不是的话记得替换掉
      # 下面的命令你可以直接在命令行里面执行。自动生成/etc/init.d/redis,并且写入了相应的内容
      
      echo '#!/bin/sh' >/etc/init.d/redis
      echo '# chkconfig: 2345 10 90' >>/etc/init.d/redis
      echo '# description:redis' >>/etc/init.d/redis
      echo '# processname:redis' >>/etc/init.d/redis
      echo '# Simple Redis init.d script conceived to work on Linux systems' >>/etc/init.d/redis
      echo '# as it does use of the /proc filesystem.' >>/etc/init.d/redis
      echo 'REDISPORT=6379' >>/etc/init.d/redis
      echo 'EXEC=/usr/local/redis-6.2.4/src/redis-server' >>/etc/init.d/redis
      echo 'CLIEXEC=/usr/local/redis-6.2.4/src/redis-cli' >>/etc/init.d/redis
      echo 'PIDFILE=/var/run/redis_${REDISPORT}.pid' >>/etc/init.d/redis
      echo 'CONF="/usr/local/redis-6.2.4/redis.conf"' >>/etc/init.d/redis
      echo 'case "$1" in' >>/etc/init.d/redis
      echo '    start)' >>/etc/init.d/redis
      echo '        if [ -f $PIDFILE ]' >>/etc/init.d/redis
      echo '        then' >>/etc/init.d/redis
      echo '                echo "$PIDFILE exists, process is already running or crashed"' >>/etc/init.d/redis
      echo '        else' >>/etc/init.d/redis
      echo '                echo "Starting Redis server..."' >>/etc/init.d/redis
      echo '                $EXEC $CONF &' >>/etc/init.d/redis
      echo '        fi' >>/etc/init.d/redis
      echo '        ;;' >>/etc/init.d/redis
      echo '    stop)' >>/etc/init.d/redis
      echo '        if [ ! -f $PIDFILE ]' >>/etc/init.d/redis
      echo '        then' >>/etc/init.d/redis
      echo '                echo "$PIDFILE does not exist, process is not running"' >>/etc/init.d/redis
      echo '        else' >>/etc/init.d/redis
      echo '                PID=$(cat $PIDFILE)' >>/etc/init.d/redis
      echo '                echo "Stopping ..."' >>/etc/init.d/redis
      echo '                $CLIEXEC -p $REDISPORT shutdown' >>/etc/init.d/redis
      echo '                while [ -x /proc/${PID} ]' >>/etc/init.d/redis
      echo '                do' >>/etc/init.d/redis
      echo '                    echo "Waiting for Redis to shutdown ..."' >>/etc/init.d/redis
      echo '                    sleep 1' >>/etc/init.d/redis
      echo '                done' >>/etc/init.d/redis
      echo '                echo "Redis stopped"' >>/etc/init.d/redis
      echo '        fi' >>/etc/init.d/redis
      echo '        ;;' >>/etc/init.d/redis
      echo '    status)' >>/etc/init.d/redis
      echo '        if [ -f $PIDFILE ]' >>/etc/init.d/redis
      echo '        then' >>/etc/init.d/redis
      echo '                echo "Redis running"' >>/etc/init.d/redis
      echo '        else' >>/etc/init.d/redis
      echo '                echo "Redis stopped"' >>/etc/init.d/redis
      echo '        fi' >>/etc/init.d/redis
      echo '        ;;' >>/etc/init.d/redis
      echo '    restart)' >>/etc/init.d/redis
      echo '        if [ -f $PIDFILE ]' >>/etc/init.d/redis
      echo '        then' >>/etc/init.d/redis
      echo '                PID=$(cat $PIDFILE)' >>/etc/init.d/redis
      echo '                echo "Stopping ..."' >>/etc/init.d/redis
      echo '                $CLIEXEC -p $REDISPORT shutdown' >>/etc/init.d/redis
      echo '                while [ -x /proc/${PID} ]' >>/etc/init.d/redis
      echo '                do' >>/etc/init.d/redis
      echo '                    echo "Waiting for Redis to shutdown ..."' >>/etc/init.d/redis
      echo '                    sleep 1' >>/etc/init.d/redis
      echo '                done' >>/etc/init.d/redis
      echo '                echo "Redis stopped"' >>/etc/init.d/redis
      echo '                echo "Starting Redis server..."' >>/etc/init.d/redis
      echo '                $EXEC $CONF &' >>/etc/init.d/redis
      echo '        else' >>/etc/init.d/redis
      echo '                echo "Redis stopped"' >>/etc/init.d/redis
      echo '                echo "Starting Redis server..."' >>/etc/init.d/redis
      echo '                $EXEC $CONF &' >>/etc/init.d/redis
      echo '        fi' >>/etc/init.d/redis
      echo '        ;;' >>/etc/init.d/redis
      echo '    *)' >>/etc/init.d/redis
      echo '        echo "require start|stop|status|restart"' >>/etc/init.d/redis
      echo '        ;;' >>/etc/init.d/redis
      echo 'esac' >>/etc/init.d/redis
      

    修改 /etc/init.d/redis 文件权限

    chmod 755 /etc/init.d/redis
    
  • 添加redis服务

    # 添加到服务列表
    chkconfig --add redis
    # 设置开启启动
    chkconfig redis on
    
  • 启动redis

    # 启动redis
    # 如果报错,env: “/etc/init.d/redis”: 权限不够,记得修改/etc/init.d/redis文件的权限
    # 如果报错,/var/run/redis_6379.pid exists, process is already running or crashed,那么删掉 rm -rf /var/run/redis_6379.pid 在试下
    service redis start
    

三 Redis数据类型

Redis是key-value的存储服务器(数据结构服务器),Redis的keys是二进制安全的,这一意味着你可以用任何二级制序列作为key值。Redis的value更加的强大,它支持不同类型的值。你不必仅仅可以把字符串当作value对应的值。下列这些数据类型都可作为value的类型:

  • Strings:二进制安全的字符串。
  • Lists:按插入顺序排序的字符串元素的集合。他们基本上就是链表(linked lists)
  • Hashes:就是我们经常使用的Map。field和value都是字符串。
  • Sets:不重复且无序的字符串元素的集合。
  • Zsets(Sorted sets):类似Sets,但是每个字符串元素都关联到一个叫score浮动数值(floating number value)。里面的元素总是通过score进行排序,所以不同的是,它可以对元素进行排序。(比如你可能会问:给我前面10个或者后面10个元素)。
  • Bitmap(或者说 simply bitmaps):二进制数组。通过特殊的命令,我们可以将 String 值当作一系列 bits 处理,可以设置和清除单独的 bits,数出所有设为 1 的 bits 的数量,找到最前的被设为 1 或 0 的 bit,等等。Bit arrays属于Strings类型。
  • HyperLogLogs(基数):HyperLogLogs是一种基数估算算法,所谓基数估算,就是估算在一批数据中,不重复元素的个数有多少。
  • Geospatial(地理空间):Redis提供的地理空间以及索引半径查询的功能的数据结构。这在需要地理位置的应用上或许可以一展身手。

二进制安全的字符串:不仅可以保存文本,还可以保存任意格式的二进制数据。

Strings,Lists,Hashes,Sets,Zsets是Redis里面的五大基本数据类型。Bitmap,HyperLogLogs,Geospatial数据扩展数据类型。

3.1 Strings(二进制安全的字符串)

Redis Strings 二进制安全的字符串。意味着Redis的Strings可以包含任何数据。比如jpg图片或者序列化对象。Strings类型的值最大能存储512MB

3.1.1 Strings常用操作

# 字符串常用操作
set key value						# 设置字符串键值对
mset key value [key value ...]		# 批量存入字符串键值对
mget key [key ...]					# 批量获取字符串键值
setnx key value						# 存入一个不存在的字符串键值对


get key								# 获取一个字符串键值

del key [key ...]					# 删除一个键

expire key seconds					# 设置一个键的过期时间

# 原子加减操作
incr key							# 将key中存储的数字值加1
decr key							# 将key中存储的数字值减1
incrby key increment				# 将key中存储的数字值加increment
decrby key decrement				# 将key中存储的数字值加decrement
incrbyfloat key increment   		# 将key中存储的值加上指定的浮点数

apend key value 					# 在尾部追加

getrange key start end  			# 获取指定key中存储的value范围内的值
setrange key offset value  			# 设置指定key中存储的value从索引位置开始设置后面的值

strlen key 							# 返回指定key对应value的长度

bitcount key [start end] 			# 统计指定位区间上值为1的个数

getbit key offset  					# 指定key对应value,获取指定位置的二进制位的值
setbit key offset value 			# 指定key对应value,设置指定位置的二进制位的值

bitop operation destkey key [key ...] # 对一个或多个保存二进制位的字符串key进行位元操作,并将结果保存到destkey上,operation可以是AND、OR、NOT、XOR这四种操作中的任意一种

3.1.2 Strings应用场景

  • 存储对象:利用JSON强大的兼容性、可读性和易用性,将对象转换为JSON字符串,再存储在string类型中,是个不错的选择。
  • 分布式锁:String类型的setnx的作用是“当key不存在时,设值并返回1,当key已经存在时,不设值并返回0”,“判断key是否存在”和“设值”两个操作是原子性地执行的,因此可以用String类型作为分布式锁,返回1表示获得锁,返回0表示没有获得锁。
  • 计数器:String类型的incr和decr命令的作用是将key中储存的数字值加一/减一,这两个操作具有原子性,总能安全地进行加减操作,因此可以用String类型进行计数,如微博的评论数、点赞数、分享数,抖音作品的收藏数,京东商品的销售量、评价数等。

3.2 Lists(列表)

Redis Lists 列表,列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。Lists类型经常会被用于消息队列的服务,以完成多程序之间的消息交换。列表最多可存储 2 32 2^{32} 232-1 元素 (4294967295, 每个列表可存储40多亿)。Reids里面Lists是基于Linked Lists实现。这意味着即使在一个List中有数百万个元素,在头部或尾部添加一个元素的操作,其时间复杂度也是常数级别的。当然也有缺点,因为是基于Linked Lists实现的,所以访问元素就没那么快(数组的实现,访问元素是很快的)。

3.2.1 Lists常用操作

lpush key element [element ...]  		# 将一个或多个值插入到列表头部(最左边)
rpush key element [element ...] 		# 将一个或多个值插入到列表的尾部(最右边)
lrange key start stop 					# 返回列表中指定区间内的元素
lindex key index 						# 用于通过索引获取列表中的元素
lpop key [count] 						# 用于移除列表的第一个元素(左边),返回值为移除的元素
rpop key [count] 						# 用于移除列表的最后一个元素(右边),返回值为移除的元素
llen key 								# 返回列表的长度
lrem key count element 					# 根据参数 COUNT 的值,移除列表中与参数 element 相等的元素。count > 0 : 从表头开始向表尾搜索,移除与 element 相等的元素,数量为 COUNT ;count < 0 : 从表尾开始向表头搜索,移除与 element 相等的元素,数量为 COUNT 的绝对值;count = 0 : 移除表中所有与 VALUE 相等的值
blpop key [key ...] timeout 			# 移出并获取列表的第一个元素(左边), 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
brpop key [key ...] timeout 			# 移出并获取列表的最后一个元素(右边), 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
rpoplpush source destination 			# 用于移除列表的最后一个元素,并将该元素添加到另一个列表并返回
brpoplpush source destination timeout	# 从列表中取出最后一个元素,并插入到另外一个列表的头部; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

3.2.2 Lists应用场景

  • 消息队列。list类型的lpop和rpop能实现队列的功能。不过如果系统比较复杂要求比较高的话,还是推荐用Kafka,RabbitMQ等成熟的消息队列。
  • 文章列表或者数据分页展示的应用。比如,我们常用的博客网站的文章列表,当用户量越来越多时,而且每一个用户都有自己的文章列表,而且当文章多时,都需要分页展示,这时可以考虑使用redis的列表,列表不但有序同时还支持按照范围内获取元素,可以完美解决分页查询功能。大大提高查询效率。

3.3 Hashes(哈希)

Redis Hashes 哈希。Hash是由键值对组成的Map。是string 类型的 field 和 value 的映射表,Hash特别适合用于存储对象。每个 hash 可以存储 2 32 2^{32} 232-1 键值对(40多亿)。

3.3.1 Hashes常用操作

hset key field value [field value ...] 	# 用于为哈希表中的字段赋值
hmset key field value [field value ...] # 用于同时将多个 field-value (字段-值)对设置到哈希表中
hget key field 							# 用于返回哈希表中指定字段的值
hmget key field [field ...] 			# 用于返回哈希表中,一个或多个给定字段的值
hgetall key 							# 用于返回哈希表中,所有的字段和值
hdel key field [field ...] 				# 用于删除哈希表 key 中的一个或多个指定字段,不存在的字段将被忽略
hlen key 								# 用于返回hask里面指定key对应值的长度
hexists key field 						# 用于查看哈希表的指定字段是否存在
hkeys key 								# 用于获取哈希表中的所有域
hvals key 								# 返回哈希表所有的值
hincrby key field increment 			# 用于为哈希表中的字段值加上指定增量值
hincrbyfloat key field increment 		# 用于为哈希表中的字段值加上指定浮点数增量值
hsetnx key field value 					# 用于为哈希表中不存在的的字段赋值

3.3.2 Hashes应用场景

  • 存储对象。因为对象有多个属性,正好可以用Map表示。

3.4 Sets(集合)

Redis Set是string类型的无序集合。和列表一样,在执行插入和删除和判断是否存在某元素时,效率是很高的。集合最大的优势在于可以进行交集并集差集操作。Set可包含的最大元素数量是 2 32 2^{32} 232-1(4294967295)。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。

3.4.1 Sets常用操作

sadd key member [member ...] 			# 将一个或多个成员元素加入到集合中,已经存在于集合的成员元素将被忽略
smembers key 							# 返回集合中的所有的成员。 不存在的集合 key 被视为空集合
sismember key member 					# 判断成员元素是否是集合的成员
srem key member [member ...] 			# 用于移除集合中的一个或多个成员元素,不存在的成员元素会被忽略
scard key
srandmember key [count] 				# 用于返回集合中的一个随机元素
spop key [count] 						# 用于移除集合中的指定 key 的一个或多个随机元素,移除后会返回移除的元素
smove source destination member 		# 将指定成员 member 元素从 source 集合移动到 destination 集合
sdiff key [key ...] 					# 返回第一个集合与其他集合之间的差异
sdiffstore destination key [key ...] 	# 将给定集合之间的差集存储在指定的集合中。如果指定的集合 key 已存在,则会被覆盖
sinter key [key ...] 					# 返回给定所有给定集合的交集。 不存在的集合 key 被视为空集。 当给定集合当中有一个空集时,结果也为空集(根据集合运算定律)
sinterstore destination key [key ...] 	# 将给定集合之间的交集存储在指定的集合中。如果指定的集合已经存在,则将其覆盖
sunion key [key ...] 					# 返回给定集合的并集。不存在的集合 key 被视为空集
sunionstore destination key [key ...] 	# 将给定集合的并集存储在指定的集合 destination 中。如果 destination 已经存在,则将其覆盖

3.4.2 Sets应用场景

  • 随机展示。
  • 好友/关注/粉丝/感兴趣的人集合。(交集,并集,合集,差集的特性)。
  • 黑名单/白名单。通用需要判断用户是否在白名单或者黑名单中。

3.5 Zsets(Sorted sets有序集合)

Redis Zsets和 Sets一样也是string类型元素的集合,且不允许重复的成员。不同的是Zsets每个元素都会关联一个double类型的分数。Redis正是通过分数来为集合中的成员进行从小到大的排序。

3.5.1 Zsets常用操作

zadd key [NX|XX] [GT|LT] [CH] [INCR] score member [score member ...] 		# 用于将一个或多个成员元素及其分数值加入到有序集当中
zrange key min max [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES] 	# 返回有序集中,指定区间内的成员
zrangebyscore key min max [WITHSCORES] [LIMIT offset count] 				# 返回有序集合中指定分数区间的成员列表。有序集成员按分数值递增(从小到大)次序排列
zrem key member [member ...] 			# 删除指定的一个或多个成员
zcard key 								# 查看集合的成员个数
zincrby key increment member 			# 对指定元素的分数进行增减操作,负数为减,正数为加
zcount key min max 						# 用于计算有序集合中指定分数区间的成员数量
zrank key member 						# 获取指定成员的小标位置(排序)
zscore key member 						# 获取指定成员的分数
zrevrank key member 					# 返回有序集中成员的排名。其中有序集成员按分数值递减(从大到小)排序
zrevrange key start stop [WITHSCORES] 	# 倒序,从高到底排序输出指定范围的数据
zrevrangebyscore key max min [WITHSCORES] [LIMIT offset count] # 返回有序集中指定分数区间内的所有的成员。有序集成员按分数值递减(从大到小)的次序排列。具有相同分数值的成员按字典序的逆序(reverse lexicographical order )排列
zremrangebyrank key start stop 	# 根据坐标范围删除数据
zremrangebyscore key min max 	# 根据分数范围删除数据
zinterstore destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX] #计算给定的一个或多个有序集的交集,其中给定 key 的数量必须以 numkeys 参数指定,并将该交集(结果集)储存到 destination 
zunionstore destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX] # 计算给定的一个或多个有序集的并集,其中给定 key 的数量必须以 numkeys 参数指定,并将该并集(结果集)储存到 destination 

3.5.2 Zsets应用场景

  • 延时队列,zset会按score进行排序,如果score代表想要执行的时间戳。在某个时间将它插入zset集合中,它变会按照时间戳大小进行排序,也就是对执行时间前后进行排序。

  • 排行榜

  • 限流。滑动窗口是限流常见的一种策略。如果我们把一个用户的 ID 作为 key 来定义一个 zset ,member 或者 score 可以都为访问时的时间戳。我们只需统计某个 key 下在指定时间戳区间内的个数,就能得到这个用户滑动窗口内访问频次,与最大通过次数比较,来决定是否允许通过。

3.6 Bitmap(二进制位数组)

Redis Bitmap(二进制位数组)属于Strings类型的一个扩展功能。可以设置和清除单独的 bits,数出所有设为 1 的 bits 的数量,找到最前的被设为 1 或 0 的 bit,等等。

Bitmap有非常多的优点:

  1. 基于最小的单位bit进行存储,所以非常省空间。
  2. 设置时候时间复杂度O(1)、读取时候时间复杂度O(n),操作是非常快的。
  3. 二进制数据的存储,进行相关计算的时候非常快。
  4. 方便扩容

Bitmap缺点:

Redis中的bit映射被限制在512MB内,所以最大是 2 32 2^{32} 232位。

3.6.1 Bitmap常用操作

bitcount key [start end] 				# 统计指定位区间上值为1的个数

getbit key offset  						# 指定key对应value,获取指定位置的二进制位的值
setbit key offset value 				# 指定key对应value,设置指定位置的二进制位的值

bitop operation destkey key [key ...] 	# 对一个或多个保存二进制位的字符串key进行位元操作,并将结果保存到destkey上,operation可以是AND、OR、NOT、XOR这四种操作中的任意一种

3.6.2 Bitmap应用场景

  • 用户在线状态,用户id为偏移量offset,如果在线就设置为1,不在线就设置为0。
  • 统计活跃用户。
  • 用户签到。

3.7 HyperLogLogs(基数)

Redis HyperLogLogs HyperLogLogs是一种基数估算算法,所谓基数估算,就是估算在一批数据中,不重复元素的个数有多少。HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2 64 2^{64} 264 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

什么是基数:比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。

HyperLoglog是Redis新支持的两种类型中的另外一种(上一种是位图类型Bitmaps)。主要适用场景是海量数据的计算。特点是速度快。占用空间小。HyperLoglog在适用场景方面与Bitmaps方面有什么不同呢。我个人的理解是,Bitmaps更适合用于验证的大数据,比如签到,记录某用户是不是当天进行了签到,签到了多少天的时候。也就是说,你不光需要记录数据,还需要对数据进行验证的时候使用Bitmaps。HyperLoglog则用于只记录的时候,比如访问的uv统计。

3.7.1 HyperLogLogs常用操作

pfadd key element [element ...]   			# 添加指定元素到 HyperLogLog 中
pfcount key [key ...] 						# 返回给定 HyperLogLog 的基数估算值
pfmerge destkey sourcekey [sourcekey ...]	# 将多个 HyperLogLog 合并为一个 HyperLogLog

3.7.1 HyperLogLogs应用场景

  • UV(访客数),指定时间内的访客数量,使用HyperLogLogs就可以很方便的做到了,每个访客都有一个独立的id。访问一次则往HyperLogLogs里面加一个。

3.8 Geospatial(地理空间)

Redis Geospatial 是Redis提供的地理空间以及索引半径查询的功能的数据结构。这在需要地理位置的应用上或许可以一展身手。

Geospatial底层的实现原理其实就是Zsets!我们可以使用Zset命令来操作Geospatial。

3.8.1 Geospatial常用操作

geoadd key [NX|XX] [CH] longitude latitude member [longitude latitude member ...] # 添加一个或多个地理位置元素到一个key中 
geodist key member1 member2 [m|km|ft|mi] # 返回一个key中指定两个位置之间的距离
geohash key member [member ...] # 返回一个或多个位置元素的 Geohash 表示,Geohash是一种经纬度散列算法
geopos key member [member ...] # 返回一个或多个位置的经纬度信息,由于采用了geohash算法,返回的经纬度和添加时的数据可能会有细小误差
georadius key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key] [STOREDIST key]
 # 以给定位置为中心,半径不超过给定半径的附近所有位置
georadiusbymember key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key] [STOREDIST key] # 和GEORADIUS相似,只是中心点不是指定经纬度,而是指定已添加的某个位置作为中心
zrange key min max [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES] # 获得指定key中所有坐标信息
zrem key member [member ...] # 删除指定key下指定目标的数据

3.8.2 Geospatial应用场景

  • 朋友圈定位。
  • 附件的人。
  • 打车距离计算,两个位置之间的距离。

四 Redis持久化

Redis是一个内存数据库,数据保存在内存中,但是我们都知道内存的数据变化是很快的,也容易发丢失。幸好Redis还为我们提供了持久化的机制,分别是RDB(Rdis DataBase)和AOF(Apend Only File)。

4.1 RDB

RDB其实就是把数据以快照的形式保存在磁盘上。什么是快照呢,你可以理解成把当前时刻的数据拍成一张照片保存下来。

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。这也是Redis默认的持久化方式,这种方式是将内存中的数据以快照的形式写入到二进制文件中。默认的文件命名为dump.rdb。

  • 自动触发

    自动触发是由我们的配置文件来完成的。在redis.conf配置文件中,里面有如下配置:

    save: 这里是用来配置触发Redis的RDB持久化条件,也就是什么时候将内存中的数据保存到硬盘。比如“save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave。
    #save 900 1 	表示900秒内如果至少有1个key的值变化
    #save 300 10 	表示300秒内如果至少有10个 key的值变化
    #save 60 10000 	表示60秒内如果至少有10000个key 的值变化
    stop-writes-on-bgsave-error: 默认值为yes。当启用了RDB且最后一次后台保存数据失败,Redis是否停止接收数据。这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster)发生了。如果Redis重启了,那么又可以重新开始接收数据了
    rdbcompression: 默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储
    rdbchecksum: 默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能
    dbfilename: 设置快照的文件名,默认是 dump.rdb
    dir: 设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名
    
  • save触发方式

    save命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,知道RDB过程完成为止。执行完成时候如果存在老的RDB文件,就把新的替换掉旧的。我们的客户端可能都是几万或者几十万,这种方式显然是不可取的。

  • bgsave触发方式

    bgsave命令,Redis会在后台异步执行快照操作,快照的同事还可以相应客户端请求。bgsave操作就是fork操作创建子线程,RDB持久化过程由子进程负责,完成后自动接收。阻塞只发生在fork阶段,一般时间很短。基本上Redis内部所有的RDB操作都是采用的bgsave命令。

RDB优点

  • RDB文件紧凑,全量备份,非常适合用于进行备份和灾难恢复
  • 生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作
  • RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快

RDB缺点

  • RDB快照是一次全量备份,存储的是内存数据的二进制序列化形式,存储上非常紧凑。当进行快照持久化时,会开启一个子进程专门负责快照持久化,子进程会拥有父进程的内存数据,父进程修改内存子进程不会反应出来,所以在快照持久化期间修改的数据不会被保存,可能丢失数据

4.2 AOF

RDB全量备份总是耗时的,Redis提供了一种更加高效的方式AOF,工作机制很简单Redis会将每一个收到的写命令都通过write函数追加到文件中。通俗的理解就是日志记录。

AOF也有三种触发方式:always、everysec、no

appendfsync everysec  # always: 每修改同步同步,缺点就是IO开销比较大; everysec: 每秒同步:异步操作,每秒记录 如果一秒内宕机,有数据丢失; no: 从不同步

AOF优点

  • AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据
  • AOF日志文件没有任何磁盘寻址的开销,写入性能非常高,文件不容易破损
  • AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写
  • AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据

AOF缺点

  • 对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大
  • AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的
  • 以前AOF发生过bug,就是通过AOF记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来

五 Redis可能存在的问题

Redis经常会被我们用作缓存层。缓存层是为了缓解持久层数据库的压力而添加的一层保护层。如果数据在缓存(Redis)里面查询不到,我们就需要去持久层数据库里面查询。主要步骤如下:

  • 根据所传入的key去缓存层Redis里面查询对应的值。
  • 如果缓存层Redis里面查询到了,则直接返回。
  • 如果缓存层Redis里面没有查到,则到持久成数据库里面去查询。
  • 如果持久层数据库里面有查询到,则缓存到缓存层Redis里面,并且返回。
  • 如果持久层数据库里面没有查询到,则返回没有查询到。

下面我们针对Redis作为缓冲层使用过程中可能会出现的三个问题:缓存穿透,缓存击穿,缓存雪崩,做一个简单的了解。

5.1 缓存穿透

缓存穿透是指需要查询的key对应的数据,在缓存层Redis和持久层数据库里面都没有,但是用户又在不断的发起这个key对应的请求。比如正常情况下用户id一般都是大于0的。但是在某一时刻由于某些黑客的攻击,一直发送id < 0的请求,去查询用户信息。因为缓存层Redis里面没有,所有的请求都会打到持久层数据库中去。在这一时刻,会给持久层数据库造成很大的压力,此时缓存层Redis好像被“穿透”了一样,起不到任何作用。

缓存穿透的原因:

  • 自身业务代码或者数据出现问题。
  • 一些恶意攻击,爬虫等造成大量空命中。

缓存穿透的解决方法:

  • 接口校验:在正常业务流程中可能会存在少量访问不存在 key 的情况,但是一般不会出现大量的情况,所以这种场景最大的可能性是遭受了非法攻击。可以在最外层先做一层校验:用户鉴权、数据合法性校验等,例如商品查询中,商品的ID是正整数,则可以直接对非正整数直接过滤等等。

  • 缓存空对象:当访问缓存层和持久层数据库都没有查询到值的时候,可以将空值写进缓存。并且设置较短过期时间。

  • 布隆过滤器:使用布隆过滤器存储所有可能访问的key,不存在的key直接过滤掉,存在的key则在进一步查询。当布隆过滤器说某个值存在时,这个值可能不存在;当它说某个值不存在时,那这个值肯定不存在。

5.2 缓存击穿

缓存击穿是指某一个热点key(这个key非常的热门,很多人都需要对它进行查询),在缓存过期的一瞬间,同时有大量的请求打进来。因为这个key在缓存层的过期时间过了,所以这一瞬间所有的请求都打到了持久层数据库中,引起持久层数据库压力瞬间增大,造成持久层数据库过大的压力。(注意:是指同一个key,缓存过期的一瞬间有大量的请求打进来)

缓存击穿的原因:

  • 设置了过期时间的key,承载着高并发,是一种热点数据。从这个key过期到重新从MySQL加载数据放到缓存的一段时间,大量的请求过来。

缓存击穿的解决方法:

  • 设置热点数据永不过期。直接将缓存设置不过期。
  • 加互斥锁,同一个key的请求互斥。

5.3 缓存雪崩

缓存雪崩是指大量的热点key设置了相同的过期时间,导致缓存层Redis有大量的key在同一时刻全部失效,造成瞬间持久层数据库的压力过大。当然了如果在某一时刻Redis挂了也会出现缓存雪崩的情况。

缓存雪崩的原因:

  • 大量的热点key设置了相同的过期时间。
  • Redis崩溃了,彻底挂了。

缓存雪崩的解决办法:

  • 过期时间打散,缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  • 设置热点数据永不过期。
  • 如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
  • 数据预热:数据预热的含义就是在正式部署之前,我们先把可能的数据先预先访问一遍,这样部分可能大量访问的数据都会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

缓存击穿和缓存雪崩的区别在:缓存击穿是一个key的失效情况,而缓存雪崩是大批量key的失效情况。

六 Redis开发规范

6.1 键值设计

6.1.1 key设计

  • 可读性和可管理性:以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id。
  • 简洁性:保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视。
  • 不要包含特殊字符:不能包含空格、换行、单双引号以及其他转义字。

6.1.2 value设计

  • 拒绝大key:防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。

  • 选择合适的数据类型。

  • 控制key的生命周期:redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。

    项目里一般强制要求所有都设置过期时间,避免由于redis问题导致服务不可用。

6.2 Redis命令的使用

  • O(N)命令关注N的数量:例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
  • 禁用命令:禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。
  • 合理使用select命令:redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
  • 推荐使用批量操作命令提高效率:原生命令:例如mget、mset。 非原生命令:可以使用pipeline提高效率。
  • 不建议过多使用Redis的事务功能:Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。

6.3 Redis客户端的使用