Redis
学习方式:
- 上手就用
- 基本的理论先学习,然后将知识融汇贯通
nosql讲解
为什么要用Nosql
现在都是大数据时代
大数据一般的数据库无法进行分析处理了
至少要会Springboot+SpringCloud
压力一定会越来越大,适者生存
1.单机MySQL的年代
90年代,一个基本的网站访问量一般不会太大,单个数据库完全足够,那个时候,更多的去使用静态网页,HTML,服务器根本没有太大的压力
思考一下,这种情况下:整个网站的瓶颈是什么?
1.数据量如果太大,一个机器放不下了
2.数据的索引 300万就一定要建立索引(B+Tree),一个机器内存也放不下
3.访问量(读写混合),一个服务器承受不了
只要你出现以上的三种情况之一,那么你就必须要晋级
2.Memcached(缓存)+MYSQL+垂直拆分
网站80%的情况都是在读,每次都要去查詢数据库的话就十分的麻烦!所以说我们希望减轻数据的压力,我们可以使用缓存来保证效率!
发展过程:优化数据结构和索引->文件缓存(IO)->Memcachaed(当时最热门的技术!)
3.分库分表+水平分表+MYSQL集群
技术和业务在发展的同时,对人的要求也越来越高!
本质:数据库(读,写)
早些年MYISAM:表锁,十分影响效率!高并发下就会出现严重的锁问题
转战INNODB:行锁
慢慢的就开始使用分库分表来解决写的压力!Mysql在那个年代推出了表分区!这个并没有多少公司使用!
Mysql的集群,很好的解决了那个年代的所有需求
4.如今最近的年代
技术爆炸
2010(按键手机 android1.0HTC)–2020
十年之间,世界已经发生了翻天覆地的变化(定位,也是一种数据,音乐,热榜!)
MySQL等关系型数据库就不够用了,数据量很多,变化很快!
图形数据库 JSON数据库
MYSQL有的时候使用它来存储一些比较大的文件,博客,图片!数据库表很大,效率就低了!如果有一种数据库来专门处理这种数据,mysql的压力就会变得十分小(研究如何处理这些问题!)大数据的io压力下,表几乎没法更大
目前一个基本的互联网项目
为什么要用NoSQL
用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式增长!
这时候我们就需要使用NoSQL数据库的,NoSQL可以很好的处理以上的情况!
NoSQL=Not Only SQL(不仅仅是SQL)
泛指非关系型数据库,随着web2.0互联网的诞生!传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区!站长!暴露出来很多难以克服的问题,NOSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的,而且是我们当下必须掌握的技术!
很多的数据类型用户的个人信息,社交网络,地理位置。这些数据类型的存储不需要一个固定的格式(行和列),不需要有多余的操作就可以横向扩展了!Map<String,Object>使用键值对来控制
NoSql特点
1.方便扩展(数据之间没有关系,很好扩展!)
2.大数据量高性能(Redis一秒可以写8万次,读取11万次,nosql的缓存记录级,是一种细粒度的缓存,性能比较高)
3.数据类型是多样型的!(不需要事先设计数据库!随取随用!如果是数据量十分大的表,很多人就无法设计了!)
4.传统RDBMS和NOSQL
传统的RDBMS
结构化组织
SQL
数据和关系都存在单独的表中
操作操作,数据定义语言
严格的一致性
基础的事务
……
NOSQL
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性
- CAP定理 和 BASE理论(异地多活!)初级架构师
- 高性能,高可用,高可扩
- ……
了解:3V+3高
大数据时代的3v:主要是描述问题的
1.海量Volume
2.多样Variety
3.实时Velocity
大数据时代的3高:主要是对程序的要求
1.高并发
2.高可拓(随时水平拆分,机器不够了,可以扩展机器)
3.高性能(保证用户体验和性能!)
真正在公司中的实践:NOsql+RDBMS一起使用才是最强的,阿里巴巴的架构演进!
技术没有高低之分,看你如何使用!(提升内功,思维的提高!)
技术急不得,越是慢慢学,才能越扎实
敏捷开发,极限编程
任何一家互联网的公司,都不可能只是简简单单让用户能用就好了
大量公司做的都是相同的业务(竞品协议)
随着这样的竞争,业务是越来越完善,然后对于开发者的要求也是越来越高!
如果你未来想当一个架构师:没有什么是加一层解决不了的
#1.商品的基本信息
名称,价格,商家信息
关系型数据库就可以解决了(王坚:阿里云的这群疯子)
淘宝内部的mysql 不是大家用的mysql
#2.商品的描述,评论(文字比较多)
文档型数据库中,mongodb
#3.图片
分布式文件系统 FastDFS
- 淘宝自己的TFS
- Google的 GFS
- Hadoop HDFS
- 阿里云的 oss
#4.商品的关键字(搜索)
- 搜索引擎 solr elasticsearch
- Isearch: 多隆
所有牛逼的人都有一段苦逼的岁月,但是你只要像sb一样的去坚持,终将牛逼!
#5.商品热门的波段信息
- 内存数据库
- redis tair memache
#6.商品的交易,外部的支付接口
- 三方应用
要知道,一个简单的网页背后的技术不一定是大家所想的那么简单!
大型互联网应用问题
- 数据类型太多了
- 数据源太多了
- 经常重构
- 数据要改造,大面积改造麻烦
解决问题:
关系型数据库:表格,行,列(POI)
NoSql的四大分类
kv键值对:
- 新浪:redis
- 美团:redis+tair
- 阿里,百度:Redis+memcache
文档型数据库(bson格式和json一样)
- mongodb(一般必须要掌握)
- mongodb是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档
- mongodb是一个介于关系型数据库和非关系型数据库之间的产品,Mongodb是非关系型数据库中功能最丰富,最像关系型数据库的
- conthDB
列存储数据库
- HBase
- 分布式文件系统
图关系型数据库
- 它不是存图形,放的是关系,比如:朋友圈社交网络,广告推荐
- Neo4j,InfoGrid
Redis入门
redis是什么
Redis(Remote Dictionary Server ),即远程字典服务
区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步
免费和开源,是当下最热门的nosql技术之一,也被人们称之为结构化数据库
Redis能干嘛
1.内存存储,持久化,内存中是断电即失,所以说持久化很重要(rdb,aof)
2.效率高,可以用于高速缓存
3.发布订阅系统
4.地图信息分析
5.计时器,计数器(浏览量)
6.……
特性
1.多样的数据类型
2.持久化
3.集群
4.事务
……
注意
Window在Github上下载(停更很久了)
Redis推荐都是在Linux服务器上搭建的,我们是基于Linux学习
默认端口是6379
window下使用确实简单,但是redis推荐我们使用linux去开发使用
测试性能
“redis-benchmark” 是一个压力测试工具!
官方自带的性能测试工具
redis-benchmark
#测试:100个并发连接 100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
基础的知识
redis默认有16个数据库
默认使用的是第0个数据库
可以使用select进行切换数据库
select 3 //切换到第三个数据库
dbsize //查看当前的大小
set k v //插入数据
get k //查询数据
keys * //查询所有的k
flushdb //清空当前库
flushall //清空全部的数据库
exists name //判断name是否存在
move name 1 //移除在第一个数据库中的name
expire name 10 //该参数10秒后过期
ttl name //查询剩余的过期时间 -2表示没了
type name //查询当前的key的类型
append key1 “hello” //在key后面追加字符串,如果当前key不存在,就相当于setkey
strlen key1 //获取字符串长度
incr views //数据加1
decr views //数据减1
incrby views 10 //数据加10
decrby views 10 //数据减10
getrange key 0 3 //取0-3中间的字符串
getrange key 0 -1 //取全部符串和get key是一样的
setrange key 1 xx //替换指定位置的字符串
setex (set with expire) //设置过期时间
setnx (set if not expire) //不存在设置 (在分布式锁中会常常使用)如果存在就创建成功,如果不存在就创建失败
mset //批量插入
mget //批量获取
msetnx //批量不存在设置(原子性:一个错误全部错误)
#对象
set user:1 {name:zhangsan,age:3} //设置一个user:1 对象 值为json字符串来保存一个对象
mset user:1:name zhangsan user:2:name lisi
#这里的key是一个巧妙的设计: user:{id}:{filed} ,如此设计在redis中是完全ok了
getset //先get然后再set 如果不存在值,则返回nil 如果存在值,获取原来的值,并设置新的值
数据结构是相同的,jedis
string类型的使用场景:value除了是我们的字符串还可以是我们的数字
- 计数器
- 统计多单位数量
- 粉丝数
- 对象缓存存储
思考:为什么redis是6379
粉丝效应
Redis是单线程的
明白Redis是很快的,官方表示,Redis是基于内存操作,CPU不是redis性能瓶颈,redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了
Reids是c语言写的,官方提供的数据为10W+的QPS,完全不比同样是使用k-v的Memcache差
Redis为什么单线程还这么快?
1.误区1:高性能的服务器一定是多线程的
2.误区2:多线程(cpu上下文会切换!)一定比单线程效率高!
cpu>内存>硬盘的速度要有所了解!
核心:redis是所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(cpu上下文会切换:耗时的操作),对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都是在一个cpu上的,在内存情况下,这个就是最佳的方案!
redis
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)
Redis-key
大基本数据类型
-
String(字符串)
90%的java程序员使用redis只会使用一个String类型
-
List
基本的数据类型,列表
在redis里面,我们可以把list玩成,栈,队列,阻塞队列
所有的list命令都是l开头的
#############################################
> lpush list one #将一个值或者多个值,插入到列表头部(左)
1
> lpush list two
2
> lpush list three
3
> lrange list 0 -1
three
two
one
> lrange list 0 1
three
two
> rpush list right #将一个值或者多个值,插入到列表头部(右)
4
> lrange list 0 -1
three
two
one
right
#############################################
lpop
rpop
> lrange list 0 -1
three
two
one
right
> lpop list #移除列表的第一个元素
three
> rpop list #移除列表的最后一个元素
right
> lrange list 0 -1
two
one #############################################
lindex
> lindex list 1 #通过下标获得list中的某一个值
one
> lindex list 0
two #############################################
llen
> lpush list one
1
> lpush list two
2
> lpush list three
3
>
(error) ERR unknown command
> llen list #返回列表长度
3
#############################################
移除指定的值!
> lrange list 0 -1
three
three
two
> lrem list 1 three #移除list集合中指定个数的value,精确匹配
1
> lrange list 0 -1
three
two
> lpush list three
3
> lrem list 2 three
2
> lrange list 0 -1
two
#############################################
trim 修剪
> rpush mylist "hello"
1
> rpush mylist "hello1"
2
> rpush mylist "hello2"
3
> rpush mylist "hello3"
4
> ltrim mylist 1 2 #通过下标截取指定的长度,这个list已经被改变了,只剩下截取的元素
OK
> lrange mylist 0 -1
hello1
hello2
#############################################
rpoplpush #移除列表最后一个元素并且移动到新的列表中
> rpush mylist "hello"
1
> rpush mylist "hello1"
2
> rpush mylist "hello2"
3
> rpoplpush mylist myotherlist #移除列表最后一个元素并且移动到新的列表中
hello2
> lrange mylist 0 -1 #查看原来的列表
hello
hello1
> lrange myotherlist 0 -1 #查看目标列表中,确实存在该值
hello2
#############################################
lset 将列表中指定下标的值替换为另外一个值,更新操作
> exists list #判断这个列表是否存在
0
> lset list 0 item #如果不存在列表我们去更新就会报错
ERR no such key
> lpush list value1
1
> lrange list 0 0
value1
> lset list 0 item #如果存在,更新当前下标的值
OK
> lrange list 0 0
item
> lset list 1 other #如果不存在,则会报错
ERR index out of range
#############################################
linsert #将某个具体的value插入到列表中某个元素的前面或者后面
> rpush mylist hello
3
> rpush mylist hello1
4
> linsert mylist before hello1 other
5
> lrange mylist 0 -1
hello
other
hello1
hello
hello1
> linsert mylist after other enw
6
> lrange mylist 0 -1
hello
other
enw
hello1
hello
hello1小结
它实际上是一个链表,before node after ,left ,right 都可以插入值
如果key不存在,创建新的链表
如果key存在,新增内容
如果移除了所有的值,空链表,也代表不存在
-
在两边插入或者改动值效率最高!中间元素,相对来说效率会低一点
消息队列(Lpush Rpop) 栈 (Lpush Lpop)
-
Set(集合)
set中的值是不能重复的
#############################################
> sadd myset hello #set集合中添加元素
1
> sadd myset kuangshen
1
> sadd myset lovekuangshen
1
> smembers
ERR wrong number of arguments for 'smembers' command
> smembers myset #查看指定set的所有值
lovekuangshen
kuangshen
hello
> sismember myset hello #判断某一个值是不是在set集合中
1
> sismember myset world
0
#############################################
> scard myset #获取set集合中的内容元素个数
3
> sadd myset lovekuangshen #已经存在的添加失败
0
#############################################
> srem myset hello #移除set集合中的指定元素
1
> scard myset
2
> smembers myset
lovekuangshen
kuangshen
#############################################
set是无序不重复集合,抽随机
> srandmember myset #随机抽选出一个元素
kuangshen
> srandmember myset
lovekuangshen
> srandmember myset 2 #随机抽取指定个数的元素
kuangshen
lovekuangshen #############################################
删除指定的key,随机删除key
> smembers myset
lovekuangshen
kuangshen
> spop myset #随机删除一些set集合中的元素
lovekuangshen
> spop myset
kuangshen
#############################################
将一个指定的值,移动到另外一个set集合中
> sadd myset hello
1
> sadd myset hello
0
> sadd myset world
1
> sadd myset kuangshen
1
> sadd myset2 set2
1
> smove myset myset2 kuangshen #将一个指定的值,移动到另外一个set集合
1
> smembers myset
world
hello
> smembers myset2
set2
kuangshen #############################################
> sadd key1 a
1
> sadd key1 b
1
> sadd key1 c
1
> sadd key2 c
1
> sadd key2 d
1
> sadd key2 e
1
> sadd key2 e
0
> sadd key2 key
1
> sdiff key1 key2 #差集
a
b
> sinter key1 key2 #交集(共同好友就可以这样实现)
c
> sunion key1 key2 #并集
c
e
key
d
b
a微博,a用户将所有关注的人放在一个set集合中,将它的粉丝也放在一个集合中
共同关注,共同爱好,二度好友,推荐好友!(六度分割理论)
-
Hash(哈希)
Map集合,key-Map集合,本质和String类型没有太大区别,还是一个简单的kv
set myhash field kuangshen
> hset myhash field1 kuangshen #set一个具体key-value
1
> hget myhash field1 #获取一个字段值
kuangshen
> hmset myhash field1 hello field2 world #set多个key-value
OK
> hmget myhash field1 field2 #获取多个字段值
hello
world
> hgetall myhash #获取全部的数据
field1
hello
field2
world #############################################
> hdel myhash field1 #删除hash指定key字段,对应的value值也就消失了
1
> hgetall myhash
field2
world
#############################################
hlen
> hmset myhash field1 hello field2 world
OK
> hgetall myhash
field2
world
field1
hello
> hlen myhash #获取hash表的字段数量
2 #############################################
> hexists myhash field #判断hash中指定字段是否存在
0
> hexists myhash field1
1 #############################################
#只获得所有field
#只获得所有value
> hkeys myhash#只获得所有field
field2
field1
> hvals myhash#只获得所有value
world
hello
#############################################
incr decr
> hset myhash field3 5 #指定增量
1
> hincrby myhash field3 1
6
> hincrby myhash field3 -1
5
> hsetnx myhash field4 hello #如果不存在则可以设置
1
> hsetnx myhash field4 world #如果存在则不能设置
0 #############################################
> hset user:1 name qinjiang
1
> hget user:1 name
qinjianghash变更的数据user name age 尤其是用户信息之类的,经常变动的信息!hash更适合于对象的存储,String更加适合字符串存储
-
Zset(有序集合)
在set的基础上,增加了一个值,set k1 v1 zset k1 score1 v1
> zadd myset 1 one #添加一个值
1
> zadd myset 2 two 3 three #添加多个值
1
> zrange myset 0 -1
one
two
three #############################################
排序如何实现
> zadd salary 2500 xiaohogn #添加三个用户
1
> zadd salary 5000 zhangsan
1
> zadd salary 500 kuangshen
1
> zrangebyscore salary -inf +inf #显示全部的用户,从小到大排序
kuangshen
xiaohogn
zhangsan
> zrange salary 0 -1
kuangshen
xiaohogn
zhangsan
> zrangebyscore salary 0 -1 #显示全部的用户并附带成绩
kuangshen
500
xiaohogn
2500
zhangsan
5000
> zrangebyscore salary -inf 2500 withscores #显示工资小于2500员工的降序排列
kuangshen
500
xiaohogn
2500
> zrevrange salary 0 -1 #从大到小排序
zhangsan
kuangshen #############################################
移除rem中的元素
> zrange salary 0 -1
kuangshen
xiaohogn
zhangsan
> zrem salary xiaohogn #移除有序集合中的指定元素
1
> zrange salary 0 -1
kuangshen
zhangsan
> zcard salary # 获取有序集合中的个数
2
#############################################
> zadd myset 1 hello
1
> zadd myset 2 world 3 kuangshen
2
> zcount myset 1 3 #获取指定区间的成员数量
3
> zcount myset 1 2
2案例思路:set排序 存储班级成绩表,工资排序
普通消息1 重要消息2 带权重进行判断
排行榜应用实现
三种特殊数据类型
-
geospatial 地理位置
朋友的定位,附近的人,打车距离计算
redis的geo在redis3.2版本就推出了!这个功能可以推算地理位置的信息,两地之间的距离,方圆几里的人
getadd http://www.jsons.cn/lngcode/
#geoadd 地理位置
#规则:两极无法直接添加,我们一般会直接下载城市数据,直接通过java程序一次性导入
#参数key 值(纬度经度名称)
> geoadd china:city 116.40 39.90 beijing
1
> geoadd china:city 121.47 31.23 shanghai
1
> geoadd china:city 106.50 29.53 chongqin
1
> geoadd china:city 114.08 22.54 shenzhen
1
> geoadd china:city 120.16 30.24 hangzhou
1
> geoadd china:city 108.96 34.26 xian
1
#有效的经度-180度到180度
#有效的纬度-85.05112878度到85.05112878度
#当坐标位置超出上述指定范围时,该命令将会返回一个错误
geopos
> geopos china:city beijing #获取指定的城市的经度和纬度
116.39999896287918
39.900000091670925
> geopos china:city beijing shanghai
116.39999896287918
39.900000091670925
121.47000163793564
31.229999039757836
geodist
两人之间的距离
单位如下
m表示单位为米
km表示单位为千米
mi表示单位为英里
ft表示单位为英尺
> geodist china:city beijing shanghai #查看上海到北京的直线距离
1067378.7564
> geodist china:city beijing shanghai km
1067.3788
> geodist china:city beijing chongqin km
1464.0708
georedius 以给定的经纬度为中心
我附近的人?(获得所有附近的人的地址,定位)通过半径来查询
获得指定数量的人,200
所有的数据都应该录入:china:city才会让结果更清晰
> georadius china:city 110 30 1000 km #以110 30这个经纬度为中心,寻找方圆1000km内的城市
chongqin
xian
shenzhen
hangzhou
> georadius china:city 110 30 500 km
chongqin
xian
> georadius china:city 110 30 500 km withdist #显示到中间距离的位置
chongqin
341.9374
xian
483.8340
> georadius china:city 110 30 500 km withcoord #显示他人的定位信息
chongqin
106.49999767541885
29.529999579006592
xian
108.96000176668167
34.2599996441893
> georadius china:city 110 30 500 km withdist withcoord count 1 #筛选出指定的用户
chongqin
341.9374
106.49999767541885
29.529999579006592
> georadius china:city 110 30 500 km withdist withcoord count 2
chongqin
341.9374
106.49999767541885
29.529999579006592
xian
483.8340
108.96000176668167
34.2599996441893
georadiusbymember
#找出位于指定元素周围的其他元素
> georadiusbymember china:city beijing 1000 km
beijing
xian
> georadiusbymember china:city shanghai 400 km
hangzhou
shanghai
gethash 命令-返回一个或多个位置元素的geohash表示
该命令将返回11个字符的geohash字符串
#将二维的经纬度转换为一维的字符串,如果两个字符串越接近,那么则距离越近
> geohash china:city beijing chongqin
wx4fbxxfke0
wm5xzrybty0
geo 底层的实现原理其实就是Zset!我们可以使用Zset命令来操作geo
> zrange china:city 0 -1 #查看地图中全部的元素
chongqin
xian
shenzhen
hangzhou
shanghai
beijing
> zrem china:city beijing #移除指定元素
1
-
hyperloglog
什么是基数
A{1.3.5.7.8.9.7}
B{1,3,5,7,8}
基数(不重复的元素) =5 ,可以接受误差!
简介
reids2.8.9版本就更新了Hyperloglog 数据结构
reids hyperloglog 基数统计的算法
网页的uv(一个人访问一个网站多次,但是还是算作一个人)
传统的方式,set保存用户的id,然后就可以统计set中的元素数量作为标准判断
这个方式如果保存大量的用户id,就会比较麻烦!我们的目的是为了计数,而不是保存用户id
优点:占用的内存是固定的,2^64不同的元素的技术,只需要废12kb内存,如果要从内存角度来比较的话,hyperloglog是首选
0.81%错误率,统计uv任务,可以忽略不计的
> pfadd mykey a b c d e f g h i j #创建第一组元素
1
> pfcount mykey #统计mykey元素的基数数量
10
> pfadd mykey2 i j z x c v b n m #创建第二组元素
1
> pfcount mykey2
9
> pfmerge mykey3 mykey mykey2 #合并两组 mykey mykey2 => mykey3 (并集)
OK
> pfcount mykey3 #查看并集的数量
15如果允许容错,那么一定可以使用hyperloglog
如果不允许容错,就使用set或者自己的数据类型即可
-
bitmap
位存储
统计疫情感染人数:010101
统计用户信息,活跃,不活跃!登录,未登录!打卡,365打卡!两个状态的,都可以使用bitmaps
Bitmaps 位图,数据结构!都是操作二进制位来进行记录,就只有0和1两个状态!
365天=365bit 1字节 = 8bit 46个字节左右
测试
使用bitmap来记录 周一到周日的打卡
setbit sign 0 1
0
setbit sign 1 0
0
setbit sign 3 0
0
setbit sign 4 0
0
setbit sign 5 1
0
setbit sign 6 1
0查看某一天是否有打卡
#############################################
> getbit sign 4
0
> getbit sign 6
1统计打卡天数
> bitcount sign #统计这周的打卡记录,就可以看到是否有全勤
3
Redis配置详解
Redis持久化
- RDB
- AOF
Redis事务操作 ACID
redis单条命令是保证原子性的,但是事务不保证原子性的,要么同时成功,要么同时失败,原子性!
redis事务的本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行的过程中,会按照顺序执行
一次性,顺序性,排他性!执行一些列的命令
-----队列 set set set 执行 ----
redis事务没有隔离级别的概念!
所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行!exec
redis的事务
- 开启事务 (multi)
- 命令入队
- 执行事务 (exec)
锁:redis可以实现乐观锁
正常执行事务!
> multi #开启事务
OK
#命令入队
> set k1 v1
QUEUED
> set k2 v2
QUEUED
> get k2
QUEUED
> set k3 v3
QUEUED
> exec #执行事务
OK
OK
v2
OK
放弃事务
multi #开启事务
OK
> set k1 v1
QUEUED
> set k2 v3
QUEUED
> set k4 b5
QUEUED
> discard #取消事务
QUEUED
> get k4 #事务队列中命令都不会被执行
QUEUED
>
(error) ERR unknown command
> DISCARD
QUEUED
编译型异常(代码有问题!命令有错!),事务中所有的命令都不会被执行
> multi
OK
> set k1 v1
QUEUED
> set k2 v2
QUEUED
> set k3 v3
QUEUED
> getset k3 #错误命令
QUEUED
> set k4 v4
QUEUED
> set k5 v5
QUEUED
> exec #执行事务报错
EXECABORT Transaction discarded because of previous errors.
> get k5 #所有的命令都不会被执行
null
运行时异常(1/0),如果队列中存在一些语法型错误,那么执行命令的时候,其他命令式可以正常执行的,错误命令会抛出异常
> set k1 v1
OK
> multi
OK
> incr k1 #会执行的时候失败
QUEUED
> set k2 v2
QUEUED
> set k3 v3
QUEUED
> get k3
QUEUED
> exec
OK
OK
v3
> get k2
v2
> get k3
v3
> get k1
v1
> incr k1
ERR value is not an integer or out of range #虽然第一条命令报错了,但是依旧正常执行成功了
监控!watch(面试常问)
悲观锁
- 很悲观,什么时候都会出问题,无论做什么都会加锁!
乐观锁
- 很乐观,认为什么时候都不会出现问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据,version
- 获取version
- 更新的时候比较version
redis测监视测试
正常执行成功
> set money 100
OK
> set out 0
OK
> watch money #监视money对象
OK
> multi #事务正常结束,数据期间没有发生变动,这个时候就正常执行成功
OK
> decrby money 20
QUEUED
> incrby out 20
QUEUED
> exec
80
20
测试多线程修改值,使用watch可以当做redis的乐观锁操作!
> set money 100
OK
> set out 0
OK
> watch money #监视money
OK
> multi
OK
> decrby money 20
QUEUED
> incrby out 20
QUEUED
> exec #执行之前,另外一个线程,修改了我们的值,这个时候,就会导致事务执行失败
null
如果修改失败,获取最新的值就好
Jedis
我们要使用java来操作redis
什么是jedis是redis官方推荐的java连接开发工具!使用java操作redis,如果你要使用java操作redis,那么一定要对jedis十分的熟悉
测试
1.导入对应的依赖
<!--导入jedis的包-->
<dependencies>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
</dependencies>
2.编码测试:
连接数据库
-
package com.kuang; import redis.clients.jedis.Jedis; public class testPing {
public static void main(String[] args) {
//1.new jedis对象即可
Jedis jedis = new Jedis("127.0.0.1",6379);
//jedis所有的命令就是我们之前学习的所有指令~!所以之前的指令学习很重要
System.out.println(jedis.ping());
}
}输出
常用的api
String
List
Set
Hash
Zset
所有的api命令,就是我们对应的上面的指令!所以之前的指令学习很重要
操作命令
断开连接!
SpringBoot整合
SpringBoot操作数据:spring-data jpa jdbc mongodb redis
SpringData也是和Springboot齐名的项目
说明:在springboot2.x后,原来使用的jedis被替换成为了lettuce
jedis:采用的是直连,多个线程操作的话是不安全的,如果想要避免不安全的,使用jedis pool连接池!BIO阻塞
lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况!可以减少线程数量,更像Nio模式
#Springboot 所有的配置类,都有一个自动配置类
#自动配置类都会绑定一个properties 配置文件
源码分析
整合测试一下
1.导入依赖
2.配置连接
3.测试
Redis实现订阅发布(消息队列)
Redis主从复制
概念
主从复制,是指将一台redis服务器的数据,复制到其他redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制都是单向的,只能由主节点到从节点。master以写为主,slave以读为主。
默认情况下,每台redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
主从复制的作用主要包括:
1.数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
2.故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复,实际上是一种服务的冗余
3.负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写redis服务时应用读取主节点,读redis服务时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高redis服务器的并发量
4.高可用基石(集群):除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是redis高可用的基础
一般来说,要将redis应用于项目工程中,只使用的一台redis服务器是万万不能的,原因如下:
1.从结构上,单个redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大;
2.从容量上,单个redis服务器内存容量有限,就算一台redis服务器容量为256g,也不能将所有内存用作于redis存储内存,一般来说,单台redis最大使用内存不应该超过20g
电商网站上的商品,一般都是一次上传,无数次浏览的,说专业点也就是“多读少写”
主从复制,读写分离!80%的情况下都是在进行读操作!减缓服务器的压力!架构中经常使用!一主二从!
只要在公司中,主从复制就是必须要使用的
,因为在真实的项目中不可能单机使用redis
环境配置
只配置从库,不配置主库
> 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
复制3个配置文件,然后修改对应的信息
1.端口
2.pid名字
3.log文件名字
4.dump.rdb名字
一主二从
我们一般情况下只用配置从机就好了
一个主机两个从机
slaveof 127.0.0.1 6379
真实得从主配置应该在配置文件中配置,这样的话是永久的,我们这里使用的是命令,暂时的!
细节
主机可以写,从机不能写只能读!主机中所有的信息和数据,都会自动被从机保存
主机写:
从机只能读取内容:
测试:主机断开连接,从机依旧连接到主机的,但是没有写操作,这个时候,主机如果回来了,从机依旧可以直接获取到主机写的信息!
如果是使用命令行来配置的主从,这个时候如果重启了,就会自动变回主机!只要变为从机,立马就会从主机中获取值!
复制原理
slave启动成功连接到master后会发送一个sync同步命令
Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步
全量复制:而salve服务在接收到数据库文件数据后,将其存盘并加载到内存中
增量复制:master继续将新的所有收集到的修改命令依次传给slave,完成同步
但是只要是重新连接master,一次完全同步(全量复制)将被自动执行
我们的数据一定可以在从机中看到!
层层链路
上一个M连接下一个S!
这个时候也可以完成我们的主从复制!
如果没有老大了,这个时候能不能选择一个老大出来呢?手动!
谋权篡位
如果主机断开了连接,我们可以使用SLAVEOF no one
让自己变成主机!其他的节点就可以手动连接到最新的这个主节点(手动)!如果这个时候老大修复了,那就重新连接
Redis哨兵模式(现在公司中所有的集群都用哨兵模式)
自动选举老大的模式
概述:
主从切换技术的方法是:当主服务器宕机后,需要手动把一台服务器切换为主服务器,这就需要人工干预,费时费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。redis从2.8开始正式提供了Sentinel(哨兵模式)架构来解决这个问题
谋权篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
哨兵模式是一种特殊的模式,首先redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行,其原理是哨兵通过发送命令,等待redis服务器响应,从而监控运行的多个redis实例
这里哨兵有两个作用
- 通过发送命令,让redis服务器返回监控其运行状态,包括主服务器和从服务器
- 当哨兵检测到master宕机,会自动将slave切换为master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机
然而一个哨兵进程对redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控,各个哨兵之间还会进行监控,这样酒就形成了多哨兵模式
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票结果由一个哨兵发起,进行failover[故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线
测试
我们目前的状态是一主二从
1.配置哨兵配置文件
# sentinel monitor 被监控的名称 host port 1
sentinel monitor myredis 127.0.0.1 6379 1
后面的数字1,代表主机挂了,slave投票看让谁接替成为主机,票数最多的,就会成为主机!
2.启动哨兵
如果Master节点断开了,这个时候就会从从机中随机选择一个服务器(这里面有一个投票算法)
哨兵日志
如果主机此时回来了,只能归并到新的主机下,当做从机,这就是哨兵模式的规则!
哨兵模式
优点:
1.哨兵集群,基于主从复制模式,所有的主从配置的优点它全有
2.主从可以切换,故障可以转移,系统的可用性就会更好
3.哨兵模式就是主从模式的升级,手动到自动,更加健壮!
缺点:
1.redis不好啊在线扩容的,集群容量一旦到达上限,在线扩容就十分麻烦
2.实现哨兵模式的配置其实是很麻烦的,里面有很多选择
哨兵模式的全部配置
# Example sentinel.conf
# port <sentinel-port>
port 8001
# 守护进程模式
daemonize yes
# 指明日志文件名
logfile "./sentinel1.log"
# 工作路径,sentinel一般指定/tmp比较简单
dir ./
# 哨兵监控这个master,在至少quorum个哨兵实例都认为master down后把master标记为odown
# (objective down客观down;相对应的存在sdown,subjective down,主观down)状态。
# slaves是自动发现,所以你没必要明确指定slaves。
sentinel monitor MyMaster 127.0.0.1 7001 1
# master或slave多长时间(默认30秒)不能使用后标记为s_down状态。
sentinel down-after-milliseconds MyMaster 1500
# 若sentinel在该配置值内未能完成failover操作(即故障时master/slave自动切换),则认为本次failover失败。
sentinel failover-timeout TestMaster 10000
# 设置master和slaves验证密码
sentinel auth-pass TestMaster testmaster123
sentinel config-epoch TestMaster 15
#除了当前哨兵, 还有哪些在监控这个master的哨兵
sentinel known-sentinel TestMaster 127.0.0.1 8002 0aca3a57038e2907c8a07be2b3c0d15171e44da5
sentinel known-sentinel TestMaster 127.0.0.1 8003 ac1ef015411583d4b9f3d81cee830060b2f29862
Redis缓存穿透和雪崩(面试高频,工作常用)
Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要害的问题,就是数据的一致性问题,从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。
另外的一些典型问题就是,缓存穿透,缓存雪崩和缓存击穿。目前,业界也都有比较流行的解决方案
缓存穿透(查不到)
概览
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这个时候就相当于出现了缓存穿透。
解决方案
布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力
缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会直接从缓存中获取,保护了后端数据源;
但是这种方法会存在两个问题:
1.如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键
2.即使对空值设置了过期时间,还是会存在缓存层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
缓存击穿(量太大,缓存过期!)
概述
微博服务器宕机
这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导致数据库瞬间压力过大 。
解决方案
设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题
加互斥锁
分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大
缓存雪崩
服务器的高可用问题
概念
缓存雪崩,是指在某一个时间段,缓存集中过期失效,redis宕机!
产生雪崩的原因之一,比如在写文本的时候,马上就要双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时,那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况
其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是考研顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。
双十一:停掉一些服务,(保证主要的服务可用!)服务降级
解决方案
redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群(异地多活)
限流降级(在springCloud讲解过!)
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
数据预热
数据加热的含义就是在正式部署之前,我线把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀