初识Redis
Redis
- 五大基本数据类型
- String
- List
- Set
- Hash
- Zset
- 三种特殊数据类型
- geo
- hyperloglog
- bitmap
Nosql 概述
为什么要用 Nosql
1、单机 MySQL 的年代!
90 年代,一个基本的网站访问量一般不会太大,单个数据库完全足够!
那个时候,更多的去使用静态网页 Html服务器根本没有太大的压力
思考一下,在这种情况下,整个网站的瓶颈是什么?
- 数据量如果太大,一个机器放不下
- 数据库索引 (B+Tree),一个机器内存也放不下
- 访问量(读写混合),一个服务器承受不了 ~
只要出现以上的三种情况之一,那么就必须要晋级!
2、Memcached(缓存)+ Mysql + 垂直拆分(读写分离)
网站 80% 的情况下都是在读,如果每次都要去查询数据库,那么就非常的麻烦!所以说我们希望减轻数据的压力,我们可以使用缓存来保障效率!
发展过程:优化数据结构和所以 --> 文件缓存(IO)-->Memcached(当时最热门的技术)
3、分库分表 + 水平拆分 +Mysql 集群
技术和业务在发展的同时,对人的要求也越来越高
本质:数据库(读 + 写)
早些年 MylSAM:表锁(100 万),十分影响效率!高并发下就会出现严重的锁问题
转战 Innodb:行锁
慢慢的就开始分库分表来解决写的压力!MySQL 在那个年代推出了表分区!并没有多少公司使用
MySQL 的集群,很好的满足了那个年代的所有需求
4、如今最近的年代
2010-2020 十年之间,世界发生了翻天覆地的变化,技术革命;(定位,也是一种数据,音乐,热榜)
MySQL 等关系型数据库就不够用了!数据量很大,而且变化很快 ~!
MySQL 有的使用它来存储一些比较大的文件,博客,图片数据!数据库表很大,效率就低了。如果有一种数据库来专门处理这种数据,MySQL 的压力就会变得十分小(研究如何处理这些问题!)大数据的 IO 压力下,表几乎没法更改
目前一个基本的互联网项目!
为什么要用 NoSQL!
用户的个人信息, 社交网络, 地理位置, 用户自己产生的数据, 用户的日志等等爆发式增长
这时候就需要 NoSQL 数据库了,NoSQL 可以很好的处理以上的情况
什么是 NoSQL
NoSQL=Not Only Sql(不仅仅是 SQL)
泛指非关系型数据库, 随着 web2.0 的诞生! 传统的关系型数据库很难对付 web2.0 时代, 尤其是超大规模的高并发的社区!NoSQL 在当今大数据环境下发展的十分迅速!Redis 是发展最快的, 而且是当下必须掌握的!
很多的数据类型, 用户的个人信息, 社交网络, 地理位置. 这些数据类型的存储不需要一个固定的格式! 不需要多余的操作就可以横向扩展!Map<String,Object> 使用键值对来控制!
NoSQL 特点
-
方便扩展(数据之间没有关系,很好扩展!)
-
大数据高性能(Redis 一秒可以写 8 万次,读取 11 万次,NoSQL 的缓存记录级的,细粒度的缓存,性能会比较高)
-
数据类型是多样型的!(不需要实现设计数据库!随取随用!如果是数据库量十分大的表,很多人是无法设计的!)
-
传统的 RDBMS 和 NoSQL
传统的RDBMS - 结构化组织 - SQL - 数据和关系都存在单独的表中 - 数据操作,数据定义语言 - 严格的一致性 - 基础的事务 - ......
Nosql - 不仅仅是数据 - 没有固定的查询语言 键值对存储,列存储,文档存储,图形数据库(社交关系) - 最终一致性 - CAP 定理 和 BASE(异地多活!) - 高性能, 高可用, 高可扩展 - ......
了解:3V+3 高
大数据时代的 3V: 主要是描述问题的
- 海量 Volume
- 多样 Variety
- 实时 Velocity
大数据时代的 3 高: 主要是对程序的要求
- 高并发
- 高可拓
- 高性能
NoSQL 的四大分类
KV 键值对
- 新浪:redis
- 美团:redis+tair
- 阿里、百度:redis+memecache
文档型数据库(bson 格式和 Json 格式一样)
- MongDB(一般必须掌握)
- MongDB 是一个基于分布式文件存储的数据库,C++ 编写,主要用来处理大量的文档!
- MongDB 是一个介于关系型数据库和非关系型数据库中中间的产品!MongDB 是非关系型数据库中功能最丰富,最像关系型数据库的!
列存储数据库
- HBase
- 分布式文件系统
图关系数据库
- 他不是村图形的,放的是关系,比如:朋友圈社交网络,广告推荐!
- Neo4j,InfoGrid;
四者对比
Redis 入门
Redis 是什么?
Redis(Remote Dictionary Server),即远程字典服务!
是一个开源的使用 ANSI C 语言编写,支持网络,可以基于内存亦可持久化的日志型、key-value 数据库,并提供多种语言的 api。
Redis 会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了 Master-slave(主从)同步。
免费和开源!是当下最热门的 NoSQL 技术之一!也被人们称之为结构化数据库!
Redis 能干嘛?
- 内存存储、持久化,内存中是断电即失,所以说持久化很重要(rdb,aof)
- 效率高,可以用于告诉缓存
- 发布订阅系统
- 地图信息分析
- 计时器、计数器(浏览量)
- ......
特性
- 多样的数据类型
- 持久化
- 集群
- 事务
- ......
学习中需要用到的东西
注意:redis 推荐在 linux 服务器上搭建
Linux 上安装 redis
-
下载压缩包
-
解压
-
安装 gcc
yum install gcc-c++
-
make
make install
-
redis 默认安装路径
/usr/local/bin
-
将 redis 配置文件 复制到当前目录下
-
redis 默认不是后台启动的,修改配置文件
-
-
启动 redis 服务
-
-
测试一下
-
-
查看 redis 进程是否开启 ps -ef|grep redis
-
关闭 redis 服务 在客户端里面 shutdown
redis-benchmark
一个官方自带的压力(性能)测试工具
我们来简单测试下:
#测试 100 并发 100000 连接数
redis-benchmark -c 100 -n 100000
如何查看这些分析呢?
redis 基础知识
redis 默认有 16 个数据库
默认使用的是第 0 个数据库
127.0.0.1:6379> select 0 #选择数据库
OK
127.0.0.1:6379> select 3
OK
127.0.0.1:6379[3]> dbsize #查看记录的数量
(integer) 0
127.0.0.1:6379[3]>
127.0.0.1:6379[3]> get name
"antake"
127.0.0.1:6379[3]> dbsize
(integer) 1
127.0.0.1:6379[3]> keys * #查看所有的 key
- "name"
127.0.0.1:6379[3]> flushdb #清空当前数据库
OK
127.0.0.1:6379[3]> keys *
(empty list or set)
127.0.0.1:6379> flushall #清空全部数据库
OK
exists key#是否存在
move key db#移除
expire name 10 #设置自动过期时间
ttl name#查看过期时间
127.0.0.1:6379> set name antake
OK
127.0.0.1:6379> set age 22
OK
127.0.0.1:6379> type name #查看数据类型
string
127.0.0.1:6379> type age
string
为什么 redis 的端口是 6379?
因为他是某人的粉丝
redis 是单线程的!
redis 是很快的,官方表示,redis 是基于内存操作的,因此 CPU 不是 redis 的拼劲,redis 的瓶颈是根据机器的内存和网络情况决定的,既然可以用单线程来实现,就是用单线程了!
redis 是 C 语言开发的,官方提供的数据是 100000+ 的 QPS,说明这个不比同样是使用 key-value 的 memecache
redis 为什么单线程还这么快?
- 误区 1:高性能服务器一定是多线程的?
- 误区 2:多线程(cpu 上下文会切换!)一定比单线程效率高!
cpu> 内存 > 硬盘
核心:redis 是将所有的数据放在内存中的,所以使用单线程去操作效率就是最高的,多线程(cpu 上下文切换:耗时太多),对于内存系统来说,如果没有上下文切换,效率就是最高的!多次读写都是在一个 cpu 上的,在内存情况下,这个就是最佳方案!
1.redis 是基于内存的,内存的读写速度非常快;
2.redis 是单线程的,省去了很多上下文切换线程的时间;
3.redis 使用多路复用技术,可以处理并发的连接;
五大基本数据类型
命令一定要全部记住
redis-key
String(字符串)
127.0.0.1:6379> set key1 v1
OK
127.0.0.1:6379> append key1 hello #拼接 如果 key 不存在,就相当于 set
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> STRLEN key1 #获取字符串长度
(integer) 7
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> decr views #减 1
(integer) 1
127.0.0.1:6379> incrby veiws 10 #指定步长增加
(integer) 10
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> decrby views 4 #指定步长减少
(integer) -3
#字符串范围
127.0.0.1:6379> get key1
"helloantake"
127.0.0.1:6379> getrange key1 0 2 #截取下标从 0 到 2,闭区间
"hel"
127.0.0.1:6379> getrange key1 0 -1 #截取获取全部字符串
"helloantake"
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> setrange key2 2 123 #替换指定位置开始的字符串
(integer) 7
127.0.0.1:6379> get key2
"ab123fg"
#setex(set with expire) 设置过期时间
127.0.0.1:6379> setex key3 10 hello
OK
127.0.0.1:6379> ttl key3
(integer) 6
127.0.0.1:6379> ttl key3
(integer) 5
127.0.0.1:6379> ttl key3
(integer) 4
#setnx(set if not expire) 不存在再设置 在分布式锁中常常使用
127.0.0.1:6379> setnx mykey redis
(integer) 1
127.0.0.1:6379> get mykey
"redis"
127.0.0.1:6379> setnx mykey redis #如果不存在才会设置 存在不设置,避免了覆盖已有的值
(integer) 0
#批量设置
127.0.0.1:6379> mset key1 v1 key2 v2 key3 v3
OK
127.0.0.1:6379> keys *
- "key1"
- "key2"
- "key3
#批量获取
127.0.0.1:6379> mget key1 key2
- "v1"
- "v2"
#同时设置多个值,不存在的情况下(原子性操作)
127.0.0.1:6379> msetnx key1 v1 key4 v4
(integer) 0
#设置对象
set user:1 {name:zangsan,age:3} #设置一个 user:1 对象,值为 json 字符串来保存信息
#据说下面这种是一种巧妙地设计 user:{id}:{filed},如此设置在 redis 中是可以
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 12
OK
127.0.0.1:6379> mget user:1:name user:1:age
- "zhangsan"
"12"
#组合命令
getset #先 get 后 set
127.0.0.1:6379> getset db redis #如果不存在值返回 nil
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db red1 #如果存在值,就返回原来的值,设置更新的值
"redis"
127.0.0.1:6379> get db
"red1"
数据结构是相同的,未来的话,jedis
String 类似的使用场景,value 除了是我们的字符串还可以是我们的数字
- 计数器
- 统计多单位的数量
- 粉丝数
- 对象缓存
List(列表)
基本的数据类型,列表
在 redis 里面,可以把 list 看成栈,对列,阻塞队列
所有的 List 命令都是用 l 开头的
127.0.0.1:6379> lpush list one #从左边插入,头插
(integer) 1
127.0.0.1:6379> lpush list two 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 for #从右边插入,尾插
(integer) 4
127.0.0.1:6379> lrange list 0 -1
- "three"
- "two"
- "one"
- "for"
#lpop
#rpop
127.0.0.1:6379> lrange list 0 -1
- "three"
- "two"
- "one"
- "for"
127.0.0.1:6379> lpop list #移除列表的第一个元素
"three"
127.0.0.1:6379> rpop list #移除列表的最后一个元素
"for"
127.0.0.1:6379> lrange list 0 -1
- "two"
- "one"
127.0.0.1:6379> lindex list 1 #获取列表指定下标的值
"one"
#llen
127.0.0.1:6379> llen list
(integer) 2
#移除指定的值
127.0.0.1:6379> lrange list 0 -1 #移除 list 集合中指定数量的 value,精确匹配
- "three"
- "two"
- "one"
127.0.0.1:6379> lpush list three
(integer) 4
127.0.0.1:6379> lrange list 0 -1
- "three"
- "three"
- "two"
- "one"
127.0.0.1:6379> lrem list 1 one
(integer) 1
127.0.0.1:6379> lrange list 0 -1
- "three"
- "three"
- "two"
127.0.0.1:6379> lrem list 2 three
(integer) 2
127.0.0.1:6379> lrange list 0 -1
- "two"
trim 修剪:list 截断!
127.0.0.1:6379> rpush mylist hello
(integer) 1
127.0.0.1:6379> rpush mylist hello1
(integer) 2
127.0.0.1:6379> rpush mylist hello2 hello3
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
- "hello"
- "hello1"
- "hello2"
- "hello3"
127.0.0.1:6379> ltrim mylist 1 2 #截取
OK
127.0.0.1:6379> lrange mylist 0 -1
- "hello1"
- "hello2"
rpoplpush #移除列表的最后一个元素并将其放在第一个
127.0.0.1:6379> rpush list hello hello1 hello2
(integer) 3
127.0.0.1:6379> lrange list 0 -
(error) ERR value is not an integer or out of range
127.0.0.1:6379> lrange list 0 -1
- "hello"
- "hello1"
- "hello2"
127.0.0.1:6379> rpoplpush list mylist
"hello2"
127.0.0.1:6379> keys *
- "mylist"
- "list"
127.0.0.1:6379> lrange list 0 -1
- "hello"
- "hello1"
127.0.0.1:6379> lrange mylist 0 -1
- "hello2"
#lset 将列表中指定下标的值替换为指定的值
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> lindex list 0
"value1"
127.0.0.1:6379> lset list 0 item
OK
#linsert
127.0.0.1:6379> lrange list 0 -1
- "item"
127.0.0.1:6379> linsert list before item values
(integer) 2
127.0.0.1:6379> lrange list 0 -1
- "values"
"item"
小结
- 他实际上是一个链表,before node after,left,right, 都可以插值
- 如果 key 不存在,创建新的链表
- 如果 key 存在,新增内容
- 如果移除了所有值,空链表,也代表不存在
- 在两边插入值或者改动值,效率最高!中间元素,效率相对会低一点
Set(集合)
set 中的值是不能重复的!
127.0.0.1:6379> sadd myset hello #向 set 里面存值
(integer) 1
127.0.0.1:6379> sadd myset antanke
(integer) 1
127.0.0.1:6379> sadd myset lvoe
(integer) 1
127.0.0.1:6379> smembers myset #查看 set 中的元素
- "antanke"
- "lvoe"
- "hello"
127.0.0.1:6379> sismember myset hello #查看指定 set 中是否存在一个值
(integer) 1
127.0.0.1:6379> scard myset #查看 set 的元素个数
(integer) 3
127.0.0.1:6379> srem myset hello #移除 set 中的一个值
(integer) 1
127.0.0.1:6379> scard myset
(integer) 2
127.0.0.1:6379> smembers myset
- "antanke"
- "lvoe"
set是无序不重复集合,抽随机
127.0.0.1:6379> smembers myset
- "antanke"
- "lvoe"
127.0.0.1:6379> srandmember myset #随机 member, 随机抽出指定个数的元素
"lvoe"
127.0.0.1:6379> srandmember myset
"antanke"
127.0.0.1:6379> srandmember myset
"antanke"
127.0.0.1:6379> srandmember myset
"lvoe"
127.0.0.1:6379> srandmember myset
"antanke"
移除指定的 key, 随机删除一个 key
127.0.0.1:6379> smembers myset #随机删除集合中的元素
- "antanke"
- "lvoe"
127.0.0.1:6379> spop myset
"antanke"
127.0.0.1:6379> smembers myset
- "lvoe"
将一个指定的值移动到另外一个set中
127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset world antake
(integer) 2
127.0.0.1:6379> smembers myset
- "antake"
- "world"
- "hello"
127.0.0.1:6379> smove myset myset2 world
(integer) 1
127.0.0.1:6379> smembers myset
- "antake"
- "hello"
127.0.0.1:6379> smembers myset2
- "worlds"
- "antakes"
- "world"
微博,b 站,共同关注(并集)
数字类集合:
127.0.0.1:6379> sadd key1 a b c d
(integer) 4
127.0.0.1:6379> sadd key2 c d e
(integer) 3
127.0.0.1:6379> smembers key1
- "a"
- "d"
- "c"
- "b"
127.0.0.1:6379> smembers key2
- "e"
- "d"
- "c"
127.0.0.1:6379> sdiff key1 key2 - 差集
- "a"
- "b"
127.0.0.1:6379> sinter key1 key2 - 交集 共同好友
- "d"
- "c"
127.0.0.1:6379> sunion key1 key2 - 并集
- "b"
- "c"
- "a"
- "e"
"d"
微博,A 用户将所有关注的人放在一个 set 集合中,将他的粉丝也放在一个集合中
共同关注,共同爱好,二度好友(六度分割),推荐好友
Hash(哈希)
Map 集合,key-map 集合,key-<key,value>,本质和 String 没有太大区别
127.0.0.1:6379> hset myhash key1 v1
(integer) 1
127.0.0.1:6379> hget myhash key1
"v1"
127.0.0.1:6379> hmset myhash key2 v2 key3 v3 #设置多个 key value
OK
127.0.0.1:6379> hmget myhash key1 key2 #获取多个 value
1) "v1"
2) "v2"
127.0.0.1:6379> hgetall myhash #获取所有的 key value
1) "key1"
2) "v1"
3) "key2"
4) "v2"
5) "key3"
6) "v3"
127.0.0.1:6379> hdel myhash key1 #删除 hash 指定的 Key, 对应的 value 也就消失了
(integer) 1
127.0.0.1:6379> hgetall myhash
- "key2"
- "v2"
- "key3"
- "v3"
127.0.0.1:6379> hlen myhash #查看 hash 字段个数
(integer) 2
hexists myhash key#判断 hash 中的指定的字段是否存在
#只获取所有的 key
hkeys myhash
#只获取所有的 value
hvals myhash
#自增
127.0.0.1:6379> HMSET myhash k1 v1 k2 v2 k3 5
OK
127.0.0.1:6379> HINCRBY myhash k3 1
(integer) 6
127.0.0.1:6379> HINCRBY myhash k3 5
(integer) 11
127.0.0.1:6379> HINCRBY myhash k3 -1
(integer) 10
#不存在才创建
127.0.0.1:6379> hsetnx myhash k4 hello
(integer) 1
127.0.0.1:6379> hsetnx myhash k4 hello
(integer) 0
hash 变更的数据 user name age , 尤其是用户信息的保存,经常变动的信息!hash 更适合于对象的存储,而 String 更适合于字符串的存储
127.0.0.1:6379> hset user:1 name antake
(integer) 1
127.0.0.1:6379> hget user:1 name
"antake"
Zset(有序集合)
在 set 的基础上,增加了一个值,set k1 v1,zset k1 score v1
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> ZADD salary 2500 xiaohong
(integer) 1
127.0.0.1:6379> ZADD salary 5000 lisi
(integer) 1
127.0.0.1:6379> ZADD salary 500 antake
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf
- "antake"
- "xiaohong"
- "lisi"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores
- "antake"
- "500"
- "xiaohong"
- "2500"
- "lisi"
- "5000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores
- "antake"
- "500"
- "xiaohong"
- "2500"
#降序?
127.0.0.1:6379> ZREVRANGE salary 0 -1
- "lisi"
- "antake"
移除元素
127.0.0.1:6379> ZREM salary xiaohong #移除有序集合中的指定元素
(integer) 1
127.0.0.1:6379> ZCARD salary #查看这里面有多少个元素
(integer) 2
127.0.0.1:6379> ZRANGE salary 0 -1
- "antake"
- "lisi"
127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 antake #查看区间中元素的个数
(integer) 2
127.0.0.1:6379> ZCOUNT myset 1 3
(integer) 3
案例思路:set 排序,存储班级成绩表,工资排序表
普通消息:1. 重要消息,2. 带权重进行判断
排行榜应用实现,取 top n
三种特殊数据类型
geospatial 地理位置
朋友定位,附近的人,打车距离计算?
geoadd
#geoadd 添加地理位置 两极无法直接添加,一般直接通过 java 程序直接导入
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 32.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqin
(integer) 1
127.0.0.1:6379> geoadd china:city 114.05 22.52 shenzhen
(integer) 1
127.0.0.1:6379> geoadd china:city 12.016 30.24 hangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 108.96 34.26 xian
(integer) 1
geopos
#geopos
127.0.0.1:6379> geopos china:city xian beijing
1) 1) "108.96000176668167114"
2) "34.25999964418929977"
2) 1) "116.39999896287918091"
2) "39.90000009167092543"
geodist
两地之间的距离
127.0.0.1:6379> geodist china:city beijing xian
"910056.5237"
127.0.0.1:6379> geodist china:city beijing xian km
"910.0565"
127.0.0.1:6379> geodist china:city beijing shenzhen km
"1945.7881"
georadius 以给定的经纬度为中心,找一些元素
我附近的人?(获取所有附近的人的地址,定位)
127.0.0.1:6379> georadius china:city 110 30 1000 km
1) "chongqin"
2) "xian"
3) "shenzhen"
127.0.0.1:6379> georadius china:city 110 30 500 km
1) "chongqin"
2) "xian"
127.0.0.1:6379> georadius china:city 110 30 500 km withcoord
1) 1) "chongqin"
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 500 km withdist withcoord #加上获取距离
-
- "chongqin"
- "341.9374"
-
- "106.49999767541885376"
- "29.52999957900659211"
-
- "xian"
- "483.8340"
-
- "108.96000176668167114"
- "34.25999964418929977"
GEORADIUSBYMEMBER 通过 member 去查找周围的元素
#找出位于指定元素周围的元素
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "shanghai"
2) "beijing"
3) "xian"
geohash 返回一个或多个位置元素的 geohash
#将二维的经纬度转换成一维的字符串,如果两个字符串越接近就说明距离越近
127.0.0.1:6379> GEOHASH china:city beijing
1) "wx4fbxxfke0"
GEO 底层的实现原理,就是 Zset!我们可以使用 Zset 来操作 geo
127.0.0.1:6379> zrem china:city beijing
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "hangzhou"
2) "chongqin"
3) "xian"
4) "shenzhen"
5) "shanghai"
Hyperloglog
什么是基础?
A{1,3,5,7,8,9}
B{1,3,5,7,8}
基数(不重复的元素)=5,可以接受误差!
简介
redis 2.8.9 版本就更新了 Hyperloglog!
Redis Hyperloglog 基数统计的算法!
优点:占用的内存是固定的,2^64 不同的元素的基数,只需要放 12KB 的内存!如果要从内存角度来比较的话
Hyperloglog 就是首选
网页的 UV(一个人访问一个网站多次,但还是算一个人)
传统的方式,set 保存用户的 id,然后就可以统计 set 中元素的数量作为标准判断
这个方式如果保存大量的用户 id,就会比较麻烦,我们的目的是为了计数,而不是保存用户 id
0.81% 错误率,部分情况是可以忽略不记的
127.0.0.1:6379> PFADD mykey a b c d e f g h i j #创建第一组元素
(integer) 1
127.0.0.1:6379> PFCOUNT mykey #统计 Mykey 基数数量
(integer) 10
127.0.0.1:6379> pfadd mykey2 i j z x c v b n m
(integer) 1
127.0.0.1:6379> PFCOUNT mykey2
(integer) 9
#并集
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2 #合并两组
OK
127.0.0.1:6379> PFCOUNT mykey3
(integer) 15
如果允许容错,就一定可以使用 Hyperloglog
如果不允许容错,就是用 set 或者自己的数据类型
Bitmaps
位存储
统计疫情感染人数:0 1 0 1 0
统计用户信息:活跃 不活跃!登录、未登录!打卡,未打卡,两个状态的都可以使用 Bitmaps
Bitmaps 位图,数据结构,都是操作二进制位来进行记录,就只有 0 和 1 两个状态
测试
周一:1 周二 :0
怎么计算一周打卡的天数呢?
查看某一天是否打卡
127.0.0.1:6379> GETBIT sign 3
(integer) 0 #未打开
127.0.0.1:6379> GETBIT sign 4
(integer) 1 #打开
127.0.0.1:6379> BITCOUNT sign 0 6 #计数,只统计值为 1 的
(integer) 3
使用
事务
Redis 事务本质:一组命令的集合!一个事务的所有命令都会被序列化,在事务执行的过程中,会按照顺序执行!
一次性,顺序性,排他性!执行一系列的命令
---队列 set set set 执行---
Redis 事务没有隔离级别的概念
所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行!Exec
Redis 单条命令是保证原子性的,但是事务不保证原子性
Redis 事务:
- 开始事务(Multi)
- 命令入队(...)
- 执行事务(Exec)
正常执行事务!
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set key1 v1
QUEUED
127.0.0.1:6379> set key2 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) (nil)
4) OK
127.0.0.1:6379> keys *
1) "key2"
2) "key1"
3) "k3"
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> DISCARD #取消事务
OK
127.0.0.1:6379> keys *
(empty list or set)
编译型异常(代码有问题!命令有错)事务中所有命令都不会被执行!
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> getset k3 v3
QUEUED
127.0.0.1:6379> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
运行时异常(1/0)如果队列中存在语法性问题,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常
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> EXEC
1) (error) ERR value is not an integer or out of range #即使有运行时异常,后面的还是可以执行成功
监控!Watch
悲观锁:
- 很悲观,什么时候都会出问题,无论做什么都会加上锁
乐观锁:
- 很乐观,什么时候都不会出现问题,所以不会上锁,执行操作的时候会判断,如果在此期间如果有错,才会加锁再次执行
redis 监视测试
正常执行成功!
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 #监视 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
多线程修改,结果是失败
unwatch 解锁
如果获取失败,获取最新的值就好
jedis
什么是 jedis 是 redis 官方推荐的 java 连接工具,使用 java 操作 redis 中间件,如果要使用 java 操作 redis,那么一定要对 jedis 十分的熟悉!
-
新建一个项目,导入 jedis 包
<!-- 导入 jedis 包 --> <dependencies> <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.2.0</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.68</version> </dependency> </dependencies>
-
编码测试:
- 连接数据库
- 操作命令
- 断开连接
public class TestPing {
public static void main(String[] args) {
//1.new Jedis 对象即可
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.ping());
}
}
输出:
常用 Api
String
List
Set
Hash
Zset
事务
成功测试
public class TestTX {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
JSONObject jsonObject=new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","antake");
// 开启事务
Transaction multi = jedis.multi();
try {
multi.set("user1",jsonObject.toJSONString());
multi.exec();
}catch (Exception e){
// 失败,放弃事务
multi.discard();
}finally {
System.out.println(jedis.get("user1"));
// 关闭连接
jedis.close();
}
}
}
Springboot 整合
说明:在 Springboot2.x 之后,原来使用的 jedis 被替换成了 lettuce?
详细说明请到 博客
jedis:采用的是直连,多个线程操作是不安全的,如果想要避免不安全,就要使用 jedis pool 连接池!更像 BIO
lettuce:采用 netty,实例可以在多个线程中进行共享,不存在线程不安全的情况!可以减少线程数量 更像 NIO
源码分析:
整合测试
- 导入依赖
<!-- 操作 redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
-
配置连接
#SpringBoot 所有的配置类,都有一个自动配置类 #自动配置类都会绑定一个 properties 配置文件 #springboot2.0x 之后默认使用的是 lettuce, 配置 jedis 已经不生效了 #配置 redis spring.redis.host=127.0.0.1 spring.redis.port=6379
-
测试
@SpringBootTest class RedisSpringbootApplicationTests { @Autowired RedisTemplate redisTemplate; @Test void contextLoads() { //redisTemplate opsForValue 操作字符串,类似 String //opsForSet 操作 set //opsForHash //opsForGeo //opsForHyperLogLog //opsForZSet
<span class="hljs-comment">//除了基本的操作,常用的方法都可以直接通过redisTemplate操作,比如事务和基本的CRUD</span> <span class="hljs-comment">//获取连接</span> <span class="hljs-comment">/*RedisConnection connection = redisTemplate.getConnectionFactory().getConnection(); connection.flushDb(); connection.flushAll();*/</span> redisTemplate.opsForValue().set(<span class="hljs-string">"mykey"</span>,<span class="hljs-string">"antake is the best"</span>); System.out.println(redisTemplate.opsForValue().get(<span class="hljs-string">"mykey"</span>)); }
}
序列化测试
- 新建一个 User
@AllArgsConstructor
@NoArgsConstructor
@Component
@Data
public class User{
private String name;
private int age;
}
- 新建测试类
@Test
public void test() throws JsonProcessingException {
// 真实的开发,一般都是使用 json 来传递对象
User antake = new User("antake", 22);
String jsonUser = new ObjectMapper().writeValueAsString(antake);
redisTemplate.opsForValue().set("user",jsonUser);
System.out.println(redisTemplate.opsForValue().get("user"));
}
- 如果直接传 user, 会报错,因此所有的对象都需要序列化
-
将 User 序列化,结果正常
在企业开发中,一般都会将所有的 pojo 序列化
据说在企业开发中,百分之 80 都不会使用原生的 redisTemplate, 因此需要自定义 redisTemplate
@Configuration
public class RedisConfig {
// 编写 redisTemplate
// 固定模板,开箱即用
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
// 为了开发方便一般使用 string,object
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//json 序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 部分版本因为漏洞或者其他原因弃用
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//String 的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key 采用的 string 序列化方式
template.setKeySerializer(stringRedisSerializer);
//hash 的 key 也采用 string 的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//value 序列化方式采用 jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
//hash 的 value 采用 jackson 序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
- 修改测试类中的 redisTemplate 为自己的 redisTemplate
@Autowired
@Qualifier("redisTemplate")
RedisTemplate redisTemplate;
-
再次测试
测试正常
数据库也正常,不存在用 jdk 序列化的转义现象出现
由于 redisTemplate 操作过于复杂重复,因此还可以自己封装一个 util,当然企业开发肯定有自己的
Redis.conf 详解
启动的时候,通过配置文件来启动
单位
1. 配置文件 unit 单位,对大小写不敏感!
包含
就好比我们学习 spring,Import,include
网络
bind 192.168.1.100 10.0.0.1 #绑定 ip
protected-mode yes #保护模式
port 6379 #端口
通用 GENERAL
daemonize yes #以守护进程的方式运行,要后台运行必须是 yes
pidfile /var/run/redis_6379.pid #如果以后台方式运行,我们就需要指定一个 pid 进程文件
#日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) 生产环境适用
# warning (only very important / critical messages are logged)
loglevel notice
logfile "" #日志文件名,为空为标准的输出
databases 16 #数据库数量,默认 16 个
always-show-logo yes #是否总是显示 logo
快照 SNAPSHOTTING
持久化,在规定的时间内执行来多少次操作,则会持久化到文件,.rdb,.aof 文件
redis 是内存数据库,如果没有持久化,那么数据断电即失!
save 900 1 #900 秒内,如果至少有一个 key 进行来修改,就进行持久化操作
save 300 10 #如果 300 秒内,至少有 10 个 key 进行来修改,就进行持久化
save 60 10000 #如果 60 秒内,至少进行来 10000 次 key 进行了修改,就进行持久化操作
stop-writes-on-bgsave-error yes #持久化如果出错,是否还需要继续工作,一般情况下 yes
rdbcompression yes #是否压缩 rdb 文件(持久化的文件),需要消耗一些 cpu 资源
rdbchecksum yes #保存 rdb 文件的时候进行错误的检查校验
dir ./ #rdb 文件保存的目录
复制 REPLICATION,主从复制的时候再深入
安全 SECURITY
可以设置,redis 默认是没有密码的
requirepass foobared #是否需要密码 设置密码 config set requirepass "123456"
#验证密码 auth passwd
限制 CLIENTS
maxclients 10000 #设置连接 redis 最大客户端的数量
maxmemory <bytes> #redis 设置最大的内存容量
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select among five behaviors:
maxmemory-policy noeviction #内存达到上限之后的处理策略
maxmemory-policy 六种方式
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : 删除lru算法的key
3、volatile-random:随机删除即将过期key
4、allkeys-random:随机删除
5、volatile-ttl : 删除即将过期的
6、noeviction : 永不过期,返回错误
APPEND ONLY 模式 aof 配置 APPEND ONLY MODE
appendonly no #默认是不开启 aof 模式的,默认是使用 rdb 方式持久化,在大部分情况下,rdb 已经够用
appendfilename "appendonly.aof" #持久化文件名字
# appendfsync always #每次修改都会同步,速度比较慢
appendfsync everysec #每秒执行一次,每秒都执行一次,可能会丢失 1s 的数据
# appendfsync no #不执行同步,这个时候操作系统自己同步数据,速度最快,但是一般不用
Redis 持久化
面试工作重点
Redis 是内存数据库,如果不能将内存中的数据库状态保存到磁盘中,那么一旦服务器进程退出,服务器中的数据库状态也随之消失,所以 redis 提供来持久化功能!
RDB(Redis Database)
什么是 RDB
在主从复制中,rdb 就是备用
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是 Snapshot 快照,恢复时将快照文件直接读到内存中
Redis 会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何 IO 操作的,这就确保了极高的性能。如果需要进行大规模数据恢复,且对于数据恢复的完整性不是非常敏感,那么 RDB 方式要比 AOF 方式更加的高效。RDB 的缺点是最后一次持久化后的数据可能丢失。我们默认的就是 RDB,一般情况下不用修改这个配置!
rdb 保存的文件是 dump.rdb 都是在配置文件的快照中进行配置的,有时候会将数据进行备份
为了进行测试,我们修改配置为 60 秒内进行 5 次操作就持久化数据
出发机制
- save 规则满足的情况下,会自动触发 rdb 规则
- 执行 flushall 命令
- 退出 redis
备份就会自动生成 dump.rdb
如何恢复 rdb 文件
- 只需要把 rdb 文件放到 redis 启动目录下就可以来,redis 启动的时候会自动恢复其中的数据
- 查看 rdb 文件存放的位置
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin" #如果在这个目录下存在 dump.rdb 文件,启动就会自动恢复数据
几乎默认的配置就已经够用
优点:
- 适合大规模的数据恢复!
- 对数据的完整性要求不高
缺点:
- 需要一定的时间间隔进行操作,如果 redis 意外宕机了,这个最后一次修改的数据就没有了
- fork 进程的时候,会占用一定的系统资源
AOF(Append Only File)
将我们的所有命令都记录下来,history,恢复的时候就把所有的命令都执行一次
是什么
以一直的形式来记录每个写操作,将 Redis 执行过的所有指令都记录下来(读操作不记录),只许追加文件但是不可以改写文件,redis 启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根据日志文件的内容将写指令执行一次以完成数据的恢复工作
aof 保存的是 appendonly.aof 文件
append
默认是不开启的,我们需要手动进行配置
重启,redis 就可以生效了
如果持久化文件有错误,这个时候 redis 是启动不起来的,我们需要修复这些持久化文件
redis 提供了工具 redis-check-aof -fix,可以检查相应的持久化文件
如果文件正常,重启就可以直接恢复了
重写规则说明
aof 默认的是文件无限追加,但是文件会越来越大
如果一个 aof 文件大于了 64mb,就会 fork 一个新的进程来将我们的文件进行重写
aof 优点和缺点
优点:
- 每一次修改修改都同步,文件完整性更好
缺点:
- 相对于数据文件来说,aof 远远大于 rdb,修复的速度也比 rdb 慢
- Aof 运行效率也要比 rdb 慢,所以说我们 redis 默认的是 rdb 而不是 aof
扩展
Redis 发布订阅
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息
Redis 客户端可以订阅任意数量的频道
订阅 / 发布消息图:
第一个:消息发送者,第二个:频道,第三个:消息订阅者
下图展示了频道 channel1,以及订阅这个频道的三个客户端 --client2,client5,client1 只间的关系:
当有新的消息通过 PUBLISH 命令发送给频道 channel1 时,这个消息就会被发送给订阅它的客户端:
命令
这些命令被广泛的用于构建即时通信应用,比如网络聊天室(chatroom)和实时广播,实时提醒等
测试
订阅端
127.0.0.1:6379> SUBSCRIBE antake #订阅一个频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "antake"
3) (integer) 1
#等待推送信息
1) "message"
2) "antake"
3) "\xe6\xb5\x8b\xe8\xaf\x95\xe4\xbf\xa1\xe6\x81\xaf"
1) "message"
2) "antake"
3) "yigiaowoligiaogiao"
发送端
127.0.0.1:6379> PUBLISH antake 测试信息 #发送信息到频道
(integer) 1
127.0.0.1:6379> PUBLISH antake yigiaowoligiaogiao
(integer) 1
原理
Redis 是使用 C 实现的,通过分析 Redis 源码里的 pubsub.c 文件,了解发布和订阅机制的底层实现,借此加深对 Redis 的了解。
Redis 通过 PUBLISH,SUBSCRIBE,PSUBSCRIBE 等命令实现发布和订阅功能。
通过 SUBSCRIBE 命令订阅某个频道后,redis-server 里维护了一个字典,字典的键就是一个个 channel,而字典的值则是一个链表,链表中保存了所有订阅这个 channel 的客户端。SUBSCRIBE 命令的关键,就是将客户端添加到指定 channel 的订阅链表中。
通过 PUBLISH 命令向订阅者发布消息,redis-server 会使用给定的频道作为键,在她所维护的 channel 字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者。
Pub/Sub 从字面上理解就是发布与订阅,在 Redis 中,你可以设定对某一个 key 值进行消息发布及消息订阅,当一个 key 值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。
Redis 主从复制
概念
主从复制,读写分离,80% 情况下都在进行读操作,减缓服务器的压力,架构中经常使用!最少一主二从
环境配置
127.0.0.1:6379> info replication #查看当前库的信息
# Replication
role:master
connected_slaves:0 #没有从机
master_replid:aa8dfb0daea095ea7e585f9c2e5e3c28c648ed7f
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
- 开启四台机器,一主二从,测试机
-
复制三个配置文件,开始修改信息
[root@iZwz9almo8p830btq7voo9Z akconfig]# ls redis79.conf redis80.conf redis81.conf redis.conf [root@iZwz9almo8p830btq7voo9Z akconfig]# vi redis79.conf [root@iZwz9almo8p830btq7voo9Z akconfig]# vi redis80.conf [root@iZwz9almo8p830btq7voo9Z akconfig]# vi redis81.conf
-
修改完毕之后,启动三个服务器,可以通过进程信息和日志文件查看查看
一主二从
默认情况下,每台 redis 服务器都是主节点;一般情况下,只用配置从机就可以了!
一主(79)二从(80,81) SLAVEOF
#配置 80 端口
[root@iZwz9almo8p830btq7voo9Z bin]# redis-cli -p 6380
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379
OK
127.0.0.1:6380> info replication
# Replication
role:slave #变成了从机
master_host:127.0.0.1 #主机信息
master_port:6379
master_link_status:up
master_last_io_seconds_ago:4
master_sync_in_progress:0
slave_repl_offset:14
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:bb4022b76995bfecc614127f1f8018675935a2d6
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
#同理配置 81
#查看主机信息
[root@iZwz9almo8p830btq7voo9Z bin]# redis-cli -p 6379
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2 #从机信息
slave0:ip=127.0.0.1,port=6380,state=online,offset=196,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=196,lag=0
master_replid:bb4022b76995bfecc614127f1f8018675935a2d6
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:196
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:196
上面是通过命令配置的,真实的配置应该在配置文件中配置,我们使用的是命令,是暂时的
REPLICATION
# replicaof <masterip> <masterport> 配置这个即可
#如果主机有密码
# masterauth <master-password>
细节
主机可以写,从机不能写,只能读,主机中的所有信息,都会被从机保存
127.0.0.1:6379> set name antake #主机写入一个内容
OK
127.0.0.1:6379> keys *
1) "name"
#查看从机中的信息
127.0.0.1:6380> keys *
1) "name"
#往从机中写数据
127.0.0.1:6380> set name 123456
(error) READONLY You can't write against a read only replica.
模拟主机宕机
[root@iZwz9almo8p830btq7voo9Z akconfig]# redis-cli -p 6379
127.0.0.1:6379> shutdown
not connected> exit
[root@iZwz9almo8p830btq7voo9Z akconfig]# ps -ef|grep redis
root 12798 2911 0 18:27 pts/6 00:00:00 grep --color=auto redis
root 19677 1 0 18:12 ? 00:00:01 redis-server 127.0.0.1:6380
root 20365 1 0 18:12 ? 00:00:01 redis-server 127.0.0.1:6381
#由于没有哨兵,不会自动推选主机
[root@iZwz9almo8p830btq7voo9Z bin]# redis-cli -p 6380
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:786
master_link_down_since_seconds:78
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:bb4022b76995bfecc614127f1f8018675935a2d6
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:786
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:786
恢复主机,查看主从机数据是否还能备份,结果是显然的
测试:如果其中一台从机掉线了,又恢复上线,那它能拿到之前的数据吗?
答:必须的
因为:
复制原理
毛毛虫模型
如果没有主节点了,能不能推举主节点呢?SLAVEOF no one
使自己变成主节点,其他的节点就可以手动连接到主节点!如果老大修复了,还是只有重新连接
哨兵模式 redis-sentinel
概述
测试
- 配置哨兵配置文件 sentinel.conf
sentinel monitor myredis 127.0.0.1 6379 1
- 启动哨兵
redis-sentinel akconfig/sentinel.conf
[root@iZwz9almo8p830btq7voo9Z bin]# redis-sentinel akconfig/sentinel.conf
9489:X 22 Apr 2020 20:16:16.005 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
9489:X 22 Apr 2020 20:16:16.005 # Redis version=5.0.8, bits=64, commit=00000000, modified=0, pid=9489, just started
9489:X 22 Apr 2020 20:16:16.005 # Configuration loaded
.
.-__ <span class="hljs-string">''</span>-._ _.-
.
. ''-._ Redis 5.0.8 (00000000/0) 64 bit
.-.-```. ```\/ _.,_ <span class="hljs-string">''</span>-._ ( <span class="hljs-string">' , .-` | `, ) Running in sentinel mode |`-._`-...-` __...-.
-.|'_.-<span class="hljs-string">'| Port: 26379 |
-. ._ / _.-'</span> | PID: 9489
-._ -._
-./ .-' .-'
|-._
-. -.__.-<span class="hljs-string">' _.-'</span>_.-<span class="hljs-string">'| |
-.-._ _.-'</span>_.-<span class="hljs-string">' | http://redis.io
-._ -._
-..-'.-' .-'
|-._
-. -.__.-<span class="hljs-string">' _.-'</span>_.-<span class="hljs-string">'| |
-.-._ _.-'</span>_.-<span class="hljs-string">' |
-._ -._
-..-'_.-'_.-'
-._
-..-'_.-'
-._ _.-<span class="hljs-string">'
-..-'
9489:X 22 Apr 2020 20:16:16.006 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
9489:X 22 Apr 2020 20:16:16.009 # Sentinel ID is b6fbb4c354cf6403bedc3153942c4883d0b59713
9489:X 22 Apr 2020 20:16:16.009 # +monitor master myredis 127.0.0.1 6379 quorum 1
9489:X 22 Apr 2020 20:16:16.009 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
9489:X 22 Apr 2020 20:16:16.028 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
如果主机宕机之后,哨兵会自动推选
#模拟主机挂掉
9489:X 22 Apr 2020 20:17:41.278 # +sdown master myredis 127.0.0.1 6379
9489:X 22 Apr 2020 20:17:41.278 # +odown master myredis 127.0.0.1 6379 #quorum 1/1
9489:X 22 Apr 2020 20:17:41.278 # +new-epoch 1
9489:X 22 Apr 2020 20:17:41.278 # +try-failover master myredis 127.0.0.1 6379
9489:X 22 Apr 2020 20:17:41.281 # +vote-for-leader b6fbb4c354cf6403bedc3153942c4883d0b59713 1
9489:X 22 Apr 2020 20:17:41.281 # +elected-leader master myredis 127.0.0.1 6379
9489:X 22 Apr 2020 20:17:41.281 # +failover-state-select-slave master myredis 127.0.0.1 6379
9489:X 22 Apr 2020 20:17:41.347 # +selected-slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
9489:X 22 Apr 2020 20:17:41.347 * +failover-state-send-slaveof-noone slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
9489:X 22 Apr 2020 20:17:41.419 * +failover-state-wait-promotion slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
9489:X 22 Apr 2020 20:17:42.165 # +promoted-slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
9489:X 22 Apr 2020 20:17:42.165 # +failover-state-reconf-slaves master myredis 127.0.0.1 6379
9489:X 22 Apr 2020 20:17:42.228 * +slave-reconf-sent slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
9489:X 22 Apr 2020 20:17:43.186 * +slave-reconf-inprog slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
9489:X 22 Apr 2020 20:17:43.186 * +slave-reconf-done slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
9489:X 22 Apr 2020 20:17:43.257 # +failover-end master myredis 127.0.0.1 6379
9489:X 22 Apr 2020 20:17:43.257 # +switch-master myredis 127.0.0.1 6379 127.0.0.1 6380
9489:X 22 Apr 2020 20:17:43.258 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6380
9489:X 22 Apr 2020 20:17:43.258 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6380
当原主机复活后,只能归并到新的主机下变成从机
#哨兵信息
9489:X 22 Apr 2020 20:20:29.111 * +convert-to-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6380
#复活主机的信息
[root@iZwz9almo8p830btq7voo9Z bin]# redis-cli -p 6379
127.0.0.1:6379> info replication
# Replication
role:slave #自动变成从机
master_host:127.0.0.1
master_port:6380
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_repl_offset:38037
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:2c0d88fe8a9e309c5556f6efd024553c3d358151
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:38037
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:36271
repl_backlog_histlen:1767
哨兵模式
优点:
- 哨兵集群,基于主从复制模式,所有主从复制模式的优点它都有
- 主从可以切换,故障可以转义,系统的可用性就会更好
- 哨兵模式就是主从模式,手动升级到自动,更加健壮
缺点:
- Redis 不好在线扩容,集群容量一旦到达上限,在线扩容十分麻烦
- 实现哨兵模式的配置其实是很麻烦的,里面有很多选择
哨兵模式的全部配置
Redis 缓存穿透和雪崩(服务的高可用问题)
Redis 缓存的使用,极大的提升了应用程序的性能和效率,尤其是数据查询方面。但同时,它也带来了一些问题。其中,最要害的一些问题,数据一致性的问题,从严格意义上来说,这个问题是无解的。如果对数据的一致性要求很高,那么就不能使用缓存。
另外的一些典型问题就是,缓存穿透,缓存雪崩和缓存击穿。目前,业界也都有比较流行的解决方案。
缓存穿透(数据查不到)
概念
用户想要查询一个数据,发现 redis 内存数据库中没有,也就是缓存没有命中,于是向持久层数据库查询,发现也没有,于是本次查询失败。当用户很多时候,缓存都没有命中,于是都去请求了持久层数据,者就会给持久层数据库造成很大的压力,这个时候就相当于出现了缓存穿透。
解决方案
布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以 hash 形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力
但是这两种方法会存在两个问题:
- 如果空值能够被缓存起来,这就意味着缓存需要更多的空间来存储更多的键,因为当中可能会有很多空值的键
- 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致性,这对于需要保持一致性的业务会有影响
缓存击穿(量太大,缓存过期)
微博服务器宕机
概念
这里需要注意和缓存穿透的区别,缓存击穿,是指一个 key 非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个 key 在失效的瞬间,持续的大并发就穿破缓存,直接请求到数据库,就像一个屏障上凿开了一个孔
当某个 key 在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同行访问数据库来查询最新的数据,并且回写缓存,会导致数据库压力瞬间增大
解决方案
设置热点永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点 key 过期后产生的问题
加互斥锁
分布式锁:使用分布式锁,保证对于每个 key 同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大
缓存雪崩
概念
缓存雪崩,是指在某一个时间段,缓存集中过期失效。例如:redis 宕机
解决办法
redis 高可用
这个思想的含义是,既然 redis 都有可能挂掉,那就多设置几台 redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建集群(异地多活)
限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询和写缓存,其他线程等待
数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一次,这样部分可能大量访问的数据就会先加到缓存中,在即将发生大并发访问前手动触发加载缓存不同的 key,设置不同的过期时间,让缓存失效的时间尽量均匀点