Redis——了解特性
特性数据存储模型以键值对Key-Value 存储属于非关系型数据库。区别于 MySQL 这类表与表存在关联的关系型数据库。存储介质以内存为主要存储媒介磁盘仅用作辅助持久化。性能特点相比 MySQL 等磁盘数据库读写速度极快。数据持久化虽基于内存运行但支持数据落盘持久化防止断电数据丢失。集群部署原生支持集群模式可分布式部署在多台服务器。高可用机制支持主从架构主节点故障时从节点可自动顶替保障服务高可用。操作方式支持原生命令行操作同时兼容 Lua 脚本可实现批量命令、原子化复杂操作。可扩展能力支持使用 C、C、Rust 等语言开发Redis 扩展自定义新数据结构本质是编译为动态链接库加载运行redis的用处数据库内存级数据库内存是主要的存储硬件磁盘用于持久化缓存比如存储冷热分离架构的热点数据或者会话存储负载均衡下把所有会话信息保存在Redis服务器中防止用户需要在不同服务器上登录多次的逻辑缺陷中间件做消息队列服务器进行削峰填谷解耦合Redis单线程Redis是单线程处理命令的所以不会有并发访问的问题。但是会有多个线程处理IO。redis为什么快它是访问内存为主磁盘为辅它逻辑简单不像Mysql一样有约束表与表之间的关联事务索引等机制当然这也说明Redis功能不如Mysql多Redis使用epoll模型而不是多线程网络IO效率高因此消耗资源少的同时反应速度也较快因为不用进行调度而是直接在用户层遍历读取Redis使用单线程模型执行命令避免了不必要的锁竞争和线程调度开销多线程提高效率是因为自己执行的是CPU密集任务调度和加锁的消耗比不上充分利用了CPU多核资源的收益。而Redis只操作数据结构加锁以及调度的开销甚至超过了并行操作本身节省下来的时间收益比不上消耗hashmap和redis的区别既然redis是一个键值对的存储方式那么其实使用hashmap是更快更简单的因为redis得通过网络访问还要维护很多数据结构但是redis有如下特性持久化存储可以扩展成分布式支持网络访问其实redis就是在键值对存储的基础之上扩展了功能要是你业务里用到这些功能那就直接使用redis省得自己手搓反之要是你不用这些功能当然也就没必要使用redis了。使用redis的方式redis是客户端-服务端架构使用redis的方式如下命令行图形化界面API程序中使用类似于mysql apiRedis中的数据类型和编码方式Redis是KV存储系统其中K的类型一定是string字符串所以我们所讨论的数据类型指的是V的类型。Redis中的数据类型有很多比如字符串列表哈希表。但是Redis只是保证我们使用某种数据类型时时间复杂度和空间复杂度等特性不低于该数据类型的预期但是Redis会针对特定情况使用不同的底层实现来提高效率节省空间等。表面的类型就叫做数据类型该类型的实际实现就叫做编码方式。我们举个简单的例子如果说这个V是存放数字的字符串那么Redis就会真的把V设置成整形类型提高运算速度但表现给用户它还是一个字符串类型。下面是一些数据类型个编码方式的对应关系不过需要注意的是在新版本的Redis里list的编码方式只有quicklist它是ziplist和linkedlist的结合体继承了他们两个的优点。大概就是让linkedlist的每个节点都是一个ziplist。Redis无字符集概念首先要知道所有的字符集对于英文几乎都是用ASCII进行翻译而Redis客户端使用ASCII解析终端发来的命令所以无论系统使用何种字符集都不影响redis客户端解析指令对于命令中携带的数据来说Redis客户端和服务端不会对其进行任何的字符集转换从终端里取出来是什么样存进内存里就是什么样。当我们从redis中读取数据的时候数据的字节流被原封不动的输出到显示器终端上同样按照系统的字符集对其进行解释。所以Redis整体来说很少出现乱码问题因为它不会改变数据的编码。这个特性很好几乎不会遇到乱码问题除非是两个主机之间编码方式不同导致解析不同。不过需要注意的是Redis客户端在输出查询结果的时候对于非ascii码表中的字符比如中文会把中文的字节流转化成十六进制字符串输出比如/x65 变成“/x65”那么终端会把它解析成英文字符也就是输出一系列十六进制而不是翻译成中文。解决这个问题得在启动Redis客户端时加--raw选项来禁止Redis做这一步转化操作。Rdis的定时删除策略惰性删除下一次查询这个key的时候先对比过期时间和当前时间如果发现过期了就立马删除返回nil定期删除定期每次抽取一部分进行过期验证这样不太消耗效率如果扫描消耗太长时间就阻塞了Rdis的单线程上面两种方式仍然可能因为已经删除的key占用了太多内存导致内存资源不足。所以redis还设置了内存淘汰策略这个我们之后的文章会介绍。所以其实可以看出来redis定时器并没有通过优先级队列等方式实现。但是下面我简单介绍一下如何使用其他方式实现较为高效的定时删除优先级队列以redis的过期时间作为key过期时间越早优先级越高。每次取堆顶查看是否过期过期则并执行回调方法只需要观察堆顶即可因为如果堆顶没过期剩下的都没过期。线程不是循环扫描堆顶而是根据当前时刻与堆顶元素的过期时间的差值设置一个等待时间等到过期时间差不多到了再去唤醒扫描线程。新任务添加成功后唤醒一下检测线程更新休眠时间时间轮时间轮是一个环形数组数组中的每个位置都是一个任务列表。维护一个指针这个指针一开始指向的是数组开始的位置每隔一段时间这个时间是自己设定指针移动到时间轮的下一个位置并遍历任务列表发现谁过期了就删除谁对比当前时间和过期时间。而我们新建一个定时任务就是按照过期时间把任务插入到时间轮的对应位置的任务列表中比如过期时间是500而时间轮间隔是100时间轮的数组大小是3那就把任务插入到500/100%3位置的任务列表中。Redis批量执行Redis中的很多命令都是可以批量执行或者说两种命令组合在一起执行的比如exists命令就是可以一次性查询多个键值对。这样的好处就是网络IO次数比较少效率变得更高了。