数据存储瓶颈是什么
数据量总大小,一个机器放不下
数据索引一个机器内存放不下
访问量一个服务器不能承受
优化数据结构–文件缓存IO
后来,随着访问量的上升,几乎大部分使用MySQL架构的网站在数据库上都开始出现了性能问题,web 程序不再仅仅专注在功能上,同时也在追求性能。程序猿们开始大量使用缓存技术来缓解数据库的压 力,优化数据库的结构和索引,开始比较流行的是通过文件缓存来缓解数据库压力,但是当访问量继续 增大的时候,多台web机器通过文件缓存不能共享,大量的小文件缓存也带了比较高的IO压力,在这个 时候,Memcached就自然的成为一个非常时尚的技术产品。
Memcached +MySQL +垂直拆分
Mysql主从读写分离
由于数据库的写入压力增加,Memcached只能缓解数据库的读取压力,读写集中在一个数据库上让数 据库不堪重负,大部分网站开始使用主从复制技术来达到读写分离,以提高读写性能和读库的可扩展 性,MySQL的master-slave模式成为这个时候的网站标配了
分库分表+水平拆分+Mysql集群
在Memcached的高速缓存,MySQL的主从复制,读写分离的基础之上,这时MySQL主库的写压力开始 出现瓶颈,而数据量的持续猛增,由于MyISAM使用表锁,在高并发下会出现严重的锁问题,大量的高 并发MySQL应用开始使用InnoDB引擎代替MyISAM。 同时,开始流行使用分表分库来缓解写压力和数据增长的扩展问题,这个时候,分表分库成了一个热门 技术,是面试的热门问题,也是业界讨论的热门技术问题。也就是在这个时候,MySQL推出了还不太稳 定的表分区,这也给技术实力一般的公司带来了希望。虽然MySQL推出了MySQL Cluster集群,但性能 也不能很好满足互联网的需求,只是在高可靠性上提供了非常大的保证。
NoSQL 特点
方便扩展
NoSQL 数据库种类繁多,但是一个共同的特点都是去掉关系数据库的关系型特性。 数据之间无关系,这样就非常容易扩展,也无形之间,在架构的层面上带来了可扩展的能力
性能高
NoSQL数据库都具有非常高的读写性能,尤其是在大数据量下,同样表现优秀。这得益于它的非关系 性,数据库的结构简单。 一般MySQL使用Query Cache,每次表的更新Cache就失效,是一种大力度的Cache,在针对Web2.0的 交互频繁应用,Cache性能不高,而NoSQL的Cache是记录级的,是一种细粒度的Cache,所以NoSQL 在这个层面上来说就要性能高很多了。 官方记录:Redis 一秒可以写8万次,读11万次!
数据类型多样
泛指非关系型的数据库,随着互联网Web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别 是超大规模和高并发的社交网络服务类型的Web2.0纯动态网站已经显得力不从心,暴露了很多难以克服 的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展,NoSQL数据库的产生就是为 了解决大规模数据集合多种数据种类带来的挑战,尤其是大数据应用难题,包括超大规模数据的存储。 (例如谷歌或Facebook每天为他们的用户收集万亿比特的数据)。这些类型的数据存储不需要固定的模 式,无需多余操作就可以横向扩展。
多样灵活的数据模型
NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式,而在关系数据库里,增删 字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是噩梦。
4、传统的RDBMS VS NoSQL 拓展:3V+3高 大数据时代的3V : 主要是对问题的描述 海量 Volume 多样 Variety 实时 Velocity 互联网需求的3高 : 主要是对程序的要求 高并发 高可用 高性能 当下的应用是 SQL 和 NoSQL 一起使用,技术没有高低之分,就看你怎么用,对吧! 经典应用分析 聊聊阿里巴巴中文网站的商品信息如何存放,以女装、包包为例:
1、商品的基本信息 2、商品描述、详情、评价信息(多文字类) 3、商品的图片 4、商品的关键字 名称、价格、出厂日期、生产厂商等 关系型数据库:mysql、oracle目前淘宝在去O化(也即,拿掉Oracle) 注意,淘宝内部用的MySQL是里面的大牛自己改造过的。 为什么去IOE: 2008年,王坚博士加入阿里巴巴,成为首席架构师。把云计算植入阿里IT基因。 2013年5月17日,阿里集团最后一台IBM小机在支付宝下线。这是自2009年“去IOE”战略透露以来,“去 IOE”非常重要的一个节点。“去 IOE”指的是摆脱掉IT部署中原有的IBM小型机、Oracle数据库以及EMC 存储的过度依赖。告别最后一台小机,意味着整个阿里集团尽管还有一些Oracle数据库和EMC存储,但是 IBM小型机已全部被替换。2013年7月10日,淘宝重中之重的广告系统使用的Oracle数据库下线,也是整 个淘宝最后一个 Oracle数据库。这两件事合在一起是阿里巴巴技术发展过程中的一个重要里程碑。 多文字信息描述类,IO读写性能变差 存在文档数据库MongDB中 商品图片展现类 分布式文件系统中 - 淘宝自己的 TFS - Google的 GFS - Hadoop的 HDFS
1、商品的基本信息 2、商品描述、详情、评价信息(多文字类) 3、商品的图片 4、商品的关键字 名称、价格、出厂日期、生产厂商等 关系型数据库:mysql、oracle目前淘宝在去O化(也即,拿掉Oracle) 注意,淘宝内部用的MySQL是里面的大牛自己改造过的。 为什么去IOE: 2008年,王坚博士加入阿里巴巴,成为首席架构师。把云计算植入阿里IT基因。 2013年5月17日,阿里集团最后一台IBM小机在支付宝下线。这是自2009年“去IOE”战略透露以来,“去 IOE”非常重要的一个节点。“去 IOE”指的是摆脱掉IT部署中原有的IBM小型机、Oracle数据库以及EMC 存储的过度依赖。告别最后一台小机,意味着整个阿里集团尽管还有一些Oracle数据库和EMC存储,但是 IBM小型机已全部被替换。2013年7月10日,淘宝重中之重的广告系统使用的Oracle数据库下线,也是整 个淘宝最后一个 Oracle数据库。这两件事合在一起是阿里巴巴技术发展过程中的一个重要里程碑。 多文字信息描述类,IO读写性能变差 存在文档数据库MongDB中 商品图片展现类 分布式文件系统中 - 淘宝自己的 TFS - Google的 GFS - Hadoop的 HDFS 5、商品的波段性的热点高频信息 6、商品的交易,价格计算,积分累计! 大型互联网应用(大数据,高并发,多样数据类型)的难点和解决方案 数据类型多样性 数据源多样性变化冲欧 数据源改造数据服务平台不需要面积大重构
以一个电商客户,订单,订购,地址模型来对比下关系型数据库和非关系型数据库 传统的关系型数据库你如何设计? ER图(1:1/1:N/N:N,主外键等常见) 用户对应多个订单多个地址 每个订单对应每个商品、价格、地址 每个商品对应产品 NoSQL你如何设计 可以尝试使用BSON。 BSON是一种类json的一种二进制形式的存储格式,简称Binary JSON,它和JSON一样,支持内嵌的文档 对象和数组对象 用BSon画出构建的数据模型 { "customer":{ "id":1000, "name":"Z3", "billingAddress":[{"city":"beijing"}], "orders":[ { "id":17, 想想关系模型数据库你如何查?如果按照我们新设计的BSon,是不是查询起来很简单。 高并发的操作是不太建议有关联查询的,互联网公司用冗余数据来避免关联查询 分布式事务是支持不了太多的并发的
KV键值
新浪:BerkeleyDB+redis 美团:redis+tair 阿里、百度:memcache+redis
文档型数据库(bson格式比较多): CouchDB MongoDB MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可 扩展的高性能数据存储解决方案。 MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰 富,最像关系数据库的。
列存储数据库: Cassandra, HBase 分布式文件系统 图关系数据库 它不是放图形的,放的是关系比如:朋友圈社交网络、广告推荐系统 社交网络,推荐系统等。专注于构建关系图谱 Neo4J, InfoGrid
关系型数据库遵循ACID规则
A (Atomicity) 原子性 C (Consistency) 一致性 I (Isolation) 隔离性 D (Durability) 持久性 CAP(三进二) 原子性很容易理解,也就是说事务里的所有操作要么全部做完,要么都不做,事务成功的条件是事务 里的所有操作都成功,只要有一个操作失败,整个事务就失败,需要回滚。 比如银行转账,从A账户转100元至B账户,分为两个步骤: 1)从A账户取100元; 2)存入100元至B账户。 这两步要么一起完成,要么一起不完成,如果只完成第一步,第二步失败,钱会莫名其妙少了100 元。 1 事务前后数据的完整性必须保持一致。 所谓的独立性是指并发的事务之间不会互相影响,如果一个事务要访问的数据正在被另外一个事务修 改,只要另外一个事务未提交,它所访问的数据就不受未提交事务的影响。比如现有有个交易是从A 账户转100元至B账户,在这个交易还未完成的情况下,如果此时B查询自己的账户,是看不到新增加 的100元的 1 持久性是指一旦事务提交后,它所做的修改将会永久的保存在数据库上,即使出现宕机也不会丢失。 C : Consistency(强一致性) A : Availability(可用性) P : Partition tolerance(分区容错性)
CPA理论就是说在分布式存储系统中,最多只能实现上面两点
而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容错性是我们必须需要实现的。 所以我们只能在一致性和可用性之间进行权衡,没有NoSQL系统能同时保证这三点。 注意:分布式架构的时候必须做出取舍。 一致性和可用性之间取一个平衡。多余大多数web应用,其实并不需要强一致性。 因此牺牲C换取P,这是目前分布式数据库产品的方向 一致性与可用性的决择 对于web2.0网站来说,关系数据库的很多主要特性却往往无用武之地 数据库事务一致性需求 很多web实时系统并不要求严格的数据库事务,对读一致性的要求很低, 有些场合对写一致性要求并不 高。允许实现最终一致性。 数据库的写实时性和读实时性需求 对关系数据库来说,插入一条数据之后立刻查询,是肯定可以读出来这条数据的,但是对于很多web应 用来说,并不要求这么高的实时性,比方说发一条消息之 后,过几秒乃至十几秒之后,我的订阅者才看 到这条动态是完全可以接受的。 对复杂的SQL查询,特别是多表关联查询的需求 任何大数据量的web系统,都非常忌讳多个大表的关联查询,以及复杂的数据分析类型的报表查询,特 别是SNS类型的网站,从需求以及产品设计角度,就避免了这种情况的产生。往往更多的只是单表的主 键查询,以及单表的简单条件分页查询,SQL的功能被极大的弱化了。 CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求, 最多只能同时较好的满足两个。因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类: CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。 CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。 AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
BASE理论是由eBay架构师提出的。BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互 联网分布式系统实践的总结,是基于CAP定律逐步演化而来。其核心思想是即使无法做到强一致性,但 每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
BASE就是为了解决关系型数据库一致性引起的问题而引起的可用性降低而提出的解决方案
基本可用
基本可用是指分布式系统在出现故障的时候,允许损失部分可用 性,即保证核心可用。电商大促时,为了应对访问量激增,部分用户可能会被引导到降级页面,服 务层也可能只提供降级服务。这就是损失部分可用性的体现。
软状态
软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用 性。分布式存储中一般一份数据至少会有三个副本,允许不同节点间副本同步的延时就是软状态的 体现。MySQL Replication 的异步复制也是一种体现。
最终一致性
最终一致性是指系统中的所有数据副本经过一定时间后,最 终能够达到一致的状态。弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况。
它的思想是通过让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能上改观。为什么 这么说呢,缘由就在于大型系统往往由于地域分布和极高性能的要求,不可能采用分布式事务来完成这 些指标,要想获得这些指标,我们必须采用另外一种方式来完成,这里BASE就是解决这个问题的办法!
解释
分布式:不同的多台服务器部署不同服务模块,他们之间通过Rpc通信和调用,对外提供服务和组内写作
集群:不同多台服务器上部署相同的服务模块,通过分布式调度软件进行统一的调度,对外提供服务和访问
Redis:REmote DIctionary Server(远程字典服务器) 是完全开源免费的,用C语言编写的,遵守BSD协议,是一个高性能的(Key/Value)分布式内存数据 库,基于内存运行,并支持持久化的NoSQL数据库,是当前最热门的NoSQL数据库之一,也被人们称为 数据结构服务器 Redis与其他key-value缓存产品有以下三个特点 Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使 用。 Redis不仅仅支持简单的 key-value 类型的数据,同时还提供list、set、zset、hash等数据结构的存 储。 Redis支持数据的备份,即master-slave模式的数据备份。 Redis能干嘛 内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务 取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面 发布、订阅消息系统 地图信息分析 定时器、计数器 ...... 特性 数据类型、基本操作和配置 持久化和复制,RDB、AOF 事务的控制 ..... 常用网站 https://redis.io/ 官网 http://www.redis.cn 中文网 Windows安装 下载地址:https://github.com/dmajkic/redis/downloads ( 素材提供 ) 解压到自己电脑的环境目录即可
# 基本的set设值 127.0.0.1:6379> set key kuangshen OK # 取出存储的值 127.0.0.1:6379> get key "kuangshen" 重要提示 由于企业里面做Redis开发,99%都是Linux版的运用和安装,几乎不会涉及到Windows版,上一步的讲 解只是为了知识的完整性,Windows版不作为重点,大家可以自己玩,企业实战就认一个版:Linux版 http://www.redis.cn/topics/introduction
安装步骤 1、下载获得 redis-5.0.7.tar.gz 后将它放到我们Linux的目录下 /opt 2、/opt 目录下,解压命令 : tar -zxvf redis-5.0.7.tar.gz 3、解压完成后出现文件夹:redis-5.0.7 4、进入目录: cd redis-5.0.7 5、在 redis-5.0.7 目录下执行 make 命令
运行make命令时故意出现的错误解析: 1. 安装gcc (gcc是linux下的一个编译程序,是c程序的编译工具) 能上网: yum install gcc-c++ 版本测试: gcc-v 2. 二次make 3. Jemalloc/jemalloc.h: 没有那个文件或目录 运行 make distclean 之后再make 4. Redis Test(可以不用执行) 6、如果make完成后继续执行 make install 7、查看默认安装目录:usr/local/bin /usr 这是一个非常重要的目录,类似于windows下的Program Files,存放用户的程序 cd /usr/local/bin ls -l # 在redis的解压目录下备份redis.conf mkdir myredis cp redis.conf myredis # 拷一个备份,养成良好的习惯,我们就修改这个文件 # 修改配置保证可以后台应用 vim redis.conf
daemonize 设置yes或者no区别 daemonize:yes redis采用的是单进程多线程的模式。当redis.conf中选项daemonize设置成yes时,代表开启 守护进程模式。在该模式下,redis会在后台运行,并将进程pid号写入至redis.conf选项 pidfile设置的文件中,此时redis将一直运行,除非手动kill该进程。 daemonize:no 当daemonize选项设置成no时,当前界面将进入redis的命令行界面,exit强制退出或者关闭 连接工具(putty,xshell等)都会导致redis进程退出。
# 【shell】启动redis服务 [root@192 bin]# cd /usr/local/bin [root@192 bin]# redis-server /opt/redis-5.0.7/redis.conf # redis客户端连接===> 观察地址的变化,如果连接ok,是直接连上的,redis默认端口号 6379 [root@192 bin]# redis-cli -p 6379 127.0.0.1:6379> ping PONG 127.0.0.1:6379> set k1 helloworld OK 127.0.0.1:6379> get k1 "helloworld" # 【shell】ps显示系统当前进程信息 [root@192 myredis]# ps -ef|grep redis root 16005 1 0 04:45 ? 00:00:00 redis-server 127.0.0.1:6379 root 16031 15692 0 04:47 pts/0 00:00:00 redis-cli -p 6379 root 16107 16076 0 04:51 pts/2 00:00:00 grep --color=auto redis # 【redis】关闭连接 127.0.0.1:6379> shutdown not connected> exit # 【shell】ps显示系统当前进程信息 [root@192 myredis]# ps -ef|grep redis root 16140 16076 0 04:53 pts/2 00:00:00 grep --color=auto redi
基础知识
压力测试工具
redis-benchmark
Redis-benchmark是官方自带的Redis性能测试工具,可以有效的测试Redis服务的性能。
# 测试一:100个并发连接,100000个请求,检测host为localhost 端口为6379的redis服务器性 能 redis-benchmark -h localhost -p 6379 -c 100 -n 100000 # 测试出来的所有命令只举例一个! ====== SET ====== 100000 requests completed in 1.88 seconds # 对集合写入测试 100 parallel clients # 每次请求有100个并发客户端 3 bytes payload # 每次写入3个字节的数据,有效载荷 keep alive: 1 # 保持一个连接,一台服务器来处理这些请求 17.05% <= 1 milliseconds 97.35% <= 2 milliseconds 99.97% <= 3 milliseconds 100.00% <= 3 milliseconds # 所有请求在 3 毫秒内完成 53248.14 requests per second # 每秒处理 53248.14 次请求
查看 redis.conf ,里面有默认的配置 databases 16 # Set the number of databases. The default database is DB 0, you can select # a different one on a per-connection basis using SELECT <dbid> where # dbid is a number between 0 and 'databases'-1 databases 16
select 命令切换数据库 不同数据库存不同数据 127.0.0.1:6379> select 7 OK 127.0.0.1:6379[7]> DBSIZE (integer) 0 127.0.0.1:6379[7]> select 0 OK 127.0.0.1:6379> DBSIZE (integer) 5 127.0.0.1:6379> keys * # 查看具体的key 1) "counter:__rand_int__" 2) "mylist" 3) "k1" 4) "myset:__rand_int__" 5) "key:__rand_int__"
FLASHDB :清空当前库 FLASHALL:清空全部的库
127.0.0.1:6379> DBSIZE (integer) 5 127.0.0.1:6379> FLUSHDB OK 127.0.0.1:6379> DBSIZE (integer) 0
Redis采用的是基于内存的采用的是单进程单线程模型的 KV 数据库,由C语言编写,官方提供的数据是 可以达到100000+的QPS(每秒内查询次数)。这个数据不比采用单进程多线程的同样基于内存的 KV 数据库 Memcached 差! Redis为什么这么快? 1)以前一直有个误区,以为:高性能服务器 一定是多线程来实现的 原因很简单因为误区二导致的:多线程 一定比 单线程 效率高,其实不然! 在说这个事前希望大家都能对 CPU 、 内存 、 硬盘的速度都有了解了! 2)redis 核心就是 如果我的数据全都在内存里,我单线程的去操作 就是效率最高的,为什么呢,因为 多线程的本质就是 CPU 模拟出来多个线程的情况,这种模拟出来的情况就有一个代价,就是上下文的切 换,对于一个内存的系统来说,它没有上下文的切换就是效率最高的。redis 用 单个CPU 绑定一块内存 的数据,然后针对这块内存的数据进行多次读写的时候,都是在一个CPU上完成的,所以它是单线程处 理这个事。在内存的情况下,这个方案就是最佳方案。 因为一次CPU上下文的切换大概在 1500ns 左右。从内存中读取 1MB 的连续数据,耗时大约为 250us, 假设1MB的数据由多个线程读取了1000次,那么就有1000次时间上下文的切换,那么就有1500ns * 1000 = 1500us ,我单线程的读完1MB数据才250us ,你光时间上下文的切换就用了1500us了,我还不 算你每次读一点数据 的时间。
五大数据类型
全段翻译: Redis是一个开放源代码(BSD许可)的内存中数据结构存储,用作数据库,缓存和消息代理。它支持数 据结构,例如字符串,哈希,列表,集合,带范围查询的排序集合,位图,超日志,带有半径查询和流 的地理空间索引。Redis具有内置的复制,Lua脚本,LRU驱逐,事务和不同级别的磁盘持久性,并通过 Redis Sentinel和Redis Cluster自动分区提供了高可用性。
# keys * 查看所有的key 127.0.0.1:6379> keys * (empty list or set) 127.0.0.1:6379> set name qinjiang OK 127.0.0.1:6379> keys * 1) "name" # exists key 的名字,判断某个key是否存在 127.0.0.1:6379> EXISTS name (integer) 1 127.0.0.1:6379> EXISTS name1 (integer) 0 # move key db ---> 当前库就没有了,被移除了 127.0.0.1:6379> move name 1 (integer) 1 127.0.0.1:6379> keys * (empty list or set) # expire key 秒钟:为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删 除。 # ttl key 查看还有多少秒过期,-1 表示永不过期,-2 表示已过期 127.0.0.1:6379> set name qinjiang OK 127.0.0.1:6379> EXPIRE name 10 (integer) 1 127.0.0.1:6379> ttl name (integer) 4 127.0.0.1:6379> ttl name (integer) 3 127.0.0.1:6379> ttl name (integer) 2 127.0.0.1:6379> ttl name (integer) 1 127.0.0.1:6379> ttl name (integer) -2 127.0.0.1:6379> keys * (empty list or set) # type key 查看你的key是什么类型 127.0.0.1:6379> set name qinjiang OK 127.0.0.1:6379> get name "qinjiang" 127.0.0.1:6379> type name string
字符串String 单值单Value 常用命令说明:
# =================================================== # set、get、del、append、strlen # =================================================== 127.0.0.1:6379> set key1 value1 # 设置值 OK 127.0.0.1:6379> get key1 # 获得key "value1" 127.0.0.1:6379> del key1 # 删除key (integer) 1 127.0.0.1:6379> keys * # 查看全部的key (empty list or set) 127.0.0.1:6379> exists key1 # 确保 key1 不存在 (integer) 0 127.0.0.1:6379> append key1 "hello" # 对不存在的 key 进行 APPEND ,等同于 SET key1 "hello" (integer) 5 # 字符长度 127.0.0.1:6379> APPEND key1 "-2333" # 对已存在的字符串进行 APPEND (integer) 10 # 长度从 5 个字符增加到 10 个字符 127.0.0.1:6379> get key1 "hello-2333" 127.0.0.1:6379> STRLEN key1 # # 获取字符串的长度 (integer) 10 # =================================================== # incr、decr 一定要是数字才能进行加减,+1 和 -1。 # incrby、decrby 命令将 key 中储存的数字加上指定的增量值。 # =================================================== 127.0.0.1:6379> set views 0 # 设置浏览量为0 OK 127.0.0.1:6379> incr views # 浏览 + 1 (integer) 1 127.0.0.1:6379> incr views # 浏览 + 1 (integer) 2 127.0.0.1:6379> decr views # 浏览 - 1 (integer) 1 127.0.0.1:6379> incrby views 10 # +10 (integer) 11 127.0.0.1:6379> decrby views 10 # -10 (integer) 1 # =================================================== # range [范围] # getrange 获取指定区间范围内的值,类似between...and的关系,从零到负一表示全部 # =================================================== 127.0.0.1:6379> set key2 abcd123456 # 设置key2的值 OK 127.0.0.1:6379> getrange key2 0 -1 # 获得全部的值 "abcd123456" 127.0.0.1:6379> getrange key2 0 2 # 截取部分字符串 "abc" # =================================================== # setrange 设置指定区间范围内的值,格式是setrange key值 具体值 # =================================================== 127.0.0.1:6379> get key2 "abcd123456" 127.0.0.1:6379> SETRANGE key2 1 xx # 替换值 (integer) 10 127.0.0.1:6379> get key2 "axxd123456" # =================================================== # setex(set with expire)键秒值 # setnx(set if not exist) # =================================================== 127.0.0.1:6379> setex key3 60 expire # 设置过期时间 OK 127.0.0.1:6379> ttl key3 # 查看剩余的时间 (integer) 55 127.0.0.1:6379> setnx mykey "redis" # 如果不存在就设置,成功返回1 (integer) 1 127.0.0.1:6379> setnx mykey "mongodb" # 如果存在就设置,失败返回0 (integer) 0 127.0.0.1:6379> get mykey "redis" # =================================================== # mset Mset 命令用于同时设置一个或多个 key-value 对。 # mget Mget 命令返回所有(一个或多个)给定 key 的值。 # 如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。 # msetnx 当所有 key 都成功设置,返回 1 。 # 如果所有给定 key 都设置失败(至少有一个 key 已经存在),那么返回 0 。原子操 作 # =================================================== 127.0.0.1:6379> mset k10 v10 k11 v11 k12 v12 OK 127.0.0.1:6379> keys * 1) "k12" 2) "k11" 3) "k10" 127.0.0.1:6379> mget k10 k11 k12 k13 1) "v10" 2) "v11" 3) "v12" 4) (nil) 127.0.0.1:6379> msetnx k10 v10 k15 v15 # 原子性操作! (integer) 0 127.0.0.1:6379> get key15 (nil) # 传统对象缓存 set user:1 value(json数据) # 可以用来缓存对象 mset user:1:name zhangsan user:1:age 2 mget user:1:name user:1:age # =================================================== # getset(先get再set) # =================================================== 127.0.0.1:6379> getset db mongodb # 没有旧值,返回 nil (nil) 127.0.0.1:6379> get db "mongodb" 127.0.0.1:6379> getset db redis # 返回旧值 mongodb "mongodb" 127.0.0.1:6379> get db "redis"
String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。 常规key-value缓存应用: 常规计数:微博数,粉丝数等。
列表List 单值多Value
# =================================================== # Lpush:将一个或多个值插入到列表头部。(左) # rpush:将一个或多个值插入到列表尾部。(右) # lrange:返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。 # 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。 # 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此 类推。 # =================================================== 127.0.0.1:6379> LPUSH list "one" (integer) 1 127.0.0.1:6379> LPUSH list "two" (integer) 2 127.0.0.1:6379> RPUSH list "right" (integer) 3 127.0.0.1:6379> Lrange list 0 -1 1) "two" 2) "one" 3) "right" 127.0.0.1:6379> Lrange list 0 1 1) "two" 2) "one" # =================================================== # lpop 命令用于移除并返回列表的第一个元素。当列表 key 不存在时,返回 nil 。 # rpop 移除列表的最后一个元素,返回值为移除的元素。 # =================================================== 127.0.0.1:6379> Lpop list "two" 127.0.0.1:6379> Rpop list "right" 127.0.0.1:6379> Lrange list 0 -1 1) "one" # =================================================== # Lindex,按照索引下标获得元素(-1代表最后一个,0代表是第一个) # =================================================== 127.0.0.1:6379> Lindex list 1 (nil) 127.0.0.1:6379> Lindex list 0 "one" 127.0.0.1:6379> Lindex list -1 "one" # =================================================== # llen 用于返回列表的长度。 # =================================================== 127.0.0.1:6379> flushdb OK 127.0.0.1:6379> Lpush list "one" (integer) 1 127.0.0.1:6379> Lpush list "two" (integer) 2 127.0.0.1:6379> Lpush list "three" (integer) 3 127.0.0.1:6379> Llen list # 返回列表的长度 (integer) 3 # =================================================== # lrem key 根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素。 # =================================================== 127.0.0.1:6379> lrem list 1 "two" (integer) 1 127.0.0.1:6379> Lrange list 0 -1 1) "three" 2) "one" # =================================================== # Ltrim key 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区 间之内的元素都将被删除。 # =================================================== 127.0.0.1:6379> RPUSH mylist "hello" (integer) 1 127.0.0.1:6379> RPUSH mylist "hello" (integer) 2 127.0.0.1:6379> RPUSH mylist "hello2" (integer) 3 127.0.0.1:6379> RPUSH mylist "hello3" (integer) 4 127.0.0.1:6379> ltrim mylist 1 2 OK 127.0.0.1:6379> lrange mylist 0 -1 1) "hello" 2) "hello2" # =================================================== # rpoplpush 移除列表的最后一个元素,并将该元素添加到另一个列表并返回。 # =================================================== 127.0.0.1:6379> rpush mylist "hello" (integer) 1 127.0.0.1:6379> rpush mylist "foo" (integer) 2 127.0.0.1:6379> rpush mylist "bar" (integer) 3 127.0.0.1:6379> rpoplpush mylist myotherlist "bar" 127.0.0.1:6379> lrange mylist 0 -1 1) "hello" 2) "foo" 127.0.0.1:6379> lrange myotherlist 0 -1 1) "bar" # =================================================== # lset key index value 将列表 key 下标为 index 的元素的值设置为 value 。 # =================================================== 127.0.0.1:6379> exists list # 对空列表(key 不存在)进行 LSET (integer) 0 127.0.0.1:6379> lset list 0 item # 报错 (error) ERR no such key 127.0.0.1:6379> lpush list "value1" # 对非空列表进行 LSET (integer) 1 127.0.0.1:6379> lrange list 0 0 1) "value1" 127.0.0.1:6379> lset list 0 "new" # 更新值 OK 127.0.0.1:6379> lrange list 0 0 1) "new" 127.0.0.1:6379> lset list 1 "new" # index 超出范围报错 (error) ERR index out of range # =================================================== # linsert key before/after pivot value 用于在列表的元素前或者后插入元素。 # 将值 value 插入到列表 key 当中,位于值 pivot 之前或之后。 # =================================================== redis> RPUSH mylist "Hello" (integer) 1 redis> RPUSH mylist "World" (integer) 2 redis> LINSERT mylist BEFORE "World" "There" (integer) 3 redis> LRANGE mylist 0 -1 1) "Hello" 2) "There" 3) "World"
性能总结 它是一个字符串链表,left,right 都可以插入添加 如果键不存在,创建新的链表 如果键已存在,新增内容 如果值全移除,对应的键也就消失了 链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就很惨淡了。 list就是链表,略有数据结构知识的人都应该能理解其结构。使用Lists结构,我们可以轻松地实现最新消 息排行等功能。List的另一个应用就是消息队列,可以利用List的PUSH操作,将任务存在List中,然后工 作线程再用POP操作将任务取出进行执行。Redis还提供了操作List中某一段的api,你可以直接查询,删 除List中某一段的元素。 Redis的list是每个子元素都是String类型的双向链表,可以通过push和pop操作从列表的头部或者尾部 添加或者删除元素,这样List即可以作为栈,也可以作为队列。
集合Set 单值多value
# =================================================== # sadd 将一个或多个成员元素加入到集合中,不能重复 # smembers 返回集合中的所有的成员。 # sismember 命令判断成员元素是否是集合的成员。 # =================================================== 127.0.0.1:6379> sadd myset "hello" (integer) 1 127.0.0.1:6379> sadd myset "kuangshen" (integer) 1 127.0.0.1:6379> sadd myset "kuangshen" (integer) 0 127.0.0.1:6379> SMEMBERS myset 1) "kuangshen" 2) "hello" 127.0.0.1:6379> SISMEMBER myset "hello" (integer) 1 127.0.0.1:6379> SISMEMBER myset "world" (integer) 0 # =================================================== # scard,获取集合里面的元素个数 # =================================================== 127.0.0.1:6379> scard myset (integer) 2 # =================================================== # srem key value 用于移除集合中的一个或多个成员元素 # =================================================== 127.0.0.1:6379> srem myset "kuangshen" (integer) 1 127.0.0.1:6379> SMEMBERS myset 1) "hello" # =================================================== # srandmember key 命令用于返回集合中的一个随机元素。 # =================================================== 127.0.0.1:6379> SMEMBERS myset 1) "kuangshen" 2) "world" 3) "hello" 127.0.0.1:6379> SRANDMEMBER myset "hello" 127.0.0.1:6379> SRANDMEMBER myset 2 1) "world" 2) "kuangshen" 127.0.0.1:6379> SRANDMEMBER myset 2 1) "kuangshen" 2) "hello" # =================================================== # spop key 用于移除集合中的指定 key 的一个或多个随机元素 # =================================================== 127.0.0.1:6379> SMEMBERS myset 1) "kuangshen" 2) "world" 3) "hello" 127.0.0.1:6379> spop myset "world" 127.0.0.1:6379> spop myset "kuangshen" 127.0.0.1:6379> spop myset "hello" # =================================================== # smove SOURCE DESTINATION MEMBER # 将指定成员 member 元素从 source 集合移动到 destination 集合。 # =================================================== 127.0.0.1:6379> sadd myset "hello" (integer) 1 127.0.0.1:6379> sadd myset "world" (integer) 1 127.0.0.1:6379> sadd myset "kuangshen" (integer) 1 127.0.0.1:6379> sadd myset2 "set2" (integer) 1 127.0.0.1:6379> smove myset myset2 "kuangshen" (integer) 1 127.0.0.1:6379> SMEMBERS myset 1) "world" 2) "hello" 127.0.0.1:6379> SMEMBERS myset2 1) "kuangshen" 2) "set2" # =================================================== - 数字集合类 - 差集: sdiff - 交集: sinter - 并集: sunion # =================================================== 127.0.0.1:6379> sadd key1 "a" (integer) 1 127.0.0.1:6379> sadd key1 "b" (integer) 1 127.0.0.1:6379> sadd key1 "c" (integer) 1 127.0.0.1:6379> sadd key2 "c" (integer) 1 127.0.0.1:6379> sadd key2 "d" (integer) 1 127.0.0.1:6379> sadd key2 "e" (integer) 1 127.0.0.1:6379> SDIFF key1 key2 # 差集 1) "a" 2) "b" 127.0.0.1:6379> SINTER key1 key2 # 交集 1) "c" 127.0.0.1:6379> SUNION key1 key2 # 并集 1) "a" 2) "b" 3) "c" 4) "e" 5) "d"
在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为 集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功 能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集 合中。
哈希Hash kv模式不变,但V是一个键值对
# =================================================== # hset、hget 命令用于为哈希表中的字段赋值 。 # hmset、hmget 同时将多个field-value对设置到哈希表中。会覆盖哈希表中已存在的字段。 # hgetall 用于返回哈希表中,所有的字段和值。 # hdel 用于删除哈希表 key 中的一个或多个指定字段 # =================================================== 127.0.0.1:6379> hset myhash field1 "kuangshen" (integer) 1 127.0.0.1:6379> hget myhash field1 "kuangshen" 127.0.0.1:6379> HMSET myhash field1 "Hello" field2 "World" OK 127.0.0.1:6379> HGET myhash field1 "Hello" 127.0.0.1:6379> HGET myhash field2 "World" 127.0.0.1:6379> hgetall myhash 1) "field1" 2) "Hello" 3) "field2" 4) "World" 127.0.0.1:6379> HDEL myhash field1 (integer) 1 127.0.0.1:6379> hgetall myhash 1) "field2" 2) "World" # =================================================== # hlen 获取哈希表中字段的数量。 # =================================================== 127.0.0.1:6379> hlen myhash (integer) 1 127.0.0.1:6379> HMSET myhash field1 "Hello" field2 "World" OK 127.0.0.1:6379> hlen myhash (integer) 2 # =================================================== # hexists 查看哈希表的指定字段是否存在。 # =================================================== 127.0.0.1:6379> hexists myhash field1 (integer) 1 127.0.0.1:6379> hexists myhash field3 (integer) 0 # =================================================== # hkeys 获取哈希表中的所有域(field)。 # hvals 返回哈希表所有域(field)的值。 # =================================================== 127.0.0.1:6379> HKEYS myhash 1) "field2" 2) "field1" 127.0.0.1:6379> HVALS myhash 1) "World" 2) "Hello" # =================================================== # hincrby 为哈希表中的字段值加上指定增量值。 # =================================================== 127.0.0.1:6379> hset myhash field 5 (integer) 1 127.0.0.1:6379> HINCRBY myhash field 1 (integer) 6 127.0.0.1:6379> HINCRBY myhash field -1 (integer) 5 127.0.0.1:6379> HINCRBY myhash field -10 (integer) -5 # =================================================== # hsetnx 为哈希表中不存在的的字段赋值 。 # =================================================== 127.0.0.1:6379> HSETNX myhash field1 "hello" (integer) 1 # 设置成功,返回 1 。 127.0.0.1:6379> HSETNX myhash field1 "world" (integer) 0 # 如果给定字段已经存在,返回 0 。 127.0.0.1:6379> HGET myhash field1 "hello"
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。 存储部分变更的数据,如用户信息等。
有序集合Zset 在set基础上,加一个score值。之前set是k1 v1 v2 v3,现在zset是 k1 score1 v1 score2 v2
# =================================================== # zadd 将一个或多个成员元素及其分数值加入到有序集当中。 # zrange 返回有序集中,指定区间内的成员 # =================================================== 127.0.0.1:6379> zadd myset 1 "one" (integer) 1 127.0.0.1:6379> zadd myset 2 "two" 3 "three" (integer) 2 127.0.0.1:6379> ZRANGE myset 0 -1 1) "one" 2) "two" 3) "three" # =================================================== # zrangebyscore 返回有序集合中指定分数区间的成员列表。有序集成员按分数值递增(从小到大) 次序排列。 # =================================================== 127.0.0.1:6379> zadd salary 2500 xiaoming (integer) 1 127.0.0.1:6379> zadd salary 5000 xiaohong (integer) 1 127.0.0.1:6379> zadd salary 500 kuangshen (integer) 1 # Inf无穷大量+∞,同样地,-∞可以表示为-Inf。 127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 显示整个有序集 1) "kuangshen" 2) "xiaoming" 3) "xiaohong" 127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # 递增排列 1) "kuangshen" 2) "500" 3) "xiaoming" 4) "2500" 5) "xiaohong" 6) "5000" 127.0.0.1:6379> ZREVRANGE salary 0 -1 WITHSCORES # 递减排列 1) "xiaohong" 2) "5000" 3) "xiaoming" 4) "2500" 5) "kuangshen" 6) "500" 127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 WITHSCORES # 显示工资 <=2500 的所有成员 1) "kuangshen" 2) "500" 3) "xiaoming" 4) "2500" # =================================================== # zrem 移除有序集中的一个或多个成员 # =================================================== 127.0.0.1:6379> ZRANGE salary 0 -1 1) "kuangshen" 2) "xiaoming" 3) "xiaohong" 127.0.0.1:6379> zrem salary kuangshen (integer) 1 127.0.0.1:6379> ZRANGE salary 0 -1 1) "xiaoming" 2) "xiaohong" # =================================================== # zcard 命令用于计算集合中元素的数量。 # =================================================== 127.0.0.1:6379> zcard salary (integer) 2 OK # =================================================== # zcount 计算有序集合中指定分数区间的成员数量。 # =================================================== 127.0.0.1:6379> zadd myset 1 "hello" (integer) 1 127.0.0.1:6379> zadd myset 2 "world" 3 "kuangshen" (integer) 2 127.0.0.1:6379> ZCOUNT myset 1 3 (integer) 3 127.0.0.1:6379> ZCOUNT myset 1 2 (integer) 2 # =================================================== # zrank 返回有序集中指定成员的排名。其中有序集成员按分数值递增(从小到大)顺序排列。 # =================================================== 127.0.0.1:6379> zadd salary 2500 xiaoming (integer) 1 127.0.0.1:6379> zadd salary 5000 xiaohong (integer) 1 127.0.0.1:6379> zadd salary 500 kuangshen (integer) 1 127.0.0.1:6379> ZRANGE salary 0 -1 WITHSCORES # 显示所有成员及其 score 值 1) "kuangshen" 2) "500" 3) "xiaoming" 4) "2500" 5) "xiaohong" 6) "5000" 127.0.0.1:6379> zrank salary kuangshen # 显示 kuangshen 的薪水排名,最少 (integer) 0 127.0.0.1:6379> zrank salary xiaohong # 显示 xiaohong 的薪水排名,第三 (integer) 2 # =================================================== # zrevrank 返回有序集中成员的排名。其中有序集成员按分数值递减(从大到小)排序。 # =================================================== 127.0.0.1:6379> ZREVRANK salary kuangshen # 狂神第三 (integer) 2 127.0.0.1:6379> ZREVRANK salary xiaohong # 小红第一 (integer) 0
和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列,比如 一个存储全班同学成绩的sorted set,其集合value可以是同学的学号,而score就可以是其考试得分, 这样在数据插入集合的时候,就已经进行了天然的排序。可以用sorted set来做带权重的队列,比如普 通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让 重要的任务优先执行。 排行榜应用,取TOP N操作 !
三种特殊数据类型
GEO地理位置 简介 Redis 的 GEO 特性在 Redis 3.2 版本中推出, 这个功能可以将用户给定的地理位置信息储存起来, 并对 这些信息进行操作。来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能。geo的数据类型为 zset。 GEO 的数据结构总共有六个常用命令:geoadd、geopos、geodist、georadius、 georadiusbymember、gethash 官方文档:https://www.redis.net.cn/order/3685.htm
geoadd
# 语法 geoadd key longitude latitude member ... # 将给定的空间元素(纬度、经度、名字)添加到指定的键里面。 # 这些数据会以有序集he的形式被储存在键里面,从而使得georadius和georadiusbymember这样的 命令可以在之后通过位置查询取得这些元素。 # geoadd命令以标准的x,y格式接受参数,所以用户必须先输入经度,然后再输入纬度。 # geoadd能够记录的坐标是有限的:非常接近两极的区域无法被索引。 # 有效的经度介于-180-180度之间,有效的纬度介于-85.05112878 度至 85.05112878 度之间。, 当用户尝试输入一个超出范围的经度或者纬度时,geoadd命令将返回一个错误。 127.0.0.1:6379> geoadd china:city 116.23 40.22 北京 (integer) 1 127.0.0.1:6379> geoadd china:city 121.48 31.40 上海 113.88 22.55 深圳 120.21 30.20 杭州 (integer) 3 127.0.0.1:6379> geoadd china:city 106.54 29.40 重庆 108.93 34.23 西安 114.02 30.58 武汉 (integer) 3
geopos
# 语法 geopos key member [member...] #从key里返回所有给定位置元素的位置(经度和纬度) 127.0.0.1:6379> geopos china:city 北京 1) 1) "116.23000055551528931" 2) "40.2200010338739844" 127.0.0.1:6379> geopos china:city 上海 重庆 1) 1) "121.48000091314315796" 2) "31.40000025319353938" 2) 1) "106.54000014066696167" 2) "29.39999880018641676" 127.0.0.1:6379> geopos china:city 新疆 1) (nil)
geodist
# 语法 geodist key member1 member2 [unit] # 返回两个给定位置之间的距离,如果两个位置之间的其中一个不存在,那么命令返回空值。 # 指定单位的参数unit必须是以下单位的其中一个: # m表示单位为米 # km表示单位为千米 # mi表示单位为英里 # ft表示单位为英尺 # 如果用户没有显式地指定单位参数,那么geodist默认使用米作为单位。 #geodist命令在计算距离时会假设地球为完美的球形,在极限情况下,这一假设最大会造成0.5%的误 差。
127.0.0.1:6379> geodist china:city 北京 上海 "1088785.4302" 127.0.0.1:6379> geodist china:city 北京 上海 km "1088.7854" 127.0.0.1:6379> geodist china:city 重庆 北京 km "1491.6716"
georadius
# 语法 georadius key longitude latitude radius m|km|ft|mi [withcoord][withdist] [withhash][asc|desc][count count] # 以给定的经纬度为中心, 找出某一半径内的元素 测试:重新连接 redis-cli,增加参数 --raw ,可以强制输出中文,不然会乱码 [root@kuangshen bin]# redis-cli --raw -p 6379 # 在 china:city 中寻找坐标 100 30 半径为 1000km 的城市 127.0.0.1:6379> georadius china:city 100 30 1000 km 重庆 西安 # withdist 返回位置名称和中心距离 127.0.0.1:6379> georadius china:city 100 30 1000 km withdist 重庆 635.2850 西安 963.3171 # withcoord 返回位置名称和经纬度 127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord 重庆 106.54000014066696167 29.39999880018641676 西安 108.92999857664108276 34.23000121926852302 # withdist withcoord 返回位置名称 距离 和经纬度 count 限定寻找个数 127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord withdist count 1 重庆 635.2850 106.54000014066696167 29.39999880018641676 127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord withdist count 2 重庆 635.2850 106.54000014066696167 29.39999880018641676 西安 963.3171 108.92999857664108276 34.23000121926852302
georadiusbymember
# 语法 georadiusbymember key member radius m|km|ft|mi [withcoord][withdist] [withhash][asc|desc][count count] # 找出位于指定范围内的元素,中心点是由给定的位置元素决定
127.0.0.1:6379> GEORADIUSBYMEMBER china:city 北京 1000 km 北京 西安 127.0.0.1:6379> GEORADIUSBYMEMBER china:city 上海 400 km 杭州 上海
geohash
# 语法 geohash key member [member...] # Redis使用geohash将二维经纬度转换为一维字符串,字符串越长表示位置更精确,两个字符串越相似 表示距离越近。
127.0.0.1:6379> geohash china:city 北京 重庆 wx4sucu47r0 wm5z22h53v0 127.0.0.1:6379> geohash china:city 北京 上海 wx4sucu47r0 wtw6sk5n300
zrem
GEO没有提供删除成员的命令,但是因为GEO的底层实现是zset,所以可以借用zrem命令实现对地理位 置信息的删除
127.0.0.1:6379> geoadd china:city 116.23 40.22 beijin 1 127.0.0.1:6379> zrange china:city 0 -1 # 查看全部的元素 重庆 西安 深圳 武汉 杭州 上海 beijin 北京 127.0.0.1:6379> zrem china:city beijin # 移除元素 1 127.0.0.1:6379> zrem china:city 北京 # 移除元素 1 127.0.0.1:6379> zrange china:city 0 -1 重庆 西安 深圳 武汉 杭州 上海
Redis 在 2.8.9 版本添加了 HyperLogLog 结构。 Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积 非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。 在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。 HyperLogLog则是一种算法,它提供了不精确的去重计数方案。 举个栗子:假如我要统计网页的UV(浏览用户数量,一天内同一个用户多次访问只能算一次),传统的 解决方案是使用Set来保存用户id,然后统计Set中的元素数量来获取页面UV。但这种方案只能承载少量 用户,一旦用户数量大起来就需要消耗大量的空间来存储用户id。我的目的是统计用户数量而不是保存 用户,这简直是个吃力不讨好的方案!而使用Redis的HyperLogLog最多需要12k就可以统计大量的用户 数,尽管它大概有0.81%的错误率,但对于统计UV这种不需要很精确的数据是可以忽略不计的。
什么是基数
比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。
基本命令
127.0.0.1:6379> PFADD mykey a b c d e f g h i j 1 127.0.0.1:6379> PFCOUNT mykey 10 127.0.0.1:6379> PFADD mykey2 i j z x c v b n m 1 127.0.0.1:6379> PFMERGE mykey3 mykey mykey2 OK 127.0.0.1:6379> PFCOUNT mykey3 15
在开发中,可能会遇到这种情况:需要统计用户的某些信息,如活跃或不活跃,登录或者不登录;又如 需要记录用户一年的打卡情况,打卡了是1, 没有打卡是0,如果使用普通的 key/value存储,则要记录 365条记录,如果用户量很大,需要的空间也会很大,所以 Redis 提供了 Bitmap 位图这中数据结构, Bitmap 就是通过操作二进制位来进行记录,即为 0 和 1;如果要记录 365 天的打卡情况,使用 Bitmap 表示的形式大概如下:0101000111000111...........................,这样有什么好处呢?当然就是节约内存 了,365 天相当于 365 bit,又 1 字节 = 8 bit , 所以相当于使用 46 个字节即可。 BitMap 就是通过一个 bit 位来表示某个元素对应的值或者状态, 其中的 key 就是对应元素本身,实际上 底层也是通过对字符串的操作来实现。Redis 从 2.2 版本之后新增了setbit, getbit, bitcount 等几个 bitmap 相关命令。
setbit 设置操作
SETBIT key offset value : 设置 key 的第 offset 位为value (1或0)
用 bitmap 来记录上述事例中一周的打卡记录如下所示: # 周一:1,周二:0,周三:0,周四:1,周五:1,周六:0,周天:0 (1 为打卡,0 为不打卡) 127.0.0.1:6379> setbit sign 0 1 0 127.0.0.1:6379> setbit sign 1 0 0 127.0.0.1:6379> setbit sign 2 0 0 127.0.0.1:6379> setbit sign 3 1 0 127.0.0.1:6379> setbit sign 4 1 0 127.0.0.1:6379> setbit sign 5 0 0 127.0.0.1:6379> setbit sign 6 0 0
getbit 获取操作
GETBIT key offset 获取offset设置的值,未设置过默认返回0
127.0.0.1:6379> getbit sign 3 # 查看周四是否打卡 1 127.0.0.1:6379> getbit sign 6 # 查看周七是否打卡 0
bitcount 统计操作
bitcount key [start, end] 统计 key 上位为1的个数 # 统计这周打卡的记录,可以看到只有3天是打卡的状态: 127.0.0.1:6379> bitcount sign 3
Redis.conf
Redis 的配置文件位于 Redis 安装目录下,文件名为 redis.conf
config get * # 获取全部的配置
配置大小单位,只支持bytes,不支持bit
对大小写不敏感
INCLUDES
和Spring配置文件类似,可以通过includes包含,redis.conf 可以作为总文件,可以包含其他文件
NETWORK 网络配置
bind 127.0.0.1 # 绑定的ip protected-mode yes # 保护模式 port 6379 # 默认端口
GENERAL
daemonize yes # 默认情况下,Redis不作为守护进程运行。需要开启的话,改为 yes supervised no # 可通过upstart和systemd管理Redis守护进程 pidfile /var/run/redis_6379.pid # 以后台进程方式运行redis,则需要指定pid 文件 loglevel notice # 日志级别。可选项有: # debug(记录大量日志信息,适用于开发、测试阶段); # verbose(较多日志信息); # notice(适量日志信息,使用于生产环境); # warning(仅有部分重要、关键信息才会被记录)。 logfile "" # 日志文件的位置,当指定为空字符串时,为标准输出 databases 16 # 设置数据库的数目。默认的数据库是DB 0 always-show-logo yes # 是否总是显示logo
SNAPSHOPTING
# 900秒(15分钟)内至少1个key值改变(则进行数据库保存--持久化) save 900 1 # 300秒(5分钟)内至少10个key值改变(则进行数据库保存--持久化) save 300 10 # 60秒(1分钟)内至少10000个key值改变(则进行数据库保存--持久化) save 60 10000 stop-writes-on-bgsave-error yes # 持久化出现错误后,是否依然进行继续进行工作 rdbcompression yes # 使用压缩rdb文件 yes:压缩,但是需要一些cpu的消耗。no:不压 缩,需要更多的磁盘空间 rdbchecksum yes # 是否校验rdb文件,更有利于文件的容错性,但是在保存rdb文件的时 候,会有大概10%的性能损耗 dbfilename dump.rdb # dbfilenamerdb文件名称 dir ./ # dir 数据目录,数据库的写入会在这个目录。rdb、aof文件也会写在这个目录
SECURITY
# 启动redis # 连接客户端 # 获得和设置密码 config get requirepass config set requirepass "123456" #测试ping,发现需要验证 127.0.0.1:6379> ping NOAUTH Authentication required. # 验证 127.0.0.1:6379> auth 123456 OK 127.0.0.1:6379> ping PONG
maxclients 10000 # 设置能连上redis的最大客户端连接数量 maxmemory <bytes> # redis配置的最大内存容量 maxmemory-policy noeviction # maxmemory-policy 内存达到上限的处理策略 #volatile-lru:利用LRU算法移除设置过过期时间的key。 #volatile-random:随机移除设置过过期时间的key。 #volatile-ttl:移除即将过期的key,根据最近过期时间来删除(辅以TTL) #allkeys-lru:利用LRU算法移除任何key。 #allkeys-random:随机移除任何key。 #noeviction:不移除任何key,只是返回一个写错误。
append only 模式
appendonly no # 是否以append only模式作为持久化方式,默认使用的是rdb方式持久化,这种 方式在许多应用中已经足够用了 appendfilename "appendonly.aof" # appendfilename AOF 文件名称 appendfsync everysec # appendfsync aof持久化策略的配置 # no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。 # always表示每次写入都执行fsync,以保证数据同步到磁盘。 # everysec表示每秒执行一次fsync,可能会导致丢失这1s数据
1、Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程 daemonize no 2、当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指 定 pidfile /var/run/redis.pid 3、指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认 端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字 port 6379 4、绑定的主机地址 bind 127.0.0.1 maxclients 10000 # 设置能连上redis的最大客户端连接数量 maxmemory <bytes> # redis配置的最大内存容量 maxmemory-policy noeviction # maxmemory-policy 内存达到上限的处理策略 #volatile-lru:利用LRU算法移除设置过过期时间的key。 #volatile-random:随机移除设置过过期时间的key。 #volatile-ttl:移除即将过期的key,根据最近过期时间来删除(辅以TTL) #allkeys-lru:利用LRU算法移除任何key。 #allkeys-random:随机移除任何key。 #noeviction:不移除任何key,只是返回一个写错误。 appendonly no # 是否以append only模式作为持久化方式,默认使用的是rdb方式持久化,这种 方式在许多应用中已经足够用了 appendfilename "appendonly.aof" # appendfilename AOF 文件名称 appendfsync everysec # appendfsync aof持久化策略的配置 # no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。 # always表示每次写入都执行fsync,以保证数据同步到磁盘。 # everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。 1 2 3 4 5 6 7 8 5、当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能 timeout 300 6、指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为 verbose loglevel verbose 7、日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方 式为标准输出,则日志将会发送给/dev/null logfile stdout 8、设置数据库的数量,默认数据库为0,可以使用SELECT 命令在连接上指定数据库id databases 16 9、指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合 save Redis默认配置文件中提供了三个条件: save 900 1 save 300 10 save 60 10000 分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更 改。 10、指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时 间,可以关闭该选项,但会导致数据库文件变的巨大 rdbcompression yes 11、指定本地数据库文件名,默认值为dump.rdb dbfilename dump.rdb 12、指定本地数据库存放目录 dir ./ 13、设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master 进行数据同步 slaveof 14、当master服务设置了密码保护时,slav服务连接master的密码 masterauth 15、设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH 命令提供密码, 默认关闭 requirepass foobared 16、设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可 以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时, Redis会关闭新的连接并向客户端返回max number of clients reached错误信息 maxclients 128 17、指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝 试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作, 但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区 maxmemory 18、指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不 开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来 同步的,所以有的数据会在一段时间内只存在于内存中。默认为no appendonly no 19、指定更新日志文件名,默认为appendonly.aof appendfilename appendonly.aof 20、指定更新日志条件,共有3个可选值: no:表示等操作系统进行数据缓存同步到磁盘(快) always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全) everysec:表示每秒同步一次(折衷,默认值) appendfsync everysec 21、指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将 访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔 细分析Redis的VM机制) vm-enabled no 22、虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享 vm-swap-file /tmp/redis.swap 23、将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据 都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有 value都存在于磁盘。默认值为0 vm-max-memory 0 24、Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多 个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page 大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用 默认值 vm-page-size 32 25、设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中 的,,在磁盘上每8个pages将消耗1byte的内存。 vm-pages 134217728 26、设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都 是串行的,可能会造成比较长时间的延迟。默认值为4 vm-max-threads 4 27、设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启 glueoutputbuf yes 28、指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法 hash-max-zipmap-entries 64 hash-max-zipmap-value 512 29、指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍) activerehashing yes 30、指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各 个实例又拥有自己的特定配置文件 include /path/to/local.conf