Redis 教程 Nosql 概述 为什么要用 Nosql
1、最早的单机 Mysql 的年代
90年代,一个网站的访问量一般不会太大,单个数据库完全足够!
大部分都是使用静态 html 网页,服务器根本没有压力
瓶颈如下:
1、数据量太大,一个机器放不下
2、数据索引(B+Tree),一个机器内存也放不下
3、访问量太大(读写混合),一个服务器承受不了
2、Memcached(缓存)+ MySQL + 垂直拆分(读写分离)
网站 80% 的情况都是在读,每次都去查询数据库就很麻烦,所以使用缓存来保证效率
发展过程:优化数据库结构和索引 → 文件缓存(IO)→ Memcache(当时最热门的技术)
3、分库分表 + 水平拆分 + 集群
早些年 MyISAM:表锁,十分影响效率,高并发下会出现严重的锁问题
转战 InnoDB:行锁
使用分库分表解决写的压力
4、如今的互联网架构模型
MySQL 等关系型数据库已经不够用了,各种各样的数据(音乐、热榜、图片等)都存在 MySQL 的话,数据库表很大,压力爆表,效率自然低下
为什么要用 NoSQL
用户的个人信息、社交网络、自理位置、用户日志等等爆发式增长!
这个时候就需要用 NoSQL 数据库,NoSQL 可以很好处理以上情况
什么是 NoSQL
NoSQL
NoSQL = Not Only Sql (不仅仅是sql)
泛指非关系型数据库,随着 web2.0 互联网的诞生,传统的关系型数据库很难应对,尤其是超大规模的高并发社区,暴露出来很多难以克服的问题。NoSQL 在当今大数据环境下发展十分迅速,Redis 是发展最快,当下必须要掌握的技术
很多的数据类型用户的个人信息、社交网络、自理位置、用户日志这些数据不需要固定格式,不需要多元的操作就可以横向扩展。
NoSQL 特点
1、方便扩展(数据之间没有关系)
2、大数据量高性能(Redis 一秒写 8 万次,一秒读取 11 万次,缓存记录集,性能高)
3、多种多样的数据类型(不需要事先设计数据库,随取随用)
4、传统的 RDBMS 和 NoSQL
1 2 3 4 5 6 7 8 传统的 RDBMS - 结构化组织 - SQL - 数据和关系存在单独的表中 row clo - 多元操作 - 严格的一致性 - 事务 - …………
1 2 3 4 5 6 7 8 NoSQL - 不仅仅是数据 - 没有固定的查询语言 - 键值对存储、列存储、文档存储、图形数据库(社交关系) - 最终一致性 - CAP 定理和 BASE - 高性能,高可用,高可扩展 - …………
了解:3V+3高
大数据时代的3V:
海量数据
多样
实时
大数据时代的3高:
高并发
高可扩
高性能
大厂的架构 - 商品
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 关系型数据库 - MySQL - Oracle 文档型数据库 - MongoDB 分布式文件系统 - FastDFS - TFS(淘宝) - GFS(Google) - Hadoop - oss(阿里云) 搜索引擎 - solr elasticsearch - Isearch 内存数据路 - redis - Memcache
Nosql 的四大分类 KV 键值对
新浪:Redis
美团:Redis + Tair
阿里、百度:Redis + Memcache
文档型数据库
MongoDB
MongoDB 是一个基于分布式文件存储的数据库,C++ 编写
主要用来处理大量的文档
MongoDB 是一个介于关系型数据库和非关系型数据库中间的产品,MongoDB 是非关系型数据库中功能最丰富,最像关系型数据库的
CouchDB
列存储数据库
图关系数据库(不是存图形,存的是关系,比如社交网络、广告推荐)
四者对比
Redis 入门 概述
Redis 是什么?
Redis(Re mote Di ctionary S erver ),即远程字典服务。是一个开源的使用ANSI C语言 编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库 ,并提供多种语言的API
与 memcached 一样,为了保证效率,数据都是缓存在内存中。区别的是 redis 会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave (主从)同步。
免费和开源!当下最热门的 NoSQL 技术之一,也被人们成为结构化数据库
Redis 能干嘛?
1、内存存储、持久化(rdb、aof)
2、效率高,可以用于高速缓存
3、发布订阅系统
4、地图信息分析
5、计时器、计数器(浏览量)
6、…………
Redis 特性
1、多样的数据类型
2、持久化
3、集群、事务
4、…………
Redis 推荐在 Linux 服务器上搭建
官网:https://redis.io/
中文网:http://redis.cn/
下载地址:通过官网下载即可
注意:Windows 在 GitHub 上下载(已停更)
Linux 安装 1、下载安装包
2、解压安装包
3、进入解压后的文件夹,可以看到配置文件
4、基本的环境安装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 [root@localhost redis-6.0.5] [root@localhost redis-6.0.5] make[1]: *** [server.o] Error 1 make[1]: Leaving directory `/usr/local/src/redis-6.0.5/src' make: *** [all] Error 2 # 升级 gcc [root@localhost redis-6.0.5]# yum -y install centos-release-scl [root@localhost redis-6.0.5]# yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils # 临时启用 gcc9 [root@localhost redis-6.0.5]# scl enable devtoolset-9 bash # 长期使用 [root@localhost redis-6.0.5]# echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile # 安装成功会出现 Hint: It' s a good idea to run 'make test' ;)[root@localhost redis-6.0.5] cd src && make installmake[1]: Entering directory `/usr/local/src/redis-6.0.5/src' Hint: It' s a good idea to run 'make test' ;) INSTALL install INSTALL install INSTALL install INSTALL install INSTALL install make[1]: Leaving directory `/usr/local/src/redis-6.0.5/src' [root@localhost redis-6.0.5]#
5、redis 的默认安装路径:/usr/local/bin
6、将 redis 配置文件复制到当前目录
7、redis 默认不是后台启动的,修改配置文件
8、启动 redis 服务
性能测试 redis-benchmark 是一个官方自带的性能测试工具
命令参数如下:
简单测试一下
1 2 redis-benchmark -h localhost -p 6379 -c 100 -n 100000
如何分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 [root@localhost bin] ====== SET ====== 100000 requests completed in 3.47 seconds 100 parallel clients 3 bytes payload keep alive: 1 host configuration "save" : 900 1 300 10 60 10000 host configuration "appendonly" : no multi-thread: no 0.00% <= 0.6 milliseconds 0.02% <= 0.7 milliseconds 3.56% <= 1.2 milliseconds 6.57% <= 1.3 milliseconds 10.28% <= 1.4 milliseconds 14.41% <= 1.5 milliseconds 18.84% <= 1.6 milliseconds 23.53% <= 1.7 milliseconds 99.91% <= 28 milliseconds 99.96% <= 29 milliseconds 99.98% <= 32 milliseconds 99.99% <= 33 milliseconds 100.00% <= 33 milliseconds 28785.26 requests per second
基础知识 Redis 默认有 16 个数据库(配置文件中)
默认使用的是第 0 个
可以使用 select 进行切换数据库
1 2 3 4 5 6 7 127.0.0.1:6379> select 3 OK 127.0.0.1:6379[3]> dbsize (integer ) 0 127.0.0.1:6379[3]>
1 2 3 127.0.0.1:6379[3]> keys * 1) "name" 127.0.0.1:6379[3]>
清空当前数据库:flushdb
1 2 3 4 5 127.0.0.1:6379[3]> flushdb OK 127.0.0.1:6379[3]> keys * (empty array) 127.0.0.1:6379[3]>
清空所有数据库:flushall
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 127.0.0.1:6379[3]> select 0 OK 127.0.0.1:6379> keys * 1) "mylist:{tag}" 2) "counter:{tag}:__rand_int__" 3) "key:{tag}:__rand_int__" 127.0.0.1:6379> select 3 OK 127.0.0.1:6379[3]> flushall OK 127.0.0.1:6379[3]> select 0 OK 127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379>
Redis 是单线程的!
Redis 是基于内存操作的,CPU 不是 Redis 的性能瓶颈,Redis 的瓶颈是在于机器的内存和网络的带宽。所以就使用了单线程
Redis 单线程为什么这么快?
Redis 是 C 语言写的,官方提供的数据为 100000+ 的 QPS,完全不比同样使用 key => value 的 Memcache 差
误区:
核心:Redis 是将数据存储在内存的,所以单线程处理是效率最好的,对于内存系统来说,如果没有上下文切换,效率就是最高的,多次读写都是在一个 CPU 上的
五大数据类型
官网文档
Redis-key 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 [root@localhost bin]# ./redis-cli -p 6379 127.0.0.1:6379> ping PONG 127.0.0.1:6379> set name cat # set key OK 127.0.0.1:6379> keys * 1) "name" 127.0.0.1:6379> set age 1 OK 127.0.0.1:6379> keys * 1) "age" 2) "name" 127.0.0.1:6379> move age 1 # 移除当前 key (integer) 1 127.0.0.1:6379> keys * 2) "name" 127.0.0.1:6379> exists name # 判断当前 key 是否存在 (integer) 1 127.0.0.1:6379> type name # 查看当前 key 的类型 string 127.0.0.1:6379> expire name 10 # 设置 key 的过期时间 (integer) 1 127.0.0.1:6379> get name "cat" 127.0.0.1:6379> ttl name # 查看当前 key 的剩余时间 (integer) -2 127.0.0.1:6379> get name (nil) 127.0.0.1:6379>
使用官网查询命令
String(字符串) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 127.0.0.1:6379> set key1 v1 OK 127.0.0.1:6379> get key1 "v1" 127.0.0.1:6379> keys * 1) "key1" 127.0.0.1:6379> exists key1 (integer ) 1 127.0.0.1:6379> append key1 hello (integer ) 7 127.0.0.1:6379> get key1 "v1hello" 127.0.0.1:6379> append key1 ", world" (integer ) 14 127.0.0.1:6379> strlen key1 (integer ) 14 127.0.0.1:6379> get key1 "v1hello, world" 127.0.0.1:6379> 127.0.0.1:6379> set views 0 OK 127.0.0.1:6379> get views "0" 127.0.0.1:6379> incr views (integer ) 1 127.0.0.1:6379> incr views (integer ) 2 127.0.0.1:6379> get views "2" 127.0.0.1:6379> decr views (integer ) 1 127.0.0.1:6379> get views "1" 127.0.0.1:6379> incrby views 10 (integer ) 11 127.0.0.1:6379> incrby views 10 (integer ) 21 127.0.0.1:6379> decrby views 5 (integer ) 16 127.0.0.1:6379> 127.0.0.1:6379> set key1 "hello world" OK 127.0.0.1:6379> get key1 "hello world" 127.0.0.1:6379> getrange key1 0 5 "hello " 127.0.0.1:6379> getrange key1 0 -1 "hello world" 127.0.0.1:6379> 127.0.0.1:6379> set key2 "abcdefg" OK 127.0.0.1:6379> get key2 "abcdefg" 127.0.0.1:6379> setrange key2 0 "xx" (integer ) 7 127.0.0.1:6379> get key2 "xxcdefg" 127.0.0.1:6379> 127.0.0.1:6379> setex key3 10 "2020" OK 127.0.0.1:6379> ttl key3 (integer ) 3 127.0.0.1:6379> get key3 (nil) 127.0.0.1:6379> setnx mykey "apple" (integer ) 1 127.0.0.1:6379> keys * 1) "key2" 2) "mykey" 3) "key1" 127.0.0.1:6379> setnx mykey "banner" (integer ) 0 127.0.0.1:6379> get mykey "apple" 127.0.0.1:6379> 127.0.0.1:6379> flushall OK 127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 OK 127.0.0.1:6379> keys * 1) "k2" 2) "k1" 3) "k3" 127.0.0.1:6379> mget k1 k2 k3 1) "v1" 2) "v2" 3) "v3" 127.0.0.1:6379> msetnx k1 v1 k4 v4 (integer ) 0 127.0.0.1:6379> get k4 (nil) 127.0.0.1:6379> 127.0.0.1:6379> getset db redis (nil) 127.0.0.1:6379> get db "redis" 127.0.0.1:6379> getset db mongodb "redis" 127.0.0.1:6379> get db "mongodb" 127.0.0.1:6379>
想象一个简单业务场景:
要存储用户1的关注数 10、粉丝数 20;用户2的关注数 5、粉丝数 30;
使用 redis 如何存储
方案一:
1 2 3 4 5 6 127.0.0.1:6379> mset user:1 {follow:10,fans:20} user:2 {follow:5,fans:30} OK 127.0.0.1:6379> mget user:1 user:2 1) "{follow:10,fans:20}" 2) "{follow:5,fans:30}" 127.0.0.1:6379>
得到数据后,进行解析
方案二:
1 2 3 4 5 6 7 8 127.0.0.1:6379> mset user:1:follow 10 user:1:fans 20 user:2:follow 5 user:2:fans 30 OK 127.0.0.1:6379> mget user:1:follow user:1:fans user:2:follow user:2:fans 1) "10" 2) "20" 3) "5" 4) "30" 127.0.0.1:6379>
得到数据后,不用解析
List
在 redis 里面,栈、堆、队列、阻塞队列都可以用 list 来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 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> lrange list 0 -1 1) "three" 2) "two" 3) "one" 127.0.0.1:6379> lrange list 0 1 1) "three" 2) "two" 127.0.0.1:6379> rpush list xxx (integer ) 4 127.0.0.1:6379> lrange list 0 -1 1) "three" 2) "two" 3) "one" 4) "xxx" 127.0.0.1:6379> 127.0.0.1:6379> lrange list 0 -1 1) "three" 2) "two" 3) "one" 4) "xxx" 127.0.0.1:6379> lpop list "three" 127.0.0.1:6379> rpop list "xxx" 127.0.0.1:6379> lrange list 0 -1 1) "two" 2) "one" 127.0.0.1:6379> 127.0.0.1:6379> lrange list 0 -1 1) "two" 2) "one" 127.0.0.1:6379> lindex list 1 "one" 127.0.0.1:6379> lindex list 0 "two" 127.0.0.1:6379> 127.0.0.1:6379> lpush list one two three (integer ) 3 127.0.0.1:6379> llen list (integer ) 3 127.0.0.1:6379> 127.0.0.1:6379> lpush list three (integer ) 4 127.0.0.1:6379> lrange list 0 -1 1) "three" 2) "three" 3) "two" 4) "one" 127.0.0.1:6379> lrem list 1 one (integer ) 1 127.0.0.1:6379> lrange list 0 -1 1) "three" 2) "three" 3) "two" 127.0.0.1:6379> lrem list 1 three (integer ) 1 127.0.0.1:6379> lrange list 0 -1 1) "three" 2) "two" 127.0.0.1:6379> lpush list three (integer ) 3 127.0.0.1:6379> lrem list 2 three (integer ) 2 127.0.0.1:6379> lrange list 0 -1 1) "two" 127.0.0.1:6379> 127.0.0.1:6379> lpush list 'helllo' 'hello1' 'hello2' 'hello3' (integer ) 4 127.0.0.1:6379> lrange list 0 -1 1) "hello3" 2) "hello2" 3) "hello1" 4) "helllo" 127.0.0.1:6379> ltrim list 1 2 OK 127.0.0.1:6379> lrange list 0 -1 1) "hello2" 2) "hello1" 127.0.0.1:6379> 127.0.0.1:6379> lpush mylist "hello" (integer ) 1 127.0.0.1:6379> lpush mylist "hello1" (integer ) 2 127.0.0.1:6379> lpush mylist "hello2" (integer ) 3 127.0.0.1:6379> rpoplpush mylist mylist1 "hello" 127.0.0.1:6379> lrange mylist 0 -1 1) "hello2" 2) "hello1" 127.0.0.1:6379> lrange mylist1 0 -1 1) "hello" 127.0.0.1:6379> 127.0.0.1:6379> flushdb OK 127.0.0.1:6379> exists list (integer ) 0 127.0.0.1:6379> lset list 0 item (error) ERR no such key 127.0.0.1:6379> lpush list value1 (integer ) 1 127.0.0.1:6379> lrange list 0 0 1) "value1" 127.0.0.1:6379> lset list 0 item OK 127.0.0.1:6379> lrange list 0 0 1) "item" 127.0.0.1:6379> 127.0.0.1:6379> rpush mylist hello (integer ) 1 127.0.0.1:6379> rpush mylist world (integer ) 2 127.0.0.1:6379> linsert mylist before world other (integer ) 3 127.0.0.1:6379> lrange mylist 0 -1 1) "hello" 2) "other" 3) "world" 127.0.0.1:6379>
list 小结
实际上是一个链表,left、right 都可以插入值
如果 key 不存在,创建新的链表
如果 key 存在,新增内容
如果移除了所有值,就是空链表,也意味着该 key 也不存在了
在两边插入或改动值,效率最高。
消息队列(lpush rpop),栈(lpush lpop)
Set(集合) set 中的值是不能重复的(无序不重复集合)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 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> smembers myset 1) "hello" 2) "world" 127.0.0.1:6379> sismember myset hello (integer ) 1 127.0.0.1:6379> sismember myset cat (integer ) 0 127.0.0.1:6379> scard myset (integer ) 2 127.0.0.1:6379> srem myset world (integer ) 1 127.0.0.1:6379> smembers myset 1) "hello" 127.0.0.1:6379> 127.0.0.1:6379> SRANDMEMBER myset "world" 127.0.0.1:6379> SRANDMEMBER myset "cat" 127.0.0.1:6379> SRAND2MEMBER myset "dog" 127.0.0.1:6379> 127.0.0.1:6379> spop myset "hello" 127.0.0.1:6379> spop myset "dog" 127.0.0.1:6379> SMEMBERS myset 1) "cat" 2) "world" 127.0.0.1:6379> 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 dog (integer ) 1 127.0.0.1:6379> SMEMBERS myset 1) "hello" 2) "dog" 3) "world" 127.0.0.1:6379> sadd myset2 cat (integer ) 1 127.0.0.1:6379> SMEMBERS myset2 2) "dog" 127.0.0.1:6379> smove myset myset2 dog (integer ) 1 127.0.0.1:6379> SMEMBERS myset 1) "hello" 2) "world" 127.0.0.1:6379> SMEMBERS myset2 1) "cat" 2) "dog" 127.0.0.1:6379> 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) "b" 2) "a" 127.0.0.1:6379> SINTER key1 key2 1) "c" 127.0.0.1:6379> SUNION key1 key2 1) "b" 2) "c" 3) "a" 4) "e" 5) "d" 127.0.0.1:6379>
Hash(哈希) Map 集合,key-map 这时候的值是一个 map 集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 127.0.0.1:6379> hset myhash field1 hello (integer ) 1 127.0.0.1:6379> hget myhash field1 "hello" 127.0.0.1:6379> hmset myhash field1 hello field2 world (integer ) 1 127.0.0.1:6379> hget myhash field1 "hello" 127.0.0.1:6379> hget myhash field2 "world" 127.0.0.1:6379> hmget myhash field1 field2 1) "hello" 2) "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" 127.0.0.1:6379> hlen myhash (integer ) 1 127.0.0.1:6379> hmset myhash field1 hello OK 127.0.0.1:6379> hlen myhash (integer ) 2 127.0.0.1:6379> hgetall myhash 1) "field2" 2) "world" 3) "field1" 4) "hello" 127.0.0.1:6379> HEXISTS myhash field1 (integer ) 1 127.0.0.1:6379> HEXISTS myhash field3 (integer ) 0 127.0.0.1:6379> hkeys myhash 1) "field2" 2) "field1" 127.0.0.1:6379> hvals myhash 1) "world" 2) "hello" 127.0.0.1:6379> HINCRBY myhash field3 1 (integer ) 1 127.0.0.1:6379> HINCRBY myhash field3 3 (integer ) 4 127.0.0.1:6379> HINCRBY myhash field3 -1 (integer ) 3 127.0.0.1:6379> HSETNX myhash field4 dog (integer ) 1 127.0.0.1:6379> HSETNX myhash field4 cat (integer ) 0 127.0.0.1:6379>
Zset(有序集合) 在 set 的基础上,增加了一个值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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" 127.0.0.1:6379> 127.0.0.1:6379> zadd salary 2500 zhangsan (integer ) 1 127.0.0.1:6379> zadd salary 3000 zlisi (integer ) 1 127.0.0.1:6379> zadd salary 100 wangwu (integer ) 1 127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf 1) "wangwu" 2) "zhangsan" 3) "zlisi" 127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores 1) "wangwu" 2) "100" 3) "zhangsan" 4) "2500" 5) "zlisi" 6) "3000" 127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 1) "wangwu" 2) "zhangsan" 127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores 1) "wangwu" 2) "100" 3) "zhangsan" 4) "2500" 127.0.0.1:6379> 127.0.0.1:6379> zrange salary 0 -1 1) "wangwu" 2) "zhangsan" 3) "zlisi" 127.0.0.1:6379> zrem salary zlisi (integer ) 1 127.0.0.1:6379> zrange salary 0 -1 1) "wangwu" 2) "zhangsan" 127.0.0.1:6379> ZREVRANGE salary 0 -1 1) "zhangsan" 2) "wangwu" 127.0.0.1:6379> zcard salary (integer ) 2 127.0.0.1:6379> 127.0.0.1:6379> zadd myset 1 hello 2 world 3 cat (integer ) 3 127.0.0.1:6379> zcount myset 1 3 (integer ) 3 127.0.0.1:6379> zcount myset 1 2 (integer ) 2 127.0.0.1:6379>
三种特殊类型 geospatial 地理位置 可以推算地理位置的信息,比如两地之间的距离,方圆几里的人
可以查询一些测试数据:http://www.jsons.cn/lngcode/
中文官网文档:https://www.redis.net.cn/order/3685.html
只有六个命令:
1、geoadd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing (integer ) 1 127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai (integer ) 1 127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shenzhen (integer ) 2 127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou 108.96 34.26 xian (integer ) 2 127.0.0.1:6379>
geopos
获得的定位一定是一个坐标值
1 2 3 4 5 6 127.0.0.1:6379> geopos china:city beijing shanghai 1) 1) "116.39999896287918091" 2) "39.90000009167092543" 2) 1) "121.47000163793563843" 2) "31.22999903975783553" 127.0.0.1:6379>
geodist
GEODIST 命令 - 返回两个给定位置之间的距离
如果两个位置之间的其中一个不存在, 那么命令返回空值。
指定单位的参数 unit 必须是以下单位的其中一个:
m 表示单位为米。
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺。
如果用户没有显式地指定单位参数, 那么 GEODIST
默认使用米作为单位。
GEODIST
命令在计算距离时会假设地球为完美的球形, 在极限情况下, 这一假设最大会造成 0.5% 的误差。
返回值
计算出的距离会以双精度浮点数的形式被返回。 如果给定的位置元素不存在, 那么命令返回空值。
1 2 3 4 5 127.0.0.1:6379> GEODIST china:city beijing shanghai "1067378.7564" 127.0.0.1:6379> GEODIST china:city beijing shanghai km "1067.3788" 127.0.0.1:6379>
georadius
GEORADIUS 命令 - 以给定的经纬度为中心, 找出某一半径内的元素
以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
范围可以使用以下其中一个单位:
m 表示单位为米。
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺。
在给定以下可选项时, 命令会返回额外的信息:
WITHDIST
:在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。
WITHCOORD
:将位置元素的经度和维度也一并返回。
WITHHASH
:以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。
命令默认返回未排序的位置元素。 通过以下两个参数, 用户可以指定被返回位置元素的排序方式:
ASC
:根据中心的位置, 按照从近到远的方式返回位置元素。
DESC
:根据中心的位置, 按照从远到近的方式返回位置元素。
在默认情况下, GEORADIUS 命令会返回所有匹配的位置元素。 虽然用户可以使用 COUNT <count>
选项去获取前 N 个匹配元素, 但是因为命令在内部可能会需要对所有被匹配的元素进行处理, 所以在对一个非常大的区域进行搜索时, 即使只使用 COUNT
选项去获取少量元素, 命令的执行速度也可能会非常慢。 但是从另一方面来说, 使用 COUNT
选项去减少需要返回的元素数量, 对于减少带宽来说仍然是非常有用的。
返回值
在没有给定任何 WITH
选项的情况下, 命令只会返回一个像 [“New York”,”Milan”,”Paris”] 这样的线性(linear)列表。
在指定了 WITHCOORD
、 WITHDIST
、 WITHHASH
等选项的情况下, 命令返回一个二层嵌套数组, 内层的每个子数组就表示一个元素。
在返回嵌套数组时, 子数组的第一个元素总是位置元素的名字。 至于额外的信息, 则会作为子数组的后续元素, 按照以下顺序被返回:
以浮点数格式返回的中心与位置元素之间的距离, 单位与用户指定范围时的单位一致。
geohash 整数。
由两个元素组成的坐标,分别为经度和纬度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 127.0.0.1:6379> GEORADIUS china:city 110 30 500 km 1) "chongqing" 2) "xian" 127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km 1) "chongqing" 2) "xian" 3) "shenzhen" 4) "hangzhou" 127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist 1) 1) "chongqing" 2) "341.9374" 2) 1) "xian" 2) "483.8340" 127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord 1) 1) "chongqing" 2) 1) "106.49999767541885376" 2) "29.52999957900659211" 2) 1) "xian" 2) 1) "108.96000176668167114" 2) "34.25999964418929977" 127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withcoord withdist count 2 1) 1) "chongqing" 2) "341.9374" 3) 1) "106.49999767541885376" 2) "29.52999957900659211" 2) 1) "xian" 2) "483.8340" 3) 1) "108.96000176668167114" 2) "34.25999964418929977" 127.0.0.1:6379>
georadiusbymember
1 2 3 4 127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km 1) "beijing" 2) "xian" 127.0.0.1:6379>
geohash
该命令将返回11个字符的 Geohash 字符串
1 2 3 4 5 127.0.0.1:6379> GEOHASH china:city beijing shanghai 1) "wx4fbxxfke0" 2) "wtw3sj5zbj0" 127.0.0.1:6379>
geo 底层就是基于 zset,可以通过 zset 命令操作 geo
1 2 3 4 5 6 7 8 9 10 127.0.0.1:6379> zrange china:city 0 -1 1) "chongqing" 2) "xian" 3) "shenzhen" 4) "hangzhou" 5) "shanghai" 6) "beijing" 127.0.0.1:6379> zrem china:city beijing (integer ) 1 127.0.0.1:6379>
hyperloglog
简介
hyperloglog 是用来统计基数的算法
网页的 UV (一个人访问网站多次,但还是算作一个人)
传统的方式,利用 set 来保存用户id,因为 set 不允许重复,就可以统计 set 中的元素作为标准判断
这个方式如果保存大量的用户 id,就会比较麻烦(消耗大量的空间保存用户id),我们的目的是计数,而不是用来保存用户id
优点:Hyperloglog 占用的内存是固定的,如果从内存角度来说,hyperloglog 一定是首选
0.81% 错误率
1 2 3 4 5 6 7 8 9 10 11 12 13 127.0.0.1:6379> PFADD mykey a b c d e f g (integer ) 1 127.0.0.1:6379> PFCOUNT mykey (integer ) 7 127.0.0.1:6379> PFADD mykey2 e f g h i j k (integer ) 1 127.0.0.1:6379> PFCOUNT mykey2 (integer ) 7 127.0.0.1:6379> PFMERGE mykey3 mykey mykey2 OK 127.0.0.1:6379> PFCOUNT mykey3 (integer ) 11 127.0.0.1:6379>
如果允许容错,一定使用 hyperloglog
如果不允许容错,使用 set 或自己的数据类型就可以
bitmaps
位存储
一种位图数据结构,操作二进制来进行记录,只有 0 和 1 两个状态
业务场景一:
记录用户周一到周日(0-6)的签到情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 127.0.0.1:6379> setbit sign_1 0 1 (integer ) 0 127.0.0.1:6379> setbit sign_1 1 1 (integer ) 0 127.0.0.1:6379> setbit sign_1 2 1 (integer ) 0 127.0.0.1:6379> setbit sign_1 3 0 (integer ) 0 127.0.0.1:6379> setbit sign_1 4 0 (integer ) 0 127.0.0.1:6379> setbit sign_1 5 1 (integer ) 0 127.0.0.1:6379> setbit sign_1 6 0 (integer ) 0 127.0.0.1:6379> getbit sign_1 2 (integer ) 1 127.0.0.1:6379> bitcount sign_1 (integer ) 4 127.0.0.1:6379>
业务场景二:
用户在线状态
1 2 3 4 5 6 7 8 9 10 11 12 127.0.0.1:6379> setbit online 710 1 (integer ) 0 127.0.0.1:6379> setbit online 711 0 (integer ) 0 127.0.0.1:6379> getbit online 710 (integer ) 1 127.0.0.1:6379> getbit online 711 (integer ) 0 127.0.0.1:6379>
事务 ACID 原则:
原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency)
事务前后数据的完整性必须保持一致。
隔离性(Isolation)
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
redis 的事务没有隔离性的概念
redis 事务的特性:
一次性、顺序性、排他性
redis 事务的基础命令:
开启事务(multi)
命令入队(……)
执行事务(exec)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 127.0.0.1:6379> multi OK 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> get k2 QUEUED 127.0.0.1:6379> set k3 v3 QUEUED 127.0.0.1:6379> exec 1) OK 2) OK 3) "v2" 4) OK 127.0.0.1:6379> 127.0.0.1:6379> multi OK 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> set k4 v4 QUEUED 127.0.0.1:6379> discard OK 127.0.0.1:6379> get k4 (nil) 127.0.0.1:6379>
编译型异常(命令错误,事务中所有的命令都不会被执行)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 127.0.0.1:6379> multi OK 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> set k3 v3 QUEUED 127.0.0.1:6379> getset k3 (error) ERR wrong number of arguments for 'getset' command 127.0.0.1:6379> set k4 v4 QUEUED 127.0.0.1:6379> set k5 v5 QUEUED 127.0.0.1:6379> exec (error) EXECABORT Transaction discarded because of previous errors. 127.0.0.1:6379> get k1 (nil) 127.0.0.1:6379>
运行时异常(运行错误,事务中的命令是可以正常执行的。运行错误的命令抛出异常)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 127.0.0.1:6379> set k1 "v1" OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> incr k1 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> set k3 v3 QUEUED 127.0.0.1:6379> get k3 QUEUED 127.0.0.1:6379> exec 1) (error) ERR value is not an integer or out of range 2) OK 3) OK 4) "v3" 127.0.0.1:6379> get k2 "v2" 127.0.0.1:6379>
redis 事务实现监控
正常执行成功的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 127.0.0.1:6379> set money 100 OK 127.0.0.1:6379> set out 0 OK 127.0.0.1:6379> watch money OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> DECRBY money 20 QUEUED 127.0.0.1:6379> INCRBY out 20 QUEUED 127.0.0.1:6379> exec 1) (integer ) 80 2) (integer ) 20 127.0.0.1:6379>
测试多线程修改值,使用 watch
实现乐观锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 127.0.0.1:6379> WATCH money OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> DECRBY money 10 QUEUED 127.0.0.1:6379> INCRBY out 10 QUEUED 127.0.0.1:6379> exec (nil) 127.0.0.1:6379> UNWATCH OK 127.0.0.1:6379> watch money OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> DECRBY money 100 QUEUED 127.0.0.1:6379> incrby out 100 QUEUED 127.0.0.1:6379> exec 1) (integer ) 900 2) (integer ) 120 127.0.0.1:6379>
redis.conf 详解 启动 redis 服务时,就是使用 redis.conf 启动的
单位
配置文件中的 unit 单位对大小写不敏感
包含文件
可以包含其他配置文件
网络
1 2 3 bind 127.0.0.1 protected-mode yes port 6379
通用 GENERAL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 daemonize yes pidfile /var/run/redis_6379.pid loglevel notice logfile "" databases 16 always-show-logo yes
快照
redis 是内存数据库,如果没有持久化,那么数据断电及失
1 2 3 4 5 6 7 8 9 10 11 save 900 1 save 300 10 save 60 1000 stop-writes-on-bgsave-error yes rdbcompression yes rdbchecksum yes dir ./
REPLICATION 主从配置
SECURITY 安全
可以设置 redis 的密码,默认是没有密码的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 127.0.0.1:6379> ping PONG 127.0.0.1:6379> config get requirepass 1) "requirepass" 2) "" 127.0.0.1:6379> config set requirepass "123456" OK 127.0.0.1:6379> [root@localhost bin] 127.0.0.1:6379> ping (error) NOAUTH Authentication required. 127.0.0.1:6379> config get requirepass (error) NOAUTH Authentication required. 127.0.0.1:6379> auth 123456 OK 127.0.0.1:6379> ping PONG 127.0.0.1:6379> config get requirepass 1) "requirepass" 2) "123456" 127.0.0.1:6379>
CLIENTS 限制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 maxclients 10000 maxmemory <bytes> maxmemory-policy noeviction
APPEND ONLY MODE aof 配置
1 2 3 4 5 6 appendonly no appendfilename "appendonly.aof" appendfsync everysec
redis 持久化 redis 是内存数据库,如果不将内存中的数据保存到磁盘,那么一旦服务器进程退出,服务器中的数据也会丢失,所以 redis 提供了持久化功能!
RDB(Redis DataBase)
什么是 RDB
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的 Snapshot 快照,它恢复时是将快照中的文件直接读到内存中。
redis 会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程不进行任何 IO 操作,这就确保了极高的性能。如果需要大规模数据的恢复,且对于数据恢复的完整性不是很敏感,那么 RDB 方式要比 ROF 方式更高效,RBD 的缺点就是最后一次持久化后的数据可能丢失,默认的就是 RDB,一般情况下不需要修改这个配置
rdb 保存的文件:dump.rdb
触发机制
满足 save 规则时,会触发 rdb
执行 flushall,也会触发 rdb
退出 redis ,也会触发 rdb
如何恢复 rdb 文件
只要将 rdb 文件放在 redis 启动目录中,redis 启动的时候就会自动检查 dump.rdb 恢复其中的数据
查看 rdb 文件存放位置
1 2 3 4 127.0.0.1:6379> config get dir 1) "dir" 2) "/usr/local/bin" 127.0.0.1:6379>
优点
适合大规模数据恢复
对数据完整性要求不高
缺点
需要一定时间间隔进行操作,如果 redis 意外宕机了,最后一次修改的数据就没有了
fork 进程时,会占用一定的内存空间
AOF(Append Only File) 将所有写入的命令记录下来,恢复时将命令重新执行一遍
AOF 是什么
以日志的形式记录每个写操作,将 redis 执行过的所有执行记录下来(读操作不记录),只许追加文件但不可以改写文件,redis 启动时会读取该文件重新构建数据。换言之,redis 重启就是根据日志文件的内容将指令从前往后执行一次,完成数据的恢复工作
AOF 保存的文件:appendonly.aof
默认是不开启的,需要手动开启 appendonly yes
重启 redis 即可生效
进入 redis 执行命令,然后关机退出
查看 appendonly.aof
1 vim /usr/local/bin/appendonly.aof
手动修改 appendpnly.aof ,故意破坏内容后再次启动 redis 发现启动失败
Redis 提供一个工具,用来修复 aof 文件
修复成功后,重新启动 redis,会发现数据已恢复
但是发现,k3 的数据丢失了!
优点
每一次修改都会同步,文件的完整性会更好
拥有不同的同步策略
1 2 3 4 5 6 appendonly no appendfilename "appendonly.aof" appendfsync everysec
缺点
aof 的数据文件远远大于 rdb,恢复速度很慢
运行效率也低于 rdb,所以 redis 默认是用 rdb 持久化
aof 文件的重新规则
Aof 的规则就是无限追加,文件就会越来越大
文件大于 64 M,就会 fork 一个新的进程重写文件
RBD 和 ROF 的对比
扩展:
RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储
AOF 持久化方式记录每次对服务器写的操作,当服务器重启时会重新执行这些命令来回复数据,AOF 命令以 Reids 协议追加保存每次写的操作到文件末尾(redis 还能对 aof 文件进行后台重写,使得 aof 文件的体积不至于过大)
如果只做缓存,只希望数据在服务器运行的时候存在,就不使用任何持久化
同时开启两种持久化方式
这种情况下,当 redis 重启的时候会优先载入 aof 文件来恢复原始的数据,因为在通常的情况下 aof 文件保存的数据集要比 rdb 文件保存的数据集更加完整
rdb 的数据不实时,同时使用两者时服务器重启也会只找 aof 文件。那是不是只使用 aof 呢?建议不要,因为 rdb 更适合用于备份数据库(aof 在不断变化不好备份),快速重启,而且不会有 aof 可能潜在的bug,留着作为一个万一的手段
性能建议
因为 rdb 文件只用作备份用途,建议只在 slave 上持久化 rdb 文件,而且只要 15 分钟备份一次就够了,只保留 save 900 1
这条规则
如果开启 aof,好处是在最恶劣情况下丢失数据也不会超过两秒数据,启动脚本简单只需要 load 自己的 aof 文件即可。代价一是带来了持续的 IO,二是 aof rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少 aof rewrite 的频率,aof rewrite 的基础大小默认值 64M 太小了,可以设置到 5G 以上,默认超过原大小 100% 重新可以改到适当的数值
如果不开启 aof,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔 IO,也减少了 rewrite 时带来的系统波动。代价是如果 Master/Slave 同时宕掉,会丢失十几分钟的数据,启动脚本也要比较两个 Master/Slave 中的 rdb 文件,载入比较新的那一个,微博就是这种
Redis 订阅发布 Redis 订阅发布(pub/sub)是一种消息通信模式
发布者(pub)发送消息、订阅者(sub)接收消息
redis 一个客户端可以订阅任意数量的频道
订阅发布消息图:
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
命令
测试
订阅端:
1 2 3 4 5 6 7 8 9 10 11 12 13 127.0.0.1:6379> SUBSCRIBE star Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "star" 3) (integer ) 1 1) "message" 2) "star" 3) "liuyifei" 1) "message" 2) "star" 3) "dilireba"
发送端:
1 2 3 4 5 127.0.0.1:6379> PUBLISH star "liuyifei" (integer ) 1 127.0.0.1:6379> PUBLISH star "dilireba" (integer ) 1 127.0.0.1:6379>
原理
通过 SUBSCRIBE 命令订阅某频道后,redis-server 里维护了一个字典,字典的键就是一个个的 channel,而字典的值则是一个链表,链表保存了所有订阅这个 channel 的客户端,SUBSCRIBE 命令的关键,就是将客户端添加到给定 channel 的订阅链表中
通过 PUBLISH 命令向订阅者发送消息,redis-server 会使用给定的 channel 作为键,在它维护的字典中查找记录了订阅这个 channel 的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者
Pub/Sub 从字面上理解就是发布(Publish)和订阅(Subscribe),在 Redis 中,你可以设定对某一个 key 值进行消息发布及消息订阅,当一个 key 值进行消息发布后,所有订阅它的客户端都会收到对应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天、群聊等
Redis 主从复制 概念 主从复制,是指将一台 Redis 服务器的数据,复制到其他的 Redis 服务器。前者称为主节点(master),后者称为从节点(slave)。数据的复制是单向的,只能由主节点到从节点 。Master 以写为主,Slave 以读为主
默认情况下,每台 Redis 服务器都是主节点
一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点
主从复制的作用主要包括:
数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis 数据时应用连接主节点,读 Redis 数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高 Redis 服务器的并发量。
高可用(集群)基石:主从复制还是哨兵和集群能够实施的基础,因此说主从复制是 Redis 高可用的基础。
一般来说,要将 Redis 运用于工程项目中,只使用一台 Redis 是万万不能的,原因如下:
从结构上,单个 Redis 服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大
从容量上,单个 Redis 服务器内存容量有限,就算一台 Redis 服务器内容容量为 256G,也不能将所有内容用作 Redis 存储内存,一般来说,单台 Redis 最大使用内存不应该超过20G
电商网站上的商品,一般都是一次上传,无数次浏览,多读少写
对于这种场景,使用如下架构:
环境配置 只配置从库,不用配置主库
1 2 3 4 5 6 7 8 9 10 11 12 13 127.0.0.1:6379> info replication role:master connected_slaves:0 master_replid:b2f799e62d946ebb93873e5dae635337433fecd6 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:0 second_repl_offset:-1 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0 127.0.0.1:6379>
复制配置文件进行修改
1 2 3 4 5 6 7 8 9 [root@localhost config] /usr/local/bin/config [root@localhost config] total 336 -rw-r--r--. 1 root root 82658 Jul 10 08:32 redis6379.conf -rw-r--r--. 1 root root 82658 Jul 10 08:33 redis6380.conf -rw-r--r--. 1 root root 82658 Jul 10 08:35 redis6381.conf -rw-r--r--. 1 root root 82646 Jul 10 06:47 redis.conf [root@localhost config]
修改 redis6379.conf、redis6380.conf、redis6381.conf
端口
pid 文件
log 文件名字 logfile
dump.db 文件名字 dbfilename
依次使用这三个配置文件,启动三个服务
1 2 3 [root@localhost bin] [root@localhost bin] [root@localhost bin]
分别连接三个服务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 [root@localhost bin] 127.0.0.1:6379> info replication role:master connected_slaves:0 master_replid:b2f799e62d946ebb93873e5dae635337433fecd6 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:0 second_repl_offset:-1 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0 127.0.0.1:6379> [root@localhost bin] 127.0.0.1:6380> info replication role:master connected_slaves:0 master_replid:16ec0af43a4a5298c2fe49d925647fb1f86e670f master_replid2:0000000000000000000000000000000000000000 master_repl_offset:0 second_repl_offset:-1 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0 127.0.0.1:6380> [root@localhost bin] 127.0.0.1:6381> info replication role:master connected_slaves:0 master_replid:85dc09574791d7773085c83463bcb4338093252b master_replid2:0000000000000000000000000000000000000000 master_repl_offset:0 second_repl_offset:-1 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0 127.0.0.1:6381>
一主二从 默认情况下,每台 redis 服务器都是主节点 ,所以只用配置从机就好了
我们使用 6379 端口的作为主机,6380、6381 作为从机
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 OK 127.0.0.1:6380> info replication role:slave master_host:127.0.0.1 master_port:6379 master_link_status:up master_last_io_seconds_ago:5 master_sync_in_progress:0 slave_repl_offset:14 slave_priority:100 slave_read_only:1 connected_slaves:0 master_replid:f9577f9528ab15f137d134365fb44acf24336789 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:14 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:14 127.0.0.1:6380> 127.0.0.1:6379> info replication role:master connected_slaves:1 slave0:ip=127.0.0.1,port=6380,state=online,offset=42,lag=1 master_replid:f9577f9528ab15f137d134365fb44acf24336789 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:42 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:42 127.0.0.1:6379> 127.0.0.1:6381> SLAVEOF 127.0.0.1 6379 OK 127.0.0.1:6381> info replication role:slave master_host:127.0.0.1 master_port:6379 master_link_status:up master_last_io_seconds_ago:5 master_sync_in_progress:0 slave_repl_offset:280 slave_priority:100 slave_read_only:1 connected_slaves:0 master_replid:f9577f9528ab15f137d134365fb44acf24336789 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:280 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:281 repl_backlog_histlen:0 127.0.0.1:6381> 127.0.0.1:6379> info replication role:master connected_slaves:2 slave0:ip=127.0.0.1,port=6380,state=online,offset=364,lag=0 slave1:ip=127.0.0.1,port=6381,state=online,offset=364,lag=1 master_replid:f9577f9528ab15f137d134365fb44acf24336789 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:364 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:364 127.0.0.1:6379>
真实的主从配置应该从配置文件中配置,我们这里使用命令进行配置,只是暂时的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
细节
主机可以读写,从机只能读,主机中的所有信息和数据,都会同步到从机
主机写:
从机读:
测试:主机断开连接,从机是依旧链接到主机的,当主机重连时,从机依旧可以连接到主机
如果使用命令行配置主从,从机断开连接重新连接后,默认又会变回成主机的状态,再次配置为从机后,立马会将主机的数据同步过来
复制原理
Slave 启动成功连接到 Master 后会发送一个 sync 同步命令
Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕后,Master 将传送整个数据文件到 Slave,并完成一次完全同步
全量复制:Slave 服务在接受到数据文件数据后, 将其存盘并加载到内存中
增量复制:Master 继续将所有收集到的修改命令依次传给 Slave,完成同步
当 Slave 第一次连接或重新连接到 Master 时,一次完全同步(全量复制)将被自动执行
层层链路
也可以实现主从复制
当 Master 断开连接,是否可以选择一台 Slave 作为新的 Master
当主机断开连接时,使用 SLAVEOF no one
设置从机成为新的主机,其他从机就可以连接新的主机了(手动)
哨兵模式
概述
当主机宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费时费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候我们优先考虑哨兵模式,Redis 从 2.8 就开始正式提供了 Sentinel (哨兵)架构来解决这个问题。
哨兵模式是一种特殊的模式,首先 Redis 提供了哨兵的命令,哨兵是一个独立运行的进程,其原理是哨兵通过发送命了,等待 Redis 服务器响应,从而监控运行的多个 Redis 实例
哨兵的两个作用
通过发送命令,让 Redis 服务器返回信息,监控其运行状态,包括主服务器和从服务器
当哨兵检测到 Master 宕机,会自动将 Slave 切换成 Matser,然后通过订阅发布模式 通知其他从服务器,修改配置文件,使从服务器重新绑定主服务器
然后一个哨兵进程对多个 Redis 服务器进行监控,可能会出现问题,为此,我们使用多个哨兵进行监控,各个哨兵之间还会进行互相监控,这样就形成了多哨兵模式
假设主服务器宕机,哨兵 1 先检测到这个结果,系统不会马上进行 failover 过程,仅仅是哨兵 1 主观的认为主服务器不可用,最个现象称为主观下线 。当后面的哨兵也检测到主服务器不可用,且数量达到一定值的时候,哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行 failover(故障转移)的操作。切换成功后,通过订阅发布模式,由各个哨兵将自己监控的服务器切换主机,这个过程称为客观下线
测试
目前的状态是一主二从(6379 主机、6380、6381从机)
1、配置哨兵配置文件 sentinel.conf
1 2 3 sentinel monitor myredis 127.0.0.1 6379 1
后面数字1,代表主机挂了,进行投票看让哪一台 slave 接替成为主机,票高者当选
2、启动哨兵
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 [root@localhost bin] 9761:X 10 Jul 2020 10:53:43.059 9761:X 10 Jul 2020 10:53:43.059 9761:X 10 Jul 2020 10:53:43.059 9761:X 10 Jul 2020 10:53:43.060 * Increased maximum number of open files to 10032 (it was originally set to 1024). _._ _.-``__ '' -._ _.-`` `. `_. '' -._ Redis 6.0.5 (00000000/0) 64 bit .-`` .-```. ```\/ _.,_ '' -._ ( ' , .-` | `, ) Running in sentinel mode |`-._`-...-` __...-.``-._|' ` _.-'| Port: 26379 | `-._ `._ / _.-' | PID: 9761 `-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-' _.-'| | `-._`-._ _.-' _.-' | http://redis.io `-._ `-._`-.__.-' _.-' _.-' |`-._`-._ `-.__.-' _.-' _.-'| | `-._`-._ _.-' _.-' | `-._ `-._`-.__.-' _.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-' 9761:X 10 Jul 2020 10:53:43.061 9761:X 10 Jul 2020 10:53:43.067 9761:X 10 Jul 2020 10:53:43.067 9761:X 10 Jul 2020 10:53:43.069 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379 9761:X 10 Jul 2020 10:53:43.075 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
如果此时主机宕机,哨兵会从从机中选择一台服务器成为新的主机(投票算法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 9761:X 10 Jul 2020 10:56:39.583 9761:X 10 Jul 2020 10:56:39.583 9761:X 10 Jul 2020 10:56:39.584 9761:X 10 Jul 2020 10:56:39.584 9761:X 10 Jul 2020 10:56:39.590 9761:X 10 Jul 2020 10:56:39.590 9761:X 10 Jul 2020 10:56:39.590 9761:X 10 Jul 2020 10:56:39.660 9761:X 10 Jul 2020 10:56:39.660 * +failover-state-send-slaveof-noone slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379 9761:X 10 Jul 2020 10:56:39.739 * +failover-state-wait-promotion slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379 9761:X 10 Jul 2020 10:56:39.858 9761:X 10 Jul 2020 10:56:39.858 9761:X 10 Jul 2020 10:56:39.921 * +slave-reconf-sent slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379 9761:X 10 Jul 2020 10:56:40.890 * +slave-reconf-inprog slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379 9761:X 10 Jul 2020 10:56:40.890 * +slave-reconf-done slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379 9761:X 10 Jul 2020 10:56:40.993 9761:X 10 Jul 2020 10:56:40.993 9761:X 10 Jul 2020 10:56:40.993 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6381 9761:X 10 Jul 2020 10:56:40.993 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381 9761:X 10 Jul 2020 10:57:11.030
可以看到 6381 成为了主机
1 2 3 4 5 6 7 8 9 10 11 12 13 14 127.0.0.1:6381> info replication role:master connected_slaves:1 slave0:ip=127.0.0.1,port=6380,state=online,offset=19800,lag=1 master_replid:1610d6694c340595a8713a46ccd630deadbf3fda master_replid2:6aaa92137ca176c42b8138eea5a3f18714b63916 master_repl_offset:19932 second_repl_offset:18298 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1220 repl_backlog_histlen:18713 127.0.0.1:6381>
此时之前宕掉的主机重新连接后,只能归到新的主机成为从机,再次查看 6381
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 127.0.0.1:6381> info replication role:master connected_slaves:2 slave0:ip=127.0.0.1,port=6380,state=online,offset=42197,lag=0 slave1:ip=127.0.0.1,port=6379,state=online,offset=42197,lag=1 master_replid:1610d6694c340595a8713a46ccd630deadbf3fda master_replid2:6aaa92137ca176c42b8138eea5a3f18714b63916 master_repl_offset:42197 second_repl_offset:18298 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1220 repl_backlog_histlen:40978 127.0.0.1:6381>
优缺点
优点:
哨兵集群,基于主从复制模式,所有主从配置的有点,它全有
主从可以切换,故障可以转移,系统的可用性更好
哨兵模式是主从模式的升级,手动到自动,更加健壮
缺点:
Redis 不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
哨兵模式的配置比较麻烦,很多的配置项
哨兵模式的全部配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 port 26379 dir /tmp sentinel monitor mymaster 127.0.0.1 6379 2 sentinel auth-pass mymaster MySUPER--secret-0123passw0rd sentinel down-after-milliseconds mymaster 30000 sentinel parallel-syncs mymaster 1 sentinel failover-timeout mymaster 180000 sentinel notification-script mymaster /var/redis/notify.sh sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
Redis 缓存穿透和雪崩 Redis 缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题,其中最要害的问题,就是数据一致性的问题,从严格意义上讲,此问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。
另外一些典型的问题比如:缓存穿透、缓存雪崩和缓存击穿。目前业界都有比较流行的解决方案
缓存穿透
概念
缓存穿透的概念很简单,客户端发起请求查询一个数据,发现 redis 内存数据库中没有,也就是缓存没有命中,于是向持久层数据库发起查询,发现也没有,查询失败。当用户很多的时候回,大量缓存没有命中,于是都去请求了持久层数据库,这样就给持久层数据库造成了跟大的压力,这时候就相当于出现了缓存穿透
解决方案
布隆过滤器
布隆过滤器是一种概率型数据结构,对所有可能查询的参数以 hash 形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的压力。
相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的
原理:
布隆过滤器是一个叫“布隆”的人提出的,它本身是一个很长的二进制向量,既然是二进制的向量,那么显而易见的,存放的不是 0 ,就是 1。
现在我们新建一个长度为 8 的布隆过滤器,默认值都是 0,就像下面这样:
现在我们添加 2008 这个数据,先通过三种不同的计算方式(hash1、hash2、hash3)得到三个数字
1 2 3 hash1('2008' ) = 1; hash2('2008' ) = 4; hash3('2008' ) = 6;
将得到的三个数字,填充至创建的布隆过滤器
添加 2020 这个数据,先通过三种不同的计算方式(hash1、hash2、hash3)得到三个数字
1 2 3 hash1('2020' ) = 2; hash2('2020' ) = 4; hash3('2020' ) = 7;
将得到的三个数字,填充至创建的布隆过滤器
可以看出,仅仅从布隆过滤器本身而言,根本没有存放完整的数据,只是运用一系列随机映射函数计算出位置,然后填充二进制向量。
值得注意的是,4 这个 bit 位由于两个值的哈希函数都返回了这个 bit 位,因此它被覆盖了。现在我们如果想查询 “2022” 这个值是否存在,哈希函数返回了 1、4、8 三个值,结果我们发现 8 这个 bit 位上的值为 0,说明没有任何一个值映射到这个 bit 位上,因此我们可以很确定地说 “2020” 这个值不存在。而当我们需要查询 “2006” 这个值是否存在的话,假设哈希函数返回 1、4、7,然后我们检查发现这三个 bit 位上的值均为 1,那么我们可以说 “2006” 存在了么?答案是不可以,只能是 “2006” 这个值可能存在。
这是为什么呢?答案跟简单,因为随着增加的值越来越多,被置为 1 的 bit 位也会越来越多,这样某个值 “2006” 即使没有被存储过,但是万一哈希函数返回的三个 bit 位都被其他值置位了 1 ,那么程序还是会判断 “2006” 这个值存在。
也就是说布隆过滤器只能判断数据是否一定不存在,而无法判断数据是否一定存在。
遗憾的是布隆过滤器是很难做到删除数据的
所以:
优点:由于存放的不是完整的数据,所以占用的内存很少,而且新增,查询速度够快
缺点: 随着数据的增加,误判率随之增加;无法做到删除数据;只能判断数据是否一定不存在,而无法判断数据是否一定存在
缓存空对象
当存储层不命中后,即时返回空对象也要缓存起来,同时设置一个过期时间,之后在访问这个数据将会从缓存中获取,保护了后端数据源
但这个方法存在两个问题:
当空值被缓存起来时,这就意味这缓存需要更多的空间存储更多的键,因为这当中可能包含很多空值的键
即使对空值设置了过期时间,还是会存在缓存层和存储层的数据有一段时间的不一致,这个对于需要保持数据一致性的业务会有很大影响
缓存击穿
概述
这里需要注意和缓存穿透的区别,缓存击穿是指一个 key 非常热点,在不停的扛着大并发集中对着一个点进行访问。当这个 key 在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
在某个 key 在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导致数据库瞬间压力过大(微博宕机)
解决方案
设置缓存永不过期
如果缓存永不过期,就肯定不会出现被击穿的可能
加互斥锁
使用分布式锁,保证对于每个 key 同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需等待即可,这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大
缓存雪崩
概念
缓存雪崩,是指在某一段时间,缓存集中过期失效,或者 redis 宕机
当缓存集中过期或服务宕机时,所有请求到达存储层,也会造成存储层压力过大
集中过期不是非常致命,比较致命的缓存雪崩是缓存服务器再某个节点断网或宕机。因为自然形成的缓存雪崩,一定是在某个时间段内的,这个时候数据库也是可以抗住压力的,无非就是对数据库产生周期性的压力而已。而缓存服务器宕机,对数据库服务器造成的压力是不可预知的,有可能瞬间就压垮数据库服务器
解决方案
redis 高可用
这个思想是多增设几台 redis ,这样一台挂掉其他的还可以继续工作,其实就是集群的概念
限流降级
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待
数据预热
数据预热的含义是在正式部署前,先把可能的数据都预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中,在即将发生大并发访问前手动触发加载缓存不同的 key,设置不同的过期时间,让缓存失效的时间点尽量均匀
该笔记整理与 B 站 up 主:狂神说Java
完整视频地址: https://www.bilibili.com/video/BV1S54y1R7SB
感谢狂神!