Bilibili视频参考:狂神聊Redis
学习方式:不是为了面试和工作而学习,仅仅是为了兴趣!兴趣才是最好的老师
基本的理论先学习,然后将知识融会贯通
Redis学习目录
单机MySQL的年代:90年代,一个基本的网站访问量不会太大,单个数据库完全足够!
1. Memcached(缓存) + MySQL + 垂直拆分(读写分离)
网站80%都是在读数据,每一次都要去查询数据库的话就十分麻烦!所以说我们希望减轻数据的压力,我们可以使用缓存来保证效率
发展过程:优化数据结构和索引 ——> 文件缓存(IO)——> Memcached(当时最热门的技术!)
2. 分库分表 + 水平拆分 + MySQL集群
技术和业务在发展的同时,对人的要求也越来越高!
早些年MyISAM:表锁,十分影响效率!高并发下就会出现严重的锁问题
转战Innodb:行锁
慢慢地就开始使用分库分表来解决写的压力!MySQL在那个年代推出了表分区!然而并没有多少公司使用!
MySQL的集群,很好的解决了那个年代的所有需求。
3. 如今最近的年代
2010 — 2020 十年之间,世界已经发生了翻天覆地的变化;(定位:也是一种数据、热榜)
MySQL 等关系型数据库就不够用了!数据量很多,变化很快~
MySQL 有的使用它来存储一些比较大的文件,如博客、图片。数据库表很大,效率就低了,如果有一种数据库来专门处理这种数据,MySQL 的压力就会变得十分小(研究如何处理这些问题)但在大数据的IO压力下,表几乎没法更大!
4. 为什么要用NoSQL
用户的个人信息、社交网络、地理位置,用户自己产生的数据、用户日志等等爆发式增长!
这个时候我们就需要使用NoSQL数据库,NoSQL可以很好的处理以上的情况
NoSQL = Not Only SQL(不仅仅是SQL)
关系型数据库:表格,行、列
泛指非关系型数据库,随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0时代的产物!尤其是超大规模的高并发的社区!暴露出很多难以克服的问题,NoSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的,而且是我们当下必须要掌握的一个技术!
很多数据类型如:用户的个人信息,社交网络,地理位置。这些数据类型的存储不需要一个固定的格式,不需要多种的操作就可以横向扩展的!Map
NoSQL特点
方便扩展(因为数据之间没有关系)
大数据量高性能(Redis一秒写8万次,读取11万次,NoSQL的缓存记录级是一种细粒度的缓存,性能会比较高)
数据类型是多样型的(不需要事先设计数据库,随取随用)
传统 RDBMS 和 NoSQL
传统的RDBMS
- 结构化组织
- SQL
- 数据和关系都存在单独的表中
- 操作,数据定义语言
- 严格的一致性
- 基础的事务
- ...
NoSQL
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储、列存储、文档存储、图形数据库(社交关系)
- 最终一致性
- CAP 定理和 BASE(异地多活)初级架构师!
- 高性能、高可用、高可扩
- ...
了解:大数据时代的”3V + 3高“
大数据时代的3V:主要是描述问题的
- 海量Volume
- 多样Variety
- 实时Velocity
大数据时代的3高:主要是对程序的要求
- 高并发
- 高可扩(随时水平拆分,机器不够,可通过扩展机器来解决)
- 高性能(保证用户体验)
真正在公司中的实践:NoSQL + RDBMS
技术没有高低之分,就看如何使用(内功、思维的提高很重要!)
1. 商品的基本信息
名称、价格、商家信息等
关系型数据库就可以解决了,MySQL / Oracle(淘宝早年就去IOE了 王坚:推荐文章:阿里云的这群疯子,读完需要40分钟,但很重要!)
淘宝内部的MySQL不是大家用的MySQL
2. 商品的描述、评论(文字比较多)
文档型数据库,MongoDB
3. 图片
分布式文件系统 FastDFS
- 淘宝的 TFS
- Google的 GFS
- Hadoop HDFS
- 阿里云的 OSS
4. 商品的关键字(搜索)
- 搜索引擎 solr elasticsearch
- ISeach: 多隆(多去了解一下这些技术大佬!)
所有牛逼的人都有一段苦逼的岁月,但你只要像傻逼一样的去坚持
5. 商品热门的波段信息
- 内存数据库 Redis、Tair、Memache...
6. 商品的交易,外部的支付接口
- 三方应用
要知道,一个简单的页面背后的技术一定不是平常想的那么简单
大型互联网应用问题:
KV键值对:
文档型数据库(bson格式和json一样):
列存储数据库:
图关系数据库:
它不是存图形,放的是关系,比如:朋友圈社交网络,广告推荐
Neo4j,InfoGrid;
Redis 是什么?
Redis(Remote Dictionary Server)即远程字典服务
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
Redis 会周期性地把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
免费且开源,是目前最热门的 NoSQL 技术之一!也被人们称为结构化数据库
Redis 能干嘛?
内存存储、持久化,内存中的数据是断电即失的,所以说持久化很重要(两种持久化机制:rdb、aof)
效率高,可用于高速缓存
发布订阅系统
地图信息分析
计时器、计数器(浏览量)
…
特性
多样的数据类型
持久化
集群
事务
…
学习中需要用到的东西
官网:https://redis.io/
中文网:http://www.redis.cn/
下载地址:通过官网下载即可(Windows在Github上下载,因为Redis推荐在Linux服务器上搭建)
略
1、下载安装包:redis-6.0.6.tar.gz
2、解压Redis的安装包
3、进入解压后的文件,可以看到我们redis的配置文件:redis.conf
4、基本的环境安装
yum install gcc-c++
make
make install
5、redis的默认安装路径 /usr/local/bin
6、复制/opt/redis-6.0.6/redis.conf到 /usr/local/bin/redis-conf(自建文件夹)下
7、redis默认不是后台启动的,需要修改配置文件。保证在后台启动(守护进程模式启动)
8、启动redis服务,通过指定配置文件启动(以后可能是多个配置文件启动多次,集群)
9、使用redis-cli进行连通测试
10、查看redis的进程是否已开启
11、关闭redis服务
12、再次查看进程是否存在
13、后面会使用单机多redis启动集群测试!
redis-benchmark
是一个压力测试工具,官方自带
redis-benchmark 命令参数:
简单测试
# 测试:100个并发连接 每个并发100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
Redis有16个数据库,我们默认使用的是第1个,索引号为0。通过select 命令选择数据库
清除当前数据库flushdb
清除所有数据库的数据flushall
Redis 是单线程的!
明白Redis是很快的,官方表示,Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是机器的内存和网络带宽。
Redis 为什么是单线程还那么快?
Redis 是C语言写的,官方提供的数据为 100000+ 的QPS,这个效率不比同样是key-value的Memecache差
核心:redis是将所有的数据全部放在内存中,所以说使用单线程效率就是最高的(多线程CPU上下文切换会影响效率),对于内存系统来说,没有上下文切换,效率就是最高的。多次读写都是在一个CPU上的,在内存情况下就是最佳的方案!
翻译自Redis官方的介绍
Redis 是一个开源(BSD许可)的,内存中的数据结构,它可以用作数据库、缓存和消息中间件MQ。它支持多种类型的数据结构,如字符串(strings)、散列(lists)、集合(sets)、有序集合(sorted sets)与范围查询,bitmaps,hyperloglogs和地理空间(geospatial)索引半径查询。Redis内置了复制(replication),LUA脚本(Luascripting),LRU驱动时间(LRU eviction),事务(transactions)和不同级别的磁盘持久化(persistence),并通过Redis哨兵(Sentinel)和自动区分(Cluster)提供高可用性(high availability)。
Redis命令不区分大小写
127.0.0.1:6379> set name "zhangziang" # set key
OK
127.0.0.1:6379> get name # get key
"zhangziang"
127.0.0.1:6379> type name # 查看key的数据类型
string
127.0.0.1:6379> keys * # 查看所有的key
"name"
127.0.0.1:6379> exists name # 查看key是否存在
(integer) 1
127.0.0.1:6379> move name 1 # 删除数据库1中的key
(integer) 1
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set age "22"
OK
127.0.0.1:6379> expire age 10 # 设置key的过期时间为10s
(integer) 1
127.0.0.1:6379> ttl age # 查看key的剩余有效期限
(integer) 5
127.0.0.1:6379> ttl age
(integer) 3
127.0.0.1:6379> ttl age
(integer) -1
127.0.0.1:6379> get age
(nil)
如果遇到不会的命令,可以查看官网的帮助文档
90% 的 java程序员使用 redis 只会用String
命令:
append key value:向key中追加内容
strlen key:获取key对应value的长度
incr key:key的value加1
decr key:key的value减1
incrby key num:key的value加num
decrby key num:key的value减num
setex key second value:设置key-value有效时长为second秒
setnx key value:key不存在则设置;存在则设置失败
mset k1 v1 k2 v2 k3 v3:一次设置多个key-value
mget k1 k2:一次取出多个value
msetnx k1 v1 k2 v2:一次设置多个key-value(不存在才设置,具有原子性)
getset key value:先get后set,如果不存在则创建,存在则修改
示例:
127.0.0.1:6379> get name
"zhangziang"
127.0.0.1:6379> append name hello # 向key中追加内容
(integer) 15
127.0.0.1:6379> get name
"zhangzianghello"
127.0.0.1:6379> strlen name # 获取key的值的长度
(integer) 15
# ----------------------------- value的增与减 ------------------------------
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> incr views # key的值加1
(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 # key的值减1
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> incrby views 10 # key的值加10
(integer) 11
127.0.0.1:6379> get views
"11"
127.0.0.1:6379> decrby views 5 # key的值减5
(integer) 6
# --------- setex(set with expire):设置过期时间(set key time value)---------
127.0.0.1:6379> setex name 10 tzuang # 设置key有效时长为10s
OK
127.0.0.1:6379> ttl name
(integer) 6
127.0.0.1:6379> ttl name
(integer) 3
127.0.0.1:6379> ttl name
(integer) 2
127.0.0.1:6379> get name
(nil)
# ------- setnx(set if not exist):不存在则设置(在分布式锁中会常使用)----------
127.0.0.1:6379> setnx age 22 # 如果key不存在,则设置
(integer) 1
127.0.0.1:6379> setnx age 21 # 如果key存在,则设置失败
(integer) 0
127.0.0.1:6379> get age
"22"
# ----------------------------- 设置多个键值对 -------------------------------
127.0.0.1:6379> mset name tzuang age 22 sex male # 一次设置多个值
OK
127.0.0.1:6379> keys *
1) "sex"
2) "name"
3) "age"
127.0.0.1:6379> mget sex name # 一次取出多个值
1) "male"
2) "tzuang"
127.0.0.1:6379> msetnx name zhangziang phone 123 # 一次设置多个key-value(不存在才设置)
(integer) 0
127.0.0.1:6379> get phone # 由此可见,msetnx命令具有原子性,要么都成功,要么都失败
(nil)
# --------------------------------- 对象 --------------------------------
# 这里的key是一个巧妙的设计:object:{id}:{field},如此设计在Redis中完全OK!
127.0.0.1:6379> mset user:1:name tzuang user:1:age 21
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "tzuang"
2) "21"
# ---------------------------- 组合命令 ----------------------------------
# getset:不存在则设置,存在则修改(CAS:比较并交换。原子操作)
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"
String的使用场景:value除了是字符串,还可以是数字
在Redis里面,我们可以把list当成:栈、队列、阻塞队列。
几乎所有的list命令都是以 “l” 开头的
# -------------------------------- 插入 ----------------------------------
127.0.0.1:6379> lpush list one # 将一个值或多个值插入list头部(左)
(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 # 取出list所有的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1 # 取出list[0, 1]所有的值
1) "three"
2) "two"
127.0.0.1:6379> rpush list four # 将一个值或多个值插入list尾部(右)
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "four"
# ------------------------------- 删除 ------------------------------------
127.0.0.1:6379> lpop list # 移除list首部(左)第一个元素
"three"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
3) "four"
127.0.0.1:6379> rpop list # 移除list尾部(右)第一个元素
"four"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
# ----------------------------- 下标访问 -----------------------------------
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex list 0
"two"
# ----------------------------- list长度 ----------------------------------
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> llen list
(integer) 3
# ---------------------------- 移除指定的值 ---------------------------------
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 # 移除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 2 three # 移除2个three
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "two"
# ------------------------------------------------------------------------
ltrim:修剪list
例:ltrim list 1 2 截取list[1, 2]区间的元素
rpoplpush:移除列表最后一个元素,并将他添加到一个新的列表中
例:lpoplpush list1 list2
lset:修改list指定下标的值
lset list 0 value1
linsert xxx before/after xxx xxx:将值插入列表中某个元素的前面(左)或后面(右)
linsert list before/after "world" "other"
小结:
- 他实际上是一个链表,before Node after,left,right都可以插入值
- 如果key不存在,则创建新的链表
- 如果key存在,则新增内容
- 如果移除了所有值,空链表,也代表不存在!
- 在两边插入或改动值效率最高
消息排队!消息队列(Lpush Rpop)、栈(Lpush Lpop)
set无序、不重复
127.0.0.1:6379> sadd myset "hello" # set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd myset "tzuang"
(integer) 1
127.0.0.1:6379> sadd myset "java"
(integer) 1
127.0.0.1:6379> smembers myset # 查看set中的所有值
1) "hello"
2) "java"
3) "tzuang"
127.0.0.1:6379> sismember myset tzuang # 判断某值是否在set中
(integer) 1
127.0.0.1:6379> sismember myset ziang
(integer) 0
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> smembers myset
1) "java"
2) "tzuang"
127.0.0.1:6379> srandmember myset # 随机显示一个元素
"java"
127.0.0.1:6379> spop myset # 随机删除一个元素
"tzuang"
127.0.0.1:6379> smembers myset
1) "java"
smove set1 set2 value:从set1移动一个值到set2
sdiff set1 set2:取set1、set2差集
sinter set1 set2:取set1、set2交集(共同好友)
sunion set1 set2:取set1、set2并集
Map集合,key-map。本质和String类型没有太大区别,还是一个简单的key-value
set myhash field tzuang
127.0.0.1:6379> hset myhash field1 tzuang # set一个具体 key-value
(integer) 1
127.0.0.1:6379> hget myhash field1
"tzuang"
127.0.0.1:6379> hmset myhash field1 hello field2 # set多个 key-value
OK
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> hlen myhash # 获取key-value数
(integer) 2
127.0.0.1:6379> hexists myhash field1 # 判断key是否存在
(integer) 1
127.0.0.1:6379> hkeys myhash # 获取所有的key
1) "field1"
2) "field2"
127.0.0.1:6379> hvals myhash # 获取所有的value
1) "hello"
2) "world"
127.0.0.1:6379> hdel myhash field2 field1 # 删除多个字段值
(integer) 2
127.0.0.1:6379> hgetall myhash
(empty array)
127.0.0.1:6379> hset myhash field1 5
(integer) 0
127.0.0.1:6379> hincrby myhash field1 2 # 指定增量
(integer) 7
127.0.0.1:6379> hincrby myhash field1 -3 # 指定减量
(integer) 4
127.0.0.1:6379> hsetnx myhash field4 hello # key不存在则创建
(integer) 1
127.0.0.1:6379> hsetnx myhash field4 world
(integer) 0
hash变更的数据 user name age,尤其是用户之类的、经常变动的信息。hash更适合于对象的存储,String更适合字符串
127.0.0.1:6379> hset user:1 name zhangziang
(integer) 1
127.0.0.1:6379> hget user:1 name
"zhangziang"
在set的基础上,增加了一个值,set k1 v1 zset k1 num1 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 # 查看zset所有值
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> zrangebyscore myset -inf 2 withscores # 查询 (-∞, 2]所有值,并按照分值从小到大排列
1) "one"
2) "1"
3) "two"
4) "2"
127.0.0.1:6379> zrem myset one # 移除元素
(integer) 1
127.0.0.1:6379> zrange myset 0 -1
1) "two"
2) "three"
127.0.0.1:6379> zrevrange myset 0 -1 # 倒序查看zset所有值
1) "three"
2) "two"
127.0.0.1:6379> zcard myset # 获取有序集合中的元素个数
(integer) 2
朋友的定位,附近的人,打车距离计算?
Redis 的 Geo 在 Redis 3.2 版本就推出了!这个功能可以推算地理位置信息,两地之间的距离,方圆几里的人!
可以查询一些测试数据
相关命令:
getadd:添加地理位置
# 规则:两极无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入
# 有效的精度从-180°到180°
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
(integer) 1
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 2
getpos:获得当前定位,一个坐标值
# 获取指定城市的经纬度
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city chongqing
1) 1) "106.49999767541885376"
2) "29.52999957900659211"
geodist:获取两定位点的直线距离
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"
georadius 以给定的经纬度为中心,找出某一
我附近的人?(获得所有附近的人的地址,定位)
127.0.0.1:6379> georadius china:city 110 30 1000 km # 找出以110.30为中心方圆1000km的城市
1) "chongqing"
2) "xian"
3) "hangzhou"
127.0.0.1:6379> georadius china:city 110 30 1000 km withdist # 带距离
1) 1) "chongqing"
2) "341.9374"
2) 1) "xian"
2) "483.8340"
3) 1) "hangzhou"
2) "977.5143"
127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord # 带经纬度
1) 1) "chongqing"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) 1) "108.96000176668167114"
2) "34.25999964418929977"
3) 1) "hangzhou"
2) 1) "120.1600000262260437"
2) "30.2400003229490224"
# 兼以上,并限定仅显示2条
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"
georadiusbymember:找出位于指定元素周围的其他元素
127.0.0.1:6379> georadiusbymember china:city beijing 1000 km
1) "beijing"
2) "xian"
127.0.0.1:6379> georadiusbymember china:city shanghai 500 km
1) "hangzhou"
2) "shanghai"
geohash:返回一个或多个位置元素的geohash表示
该命令将返回11个字符的geohash字符串
# 将二维码的经纬度转换为一维的字符串,如果两个字符串越接近,那么则距离越近!
127.0.0.1:6379> geohash china:city beijing chongqing
1) "wx4fbxxfke0"
2) "wm5xzrybty0"
geo底层的实现原理其实就是zset,我们可以使用zset命令来操作geo
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqing"
2) "xian"
3) "hangzhou"
4) "shanghai"
5) "beijing"
127.0.0.1:6379> zrem china:city beijing
(integer) 1
什么是基数?
A {1, 3, 5, 7, 8, 9, 7}
B {1, 3, 5, 7, 8}
基数(不重复的元素)= 5,可以接受误差
简介
Redis 2.8.9 版本就更新了 Hyperloglog 数据结构
Redis Hyperloglog 基数统计的算法
优点:占用的内存是固定的,2^64 不同的元素的技术,只需要废12KB内存,如果要从内存角度来比较的话 Hyperloglog首选!
网页的 UV(一个人访问一个网站多次,但还是算作一个人)
传统的方式,set保存用户的id,然后就可以统计set中元素数量作为标准判断
这个方式如果保存大量的用户id,就会比较麻烦。我们的目的是为了计数而不是为了统计
0.81% 错误率!统计UV任务,可以忽略不计
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 # 统计第一组元素数量
(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 mykey
(integer) 10
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 或者自己的数据类型即可!
位存储
统计用户信息,活跃,不活跃!登录、未登录!打卡,365打卡!两个状态的,都可以使用bitmaps
Bitmaps 位图,数据结构。都是操作二进制位来进行记录,就只有 0 和 1 两个状态
365 天 = 365 bit 1字节 = 8bit 46个字节左右
测试
使用bitmap记录周一到周日的打卡
周一:0,周二:1,周三:1
127.0.0.1:6379> setbit sign 0 0
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 1
(integer) 0
查看某一天是否打卡
127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 6
(integer) 1
统计操作,统计打卡的天数
127.0.0.1:6379> bitcount sign # 统计这周打卡天数
(integer) 4
原子性:要么同时成功,要么同时失败
Redis 单条命令可保证原子性,但事务不保证原子性。
Redis 事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行!
一次性、顺序性、排他性。执行一系列的命令
----- 队列 set set set 执行 -----
Redis 事务没有隔离级别的概念!
所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会执行
Redis 单条命令是保证原子性的,但是事务不保证原子性!
redis的事务
正常执行事务
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> 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> 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> exec # 执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5 # 事务队列中的命令都不会被执行
(nil)
运行时异常(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> 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) "v3"
监控!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 # 但假设在事务执行期间,另一个线程改动了money的值...
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec # 执行前,另一个线程修改了money的值,所以会导致事务执行失败
1) (integer) 80
2) (integer) 20
事务执行失败案例:测试多线程修改值,使用watch可以当作redis 的乐观锁操作
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
(nil)
# -------------------------------
127.0.0.1:6379> get money
"100"
127.0.0.1:6379> incrby money 100
(integer) 200
如果事务执行失败,获取最新的值就好
127.0.0.1:6379> unwatch # 1.如果发现事务执行失败,就先解锁
127.0.0.1:6379> watch money # 2.获取最新的值,再次监视,select version
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 # 3.比对监视的值是否发生了变化,如果没有变化就执行成功,如果有变化就执行失败
1) (integer) 190
2) (integer) 10
使用 Java 来操作 Redis
Jedis 是 Redis 官方推荐的 Java 连接开发工具,使用 Java 操作 Redis 中间件!如果你要是用 Java操作 Redis,那么一定要对 Jedis 十分熟悉
1、导入对应的依赖
<dependencies>
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>3.6.0-RC1version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.62version>
dependency>
dependencies>
2、代码测试:
String
List
Set
Hash
Zset
所有的API方法,就是我们上面学习的命令。
SpringBoot 操作数据:spring-data、jpa、jdbc、mongodb、redis
SpringData 也是和 SpringBoot 齐名的项目
说明:在 SpringBoot 2.x 之后,原来使用的 jedis 被替换为了 lettuce
jedis:采用直连,多个线程操作的话是不安全的。避免不安全就要使用 jedis pool 连接池,类似BIO模式
lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全情况,可以减少线程数量,类似NIO模式
源码分析:
@Bean
// 下面这个注解意为,没有redisTemplate这个bean时,则注入如下默认的redisTemplate,所以说我们可以自定义一个redisTemplate来替换这个默认的redisTemplate
@ConditionalOnMissingBean(name = {"redisTemplate"})
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 默认的 RedisTemplate 没有过多的设置,redis 对象都是需要序列化的
// 两个泛型都是 Object,Object的类型,我们后使用需要强制转换
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean // 由于 String 是redis中最常用的类型,所以单独提取出一个bean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
整合测试
1、导入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
2、配置连接
# 配置redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
3、测试
@Test
void contextLoads() {
// redisTemplate操作不同的数据类型,
// opsForValue():操作 String
redisTemplate.opsForValue().set("string", "zhangziang");
// opsForList():操作 List
redisTemplate.opsForList().leftPushAll("list", "name", "zhangziang", "age", "22");
// opsForSet():操作 Set
redisTemplate.opsForSet().add("set", "name", "age", "phone");
// opsForHash():操作 Hash
redisTemplate.opsForHash().put("hash", "name", "zhangziang");
// opsForZSet():操作ZSet
redisTemplate.opsForZSet().add("zset", "name", 1);
// opsForGeo()
// opsForHyperLogLog()
// ...
RedisConnection redisConnection = redisTemplate.getConnectionFactory().getConnection();
System.out.println(redisTemplate.opsForValue().get("string"));
// redisConnection.flushDb();
// redisConnection.flushAll();
// 关闭连接
redisConnection.close();
}
@Configuration
public class RedisConfig {
// 自定义的RedisTemplate
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 为了开发方便,一般直接使用
RedisTemplate<String, Object> template = new RedisTemplate<String, Obejct>();
template.setConnectionFactory(factory);
// Json序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(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;
}
}
启动的时候就通过配置文件来启动!
工作中一些小小的配置,就可以让你脱颖而出。行家有没有,出手就知道
单位
包含 INCLUDE
网络 NETWORK
bind 127.0.0.1 # 绑定的IP
protected-mode yes # 是否受保护,一般都是开启的,以保证安全性
port 6379 # 端口
通用 GENERAL
daemonize yes # 以守护进程方式运行,默认no,我们需要自行配置为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
Redis是内存数据库,如果不持久化,数据就会丢失。因为内存是断电即失的
持久化,在规定时间内执行了多少次操作才会持久化到文件(.rdb文件和.aof文件)
# 900s内(15min内)如果至少有 1 个key进行了修改,我们就进行持久化操作
save 900 1
# 300s内(5min内)如果至少有 10 个key进行了修改,我们就进行持久化操作
save 300 10
# 60s内(1min内)如果至少有 10000 个key进行了修改,我们就进行持久化操作
save 60 10000
# 之后学习Redis的持久化,会自行定义这个测试!
stop-writes-on-bgsave-error yes # 持久化如果出错,是否还继续工作
rdbcompression yes # 是否压缩rdb文件(持久化的文件),这个操作需要消耗一些CPU资源
rdbchecksum yes # 保存rdb文件时,进行错误的检查校验
dir ./ # rdb文件保存的目录
复制 REPLICATION(后面学习主从复制的时候再讲解这块配置)
安全 SECURITY
不设置密码时,直接就可以ping通
可以在配置文件中设置redis密码
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get requirepass # 获取redis密码
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "123456" # 设置redis密码
OK
127.0.0.1:6379> config get requirepass # 发现所有的命令都没有权限了
(error) NOAUTH Authentication required.
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123456 # 使用redis密码进行登录
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"
限制 CLIENTS
maxclients 10000 # 设置能连接redis的最大客户端数量
maxmemory <bytes> # redis配置最大的内存容量
maxmemory-policy noeviction # 内存到达上限之后的处理策略,有以下参数
# 1、volatile-lru:只设置了过期时间的key进行LRU(默认值)
# 2、allkeys-lru:删除lru算法的key
# 3、volatile-random:随机删除即将过期的key
# 4、allkeys-random:随即删除
# 5、volatile-ttl:删除即将过期的
# 6、noeviction:永不过期,返回错误
AOF配置 APPEND ONLY MODE
appendonly no # 默认不开启aof模式,默认使用rdb方式持久化,大部分情况下rdb完全够用!
appendfilename "appendonly.aof" # 持久化的文件的名字
# appendfsync always # 每次修改都会sync,消耗性能
appendfsync everysec # 每秒执行一次sync,可能会丢失这1s的数据
# appendfsync no # 不执行sync,这个时候操作系统自己同步数据,速度最快
具体的配置,我们在Redis持久化中详细学习
面似和工作,持久化都是重点!
Redis是内存数据库,如果不将内存中的数据库状态保存的磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以Redis提供了持久化功能!
什么是RDB
在主从复制中,RDB就是备用的,从机上面
RDB原理:
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot(快照),它恢复时是将快照文件直接读到内存里。
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程不进行任何IO操作。这就确保了极高的性能。如果需要进行大规模数据的恢复,且对数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加高效。RDB的缺点是最后一次持久化的数据可能丢失。默认就是RDB,一般情况下不需要修改这个配置!
有时在生产环境我们会对这个文件进行备份
RDB保存的文件是 dump.rdb,都是在配置文件的SNAPSHOT中进行配置的
触发机制:
数据恢复:
1、只要将rdb文件放置在redis启动目录就可以,redis启动的时候会自动检查dump.rdb,恢复其中的数据
2、查看需要存放的位置
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin" # 这个目录下存在dump.rdb文件,redis启动时就会还原其中的数据
几乎它自己默认的配置就够用了,但是我们还是需要去学习
优点:
缺点:
什么是AOF
将我们的所有命令都记录下来,history,恢复的时候就把这个文件全部执行一遍
原理:
以日志的形式来记录每次写操作,将Redis执行过的所有指令记录下来(读操作不记录),只需追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis启动的话就根据日志文件的内容将写指令从头到尾执行一次以完成数据的恢复工作。
AOF恢复的是 appendonly.aof 文件
append
默认是不开启的,我们需要手动进行配置。我们只需要将appendonly改为yes即可开启AOF!
重启redis就可以生效了!
如果aof文件内容被破坏,那么redis客户端将无法正常连接服务。如下:
可以通过redis给我们提供的工具来修复aof文件。执行命令redis-check-aof --fix appendonly.aof
然后重启redis服务,aof就可以正常恢复数据了
重写规则说明(了解即可)
AOF默认就是文件的无限追加,文件会越来越大!
如果 aof 文件大于64M,太大了!fork一个新的进程来将我们的文件进行重写
优点和缺点
appendonly no # 默认不开启aof模式,默认使用rdb方式持久化,大部分情况下rdb完全够用!
appendfilename "appendonly.aof" # 持久化的文件的名字
# appendfsync always # 每次修改都会sync,消耗性能
appendfsync everysec # 每秒执行一次sync,可能会丢失这1s的数据
# appendfsync no # 不执行sync,这个时候操作系统自己同步数据,速度最快
优点:
缺点:
扩展:
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。微信、微博、关注系统!
Redis 客户端可以订阅任意数量的频道
订阅/发布消息图:
三角色:消息发布者、频道、消息订阅者
下图展示了频道 channel1,以及订阅这个频道的三个客户端 —— client2、client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时,这个消息就会被发送给订阅它的三个客户端:
命令:
这些命令被广泛用于构建即时通信应用,比如网络聊天室(chatroom)和实时广播、实时提醒等。
测试:
订阅端:
127.0.0.1:6379> subscribe tzuang # 订阅一个频道:tzuang
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "tzuang"
3) (integer) 1
# 订阅成功 ↑↑↑↑↑↑,等待读取推送的消息 ↓↓↓↓↓↓
1) "message"
2) "tzuang"
3) "Hello,ZhangZiang"
1) "message"
2) "tzuang"
3) "keep going soldier"
发布端:
127.0.0.1:6379> publish tzuang Hello,ZhangZiang # 发布消息到频道tzuang
(integer) 1
127.0.0.1:6379> publish tzuang "keep going soldier" # 发布消息到频道tzuang
(integer) 1
原理:
Redis 是通过C实现的,通过分析 Redis 源码里的 public.c 文件,了解发布订阅机制的底层实现,籍此加深对 Redis 的理解。
Redis 通过 PUBLISH、SUBSCRIBE 和 PSUBSCRIBE 等命令实现发布和订阅功能。
通过 SUBSCRIBE 命令订阅某频道后,redis-server 里维护了一个字典,字典的键就是一个个 channel,而字典的值则是一个链表,链表中保存了所有订阅这个 channel 的客户端。SUBSCRIBE命令的关键,就是将客户端添加到给定 channel 的订阅链表中。
通过 PUBLISH 命令向订阅者发送信息,redis-server 会使用给定的频道作为键,在它所维护的 channel 字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者。
Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的作用就是用作实时消息系统,比如普通的即时聊天,群聊等功能。
使用场景:
稍微复杂的场景就会使用消息中间件 MQ(RabbitMQ、Kafka)
主从复制,是指将一台Redis服务器的数据,复制到其他Redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave以读为主。
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
主从复制的作用主要包括:
一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的,原因如下:
电商网站上的商品,一般都是一次上传,无数次浏览的,也就是“多读少写”
对于这种场景,我们可以使用如下架构:
主从复制,读写分离!80%的情况都是在进行读操作。主从复制减缓服务器压力,架构中经常使用!至少要做到一主二从。
只要在公司中,主从复制就是必须要使用的。在真实项目中不可能单机使用Redis!
只配置从库,不用配置主库
127.0.0.1:6379> info replication # 查看当前库的信息
# Replication
role:master # 角色 master
connected_slaves:0 # 没有从机
master_replid:e5983a6b2b49e33cd2adce640a5d388f8fb12bee
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
复制3个配置文件,然后修改对应的配置信息
1、端口
2、pid名字
3、log文件名
4、dump.rdb文件名
修改完后,启动3个redis服务器,可以通过进程查询命令查询
默认情况下,每台Redis服务器都是主节点,一般情况下只用配置从机就可以
认老大,一主(79)二从(80、81)
# -------------------- 从机 ---------------------
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:3
master_sync_in_progress:0
slave_repl_offset:14
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:1707caca13a58e913f2b0d2b459eba19936dc359
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:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=42,lag=0
master_replid:1707caca13a58e913f2b0d2b459eba19936dc359
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
两个都配置完:
使用命令配置是暂时的,重启Redis服务时会恢复为主机;
真实的主从复制配置应该在配置文件中配置,是永久的。
细节:
主机可以写,从机不能写只能读。主机中的所有信息和数据,都会自动被从机保存!
主机写:
从机只能读:
测试:主机断开连接,从机依旧可以连接到主机,但依然不能进行写操作。当主机重新连接时,从机才能继续获取主机写入的数据。
使用命令行来配置主从,一旦重启从机服务,从机就会恢复为主机。所以命令行配置是暂时的,要想永久化,可以在配置文件中配置。但如果从机再次连接主机,也会获取到主机中的数据
复制原理:
Slave 启动成功连接到 Master 后会发送一个sync同步命令。
Master接收到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完毕之后,Master将传送整个数据文件到Slave,并完成一次完全同步(全量复制)。
全量复制:而Slave服务在接收到数据库文件数据后,将其存盘并加载到内存中
增量复制:Master继续将新的所有收集到的修改命令依次传给Slave,完成同步
只要是重新连接Master,一次完全同步(全量复制)将被自动执行。Master后序用于修改的数据集命令传递给Slave,即增量复制。
上一个Master连接下一个Slave,下图中的第二个节点依旧是从节点,所以无法进行写操作
这个时候也可以完成主从复制
如果这时没有老大了,这时谁来当老大?
谋权篡位(我们通过手动方式选出一个老大):
如果节点一断开了连接,节点二可以使用slaveof no one
让自己变成主机。其他的节点就可以手动连接到这个最新的主节点。
(自动选举老大的模式)
概述:
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这并不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。Redis从2.8开始正式提供了Sentinel(哨兵)架构来解决这个问题。
哨兵能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
假设主服务器宕机,哨兵1先监测到这个结果,但系统并不会马上进行failover过程,因为仅仅是哨兵1主观的认为主服务器不可用,这个现象称为主观下线。当后面的哨兵也监测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover
[故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。
测试: 当前主从状态为一主二从
1、配置哨兵配置文件sentinel.conf
# sentinel monitor 被监控的名称 host port 1
entinel monitor myredis 127.0.0.1 6379 1
数字1表示:主机挂了,slave投票看让谁接替为主机,票数最多的,就成为主机
2、启动哨兵
[root@master bin]# redis-sentinel redis-config/sentinel.conf
15789:X 14 Apr 2021 13:12:33.496 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
15789:X 14 Apr 2021 13:12:33.496 # Redis version=6.0.6, bits=64, commit=00000000, modified=0, pid=15789, just started
15789:X 14 Apr 2021 13:12:33.496 # Configuration loaded
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.0.6 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 15789
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
15789:X 14 Apr 2021 13:12:33.497 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
15789:X 14 Apr 2021 13:12:33.499 # Sentinel ID is e52b2f229943c2126fa468c03026e618a22738c2
15789:X 14 Apr 2021 13:12:33.499 # +monitor master myredis 127.0.0.1 6379 quorum 1
15789:X 14 Apr 2021 13:12:33.499 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
15789:X 14 Apr 2021 13:12:33.501 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
如果Master节点断开,这时就会在从机中随机选择一个服务器(这里有一个投票算法)
如果以前的主机回来了,只能归并到新的主机下当作从机,这就是哨兵模式的规则。
哨兵模式:
优点:
缺点:
哨兵模式的全部配置
# Example sentinel.conf
# 哨兵sentinel实例运行的端口 默认26379
port 26379
# 哨兵sentinel的工作目录
dir /temp
# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字,只能由字母A-z、数字0-9,和'.'、'-'、'_'三字符组成
# quorum 配置多少个sentinel哨兵统一认为master主节点失联,那么这时客观上认为主节点失联了
# sentinel monitor
sentinel monitor mymaster 127.0.0.1 6379 2
# 3s内mymaster无响应,则认为mymaster宕机了
sentinel down-after-milliseconds mymaster 3000
#如果10秒后,mymaster仍没启动过来,则启动failover
sentinel failover-timeout mymaster 10000
# 执行故障转移时, 最多有1个从服务器同时对新的主服务器进行同步
sentinel parallel-syncs mymaster 1
是服务器的高可用问题
Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要害的问题就是数据的一致性问题,从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。
另外一些典型的问题就是:缓存穿透、缓存雪崩和缓存击穿。目前,业界也都有比较流行的解决方案。
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候(比如秒杀),缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时就相当于出现了缓存穿透。
布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;
缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源。
但是这种方法会存在两个问题:
微博服务器宕机
这里需要注意和缓存击穿的区别,缓存击穿是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并回写缓存,会导致数据库瞬间压力过大。
1. 设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题。
2. 加互斥锁
分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获取分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
雪崩发生时,没有一片雪花是无辜的
缓存雪崩是指,在某一个时间段,缓存集中过期失效,Redis 宕机。
产生雪崩的原因之一,比如在写文本的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品在这个时间点比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点,这批商品的缓存全部过期。而对这批商品的访问查询全部落到了数据库上,对于数据库而言,就会产生周期性压力波峰。于是所有的请求都会达到存储层,存储层的调用量会爆增,造成存储层也会挂掉的情况。
其实key集中过期并不非常致命,比较致命的缓存雪崩是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩一定是在某个时间段集中创建缓存,这个时候数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务器节点的宕机对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。
双11:停掉一些服务,保证主要的服务可用。比如退款时提示:业务繁忙,无法退款。
Redis高可用
这个思想的含义是:既然Redis有可能挂掉,那我就多增设几台Redis,一台挂掉后其他还能继续工作,其实就是搭建的集群。(异地多活)
限流降级
这个解决方案的思想是:在缓存失效后通过加锁或队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
数据预热
在正式部署之前,先把可能访问到的数据预先访问一遍,这样这些数据就会加载到缓存中。也就是在即将发生大并发访问前手动加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。