Redis
Nosql概述
为什么要用Nosql
1、单机Mysql年代
90年代,一个基本的网站访问量一般不大,单个数据库完全足够,那个时候,更多的去使用静态页面,服务器根本没有太大的压力!
思考一下,这种情况下,整个网站的瓶颈是什么?
数据量如果太大,一个机器放不下!
数据的索引(B+Tree),一个机器内存也放不下
访问量(读写混合),一个服务器承受不了
只要有三个问题之一,就必须晋级!
2、Memechche(缓存)+mysql+垂直拆分 读写分离
网站80%的情况都是在读,每次去查询数据库的话十分的麻烦,所以希望减轻数据的压力,可以食用缓存来保证效率!
发展过程:优化数据结构和索引-->文件缓存(IO)-->Memecached(当时最热门的技术)
3、分库分表+水平拆分+mysql集群
本质:数据库(读、写)
早些年MyISAM:表锁,十分影响效率!高并发就会出现严重的锁问题
转战InnoDB:行锁
慢慢就开始使用分库分表来解决写的压力------>mysql集群,满足了大部分需求
4、2010-2020年,最近的年代
世界发生了翻天覆地的变化,出现了各种各样的数据,定位、音乐、热榜、文件、图片、博客,mysql等关系型数据库不足够保存多样的数据。大数据的IO下,表几乎无法更大 加列!1亿数据 金丝雀发布(灰度发布)
目前最基本的互联网项目
为什么要用NoSql
用户的个人信息、社交网络、地理位置、用户自己产生的数据,用户日志等等爆发式增长!NoSQL可以很好地处理这些情况
什么是NoSQL
NoSQL
NoSQL ------> Not Only SQL (不仅仅是sql)
关系型数据库:表格 行 列 (POI)
泛指非关系型数据库,随着web2.0互联网的诞生!传统关系型数据库很难对付web2.0时代,尤其是超大规模的高并发的社区!暴露出来很多难以克服的问题,NoSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的!
很多的数据类型,用户的个人信息、社交网络、地理位置,这些数据类型的存储不需要固定的格式,不需要多余的操作就可以横向扩展!
Map<String,Object> 使用键值对来控制
NoSQL特点
1、方便扩展(数据之间没有关系,很好扩展)
2、大数据量高性能(redis一秒写8万次,读取11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)
3、数据类型是多样型的!(不需要事先设计数据库!三大范式设计!随取随用!如果数据量十分大的表,很多人就无法设计了!)
4、传统RDBMS和NoSQL
##传统的RDBMS
-结构化组织
-结构化查询sql
-数据和关系都存在单独的表中 row col
-操作,数据定义语言
-严格的一致性
-基础的事务。。。
##NoSQL
-不仅仅是数据
-没有固定的查询语言
-键值对存储,列存储,文档存储,图形数据库(社交关系)
-最终一致性
-CAP定理和BASE理论 (异地多活) 初级架构师必备!
-高性能,高可用,高可扩(拓)。。。
了解:3V+3高
大数据时代的3V:主要是描述问题的
1.海量Volume
2.多样Variety
3.实时Velocity
大数据时代的3高:
1.高并发
2.高可拓
3.高性能(用户体验)
真正公司的实践:NoSQL+RDBMS一起使用才是最***的
阿里巴巴演进
敏捷开发、极限编程
开源才有希望
作为一个架构师,没有什么是加一层解决不了的,如果有那就++!
# 1、商品的基本信息
名称、价格、商家信息、活动信息、特殊属性
关系型数据库就可以解决! 淘宝早年就去IOE了!--王坚 推荐文章-阿里云的这群疯子
# 2、商品的描述、评论(文字比较多)
文档型数据库 MongoDB
# 3、图片
分布式文件系统 FastDFS
- 淘宝自己的 TFS
- Google GFS
- Hadoop HDFS
- 阿里云 oss
# 4、商品的关键字搜索
- 搜索引擎 solr elasticsearch
- 淘宝 Isearch --多隆
# 5、商品热门的波段信息
- 内存数据库、
- Redis、Tair、Memecache。。。
# 6、商品的交易,外部的支付接口
- 三方应用
大型互联网应用问题:
- 数据类型繁多
- 数据源繁多,经常重构
- 数据要改造,大面积改造
解决问题:
参考:阿里巴巴中文站架构设计实践: https://wenku.so.com/d/3aa1d57bb5f3a2424700fa2530b440ee?src=www_rec
NoSQL的四大分类
四种非关系型数据的区别
KV键值对
- 新浪:Redis
- 美团:Redis+Tair
- 阿里、百度:Redis+Memecache
文档型数据库(bson格式和json一样)
- MongoDB(一般都要掌握)
- MongoDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档!
- MongoDB是一个介于关系型数据库和非关系型数据库中间的产品!MongoDB是非关系型数据库***能最丰富,最像关系型数据库的!
- ConthDB
列存储数据库
- HBase
- 分布式文件系统
图形关系数据库
- 存储的是关系,比如朋友圈社交网络,广告推荐!
- Neo4j、InfoGrid
Redis入门
概述
Redis是什么?
Redis(Remote Dictionary Server),即远程字典服务
Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言的 API,是当下最热门的NoSQL技术之一!也被人们称之为结构化数据库!redis会周期性的把更新的数据写入磁盘或者吧修改操作写入追加的记录文件,并且在此次基础上实现了master-slave同步
Redis能干嘛?
1、内存存储、持久化,内存中是断电即失,所以说持久化很重要(方式:RDB & AOF)
2、效率高,可以用于告诉缓存;
3、发布订阅系统;
4、地图信息分析;
5、计时器、计数器(浏览量。。。);
特性
1、多样的数据类型;
2、持久化;
3、集群
4、事务
学习中需要用到的东西
1、官网:https://redis.io/
2、中文网:http://www.redis.cn/
最新版本7.0.0
安装windows
下载安装包:http://github.co***jkic/redis/releases
redis推荐使用Linux开发使用
基础知识
redis默认16个数据库,默认使用的是第一个数据库(编号从0开始)
基础命令
1、选择数据库 :select 3
2、查看当前数据库的所有key: keys *
3、清空当前数据库:flushdb
4、清除全部数据库数据:flushall
思考:端口号为什么是6379?
###9键按MERZ对应的号码,意大利歌女Alessia Merz名字,redis作者是她的粉丝
redis是单线程的!
redis是很快的,redis是基于内存操作,CPU并不是Redis的性能瓶颈,redis的瓶颈是根据机器的内存和网络带宽,可以使用单线程来实现,没有必要使用多线程。 redis 10万+QPS(每秒查询率= 并发量/平均查询时间)
redis为什么单线程还这么快?
1、误区1:高性能的服务器一定是多线程?
2、误区2:多线程(CPU上下文切换)一定比单线程效率高;
CPU>内存>硬盘 的速度
核心:redis是将所有的数据全部放在内存中,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文切换是个耗时的操作),内存系统来说,如果没有上下文切换效率就是最高的!多次读写在同一个CPU上,在内存情况下,这个就是最优方案。
5种基本数据类型
Redis is an open source (BSD licensed), in-memory data structure store used as a database, cache, message broker, and streaming engine. Redis provides data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes, and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions, and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.
Redis 是一个开源(BSD 许可)的内存中数据结构存储,用作数据库、缓存、消息代理和流引擎。Redis 提供数据结构,例如字符串、哈希、列表、集、带有范围查询的排序集、位图、hyperloglog、地理空间索引和流。Redis 具有内置复制、Lua 脚本、LRU 逐出、事务和不同级别的磁盘持久性,并通过 Redis Sentinel 提供高可用性,并使用 Redis Cluster 进行自动分区。
Redis-key
keys * #查询所有的key
1) "age"
2) "name"
127.0.0.1:6379> exists name #判断当前的key是否存在
(integer) 1
127.0.0.1:6379> exists names
(integer) 0
127.0.0.1:6379> get name #获取key
"kak"
127.0.0.1:6379> expire name 5 #设置过期时间 单位:秒
(integer) 1
127.0.0.1:6379> ttl name #查看当前key的剩余时间,如果没有了,则返回-2;-1标识没有过期时间
(integer) -2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> set name kaka #设置key-value
OK
127.0.0.1:6379> expire name 20
(integer) 1
127.0.0.1:6379> ttl name
(integer) 17
127.0.0.1:6379> ttl name
(integer) 15
127.0.0.1:6379> ttl name
(integer) 14
127.0.0.1:6379> type age #判断当前key 的类型
string
后面如果遇到不会的,查看官网帮助文档 :(https://redis.io/commands/)
String(字符串)
127.0.0.1:6379> set name kaka
OK
127.0.0.1:6379> APPEND name rotte #追加字符串,如果key不存在,与新建key一样
(integer) 9
127.0.0.1:6379> get name
"kakarotte"
127.0.0.1:6379> STRLEN name #获取字符串的长度
(integer) 9
127.0.0.1:6379> APPEND name and bekita
(error) ERR wrong number of arguments for 'append' command
127.0.0.1:6379> APPEND name 'and bekita'
(integer) 19
#########===========加法减法 返回值为结果===============#####
127.0.0.1:6379> set views 0 #初始化0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views #自增1
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> DECR views #自减1
(integer) 0
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> INCRBY views 9 #增加9
(integer) 9
127.0.0.1:6379> get views
"9"
127.0.0.1:6379> DECRBY views 4 #减少4
(integer) 5
127.0.0.1:6379> get views
"5"
################+++++++++++++++++++++##############
127.0.0.1:6379> set name kakarottehhhhhaaaa
OK
127.0.0.1:6379> get name
"kakarottehhhhhaaaa"
127.0.0.1:6379> GETRANGE name 1 4 #截取范围内的字符串,,起始和结尾闭区间
"akar"
127.0.0.1:6379> GETRANGE name 0 -1 #0 -1表示全部字符串,和get key效果一样
"kakarottehhhhhaaaa"
127.0.0.1:6379>
####################################################
127.0.0.1:6379> SETRANGE name 2 wokao #从2开始替换字符串内容
(integer) 18
127.0.0.1:6379> get name
"kawokaotehhhhhaaaa"
127.0.0.1:6379>
#####################################################
# setex (set with expire) 设置过期时间
# setnx (set if not exist) 如果不存在,设置 (分布式锁中会常常使用)
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> get name
"kawokaotehhhhhaaaa"
127.0.0.1:6379> SETEX key3 30 kaka #设置key3值kaka,30秒后过期
OK
127.0.0.1:6379> ttl key3
(integer) 24
127.0.0.1:6379> get key3
"kaka"
127.0.0.1:6379> SETNX key4 redddd #如果不存在,创建key4,成功返回1,失败返回0
(integer) 1
127.0.0.1:6379> keys *
1) "key4"
2) "name"
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> SETNX key4 ll #
(integer) 0
#####################mset mget msetnx 批量设置和获取###########################
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> keys *
1) "key4"
2) "k2"
3) "k3"
4) "name"
5) "k1"
127.0.0.1:6379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 89 k4 v4 #该操作是原子性,k1存在,所以创建失败,k4也失败
(integer) 0
127.0.0.1:6379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"
####################################################
#存储对象
#一种设计: set user:1 {name:zhangsan,age:3} #设置一个user:1对象,值为json字符串保存一个对象
#二种设计: set user:{id}:{field} ,这样设计在redis中是完全ok的
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 1
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "1"
127.0.0.1:6379> getset name kaka #getset表示先获取,然后设置值,可以做更新数据
(nil) #如果不存在,返回nil
127.0.0.1:6379> getset name papa
"kaka"
127.0.0.1:6379> get name
"papa"
String使用场景:value除了是字符串,也可以是数字
- 计数器
- 统计多单位的数量
- 粉丝数
- 对象缓存
List(列表)
基本数据类型:列表
在redis里面(其他语言和场景也是一样的),可以通过规定使用规则,将list作为栈、队列、阻塞队列
#所有list的命令都是以L开头的
127.0.0.1:6379> lpush list1 one #将一个值或者多个值插入到列表头部
(integer) 1
127.0.0.1:6379> lpush list1 two
(integer) 2
127.0.0.1:6379> lpush list1 three
(integer) 3
127.0.0.1:6379> LRANGE list1 0 -1 #截取列表
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LRANGE list1 0 1
1) "three"
2) "two"
127.0.0.1:6379> RPUSH list1 kak #将一个值或者多个值插入到列表尾部
(integer) 4
127.0.0.1:6379> LRANGE list1 0 -1
1) "three"
2) "two"
3) "one"
4) "kak"
127.0.0.1:6379> LPOP list1 #移除列表的第一个元素
"three"
127.0.0.1:6379> LRANGE list1 0 -1
1) "two"
2) "one"
3) "kak"
127.0.0.1:6379> LINDEX list1 0 #通过索引获取值
"two"
127.0.0.1:6379> LLEN list1 #获取列表长度
(integer) 3
127.0.0.1:6379> lpush list1 one #增加重复元素one
(integer) 4
127.0.0.1:6379> LRANGE list1 0 -1 #查看元素
1) "one"
2) "two"
3) "one"
4) "kak"
127.0.0.1:6379> lrem list1 2 one #从左边删除2个one,
(integer) 2
127.0.0.1:6379> LRANGE list1 0 -1
1) "two"
2) "kak"
####################截取列表##################
127.0.0.1:6379> LRANGE list1 0 -1
1) "peizhi"
2) "kak"
3) "one"
4) "two"
5) "kak"
127.0.0.1:6379> LTRIM list1 1 2 #通过下标截取指定的长度,list被改变了
OK
127.0.0.1:6379> LRANGE list1 0 -1
1) "kak"
2) "one"
#################################################
127.0.0.1:6379> rpoplpush list1 list2 #先弹出list1的最后一个值然后设置到第二个列表list2,如果第二个列表不存在,则创建,然后填充数据
"one"
127.0.0.1:6379> LRANGE list1 0 -1
1) "kak"
127.0.0.1:6379> LRANGE list2 0 -1
1) "one"
#########################
#lset 将列表中指定下标的值替换为另外一个值,更新操作,
127.0.0.1:6379> exists list
(integer) 0
127.0.0.1:6379> lset list 0 item #如果不存在list,则报错
(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> lrange list 0 -1
1) "item"
127.0.0.1:6379> lset list 2 items #索引越界
(error) ERR index out of range
#########################
#linsert 插入操作
127.0.0.1:6379> lrange list 0 -1
1) "workd"
2) "hoell"
3) "item"
127.0.0.1:6379> LINSERT list before hoell kaka
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "workd"
2) "kaka"
3) "hoell"
4) "item"
127.0.0.1:6379> LINSERT list after hoell kakall
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "workd"
2) "kaka"
3) "hoell"
4) "kakall"
5) "item"
小结
- 实际上是一个链表,before Node after,left right都可以插入值
- 如果key不存在,创建新的链表
- 如果key存在,新增内容
- 如果移除了所有值,空链表,也代表不存在
- 在两边插入或者改动值,效率最高!中间元素相对来说效率会低一点
Set(集合)
无序不重复
############################
127.0.0.1:6379> sadd myset hello #set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd myset kaka
(integer) 1
127.0.0.1:6379> sadd myset lalal
(integer) 1
127.0.0.1:6379> SMEMBERS myset #查所有看元素
1) "kaka"
2) "lalal"
3) "hello"
127.0.0.1:6379> SISMEMBER myset hello #判断元素是否在集合中,1表示存在
(integer) 1
127.0.0.1:6379> SISMEMBER myset hel
(integer) 0
127.0.0.1:6379> scard myset #获取set集合的元素个数
(integer) 3
127.0.0.1:6379> SREM myset kaka #移除元素,移除成功,返回1,否则返回0
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "lalal"
2) "hello"
127.0.0.1:6379> SREM myset kaka
(integer) 0
####################################################
127.0.0.1:6379> SRANDMEMBER myset #随机获取一个元素
"hello"
127.0.0.1:6379> SMEMBERS myset
1) "lalal"
2) "hello"
127.0.0.1:6379> SMEMBERS myset
1) "python"
2) "poi"
3) "lalal"
4) "hello"
5) "java"
###################################################
127.0.0.1:6379> sadd myset ruby
(integer) 1
127.0.0.1:6379> SRANDMEMBER myset 2 #随机获取两个元素
1) "python"
2) "java"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "python"
2) "java"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "python"
2) "ruby"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "python"
2) "poi"
##################################################
127.0.0.1:6379> SMEMBERS myset
1) "python"
2) "hello"
3) "lalal"
4) "java"
5) "poi"
6) "ruby"
127.0.0.1:6379> SPOP myset #弹出一个值
"poi"
#############################################
127.0.0.1:6379> SMEMBERS myset
1) "python"
2) "hello"
3) "lalal"
4) "java"
5) "ruby"
127.0.0.1:6379> SMEMBERS myset
1) "1"
2) "12"
3) "123"
4) "1245"
127.0.0.1:6379> SMEMBERS myset1
1) "kak"
127.0.0.1:6379> SMOVE myset myset1 1 #移动元素到另外一个集合
(integer) 1
127.0.0.1:6379> SMEMBERS myset1
1) "1"
2) "kak"
127.0.0.1:6379> SMEMBERS myset1
1) "1"
2) "kak"
127.0.0.1:6379> SMEMBERS myset
1) "12"
2) "123"
3) "1245"
######################################
127.0.0.1:6379> SMEMBERS myset
1) "1"
2) "123"
3) "1245"
4) "12"
5) "1la"
127.0.0.1:6379> SMEMBERS myset1
1) "1"
2) "kak"
127.0.0.1:6379> SDIFF myset myset1 #差集
1) "1la"
2) "123"
3) "1245"
4) "12"
127.0.0.1:6379> SINTER myset myset1 #交集 共同好友可以实现
1) "1"
127.0.0.1:6379> SUNION myset myset1 #并集
1) "1245"
2) "12"
3) "123"
4) "kak"
5) "1"
6) "1la"
Hash(哈希)
map集合,key-map,值就是一个map
127.0.0.1:6379> hset myhash name zzm #设置一个值
(integer) 1
127.0.0.1:6379> hget myhash name #获取一个值
"zzm"
127.0.0.1:6379> hmset myhash age 1 sex nan #批量设置值
OK
127.0.0.1:6379> hmget myhash age sex name #批量获取值
1) "1"
2) "nan"
3) "zzm"
127.0.0.1:6379> hgetall myhash # 获取全部的值
1) "name"
2) "zzm"
3) "age"
4) "1"
5) "sex"
6) "nan"
127.0.0.1:6379> hdel myhash name #删除指定的key,对应的value也就消失了
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "age"
2) "1"
3) "sex"
4) "nan"
127.0.0.1:6379> HLEN myhash #查看长度
(integer) 2
################################################
127.0.0.1:6379> HEXISTS myhash name # 判断是否存在 存在返回1,不存在返回0
(integer) 0
127.0.0.1:6379> HEXISTS myhash age
(integer) 1
127.0.0.1:6379> hkeys myhash #只获取key
1) "age"
2) "sex"
127.0.0.1:6379> HVALS myhash #只获取value
1) "1"
2) "nan"
###################################################
127.0.0.1:6379> hgetall myhash
1) "age"
2) "1"
3) "sex"
4) "nan"
5) "count"
6) "5"
127.0.0.1:6379> HINCRBY myhash count 4 #增加4
(integer) 9
127.0.0.1:6379> hincrby myhash count -1 #减少1
(integer) 8
127.0.0.1:6379> hgetall myhash
1) "age"
2) "1"
3) "sex"
4) "nan"
5) "count"
6) "8"
#######################################################
127.0.0.1:6379> hsetnx myhash address gansu #field不存在则新增1,存在则新增失败0
(integer) 1
127.0.0.1:6379> hsetnx myhash address gansu
(integer) 0
127.0.0.1:6379> hgetall myhash
1) "age"
2) "1"
3) "sex"
4) "nan"
5) "count"
6) "8"
7) "address"
8) "gansu"
127.0.0.1:6379> hsetnx myhash address gan
(integer) 0
127.0.0.1:6379> hgetall myhash
1) "age"
2) "1"
3) "sex"
4) "nan"
5) "count"
6) "8"
7) "address"
8) "gansu"
hash存储变更的数据,user name age,尤其用户信息之类的保存,经常变动的信息!hash更适合对象的存储,String更适合字符串的存储
Zset(有序集合)
在set的基础上,增加了一个值 set k1 v1 ==> set k1 score v1
127.0.0.1:6379> zadd myzset 1 one #新增one
(integer) 1
127.0.0.1:6379> zadd myzset 2 two 3 three -1 five #批量新增元素
(integer) 3
127.0.0.1:6379> zrange myzset 0 -1 #遍历
1) "five"
2) "one"
3) "two"
4) "three"
###############################################
127.0.0.1:6379> ZRANGEBYSCORE myzset -inf inf #从 Redis 版本 6.2.0 开始,此命令被视为已弃用。在迁移或编写新代码时,可以用参数将其替换为 ZRANGE
1) "five"
2) "one"
3) "two"
4) "three"
127.0.0.1:6379> ZRANGEBYSCORE myzset inf -inf
(empty list or set)
127.0.0.1:6379> ZRANGEBYSCORE myzset -inf inf withscores #显示全部,从小到大
1) "five"
2) "-1"
3) "one"
4) "1"
5) "two"
6) "2"
7) "three"
8) "3"
127.0.0.1:6379> zrevrange myzset 0 -1 #全部显示,从大到小
1) "nine"
2) "eight"
3) "three"
4) "two"
5) "one"
127.0.0.1:6379> ZRANGEBYSCORE myzset -inf inf withscores limit 0 2
1) "five"
2) "-1"
3) "one"
4) "1"
127.0.0.1:6379> ZRANGEBYSCORE myzset -inf inf withscores limit 0 3
1) "five"
2) "-1"
3) "one"
4) "1"
5) "two"
6) "2"
上面命令可查看官网改动之处
#####################################################################
最新版:
ZRANGE zset (1 5 BYSCORE
ZRANGE zset 10 5 REV BYSCORE
###################################################################
127.0.0.1:6379> zrem myzset five #移除元素
(integer) 1
127.0.0.1:6379> zrange myzset 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> zcard myzset #集合的大小
(integer) 3
127.0.0.1:6379> ZCOUNT myzset 1 3
(integer) 3
127.0.0.1:6379> ZCOUNT myzset 1 (3 #统计范围内有多少
(integer) 2
set 排序:存储班级成绩表 工资表排序等
普通消息 1、重要消息 2,带权重进行判断!
排行榜应用实现
3种扩展数据类型
geospatial(地理位置)
朋友的定位、附近的人,打车距离计算?
#新增地理元素成
redis:6379> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" 员
(integer) 2
#计算两个元素之间的距离
redis:6379> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2
redis:6379> GEODIST Sicily Palermo Catania
"166274.1516"
redis:6379> GEODIST Sicily Palermo Catania km
"166.2742"
redis:6379> GEODIST Sicily Palermo Catania mi
"103.3182"
redis:6379> GEODIST Sicily Foo Bar
(nil)
#显示元素的hash值
redis:6379> GEOHASH Sicily Palermo Catania
1) "sqc8b49rny0"
2) "sqdtr74hyu0"
#显示元素具体的地理位置信息,但是数据跟存储的不是完全一致,有偏差
redis:6379> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2
redis:6379> GEOPOS Sicily Palermo Catania NonExisting
1) 1) "13.36138933897018433"
2) "38.11555639549629859"
2) 1) "15.08726745843887329"
2) "37.50266842333162032"
3) (nil)
#显示距离指定位置距离小于200km的元素,
redis:6379> GEORADIUS Sicily 15 37 200 km WITHDIST
1) 1) "Palermo"
2) "190.4424"
2) 1) "Catania"
2) "56.4413"
redis:6379> GEORADIUS Sicily 15 37 200 km WITHCOORD
1) 1) "Palermo"
2) 1) "13.36138933897018433"
2) "38.11555639549629859"
2) 1) "Catania"
2) 1) "15.08726745843887329"
2) "37.50266842333162032"
redis:6379> GEORADIUS Sicily 15 37 200 km WITHDIST WITHCOORD
1) 1) "Palermo"
2) "190.4424"
3) 1) "13.36138933897018433"
2) "38.11555639549629859"
2) 1) "Catania"
2) "56.4413"
3) 1) "15.08726745843887329"
2) "37.50266842333162032"
#####GEOSEARCH key FROMMEMBER member | FROMLONLAT longitude latitude BYRADIUS radius M | KM | FT | MI | BYBOX width height M | KM | FT | MI [ ASC | DESC] [ COUNT count [ANY]] [WITHCOORD] [WITHDIST] [WITHHASH]
redis:6379> GEOSEARCH Sicily FROMLONLAT 15 37 BYRADIUS 200 km ASC
1) "Catania"
2) "Palermo"
redis:6379> GEOSEARCH Sicily FROMLONLAT 15 37 BYBOX 400 400 km ASC WITHCOORD WITHDIST
1) 1) "Catania"
2) "56.4413"
3) 1) "15.08726745843887329"
2) "37.50266842333162032"
2) 1) "Palermo"
2) "190.4424"
3) 1) "13.36138933897018433"
2) "38.11555639549629859"
3) 1) "edge2"
2) "279.7403"
3) 1) "17.24151045083999634"
2) "38.78813451624225195"
4) 1) "edge1"
2) "279.7405"
3) 1) "12.7584877610206604"
2) "38.78813451624225195"
##此命令类似于 GEOSEARCH,但将结果存储在目标密钥中。此命令取代了现已弃用的 GEORADIUS 和 GEORADIUSBYMEMBER
redis:6379> GEOSEARCHSTORE key1 Sicily FROMLONLAT 15 37 BYBOX 400 400 km ASC COUNT 3
(integer) 3
redis:6379> GEOSEARCH key1 FROMLONLAT 15 37 BYBOX 400 400 km ASC WITHCOORD WITHDIST WITHHASH
1) 1) "Catania"
2) "56.4413"
3) (integer) 3479447370796909
4) 1) "15.08726745843887329"
2) "37.50266842333162032"
2) 1) "Palermo"
2) "190.4424"
3) (integer) 3479099956230698
4) 1) "13.36138933897018433"
2) "38.11555639549629859"
3) 1) "edge2"
2) "279.7403"
3) (integer) 3481342659049484
4) 1) "17.24151045083999634"
2) "38.78813451624225195"
redis:6379> GEOSEARCHSTORE key2 Sicily FROMLONLAT 15 37 BYBOX 400 400 km ASC COUNT 3 STOREDIST
(integer) 3
redis:6379> ZRANGE key2 0 -1 WITHSCORES
1) "Catania"
2) "56.441257870158204"
3) "Palermo"
4) "190.44242984775784"
5) "edge2"
6) "279.7403417843143"
geo底层的实现原理就是zset,我们可以使用zset命令操作元素
zrange key 0 -1 #获取全部的成员
zrem key member #移除成员
Hyperloglog
什么是基数
A{1,2,3,4,5,7,5,8,9}
B{1,3,5,7,8}
基数(不重复的元素个数) 可以接受误差
简介
redis2.8.9版本更新了此数据类型 hyperloglog!
redis hyperloglog基数统计的算法!
优势:占用的内存是固定的,2^64 不同的元素的基数,只需要消耗12KB的内存!如果从内存角度来比较的话,这种数据类型应该是首选
缺点:存在0.81的错误率!统计UV PV 任务是可以忽略不计的!
如果不容错的话,就使用set或者自己的数据类型
网页的UV(一个人访问一个网站多次,但是还算一个人)
传统的方式:set保存用户的id,然后可以统计set中元素数量作为标准判断!
这个方式如果保存了海量的用户id,就比较麻烦,非常吃内存!我们的目的只是为了计数,而不是存储用户id;
#添加元素 计数
redis:6379> PFADD hll a b c d e f g
(integer) 1
redis:6379> PFCOUNT hll
(integer) 7
#pfmerge C A B 将a和b合并到c PFMERGE destkey sourcekey [sourcekey ...]
redis:6379> PFADD hll1 foo bar zap a
(integer) 1
redis:6379> PFADD hll2 a b c foo
(integer) 1
redis:6379> PFMERGE hll3 hll1 hll2
"OK"
redis:6379> PFCOUNT hll3
(integer) 6
Bitmaps
位存储
统计状态信息:活跃状态、登录状态、打卡!涉及两个状态的,都可以使用!
Bitmaps位图,数据结构!都是操作二进制位来记录,就只有0和1两个状态
redis:6379> SETBIT mykey 7 1 #设置值
(integer) 0
redis:6379> GETBIT mykey 0
(integer) 0
redis:6379> GETBIT mykey 7 #获取值
(integer) 1
redis:6379> GETBIT mykey 100
(integer) 0
redis:6379> BITCOUNT mykey #设置为 1 的位数
(integer) 26
redis:6379> BITCOUNT mykey 0 0
(integer) 4
redis:6379> BITCOUNT mykey 1 1
(integer) 6
redis:6379> SET key1 "foobar"
"OK"
redis:6379> SET key2 "abcdef"
"OK"
redis:6379> BITOP AND dest key1 key2
(integer) 6
redis:6379> GET dest
"`bc`ab"
####总共有以下命令
BITCOUNT
BITFIELD
BITFIELD_RO #ro一般是只读的意思
BITOP #BITOP operation destkey key [key ...] ==>AND, OR, XOR and NOT 与、或、异或、非
BITPOS #该命令根据请求返回设置为 1 或 0 的第一个位的位置。
GETBIT
事务
redis单条命令是保持原子性的,但是事务不保证原子性!
redis事务的本质:一组命令的集合!一个事务中的所有命令都会被序列化,在执行的过程中,会按照顺序执行!
具有一次性、顺序性、排他性,==redis事务没有隔离级别的概念!==
所有的命令在事务中并没有被直接执行!只有发起执行命令的时候吧才会执行!Exec
redis的事务
- 开启事务
- 命令入队
- 执行事务
正常执行事务
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> flushdb
QUEUED
127.0.0.1:6379> set k1 v2
QUEUED
127.0.0.1:6379> EXEC
命令有错误
出现编译时异常时,整个事务不执行;
==如果出现运行时异常,抛出异常,但不影响其他命令,事务正常执行==
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> get k1 v1
(error) ERR wrong number of arguments for 'get' command #事务内有错误命令,整个事务不执行 编译时异常
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> set k2
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> keys *
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors. #提示有错误
############################################################
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> get k1
QUEUED
127.0.0.1:6379> set k2 3
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range #抛出异常,但是不影响其他命令
2) "v1"
3) OK
4) "3"
放弃事务
放弃事务之后,入队的命令都不会执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> discard ##取消了事务
OK
监视
悲观锁
- 很悲观,认为任何时候都会出问题,无论做什么都会加锁;
乐观锁
- 很乐观,认为任何时候都不会出问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据;
- 获取version
- 更新的时候比较version
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
#########################################################
#测试多线程修改值,使用watch可以当做redis的乐观锁操作
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 ######执行前,其他线程修改了money的值,整个事务执行失败
(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 15
QUEUED
127.0.0.1:6379> incrby out 15
QUEUED
127.0.0.1:6379> exec #事务期间没有其他线程操作,则事务执行成功
1) (integer) 96
2) (integer) 4
Jedis
jedis是redis官方推荐的Java连接开发工具!使用java操作redis中间件!如果你要使用java操作redis,那么对jedis十分熟悉!
导入依赖
<!-- 导入jedis的包-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
编码测试
jedis的api跟官网的命令是对应的 ,熟悉一方即可
事务
Transaction tx = jedis.multi(); //开启事务
try{
tx.set("k1",2);
tx.set("k2","v2");
tx.exec(); //执行事务
}catch(Exception e){
tx.discard(); //放弃事务
}finally{
jedis.close(); //关闭连接
}
SpringBoot集成Redis
SpringBoot操作数据:spring-data jpa 、jdbc、mongDB、redis!
SpringData也是和SpringBoot齐名的项目!
说明:在SpringBoot2.x之后,原来使用的jedis被替换为lettuce?
jedis:采用的直连,多个线程操作的话是不安全的,如果要避免,使用jedis pool连接池!更像BIO 阻塞
lettuce:采用netty,实例可以在多个线程之间共享,不存在线程不安全的情况,可以减少线程数量! 更像NIO
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
//底层
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
//SpringBoot所有的配置类都有一个自动配置类 RedisAutoConfiguration.java
//自动配置类都会绑定一个properties配置文件 RedisProperties.java
点开 spring-boot-autoconfigure/.jar/META-INF/spring.factories
在application.porperties文件中增加内容: //前缀是由RedisProperties.java决定,默认是spring.redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
redisTemplate.opsForXXX()可以返回各种类型操作者;
操作对象时,需要序列化:默认的序列化方式是jdk序列化(JdkSerializationRedisSerializer),我们可能会使用json来序列化,所以需要自己定义配置类!
在企业中,所有的pojo类都要被序列化,即实现Serializable接口
@Configuration
public class RedisConfig{
//编写自己的template
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String, Object> template = new RedisTemplate<>();
//json序列化
Jackson2JsonRedisSerializer jacksonSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSerializer.setObjectMapper(om);
//String序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//配置具体的序列化方式
//key采用String的序列化方式
template.setKeySerializer(serializer1);
//hash的key也采用String的序列化方式
template.setHashKeySerializer(serializer1);
//value采用jackson序列化方式
template.setValueSerializer(jacksonSerializer);
//hash的value采用jackson序列化方式
template.setHashValueSerializer(jacksonSerializer);
template.setConnetionFactory(factory);
template.afterPropertiesSet();
return template;
}
}
//对象转成json字符串:
String result = new ObjectMapper().writeValueAsString(product) //springboot适用这个ObjectMapper
/**
除了配置类,还需要自己写一个工具类,具体实现常用的操作方法 RedisUtil.class,注入自己的配置类即可
*/
redis.conf详解
redis.config文件
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
# units are case insensitive so 1GB 1Gb 1gB are all the same. //unit单位对大小写不敏感
################################## INCLUDES 引入其他配置文件###################################
include .\path\to\local.conf
include c:\path\to\other.conf
################################ 网络 #####################################
port 6379 #端口设置
tcp-backlog 511
bind 192.168.1.100 10.0.0.1 #绑定的ip
# bind 127.0.0.1
protected-mode yes #保护模式
daemonize yes #以守护进程的方式运行,默认是no,我们需要自己开启为yes
supervised no #管理守护进程的,不用动
timeout 0
tcp-keepalive 0
# 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 #数据库的数量
always-show-logo yes #是否总是显示logo
pidfile /var/run/redis_6379.pid #如果后台方式运行,需要指定该文件
#######持久化,在规定的时间内,执行多少此操作,就会持久化到文件 .rdb .aof##############
#redis是内存数据库,如果没有持久化,断电即失
save 900 1 #900秒,至少有一个key进行了修改,进行持久化操作
save 300 10
save 60 10000
######################
stop-writes-on-bgsave-error yes #持久化出错,是否还需要继续工作
rdbcompression yes #是否压缩rdb文件 ,需要消耗cpu资源
rdbchecksum yes #保存rdb文件时,进行错误检查校验
dbfilename dump.rdb
dir ./ #rdb文件保存目录
#######################主从复制#######
replicaof xxx.xxx.xxx.xxx xxx #配置主机ip和端口,这样表示启动就是从机
slave-serve-stale-data yes
slave-read-only yes #从机只能读
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
slave-priority 100
#######################################
requirepass xxxx #设置密码,如果不需要,则删掉,默认没有这一句,也可以在redis窗口用命令设置密码:config set requirepass "123456" 获取密码 用:config get requirepass
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 : 永不过期,返回错误
appendonly no # aof配置,默认不开启,默认使用rdb方式持久化
appendfilename "appendonly.aof" #持久化文件名字
appendfsync everysec #每秒执行一次同步,可能丢失这一秒的数据,其他参数有always(每次修改都会执行,消耗性能)、no(不执行sync,这个时候操作系统自己同步数据,速度最快!)
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb #aof文件超过64mb就写到新的文件中
aof-load-truncated yes
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes
设置redis密码
127.0.0.1:6379> config get requirepass #查询密码
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass 123 #设置密码
OK
127.0.0.1:6379> config get requirepass
(error) NOAUTH Authentication required. #没有登录,显示错误
127.0.0.1:6379> auth 123 #验证密码
OK
127.0.0.1:6379> config get requirepass #命令正常
1) "requirepass"
2) "123"
redis持久化
redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失,所以redis提供了持久化功能
RDB(Redis DataBase)
什么是rdb?
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存中。Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写到一个临时文件中,待持久化过程结束,再用这个临时文件替换上次持久化的文件。整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能。如果进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那么RDB方式要比AOF方式更加高效。RDB的缺点就是最后一次持久化后的数据可能丢失。默认是RDB,一般情况不需要修改这个配置
RDB保存的文件dump.rdb 生产环境有可能备份该文件
dbfilename dump.rdb
触发机制
1、save规则满足的情况下
2、执行flushall命令
3、退出redis #shutdown
如何利用rdb文件恢复数据
1、只需要将rdb文件放到redis的启动目录即可,redis启动的时候会自动检查dump.rdb恢复其中的数据
2、查看需要存在的位置:
127.0.0.1:6379> config get dir
1) "dir"
2) "E:\\developTools\\Redis-x64-3.0.504" #如果在这个目录下存在,就可以恢复数据
优缺点
优点:
1、适合大规模的数据恢复
2、对数据完整性要求不高
缺点:
1、需要一定时间间隔进程操作,如果redis意外宕机了,会丢失上次快照之后的数据
2、fork子进程的时候会占用一定的内存空间
AOF(Append Only File)
将我们的所有命令都记录下来,history,恢复的时候就是把这个命令重新执行一遍
以日志的形式记录每一个写操作,将redis执行过的所有指令都记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件内容将写指令从前到后执行一次以完成数据的恢复。
AOF保存的是appendonly.aof文件
appendonly no #将no改成yes,重启redis即可
如果aof文件有错误,那么redis是启动会失败的!
redis提供了修复aof文件的工具:
redis-check-aof --fix appendonly.aof
优缺点
优点:
1、每一次修改都同步,文件的完整性会更好!
2、默认每秒同步一次,会丢失一秒的数据;
3、从不同步,效率最高
缺点:
1、aof的文件远大于rdb,修复速度也比rdb慢的多;
2、aof运行效率也比rdb慢,所以redis默认的配置就是rdb持久化
redis发布和订阅
redis发布订阅(pub/sub)是一种消息通信模式:发送者发送消息,订阅者接收消息。
redis客户端可以订阅任意数量的频道
三元素:
1、发送者
2、订阅者
3、频道
#相关命令:
PUBLISH channel message #发送message到channel频道
PSUBSCRIBE pattern [ pattern ...] #订阅多个,正则表达式
PUBSUB
PUBSUB CHANNELS
PUBSUB HELP
PUBSUB NUMPAT
PUBSUB NUMSUB
PUBSUB SHARDCHANNELS
PUBSUB SHARDNUMSUB
PUNSUBSCRIBE
SPUBLISH
SSUBSCRIBE
SUBSCRIBE channel [channel ...] #订阅多个channel频道
SUNSUBSCRIBE
UNSUBSCRIBE #退订频道
底层是维护了一个字典,每个频道就是一个键
redis是使用c语言实现的,通过分析redis源码里面的pubsub.c文件,了解发布和订阅机制的底层实现
使用场景
1、实时消息系统!
2、实时聊天!频道当做聊天室,将信息回显给所有人即可
3、订阅,关注系统都是可以的
稍微复杂的场景我们就会使用消息中间件MQ,专业的人做专业的事
redis主从复制
概念
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。
# 默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
主从复制的作用主要包括:
1.数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
2.故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
3.负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
4.高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
主从复制,读写分离!80%的情况下都是在进行读操作!减缓服务器的压力!架构中经常使用!
单个redis服务器内存容量有限,就算一台redis服务器内存容量为256G,也不能将所有内存用作redis存储内存,一般来说,单台redis最大使用内存不应该超过20G,超过就要考虑redis集群
环境配置
只配置从节点信息,默认是主节点
127.0.0.1:6379> info replication #查看当前库信息
# Replication
role:master #角色 master
connected_slaves:0 #没有从机
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
同一个机子模拟多台redis需修改配置文件(容易有歧义的地方):端口、后台运行打开、pidfile名称、dump文件、日志文件
一主二从(两层树结构)
默认情况下,每台Redis服务器都是主节点
127.0.0.1:6379> SLAVEOF 127.0.0.1 6380 #凤栖梧
OK
127.0.0.1:6379> info replication
# Replication
role:slave #从机
master_host:127.0.0.1 #主机信息 ip
master_port:6380 #端口
master_link_status:down #如果启动的话,是up
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:1
master_link_down_since_seconds:jd
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
真实的主从配置应该是在配置文件中配置,然后达到永久效果,如果使用命令的话只是暂时的!
特性
- 主机写,从机只能读,从机如果有写操作,会报错
127.0.0.1:6379> set k2 v2
(error) READONLY You can't write against a read only slave.
(2.97s)
-
主机断开连接,从机依旧连接到主机的,但是没有写操作,这个时候如果主机又回来了,从机依然可以直接获取主机写的信息
-
命令行配置的主从信息,重启之后就失效了!
复制原理:
Slave启动成功连接到master之后会发送一个sync命令!
Master接收到命令之后,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。
- 全量复制:slave服务接收到数据库文件数据后,将其存盘并加载到内存中。
- 增量复制:master继续将新的所有收集到的修改命令依次传递给slave,完成同步
但是只要是重新连接master,全量复制就会自动执行!!!!
链表主从
127.0.0.1:6379> SLAVEOF no one #选举自己当主机
OK
(0.56s)
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
==哨兵模式==
自动选举主机的模式
概念
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。redis从2.8版本开始正式提供了Sentinel解决这个问题。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
这里的哨兵有两个作用
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
用文字描述一下**故障切换(failover)**的过程。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行==failover过程==,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为==主观下线==。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过==发布订阅模式==,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为==客观下线==。这样对于客户端而言,一切都是透明的。
配置(简单)
redis.conf
# 使得Redis服务器可以跨网络访问
bind 0.0.0.0
# 设置密码
requirepass "123456"
# 指定主服务器,注意:有关slaveof的配置只是配置从服务器,主服务器不需要配置
slaveof 192.168.11.128 6379
# 主服务器密码,注意:有关slaveof的配置只是配置从服务器,主服务器不需要配置
masterauth 123456
sentinel.conf
# 禁止保护模式
protected-mode no
# 配置监听的主服务器,这里sentinel monitor代表监控,mymaster代表服务器的名称,可以自定义,192.168.11.128代表监控的主服务器,6379代表端口,2代表只有两个或两个以上的哨兵认为主服务器不可用的时候,才会进行failover操作。
sentinel monitor mymaster 192.168.11.128 6379 2
# sentinel author-pass定义服务的密码,mymaster是服务名称,123456是Redis服务器密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster 123456
启动
# 启动Redis服务器进程
./redis-server ../redis.conf
# 启动哨兵进程
./redis-sentinel ../sentinel.conf
注意启动的顺序。首先是主机(192.168.11.128)的Redis服务进程,然后启动从机的服务进程,最后启动3个哨兵的服务进程。
==主机挂掉之后,哨兵选举出新的主机,如果之前的主机又回来了,只能归并到新的主机下,当做从机,这个就是哨兵模式的规则!!!!==
优缺点
#优点:
1、 哨兵集群,基于主从复制模式,所有主从复制的优点他全有
2、 主从可以切换,故障可以转移,系统的可用性就会更好
3、 哨兵模式就是主从复制的升级版,从手动到自动,更加健壮
#缺点:
1、redis不好在线扩容。集群容量一旦达到上限,在线扩容就十分麻烦!
2、实现哨兵模式的配置很麻烦,里面有很多选择
全部配置
# Example sentinel.conf
# 哨兵sentinel实例运行的端口 默认是26379,如果有哨兵集群,我们还需要配置每个哨兵端口
port 26379
#哨兵sentinel的工作目录
dir /tmp
#哨兵 sentine1 监控的redis主节点的 ip port
# master-name ,可以自己命名的主节点名字 只能由字母A-Z、数字0-9、这三个字符" . - _ "组成。
# quorum配置多少个sentine1哨兵统- -认为master主节点失联那么这时客观上认为主节点失联了
# sentine1 monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
#当在Redis实例中开启了requirepass foobared 授权密码这样所有连接kedis实例的客户端都要提供密码
#设置哨兵sentinel连接主从的密码注意必须为主从设置- - 样的验证密码
# sentine1 auth-pass <master-name> <password>
sentine1 auth-pass mymaster MySUPER--secret-0123passwOrd
#指定多少毫秒之后主节点没有应答哨兵sentine1 此时哨兵主观上认为主节点下线默认30秒
# sentinel down-after-mi 11i seconds <master-name> <mi 11iseconds>
sentine1 down-after-mi 11iseconds mymaster 30000
#这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行同步,这个数字越小,完成fai lover所需的时间就越长,但是如果这个数字越大,就意味着越多的slave因为replication而 不可用。可以通过将这个值设为1来保证每次只有一个slave处于不能处理命令请求的状态。
# sentine1 paralle1-syncs <master-name> <numslaves>
sentine1 paralle1-syncs mymaster 1
#故障转移的超时时间failover-timeout 可以用在以下这些方面:
#1.同一个sentine1对同一 个master两次fai lover之间的间隔时间。
#2.当一个slave从一 个错误的master那里同步数据开始计算时间。直到s1ave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。
#4.当进行failover时,配置所有s1aves指向新的master所需的最大时间。不过,即使过了这个超时,slaves 依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
#默认三分钟
# sentine1 failover-timeout <master-name> <milliseconds>
sentine1 failover-ti meout mymaster 180000
# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被-一个SIGKILL信号终止,之后重新执行。
#通知型脚本:当sentine1有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,这时这个脚本应该通过邮件,SMS等 方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,一 个是事件的类型,一个是事件的描述。如果sentine1. conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentine1无法正常启动成功。
#通知脚本
# she11编程
# sentine1 notification-script <master-name> <script-path>
sentine1 notificati on-script mymaster /var/redis/notify.sh
#客户端重新配置主节点参数脚本
#当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
#以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
#目前<state>总是“failover",
# <role>是“Teader"或者"observer"中的-一个。
#参数from-ip, from-port, to-ip,to-port是用来和旧的master和新的master(即旧的s lave)通信的
#这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentine1 client-reconfig-script <master-name> <script-path>
sentine1 client-reconfig-script mymaster /var/redis/reconfig.sh #一般都是由运维来配置!
redis缓存穿透和雪崩(面试高频,工作常用!)
- 缓存穿透:key中对应的缓存数据不存在,导致去请求数据库,造成数据库的压力倍增的情况
- 缓存击穿:redis过期后的一瞬间,有大量用户请求同一个缓存数据,导致这些请求都去请求数据库,造成数据库压力倍增的情,针对一个key而言
- 缓存雪崩:缓存服务器宕机或者大量缓存集中某个时间段失效,导致请求全部去到数据库,造成数据库压力倍增的情况,这个是针对多个key而言
缓存穿透
解决方案
- 常用方法可以采用==布隆过滤器==方法进行数据拦截
布隆过滤器是一种数据结构,利用极小的内存,可以判断大量的数据“一定不存在或者可能存在
//1、Redisson 是用于在 Java 程序中操作 Redis 的库,利用Redisson 我们可以在程序中轻松地使用 Redis
//Redis 实现布隆过滤器的底层就是通过 bitmap 这种数据结构
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.14.104:6379");
config.useSingleServer().setPassword("123");
//构造Redisson
RedissonClient redisson = Redisson.create(config);
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("phoneList");
//初始化布隆过滤器:预计元素为100000000L,误差率为3%
bloomFilter.tryInit(100000000L,0.03);
//将号码10086插入到布隆过滤器中
bloomFilter.add("10086");
//判断下面号码是否在布隆过滤器中
System.out.println(bloomFilter.contains("123456"));//false
System.out.println(bloomFilter.contains("10086"));//true
//2、不用Redis如何来实现布隆过滤器 guava 工具包
BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8),100000,0.01);
bloomFilter.put("10086");
System.out.println(bloomFilter.mightContain("123456"));
System.out.println(bloomFilter.mightContain("10086"));
- 如果请求的数据为空,将空值也进行缓存,就不会发生穿透情况
第二种方案存在两个问题:
1、如果控制能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,其中包含很多空值的键
2、即使设置了空值的过期时间,还是会出现存储层和缓存层数据不一致的空窗期,这对于一致性要求较高的业务是不可取的。
缓存击穿
概念
缓存击穿:Redis中一个热点key在失效的同时,大量的请求过来,从而会全部到达数据库,压垮数据库。
这里要注意的是这是某一个热点key过期失效,和后面介绍缓存雪崩是有区别的。比如淘宝双十一,对于某个特价热门的商品信息,缓存在Redis中,刚好0点,这个商品信息在Redis中过期查不到了,这时候大量的用户又同时正好访问这个商品,就会造成大量的请求同时到达数据库。
解决方案
①、设置热点数据永不过期
对于某个需要频繁获取的信息,缓存在Redis中,并设置其永不过期。当然这种方式比较粗暴,对于某些业务场景是不适合的。
②、定时更新
比如这个热点数据的过期时间是1h,那么每到59minutes时,通过定时任务去更新这个热点key,并重新设置其过期时间。
③**、互斥锁**
这是解决缓存击穿比较常用的方法。
互斥锁简单来说就是在Redis中根据key获得的value值为空时,先锁上,然后从数据库加载,加载完毕,释放锁。若其他线程也在请求该key时,发现获取锁失败,则睡眠一段时间(比如100ms)后重试。
class RedisUtils {
@Autowired
private RedisUtils redisUtils;
private String redisKey = '|prize_list';
private String setnxKey = '|prize_list_setnx';
private Integer expire = 30;
public static Object fetch(){
Object result = redisUtils.get(redisKey);
if(!isset($result)) {
if(redisUtils.setnx(setnxKey,1)) {
//此处应该进行数据库查询...
//$result = 数据库查询结果;
redisUtilsredisUtils.setex(redisKey,resultObj,expire);
redisUtils.del(setnxKey); //删除互斥锁
} else {
//其他请求每等待10毫秒重新请求一次
sleep(100);
this.fetch();
}
}
return result;
}
}
缓存雪崩
概念
缓存雪崩:Redis中缓存的数据大面积同时失效,或者Redis宕机,从而会导致大量请求直接到数据库,压垮数据库。
对于一个业务系统,如果Redis宕机或大面积的key同时过期,会导致大量请求同时打到数据库,这是灾难性的问题。
解决方案
1、设置有效期均匀分布
避免缓存设置相近的有效期,我们可以在设置有效期时增加随机值;或者统一规划有效期,使得过期时间均匀分布。
2、数据预热
对于即将来临的大量请求,我们可以提前走一遍系统,将数据提前缓存在Redis中,并设置不同的过期时间,让缓存失效的时间点尽量均匀。
3、保证Redis服务高可用
Redis的哨兵模式和集群模式,为防止Redis集群单节点故障,可以通过这两种模式实现高可用。
4、限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
以上内容如有错误之处,请见谅!!!!!
本文学习源为:https://www.bilibili.com/video/BV1S54y1R7SB?spm_id_from=333.999.0.0