redis21个基础知识(关于Redis的知识点你都学会了吗)(1)

目录Redis的介绍、优缺点、使用场景

特性(主要有8个特性):

安装

wget http://download.redis.io/releases/redis-5.0.7.tar.gz tar -zxvf redis-5.0.7.tar.gz mv redis-5.0.7 /usr/local/redis 不需要先创建/usr/local.redis文件夹 cd /usr/local/redis make make install vi redis.conf * bind 0.0.0.0 开发访问 * daemonize yes 设置后台运行 redis-server ./redis.conf 启动 redis-cli 进入命令行,进行简单的命令操作 vi redis.conf > requirepass password 修改密码 redis-cli 再次进入cmd > shutdown save 关闭redis,同时持久化当前数据 redis-server ./redis.conf 再次启动redis redis-cli 进入命令行 > auth password 将redis配置成系统服务,redis/utils中自带命令,我们只需修改参数 /usr/local/redis/utils/./install_server.sh [root~ utils]# ./install_server.sh Welcome to the redis service installer Please select the redis port for this instance: [6379] 默认端口不管 Selecting default: 6379 Please select the redis config file name [/etc/redis/6379.conf] /usr/local/redis/redis.conf 修改配置文件路径 Please select the redis log file name [/var/log/redis_6379.log] /usr/local/redis/redis.log 修改日志文件路径 Please select the data directory for this instance [/var/lib/redis/6379] /usr/local/redis/data 修改数据存储路径 Please select the redis executable path [/usr/local/bin/redis-server] Selected config: Port : 6379 Config file : /usr/local/redis/redis.conf Log file : /usr/local/redis/redis.log Data dir : /usr/local/redis/data Executable : /usr/local/bin/redis-server Cli Executable : /usr/local/bin/redis-cli chkconfig --list | grep redis 查看redis服务配置项 redis_6379 0:off 1:off 2:on 3:on 4:on 5:on 6:off 服务名是redis_6379 复制代码

可执行文件说明

redis-cli -h x.x.x.x -p x 连接 auth "password" 验证密码 redis-cli --raw可以避免中文乱码 exit 退出 select index 切换到指定的数据库 keys * 显示所有key,如果键值对多不建议使用,keys会遍历所有key,可以在从节点使用;时间复杂度O(N) dbsize 算出所有的key的数量,只是数量;时间复杂度O(1) exists key key是否存在,存在返回1,不存在返回0;时间复杂度O(1) incr key 将key的值加一,是原子操作 decr key 将key的值加一,会出现复数,是原子操作 del key 删除key,删除成功返回1,失败返回0;时间复杂度O(1) expire key seconds 设置过期时间,过期之后就不存在了;时间复杂度O(1) ttl key 查看key剩余的过期时间,key不存在返回-2;key存在没设置过期时间返回-1;(TTL Time To Live) persist key 去掉key的过期时间,再查看ttl key,返回值是-1,表示key存在并且没有设置过期时间 type key 查看类型;时间复杂度O(1) config get * 获取配置信息 set key value插入值 sadd myset 1 2 3 4 插入set get key获取值 del key删除key cat redis.conf | grep -v "#" | grep -v "^$" 查看配置文件,去除所有的#,去除所有的空格 setnx key value #key不存在,才设置 set key value xx #可以存在,才设置 set key value [exporation EX seconds | PX milliseconds] [NX|EX] mget key1 key2 key3 批量获取 1次mget=1次网络时间 n次命令时间;时间复杂度O(n) mset key1 value1 key2 value2 批量插入;时间复杂度O(n) n次get = n次网络时间 n次命令时间,mget一次就能完成,省去大量的网络时间 getset key newvalue # set key newvalue并返回旧的value append key value #将value追加到旧的value strlen key #获取value的长度,中文占2个字节 incrbyfloat key 3.5 #增加key对应的值 set/get/del, incr(自增1)/decr(自减1)/incrby(incrby key n自增n)/decrby getrange key start end #获取value从start到end的值 setrange key index value #设置指定下标为一个新的值 hset key field value #给key的field设置值 hget key field #获取key的field的值 hdel key field #删除key的field的值 hgetall key #获取key的所有值 hexists key field # 判断key的field是否存在 hlen key #获取key field的数量 hmset key field1 value1 field2 value2 hmget key field1 field2 hsetnx/hincrby/hdecry/hincrbyfloat lpush key value1 value2...valueN #从左边插入 rpush key value1 value2...valueN #从右边插入 linsert key before|after value newValue rinsert key before|after value newValue lpop key #从左边弹出一个item rpop key #从右边弹出一个item lrem key count value #若count等于0或者不填,表示删除所有的value值相等的item;若count>0,表示从左到右删除最多count个value相等的item;若count<0,表示从右到左,删除最多Math.abs(count)个value相等的项 ltrim key start end #按照索引范围修剪列表,可以用来慢删除,因为全删除可能会阻塞redis lrang key start end #获取key中从start到end的值 lindex key index #取第index的值 llen key #算出列表的长度 lset key index newValue #修改index的值为newValue blpop key timeout #lpop阻塞版本,timeout是阻塞时间,timeout=0表示死等,lpop会立马返回,有时候数据更新不那么及时,或者消息队列中消息未及时处理,我们可以使用这个 brpop key timeout lpush LPOP = STACK lpush RPOP = QUEUE lpush ltrim = 有序的集合 lpush rpop = 消息队列 sadd key value #不支持插入重复元素,失败返回0 srem key element #删除集合中的element元素 smembers key #查看集合元素 sinter key1 key2 #取出相同:交集 sdiff key1 key2 #取出key1中key2没有的元素:差集 sunion key1 key2 #取出二者所有的元素:并集 sdiff|sinter|sunion store key #将结果存到key中,有时候计算一次耗时 scard key #计算集合大小 sismember key element #判断element是否在集合中 srandmember #返回所有元素,结果是无序的,小心使用,可能结果很大 smembers key #获取集合中的所有元素 spop key #从集合中随机弹出一个元素 scan SADD = Tagging SPOP/SRANDMEMBER = Random item SADD SINTER = Social Graph zadd key score element #添加score和element O(logN): 使用xx和跳表的数据结构 zrem key element #删除元素 zscore key element #返回元素的分数 zincrby key increScore element #增加或减少元素分数 zcard key #返回元素的总个数 zrank key element #获取element的排名 zrange key start end [withscores] #返回指定索引范围内的升序元素 zrangebyscore key minScore maxScore [withscore] #返回分数在minScore和maxScore之间的元素 zcount key minScore maxScore #返回有序集合内在指定分数范围内的个数 zremrangebyrank key start end #删除指定排名内的元素 zremrangebyscore key minScore maxScore #删除指定分数内的元素 zrevrang/zrevrange/集合间的操作zsetunion info replication 查看分片,能够获取到主从的数量和状态 config get databases 获取所有数据库 复制代码

数据结构和内部编码

String

hash

list

set

zset

Redis客户端: Java的Jedis(Socket通信),Python的redis-py

瑞士军刀

慢查询

  • 生命周期

两点说明:

  1. 慢查询发生在第3阶段,比如keys *等这些需要扫描全表的操作
  2. 客户端超时不一定慢查询,但慢查询是客户端超时的一个可能因素

两个配置

  • slowlog-log-slower-than=n(微秒):命令执行时间超过x微秒,会被丢到一个固定长度的慢查询queue中;n<0表示不配置
  • slowlog-max-len: 先进先出的队列,固定长度,保存在内存中(重启redis会消失)

配置方法

默认值

  • config get slowlog-max-len=128
  • config get slowlog-log-slower-than=10000

修改配置文件重启

动态配置

  • config set slowlog-max-len 1000
  • config set slowlog-log-slower-than 1000

常用命令

  • slowlog get [n]:获取慢查询队列
  • slowlog len: 获取慢查询队列的长度
  • slowlog reset: 清空慢查询队列

运维经验

  • slowlog-max-len不要设置过大,默认10ms,通常设置1ms,根据QPS来设置
  • slowlog-log-slower-than不要设置过小,通常设置1000左右
  • 定期持久化慢查询

pipeline流水线(批量操作)

当遇到批量网络命令的时候,n次时间=n次网络时间 n次命令时间。举个例子,北京到上海的距离是1300公里,光速是3万公里/秒,假设光纤传输速度是光速的2/3,也就是万公里/秒,那么一次命令的传输时间是 1300/20000*2(来回)=13毫秒, 什么是pipeline流水线,1次pipeline(n条命令)=1次网络时间 n次命令时间;pipeline命令在redis服务端会被拆分,因此pipeline命令不是一个原子的命令。注意每次pipeline携带数据量;pipeline每次只能作用在一个Redis节点上;M操作和pipeline的区别,M(mset)操作是redis的原生命令,是原子操作,pipeline不是原子操作

for(int i = 0; i < 10000; i >) { jedis.hset(key, field, value); //1万次hset差不多要50秒 for(0->100) { Pipeline pipeline = jedis.pipelined(); for(0->100) { pipeline.hset(key,field,value); } pipeline.syncAndReturnAll(); //拆分100次,每次100个命令,大概需要0.7秒 } 复制代码

发布订阅:类似生产者消费者模型

  • 角色:发布者(publisher),频道(channel),订阅者(subscriber); 发布者将消息发布到频道中,订阅者订阅相关的频道;
  • API: publish/subscribe/unsubscribe
  • publish channel message : publish sohu:tv “hello world”
  • subscribe sohu:tv
  • unsubscribe [channel]
  • psubscribe [pattern] #订阅模式 sohu*

bitmap:位图:数据量很大的时候节省存储内存,数据量小了,不节省

hyperloglog(算法,数据结构):

  • 极小空间完成独立数量统计,本质是个string
  • api: pfadd key element[s]:向hyperloglog添加元素 pfcount key[s]:计算hyperloglog的独立总数 pfmerge key1 key2合并

GEO: 3.2提供的用于计算地理位置信息;数据类型是zset,可以使用zset的删除命令

  • 使用场景:微信摇一摇看附近好友
  • api:
  • geo key longitude latitude member #增加地理位置信息
  • geopos key member[n] #获取地理位置信息
  • geodist key member1 membe2 [unit] m米 km千米 mi英里 ft尺 获取两地位置的距离
  • georadius #算出指定范围内的地址位置信息的集合,语法复杂了点
  • 总结下Redis数据结构和类型的常见用法

类型

简介

特性

使用场景

String

二进制安全

可以包含任何数据,比如JPG图片或者序列化的对象,一个键最大能存储512M

Hash

键值对集合,即编程中的Map

适合存储对象,并且可以向数据库中那样update一个属性(Memcache中需要将字符串反序列化成对象之后再修改属性,然后序列化回去)

存取/读取/修改 用户信息

List

双向链表

增删快,提供了操作某一元段元素的API

1. 最新消息,按照时间线显示2. 消息队列

Set

哈希表实现,元素不重复

添加/删除/修改的复杂度都是O(1),为集合提供求交集/并集/差集的操作

1. 打label/tag,如文章2. 查找共同好友3. 抽奖系统

Zset

将Set中的元素增加一个double类型的权重score,按照score排序

数据插入集合就好序了

排行榜

Hyperloglog

本质是string

极小空间完成独立数据量统计

统计基数,不完全正确

GEO

数据类型是zset

存储地理位置信息,并提供计算距离等操作

微信摇一摇查看附近好友

Bitmap

位图

数据量很大的时候节省存储内存,数据量小了不节省

  • String 简单动态字符串 Simple Dynamic String, SDS

Redis没有直接使用C语言的传统字符串表示,而是自己构建了一种名为简单动态字符串(Simple Dynamic String, SDS)的抽象类型,并将SDS用作Redis的默认字符串表示。 每个sds.h/sdshdr结构表示一个SDS值: struct sdshdr { int len; // 记录buf数组中已经使用的字节数量 int free; // 记录buf数组中未使用字节的数量 char buf[]; // 字节数组,用于保存字符串。SDS遵循C字符串以空字符结尾的惯例 } 复制代码

Redis持久化
  • 持久化的作用:redis所有数据保存在内存中,对数据的更新将异步地保存到磁盘上。
  • 主流数据库持久化实现方式:快照(MySQL Dump/Redis RDB),写日志(MySQL Binlog/Redis AOF)

RDB:

  • 创建RDB文件(二进制文件)到硬盘中,启动后载入RDB文件到内存

三种触发机制

  • save(同步) - 会产生阻塞
  • 文件策略:如存在老的RDB文件,新的替换老的,新的会先生成到一个临时文件
  • bgsave(异步) - 不会阻塞
  • 客户端执行bgsave之后,redis会使用linux的一个fork()命令生成主进程的一个子进程(fork的操作会执行一个内存页的拷贝,使用copy-on-write策略),子进程会创建RDB文件,创建完毕后将成功的消息返回给redis。fork()出来的子进程执行快的话不会阻塞主进程,否则也会阻塞redis,阻塞的实际点就是生成出来这个子进程。由于是异步,在创建的过程中还有其他命令在执行,如何保证RDB文件是最新的呢?在数据量大的时候bgsave才能突出优点。

命令savebgsaveIO类型同步异步阻塞是是(阻塞发生在fork子进程复杂度O(n)O(n)优点不会消耗额外内存不阻塞客户端命令缺点阻塞客户端命令需要fork,消耗内存

  • 自动触发:多少秒内有多少changes会异步(bgsave)生成一个RDB文件,如60秒内有1W条changes,默认的规则,可以改;不是很好吧,无法控制频率;另外两条是900秒内有1条changes, 300秒内有10条changes;

配置

  • dbfilename dump.rdb
  • dir ./
  • stop-writes-on-bgsave-error yes 当bgsave发生错误是停止写RDB文件
  • rdbcompression yes 采用压缩格式
  • rdbchecksum yes 采用校验和

其他不能忽视的点:

全量复制;debug reload;shutdown save会执行rdb文件的生成

AOF:

  • RDB现存问题:耗时,耗性能(fork,IO),不可控(突然宕机)
  • AOF:redis中的cmd会先刷新到缓冲区,然后更具配置AOF的策略,异步存追加到AOF文件中,发生宕机后,可以通过- AOF恢复,基本上数据是完整的
  • AOF的三种策略(配置的三种属性)
  • always:来一条命令写一条;不丢失数据,IO开销较大
  • everysec:每秒把缓冲区fsync到AOF文件;丢1秒数据
  • no:操作系统决定什么时候把缓冲区同步到AOF就什么时候追加;不用配置,但是不可控,取决于操作系统

AOF重写

  • 如果AOF文件很大的话,恢复会很慢,AOF的重写是优化一些命名,使其变成1条,对于过期数据没必要Log,本质是把过期的没有用的,重复的过滤掉,以此减少磁盘占用量,加速恢复。极端的例子,1亿次incr,实际只需要set counter n就够了
  • 重写的两种方式
  • bgrewriteaof:异步执行,redis fork出一个子进程,然后进行AOF重写
  • AOF重写配置
  • auto-aof-rewrite-min-size: AOF文件到达多大的时候才开始重写
  • auto-aof-rewrite-percentage: AOF文件的增长率到达了多大才开始重写

统计

  • aof_current_size AOF当前尺寸 字节
  • aof_base_size AOF上次重启和重写的尺寸 字节,方便自动重写判断

重写触发机制(同时满足如下两条)

  • aof_current_size > auto-aof-rewrite-min-size
  • (aof_current_size - aof_base_size) / aof_base_size > auto-aof-rewrite-percentage

其他配置

  • appendonly yes
  • appendfilename “”
  • appendfsync everysec
  • dir /xx
  • no-appendfsync-on-rewrite yes AOF在重启之后恢复,要权衡是否开启AOF日志追加的功能,这个时候IO很大,如果设置为yes,也就意味着在恢复之前的日志数据会丢失

RDB & AOF最佳策略:RDB优先于AOF先启用

  • RDB:建议关掉,集中管理,在从节点开RDB
  • AOF:建议开启,每秒刷盘
  • 最佳策略:小分片(log文件分片)

常见问题

  • fork操作:是一个同步操作,做一个内存页的拷贝;与内存量息息相关,内存越大,耗时越长;执行info命令,有个latest_fork_usec的值,看下上次fork执行耗时
  • 进程外开销:
  • CPU:RDB AOF文件生成,属于CPU密集型操作(不要和CPU密集型应用部署在一起,减少RDB AOF频率);内存:fork内存开销;硬盘:IO开销大,选用SSD磁盘
  • AOF追加阻塞:主线程将命令刷到AOF缓冲区,同步线程同步命令到硬盘,同时主线程会对比上次fsync的时间,如果大于2秒就阻塞主线程,否则不阻塞,主线程这么做是为了达到每秒刷盘的目的,让子线程完成AOF,以此来达到数据同步。AOF发生阻塞怎么定位:redis日志/info persistence(aof_delayed_fsync累计阻塞次数,是累计,不好分清什么时候发生阻塞)
  • 单机多实例部署
高可用Redis主从复制
  • 主从复制:单机故障/容量瓶颈/QPS瓶颈;一个master可以有多个slave,一个slave只能有一个master,数据必须是单流向,从master流向slave

复制的配置:

  • 使用slaeof命令,在从redis中执行slave masterip:port使其成为master的从服务器,就能从master拉取数据了;执行slaveof no one清除掉不成为从节点,但是数据不清楚;
  • 修改配置, slaveof ip port / slave-read-only yes(从节点只做都操作);配置要更改的话,要重启,所以选择的时候谨慎

全量复制

  • run_id(使用info server可以看到run_id),重启之后run_id就没有了,当从服务器去复制主服务器,主服务器run_id会在从服务器上做一个标识,当从服务器发现主服务器的run_id发生了变化,说明主服务器发生了变化(重启或者什么的),那么从服务器就要把主服务器的数据都同步过来
  • 偏移量:部分复制中的一个依据,后面说
  • 解析下上面的全量复制的过程,slave向master发送psync的命令要去master全量复制数据(PSYNC ,其中?表示我不知道master的runId啊,第一次连嘛,-1表示我都要,这时候slava咱啥也不知道),master大人收到了小弟的请求之后,大方的把自己的runId/offset发了过去,小弟收到后先存下来;在master大人把自个的信息发给小弟之后,立马投入了创建快照RDB的工作,一个bgsave命令立马开工,RDB生产了就发给slave;咦,细心的我们发现你这不对啊,你master创建快照到创建完成这之间新增的数据咋办,master吭吭了两声,我在开始快照的那一刻,后期的所有写命令都额外往buffer中存了一份,来保证我给你的是完整的,当我发送完RDB之后,立马给你发buffer;slave小弟内心对master大人产生了膜拜之情,收到了RDB/buffer之后,先把自己的老数据flush掉,然后load RDB,把最新的buffer刷一遍,分分钟让自己向master看齐。
  • 开销:bgsave时间, RDB文件网络传输时间,从节点清空数据时间,从节点加载RDB的时间,可能的AOF重写时间
  • 解释下上面的部分复制的过程,当遇到网络抖动,那这段时间内数据在slave上就会发生丢失,那么这些数据slave是不知道的,在2.8之前redis会重新做一次全量复制,但是很显然这样做开销很大,2.8之后提出部分复制的功能;当matster发现slave连接不上的时候,master在进行写操作的时候,也会往缓冲区写,等到下一次slave连上之后,slave会发送一条pysnc {offset}{runId}的命令,其中offset是slave自己的,相当于告诉master我的偏移量是多少,master判断slave的offset在缓冲区内(缓冲区有start/end offset)就向slave发送continue命令,然后把这部分数据发送给slave;当master发现slave这个offset偏移量很大的时候,也就意味着slave丢失了很多数据,那么就进行一次全量复制

故障处理:

  • master/slave宕机的情况,主从模式没有实现故障的完全自动转移
  • 常见问题:
  • 读写分离:读流量分摊到从节点,可能遇到复制数据延迟,也可能读到过期的数据,从节点故障怎么办
  • 主从配置不一致:主从maxmemory不一致,可能会丢失数据;主从内存不一致
  • 规避全量复制:第一次不可避免;小主节点,低峰处理(夜间);主节点重启后runId发生了变化规避复制风暴
  • 单机主节点复制风暴,如果是1主N从,当master重启之后,所有的slave都会发生全量复制,可想而知这样非常容易造成redis服务的不可用
Redis事务

Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:

  • 批量操作在发送 EXEC 命令前被放入队列缓存。
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
  • Redis事务从开始到执行会经历以下三个阶段:开始事务 -> 命令入队 -> 执行事务。单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。这是官网上的说明 From redis docs on transactions: It’s important to note that even when a command fails, all the other commands in the queue are processed – Redis will not stop the processing of commands.
  • Redis 通过监听一个 TCP 端口或者 Unix socket 的方式来接收来自客户端的连接,当一个连接建立后,Redis 内部会进行以下一些操作:
  • 首先,客户端 socket 会被设置为非阻塞模式,因为 Redis 在网络事件处理上采用的是非阻塞多路复用模型。
  • 然后为这个 socket 设置 TCP_NODELAY 属性,禁用 Nagle 算法
  • 然后创建一个可读的文件事件用于监听这个客户端 socket 的数据发送
  • Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应。管道技术最显著的优势是提高了 redis 服务的性能。

Redis 分区

  • 分区是分割数据到多个Redis实例的处理过程,因此每个实例只保存key的一个子集。

分区的优势:

  • 通过利用多台计算机内存的和值,允许我们构造更大的数据库。
  • 通过多核和多台计算机,允许我们扩展计算能力;通过多台计算机和网络适配器,允许我们扩展网络带宽。

分区的不足:

  • 涉及多个key的操作通常是不被支持的。举例来说,当两个set映射到不同的redis实例上时,你就不能对这两个set执行交集操作。
  • 涉及多个key的redis事务不能使用。
  • 当使用分区时,数据处理较为复杂,比如你需要处理多个rdb/aof文件,并且从多个实例和主机备份持久化文件。
  • 增加或删除容量也比较复杂。redis集群大多数支持在运行时增加、删除节点的透明数据平衡的能力,但是类似于客户端分区、代理等其他系统则不支持这项特性。然而,一种叫做presharding的技术对此是有帮助的。
  • 分区类型:Redis 有两种类型分区。 假设有4个Redis实例 R0,R1,R2,R3,和类似user:1,user:2这样的表示用户的多个key,对既定的key有多种不同方式来选择这个key存放在哪个实例中。也就是说,有不同的系统来映射某个key到某个Redis服务,关注 转发后,私信【Redis】获取300多页的Redis实战学习笔记。

范围分区

  • 最简单的分区方式是按范围分区,就是映射一定范围的对象到特定的Redis实例。比如,ID从0到10000的用户会保存到实例R0,ID从10001到 20000的用户会保存到R1,以此类推。这种方式是可行的,并且在实际中使用,不足就是要有一个区间范围到实例的映射表。这个表要被管理,同时还需要各 种对象的映射表,通常对Redis来说并非是好的方法。

哈希分区

  • 另外一种分区方法是hash分区。这对任何key都适用,也无需是object_name:这种形式,像下面描述的一样简单:用一个hash函数将key转换为一个数字,比如使用crc32 hash函数。对key foobar执行crc32(foobar)会输出类似93024922的整数。对这个整数取模,将其转化为0-3之间的数字,就可以将这个整数映射到4个Redis实例中的一个了。93024922 % 4 = 2,就是说key foobar应该被存到R2实例中。注意:取模操作是取除的余数,通常在多种编程语言中用%操作符实现。
,