2018-09-06-浅谈Redis与中间的一些设计策略

简介

Redis是一个开源的高性能键值对数据库,基于内存且可持久化的日志,所以通常也说内存数据库,提供多种键值数据类型能够适应不同的存储需求,所以这两块构成了Redis强大的基础,基于内存的高性能和支持多种数据类型的高可用。

可执行文件

  • redis-server:Redis服务器
  • redis-cli:Redis命令行客户端
  • redis-benchmark:Redis性能测试工具
  • redis-check-aof:AOF文件修复工具
  • redis-check-dump:RDB文件检查工具

启动和停止

  • 直接启动:适用开发环境
    redis-server,默认使用6379端口,可以通过--port参数指定端口号
  • 初始化脚本启动:生产环境
    Redis源代码目录utils文件中有一个redis_init_script的初始化脚本文件,使用脚本启动需要配置Redis的运行方式和持久化文件、日志文件的存储位置等。
  • 停止:强制终止进程可能会导致数据丢失,通常是像Redis发送SHUTDOWN命令,kill进程的PID效果一样。
    redis-cli SHUTDOWN

多数据库

Redis默认支持16个数据库,可以通过配置参数databases来修改。Redis不支持自定义数据库名字,也不支持为不同库设置访问密码,都是以编号来命名的0~15,所以数据库之间并不完全隔离。所以注意不同的应用要使用不同的Redis实例存储数据。

5种数据类型

  • [string] 字符串类型:最基本的数据类型,能存储任何形式的字符串,包括二进制数据,允许最大容量是512MB

  • [string] 常用命令

    • [set/get] 赋值与取值
    • [incr] 递增数字
    • [incrby] 增加指定的整数,通过参数增加指定的数值
    • [decr] 减少指定的整数
    • [incrbyfloat] 增加指定的浮点数
    • [append] 向尾部追加值
    • [strlen] 获取字符串长度
    • [mget/mset] 同时获得/设置多个键值
    • [getbit/setbit/bitcount/bitop] 位操作
  • [hash] 散列类型:字典结构以键值对的形式存储数据,可以包含232-1个字段

  • [hash] 常用命令

    • [hset/hget] 赋值与取值
    • [hexists] 判断字段是否存在
    • [hsetnx] 当字段不存在时赋值
    • [hincrby] 增加数字
    • [hdel] 删除字段
    • [hkeys/hvalus] 只获取字段名或字段值
    • [hlen] 获取字段数量
  • [list] 列表类型:存储一个有序的字符串列表,常用的操作是向列表两端添加元素,或者获得列表的某一个片段,一个列表最多容纳232-1个元素

  • [list] 常用命令

    • [lpush/rpush] 向列表两端增加元素
    • [lpop/rpop] 从列表两端弹出元素
    • [llen] 获取列表中元素的个数
    • [lrange] 获取列表片段
    • [lrem] 删除列表中指定的值
    • [lindex/lset] 获得/设置指定索引的元素值
    • [ltrim] 只保留列表指定片段
    • [linsert] 向列表中插入元素
    • [rpoplpush] 将元素从一个列表转到另一个列表
  • [set] 集合类型:和列表类型的区别就是无序且去重

  • [set] 常用命令

    • [sadd/srem] 增加/删除元素
    • [smembers] 获得集合中的所有元素
    • [sismember] 判断元素是否在集合中
    • [sdiff/sinter/sunion] 集合间运算
    • [scard] 获得集合中元素个数
    • [sdiffstore/sinterstore/sunionstore] 进行集合运算并将结果存储
    • [srandmember] 随机获得集合中的元素
    • [spop] 从集合中弹出一个元素
  • [sorted set] 有序集合类型:和集合类型的区别就在于有序

  • [sorted set] 常用命令

    • [zadd] 增加元素
    • [zscore] 获得元素的分数,也可做为判断是否存在
    • [zrange/zreverange] 获得排名在某个范围的元素列表
    • [zrangebyscore] 获得指定分数范围的元素
    • [zincrby] 增加某个元素的分数
    • [zcard] 获得集合中元素的数量
    • [zcount] 获得指定分数范围内的元素个数
    • [zremrangebyrank] 按照排名范围删除元素
    • [zremrangebyscore] 按照分数范围删除元素
    • [zrank/zrevrank] 获得元素的排名
    • [zinterstore] 计算有序集合的交集

其它应用场景

Redis不单单可以应用在缓存、数据库、消息队列等方面,根据本身的结构和设计在一些其它的方面也有很好的体现,这里举例几个场景。

  1. 统计活跃/在线用户:可以通过bitmap实现,能降低内存的空间,包括可以使用bitop、bitcount做计算统计。
  2. 队列:基于list类型,可以实现队列。
  3. 消息通知:利用Redis的pub/sub模式,实现订阅/通知,但是不支持分组。
  4. 延时队列:基于zset类型,因为zset是通过score属性来排序的,这样的话可以通过设置score为时间,轮询的方式来检测消费删除。
    redis的应用场景特别多,大多数应用场景是根据redis所支持的数据类型延伸出来的,包括还能通过redis的原子性操作、自增、过期时间等等实现分布式锁。

事务

Redis中的事务(transaction)是一组命令的集合,Redis保证一个事务中的所有命令要么都执行,要么都不执行。 Redis事务不支持关系数据库事务提供的回滚(rollback)功能。
过程:mulit-----待执行命令----exec

错误处理

  1. 语法错误:事务中只要有一个命令有语法错误,执行exec后就会直接返回错误,语法正确的命令也不会执行。
  2. 运行错误:执行时错误,如散列类型命令操作集合类型的键,这种错误执行前redis是无法发现的,所以出现这类错误,事务中其它命令会继续执行。

WATCH命令
监视一个(或多个) key ,监控一直持续到exec命令(事务中的命令是在exec之后才执行的,所以在mulit命令后可以修改watch监控的键值),如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

UNWATCH
取消 WATCH命令对所有 key 的监视。如果在执行 WATCH命令之后, exec命令或 discard命令先被执行了的话,那么就不需要再执行 UNWATCH了。
因为 exec命令会执行事务,而discard取消事务的同时也会取消对所有key的监视。

DISCARD
取消事务,放弃执行事务块内的所有命令。

EXPIRE生存时间

在Redis中可以使用expire命令设置一个键的生存时间,到时间后Redis会自动删除它。
命令格式:expire key seconds,seconds参数表示键的生存时间,单位为秒。
取消使用persist命令

访问频率限制

通过使用incr命令递增键值来描述次数,通过设置生存时间来自动删除,从而达到限制访问的频率,同时为了保证建立键和为键设置生存时间一起执行(避免某些问题导致中间推出,避免未能设置生存时间而变成永久键),可以把事务也引用进来。

//伪代码
isKeyExists = EXISTS limiting:IP
if isKeyExists is 1
   times = INCR limiting:ip
   if times >100
      print 访问频率超过限制
      exit
   else
      MULTI
      INCR limiting:ip
      EXPIRE keyName ,60
      exit

实现缓存

上面说到使用expire命令来设置了生存时间,就可以用来满足缓存实现的要求,但是为了提高缓存的命用率以及更合理的使用内存资源,需要让Redis按照一定的规则淘汰不需要的缓存键
淘汰规则

规则 说明
volatile-lru 使用LRU算法删除一个键(已设置生存时间的键)
allkeys-lru 使用LRU算法删除一个键
volatile-random 随机删除一个键(已设置生存时间的键)
allkeys-random 随机删除一个键
volatile-ttl 删除生存时间最近的一个键
noeviction 不删除,只返回错误

排序

前面说到了列表类型,有序集合类型都是满足有序的,这里讲讲怎么完成排序。
sort命令
sort命令可以对列表类型、集合类型和有序集合类型的键进行排序,还可以通过ALPHA参数实现按照字典顺序排列,DESC参数倒序等。
by参数
如果提供了by参数,sort命令将不再依据元素自身的值进行排序,而是对每个元素使用元素的值替换参考键中的第一个“*”并获取其值,然后依据该值对元素排序

## 键名->字段名
sort tag:posts by  post:*->time desc

get参数
get参数不影响排序,它的作用是使sort命令的返回结果不再是元素自身的值,而是get参数中指定的键值。
store参数
默认情况下sort会直接返回排序结果,如果希望保存结果,需要使用store参数
性能优化
sort是redis种最强大最复杂的命令之一,时间复杂度是O(n+mlogm),n表示排序的元素个数,m表示返回的元素个数。

  1. 尽可能减少待排序键中元素的数量,n尽可能小。
  2. 使用limit参数只获取需要的数据,m尽可能小。
  3. 如果要排序的数据数量较大,尽可能使用store参数将结果缓存。

消息

任务队列
说到队列通常使用的是Redis的列表类型,使用lpush和rpop命令实现队列。只需要让生产者将任务使用lpush命令加入到某个键中,另一边让消费者不断使用rpop命令从该键中取出任务即可。
优先级队列
可以通过brpop命令来解决,brpop可以同时接收多个键,意义是同时检测多个键,如果所有键都没有元素则阻塞,如果其中有一个键有元素则会从该键中弹出元素,如果多个键都有元素则按照从左到右的顺序去第一个键中的元素。借此特性可以实现区分优先级的任务队列。
发布/订阅
Redis提供了一组命令可以让开发者实现发布/订阅(publish/subscribe)模式。该模式包含了两种角色,发布者和订阅者;可以用来实现消息通知等。
订阅者可以订阅一个或者若干个频道(channel),而发布者可以向指定的频道发送消息,订阅该频道都会收到此消息。
管道
客户端和Redis使用TCP协议连接,无论是发送命令还是返回执行结果都需要经过网络传输,如果执行的命令较多,延时的累加起来对性能还是有一定影响。
管道(pipelining)可以一次性发送多条命令并在执行完后一次性将结果返回。

优化空间

  1. 精简键名和键值
  2. 内部编码优化

持久化

Redis支持两种方式的持久化,RDB方式、AOF方式,可单独使用,也可两者结合使用。

  1. RDB:通过快照(snapshotting)完成的,当符合一定条件时Redis自动将内存中的所有数据进行快照并存储到硬盘。由用户在配置文件中自定义,两个参数构成:时间和改动的键个数;指定时间内更改的键大于这个数值就会进行快照。默认采用这种持久化方式
    缺点是定时持久化
  2. AOF:通过appendonly参数开启,开启后每执行一条更改的命令,会将该命令写入硬盘的AOF文件,AOF方式是将执行过的写指令记录下来,在数据恢复时按照从前到后的顺序再将指令都执行一遍。AOF文件位置和RDB文件的位置相同,都是通过dir参数设置,将缓存内容同步到硬盘中默认情况下采用everysec规则,即每秒执行一次同步操作,always表示每次写入都执行同步,最安全也最慢,no表示不主动进行同步而交由操作系统(默认30秒一次),都是通过appendfsync参数设置
  3. 备份/恢复:通过save命令就能生成dump.rdb文件,如果要恢复的话,把文件copy到安装文件目录下,启动服务就可以了,可以通过config get dir查看目录地址。

多机数据库

主要是适用于大型分布式场景

  1. 复制:简洁来说就是主从复制,主数据库(master),从数据库(slave);主数据库可以进行读写操作,发生写操作时候同步给从数据库,从数据库一般只负责读。
  • 使用复制功能很简单,在配置文件中加入“slaveof 主数据 ip 主数据库端口”即可。
  • 当从数据库启动后,会向主数据库发送sync命令,主数据库接收后开始后台保存快照及期间接收的命令缓存起来,快照完成后,将快照文件和缓存的命令发送给从数据库。从数据库接收会载入快照文件及执行缓存命令。不支持断点续传。
  • 基于复制可以实现读写分离,以及可以通过主从切换来提高数据库的高可用。比如设置主禁用持久化,从开启持久化,主数据库挂掉后,可以把从数据库提升为主数据库,主数据库重启后设置为从数据库即可将数据同步过来。这样能保证数据不会丢失。
  1. sentinel:哨兵,Redis的高可用解决方案之一,由一个或多个sentinel实例组成的sentinel系统可以监视多个主服务器,以及这些主服务器下的所有从服务器,主服务器下线后自动将下属某个从服务器升级为新的主服务器。
  2. 集群:Redis提供的分布式数据库方案,集群通过分片(sharding)来进行数据共享,并提供复制和故障转移功能。
  • 客户端数据分片,基于客户端hash计算key的节点位置,可以降低服务端的集群复杂度,但是服务器集群资源难以管理,并且动态增删节难以实现,运维性差。
  • 服务端数据分配(路由),有服务端来计算key的位置,客户端发送请求到任意节点,服务端接收请求后会将查询请求发送到正确的节点上执行。开源方案:redis-cluster,内置数据自动分片机制,key的落点是通过一致性hash计算所在落点位置,以slot为单位,一共是一万六千多个slot,关于负载,节点之间是可以迁移数据的,以slot为单位的迁移,但不是自动,需要外部命令触发,关于高可用,集群中采用主从,主宕机后会把从提升为主,但如果没有设置从,会导致这个节点的读写服务不可用。


    路由方式
  • 使用代理方式(proxy),客户端通过连接代理,代理计算出节点位置,并把请求发送到对应的集群节点,但是代理需要维护集群的节点信息,虽然解耦了,但会增加结构的复杂度和运维的难度。

部分配置参数列表

参数名 默认值 使用config set设置
daemonize no no
pidfile /var/run/redis/pid no
port 6379 no
databases 16 no
save save 900 1/save 300 10/save 60 10000 yes
rdbcompression yes yes
rdbchecksum yes yes
dbfilename dump.rdb yes
dir ./ no
slaveof no
masterauth yes
slave-serve-stale-data yes yes
slave-read-only yes yes
requirepass yes
rename-command no
maxmemory yes
maxmemory-policy volatile-lru yes
maxmemory-samples 3 yes
appendonly no yes
appendfsync everysec yes
auto-aof-rewrite-percentage 100 yes
auto-aof-rewrite-min-size 64mb yes
lua-time-limit 5000 yes
slowlog-log-slower-than 10000 yes
slowlog-max-lan 128 yes
hash-max-ziplist-entries 512 yes
hash-max-ziplist-value 64 yes
list-max-ziplist-entries 512 yes
list-max-ziplist-value 64 yes
set-max-intset-entries 512 yes
zset-max-ziplist-entries 128 yes
zset-max-ziplist-value 64 yes

结语

有很多还不够完善,未完待续吧~~~
个人博客~
简书~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,863评论 2 89
  • 《Redis 入门指南》(第二版) 第一章 Redis 是什么 Redis (REmote Dictionary ...
    EdenPP阅读 67,280评论 3 10
  • 诗词创作——对仗浅谈 对仗:中古时诗歌格律的表现之一。对仗又称对偶、队仗、排偶。它是把同类或对立概念的词语放...
    ZGb5cb阅读 792评论 0 1
  • 我们永远都无法将感情圈定在自己预设的情景里,让故事按照我们所期待的轨迹发展。可世间没有哪一种感情是可以被设计的,唯...
    LL光虹阅读 438评论 0 1
  • 世界没有那么好,也并不是那么糟,我们要做的,只不过是在环境允许的情况下,善意地对所有人。在环境不允许的情况下,保...
    談談爱吃肉阅读 475评论 0 8