Redis 简介
- 非关系数据库
- 可以存储key和5种不同类型的值Value
- 可以将存储在内存中的数据持久化到硬盘
- 可以使用复制特性来扩展读性能
- 可以使用客户端分片来扩展写性能(线性提升)
- 客户端分片:基于hash或者包含键的Id将数据存储到多台机器,也可以从多台机器里面获取数据,这种办法在处理某些问题时可以获得线性级别的性能提升。
- redis 不支持嵌套结构特性(如果需要可以通过合理组织命名空间一定程度上行模拟。)
和memcache对比
- 和memcached的对比
- 都可用于存储键值对
- 性能相差无几
- 部署成本接近(要求越高memcache越便宜)
- redis集群功能受限制比较严重,大规模应用没有明显的优势
- redis单机功能完爆memcache
- redis单机很难升级(一般都是加机器做分片或者弄主从,升级到集群版依赖历史代码,集群环境下很多命令不支持,比主从的要求苛刻很多)
- Redis可以以两种方式进行持久化
- Redis除了存储字符串之外还可以存储其他4种结构
- 结论:redis可以作为主数据库使用,又可以作为其他存储系统的辅助数据库
阿里云 redis 标准版 的使用场景
- 对 Redis 协议兼容性要求较高的业务
- 标准版完全兼容 Redis 协议,业务可以平滑迁移。
- Redis 作为持久化数据存储使用的业务
- 标准版提供持久化机制及备份恢复机制,极大的保证数据可靠性。
- 单个 Redis 性能压力可控
- 由于 Redis 原生采用单线程机制,性能在10wQPS以下的业务建议使用。如果需要更高的性能要求,请选用集群版本。
- Redis 命令相对简单,排序、计算类命令较少
- 由于 Redis 的单线程机制,CPU会成为主要瓶颈。如排序、计算类较多的业务建议选用集群版配置。
持久化选项(两种方式)
- 时间点转储(快照方式)
- 指定时间段内有指定数量的写操作(bgsave)
- 使用(SAVE/BGSAVE)两者之一
- 关闭(SHUTDOWN)时会执行阻塞save
- 从服务器开始sync命令时会执行bgsave
- 只追加写入(AOF)
- 将所有修改了数据库的命令都写入一只追加的文件里面,用户可以根据数据的重要程度,将只追加的文件里面,用户可以根据数据的重要程度将只追加写入设置为:从不同步、每秒同步一次、或者每写入一次命令就同步一次。
两种方法可以都用也可以都不用很随意
bgsave的创建子进程速度非常快(几GB毫秒级,取决于剩余内存,就是有没有使用虚存),慢的是存到硬盘上
AOF的问题是AOF文件会比较大,所以需要执行BGREWRITEAOF来压缩AOF文件的体积,这个命令通过移除冗余命令来实现。
主从复制
- 执行复制的从服务器会链接上主服务器,接受主服务器发送的整个数据库的初始副本;然后主服务器执行的写命令都会发送给从服务器去执行,从而实时的更新从服务器的数据集。
从服务器在执行同步时,会清空自己的所有数据
Redis不支持主主复制(Redis cluster)
字符串处理
- 长度
- 整数:和系统的长整型相同(32/64位有符号整数)
- 浮点数:双精度浮点数(doubule)
- INCR/DECR
- 如果一个值可以被解释为整数或者浮点数那么redis允许用户执行这些命令,反之则会报错
- 对一个不存在的键执行INCR那么redis会把这个键的值当成0来处理
不常用但很有用的命令
- 字符串
SETNX、APPEND、GETRANGE、SETRANGE、GETBIT、SETBIT、BUTCOUNT、BITOP
- 链表
LINDEX、LRANGE、LTRIM
- 集合
SRANDOMMEMBER、SPOP、SMOVE
- 散列
HKEYS、HVALS
- 有序集合
有序集合并运算比较特殊可以用来解决一些权重问题:ZINTERSTORE、ZUNIONSTORE
- SORT
根据给定选项,对输入列表、集合或者有序集合进行排序,然后返回存储排序的结果
- KEY相关命令
命令 | 功能 |
---|---|
PERSIST | 移除键的过期时间 |
[P]TTL | 查看还有多久过期 |
[P]EXPIRE | 设置键的过期时间 |
[P]EXPIREAT | 设置键的过期时间为指定时间戳 |
事务和流水线
- Redis 事务以MULT开始以exec结束,在EXEC执行之前不会执行任何实际的操作,所以用户没有办法根据读取到的数据来做决定。
- 使用WATCH/DISCARD(UNWATCH)的乐观锁来避免并发环境下数据出错
为什么redis没有实现典型的悲观锁功能?
悲观锁持有锁的客户端运行越慢,等待解锁的客户端被阻塞的时间就越长。
reids的setnx命令配合lua脚本可以很方便的实现高性能的分布式悲观锁。
- 非事务流水线:multi/exec会保证事务原子性,而这也是有开销的,redis会为每个客户端创建一个任务队列处理事务,也会消耗资源。常用的客户端都封装了一种pipeline方式,来处理一个命令的执行结果不会影响另一个命令的输入的场景。
redis里就没有pipeline这个命令,只是各种redis客户端实现的一种方式(打个比方Yii2默认的reids客户端就没实现这个功能。)
典型实践
- 分布式锁:悲观锁,消除竞争条件
- 公平计数信号量:限制资源(API)的并发数量(通过计数器替换时间戳作为信号量分值)
计数器信号量和锁的区别在于客户端获取锁失败时一般会等待,而计数器信号量会选择直接返回错误
- 任务队列:
- 优先级队列:blpop命令支持多个列表作为参数并按照顺序弹出数据,实现很简单(ResQueue)
- 延迟任务队列(3种简单实现方式):
- 在任务信息中包含任务的额执行时间,如果时间没到呢,短暂等待把任务重新推入队列里面
- 使用等待列表记录需要在未来执行的任务,并在每次进行循环的时候,检查并执行已经到期的任务
- 把所有需要在未来执行的任务放有序集合里,并将执行时间设置为分值,扫描并移除合适的任务,并添加到合适的任务队列。
Redis优化
降低内存占用
降低redis内存的占用有助于减少创建快照和加载快照所需的时间、缩短从服务器进行同步所需要的时间(同步初始化那步的时间,主从同步靠的是发送命令也就是不能降低delay)
- 使用短结构来更高效的表示数据
- 使用分片技术将体积较大的结构分割为多个体积较小的结构(单机分片)
- 将固定长度的数据打包存储到字符串键里面,从而进一步降低内存占用
扩展Redis
前提:
确认合理使用了Redis提供的数据结构(比如:不把列表当集合使用;也不要获取整个散列然后在客户端里进行排序,而是直接使用有序集合);合理使用流水线和连接池;将大体积的对象缓存到Redis里面之前应该合理进行压缩(lz4、gzip、bzip2)
做完上述准备之后可以考虑架构上扩展读写性能了
- 扩展读性能
- 扩展读性能的最简单方法就是添加只读从服务器
- 使用从服务器树来解决重同步的问题
- 使用Redis Sentinel 对下线的主服务器提供故障转移功能(RS还提供了转移通知的功能,可以在故障转移的时候调用用户脚本。)
- 使用带压缩的隧道来减低从服务器的传送的数据量(如果使这个功能需要ssh自带的选项让ssh自动重连)
重同步:每当有从服务器尝试与主服务器建立连接的时候,主服务器就会为从服务器传建一个快照,如果在快照创建完毕之前,有多个服务器都尝试与主服务器进行连接,那么这些从服务器将会接收到同一个快照。从效率角度将这是合理的,但是这可能会将主服务器的带宽打满,导致主服务器的延迟变高甚至导致主服务器已经建立的链接被断开。
- 扩展写性能
前提:
1.进行功能拆分
2.在对redis写入之前,先尝试在本地内存中对将要写入的数据进行聚合计算
3.使用锁或者Lua脚本去替换redis事务段
在以上方法都试过后说明遇到了只使用一台机器带来的瓶颈,是时候将数据分片到多台机器上面了
数据分片的架构有很多种
- 可以使用分片来扩展复杂查询