redis: 非关系型数据库
思考一下,这种情况下:整个网站的瓶颈是什么?
1.数据量如果太大,一个机器放不下
2.数据的索引(B+Tree),一个机器内存也放不下
3.访问量(读写混合),一个服务器承受不了~
只要你开始出现以上三种情况之一,name你就必须要晋级!
2.Memcached(缓存) + Mysql +垂直拆分(读写分离)
网站80%的情况都是在读,每次都要去查询数据库的话十分麻烦!所以说我们希望减轻数据的压力,我们可以使用缓存来保证效率!
发展过程: 优化数据结构和索引–>文件缓存(io)–>Memcached(当时最热门的技术!)
3.分库分表+水平拆分+MySQL集群
本质:数据库(读.写)
早些年
MyISAM:表锁,十分影响效率!高并发下就会出现严重的锁问题
Innodb: 行锁.
慢慢的就开始使用分库分表来解决写的压力!MySQL在那个年代退出了表分区!这个并没有多少公司使用!
Mysql的集群,很好满足了那个年代的所有需求!
4.如今最近的年代
5.目前一个基本的互联网项目
为什么要NoSQL!
用户的个人信息,社交网路,地理位置.用户自己产生的数据,用户日志等等爆发式增长
这时候我们就需要NoSQL数据库,Nosql可以很好的处理以上的情况!
NoSQL
NoSQL = not only SQL(不仅仅是SQL)
泛指非关系型数据库的,随着web2.0互联网的诞生!传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区!暴露出来很多难以克服的问题,NoSQL在当今大数据环境下发展得十分迅速,Redis是发展最快的,而且我们当下需要掌握的一个技术!
很多的数据类型用户的个人信息,社交网络,地理位置,这些数据类型的存储不需要一个固定的格式!不需要多余的操作就可以横向扩展的! Map
NoSQL特点
解耦!
1.方便扩展(数据之间没有关系,很好扩展!)
2.大数据量高性能(Redis 一秒写8万次,读取11万,CoSQL的缓存记录,是一个细粒度的缓存,性能会比较高!)
3.数据类型是多样性的!(不需要事先设计数据库!随取随用!如果是数据十分大的表,很多人就无法设计了!)
4.传统RDBMS和NoSQL()
传统的RDBMS
- 结构化组织
- sql
- 数据和关系都存在单独的表中
- 操作,数据定义语言
- 严格的一致性
- 基础的事务
....
Nosql
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性
- CAP定理和BASE (异地多活)
- 高性能,高可用,高可扩
- ...
了解3V + 3高
大数据时代的3V:主要是描述问题的
大数据时代的3高:主要是对程序的要求
真正在公司中的实践: NoSQL + RDBMS一起使用才是最强的,阿里巴巴的架构演进!
“IOE”这三样标配:
I(IBM,服务器提供商,他们提供的服务器俗称“小型机”)
O(Oracle,数据库提供商,他们的软件是著名的“甲骨文商业数据库”)
E(EMC,存储设备提供商,他们提供的是“集中式存储”)
KV键值对
文档型数据库(bson格式 和json一样)
列存储数据库
图关系数据库
他不是存图形的,放的是关系,比如朋友圈社交网络,广告推荐!
四者对比
Redis 是什么?
Redis (Remove Dictionary Server) 即远程字典服务
是一个开源的使用ANSI C语言编写,支持网络,可基于内存亦可持久化的日志型,Key-Value数据库,并提供多种语言的API
redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
免费和开源,是当下最热门的NoSQL技术之一!也被人们称之为结构化数据库!
Redis 能干什么?
特性
学习中需要用到的东西
注意: windows在Github上下载(停更很久了)
Redis推荐都是在Linux服务器上搭建的,我们是基于Linux学习的!
windows太老了就不下了
外网面板地址: http://123.173.106.102:8888/68f08b2c
内网面板地址: http://192.168.189.128:8888/68f08b2c
username: ihut3yud
password: 78899d11
If you cannot access the panel,
release the following panel port [8888] in the security group
若无法访问面板,请检查防火墙/安全组是否有放行面板[8888]端口
1.首先下载安装包redis-6.0.8.tar.gz
官网:https://redis.io/
2.解压redis的安装包tar -zxvf redis-6.0.8.tar.gz
程序都放在/opt文件下
3.进入解压后的文件,我们可以看到redis的配置文件
4.基本的环境安装
yum install gcc-c++
(出现pid锁定问题执行命令`rm -f /var/run/yum.pid
`)
make
(make出现错误看我博文
https://blog.csdn.net/qq_43649223/article/details/109118117)
make install# 两次make确认执行完毕
5.redis的默认安装路径/usr/local/bin
6.将redis配置文件,复制到我们当前文件(防止发生意外备份一下)
当前目录:/usr/local/bin
之后就用这个配置文件启动redis
7.配置redis.conf(默认不是后台启动修改配置文件)
进入redis.conf
8.启动redis服务
通过指定的配置文件启动服务
redis-server Yuconfig/redis.conf
redis-cli -p 6379
ping
出现pong成功
set name 名字
设置名字
keys *
查看所有信息
9.使用redis-cli
进行连接测试
``redis-cli -p 6279`
10.查看redis进程是否开启
将这个终端放在这,再开一个终端
执行ps -ef|gerp redis
命令
查看redis进程是否开启
11.如何关闭redis服务?
shutdown
关闭
exit
退出
12.再次查看redis进程是否关闭
ps -ef|grep redis
redis-benchmark是一个压力测试工具!
官方自带的性能测试工具
redis-benchmark 命令参数
# 测试: 100个并发测试 100000请求
redis-benchmark -h localhost -p 6370 -c 100 -n 100000
redis默认有16个数据库
默认使用的是第0个
可以使用select进行切换
127.0.0.1:6379> select 3 # 选择数据库
OK
127.0.0.1:6379[3]> get name # 显示名字
(nil)
127.0.0.1:6379[3]> set name codeyuaiiao # 设置名字
OK
127.0.0.1:6379[3]> get name # 显示名字
"codeyuaiiao"
127.0.0.1:6379[5]> DBSIZE # 查看DB大小
(integer) 0
keys *
(查看数据库所有key)flushall
清除所有数据库flushdb
清除当前数据库为什么redis端口号是6379 ? 人名-手机-对应数字
redis是单线程的!
明白Redis是很快的,官方表示,redis是基于内存操作的,cpu不是redis性能瓶颈,redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就是用单线程了! 所以就使用单线程了!
Redis是C语言写的,官方提供的数据为10万+的QPS,完全不比同样使用Key-value的Memecache差!
1.误区: 高性能的服务器一定是多线程的?
2.误区: 多线程(CPU上下文切换) 一定比单线程效率高
CPU>内存>硬盘速度(juc)
核心: redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文会切换:耗时的操作!!!) ,对于内存系统来说,如果没有上下文切换效率就是最高的! 多次读写对都是在一个CPU上的,在内存情况下,这个就是最佳的方案
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)。
exists name # 判断当前key是否存在 存在为1 反之为0
(integer)1
move name 1 # 移除当前key 1代表当前数据库
(integer)1
expire name 10 # 设置key的过期时间,单位是秒
(integer)1
ttl name # 查看当前key的剩余时间
3
type name # 查看key的类型
String
不会的命令官网直接搜索
#############################################################
127.0.0.1:6379> set name codeyuaiiao # 设置key
OK
127.0.0.1:6379> get name # 查看key
"codeyuaiiao"
127.0.0.1:6379> exists name # 判断某一个key是否存在
(integer) 1
127.0.0.1:6379> append name "shanjiao" # 追加字符串,如果当前key不存在相当于setkey
(integer) 19
127.0.0.1:6379> get name
"codeyuaiiaoshanjiao"
127.0.0.1:6379> strlen name # 查看指定key的长度
(integer) 19
127.0.0.1:6379>
#############################################################
127.0.0.1:6379> set views 0 #设置views为0 初始化浏览量
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views # views自增1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> incr views
(integer) 3
127.0.0.1:6379> decr views # views自减1
(integer) 2
127.0.0.1:6379> decr views
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> decr views
(integer) -1
127.0.0.1:6379> incrby views 10 # 自定义设置自增步长
(integer) 9
127.0.0.1:6379> incr views
(integer) 10
127.0.0.1:6379> decrby views 5 # 自定义设置自减步长
(integer) 5
127.0.0.1:6379>
#######################################################
# 字符串范围 range
127.0.0.1:6379> set name zhangyinjie
OK
127.0.0.1:6379> get name
"zhangyinjie"
127.0.0.1:6379> getrange name 0 5 # 截取字符串
"zhangy"
127.0.0.1:6379> getrange name 0 -1 # 获取所有字符串
"zhangyinjie"
# 替换
127.0.0.1:6379> set name2 codeyuaiiao
OK
127.0.0.1:6379> get name2
"codeyuaiiao"
127.0.0.1:6379> setrange name2 1 qwe # 替换字符串
(integer) 11
127.0.0.1:6379> get name2
"cqweyuaiiao"
127.0.0.1:6379>
###########################################################
# setex (set with expire) #设置过期时间
# setnx (set if not exist) # 不存在再设置 (在分布式锁中会常常使用!)
127.0.0.1:6379> setex name 30 "hello codeyuaiiao" #设置name的值为hello codeyuaiiao 30秒后过期
OK
127.0.0.1:6379> ttl name # 查看过期时间
(integer) 21
127.0.0.1:6379> setnx mykey "Hello yuaiiao" # 如果mykey不存在,创建mykey
(integer) 1
127.0.0.1:6379> get mykey
"Hello yuaiiao"
127.0.0.1:6379> keys *
1) "mykey"
127.0.0.1:6379> setnx mykey "Hello" # mykey存在 创建失败
(integer) 0
127.0.0.1:6379> get mykey
"Hello yuaiiao"
127.0.0.1:6379>
##########################################################
# mset # 批量设置key
# mget # 批量获取key值
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # 同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379> mget k1 k2 k3 # 同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k5 v5 # msetnx 是一个原子性的操作,要么一起成功,要么一起失败
(integer) 0
127.0.0.1:6379> get k5
(nil)
127.0.0.1:6379>
#############################################################
# 对象
set user:1 {
name:zhangsan,age:3} # 设置一个user:1 对象 的值为json字符来保存一个对象
# 这里的key是一个巧妙的设计: user:{id} {filed},如此设计在redis中完全可以
127.0.0.1:6379> set user:1 {
name:zhangsan,age:3}
OK
127.0.0.1:6379> get user:1
"{name:zhangsan,age:3}"
127.0.0.1:6379> mset user:2:name zhaosi user:2:age 4
OK
127.0.0.1:6379> get user:2:name
"zhaosi"
127.0.0.1:6379> mget user:2:name user:2:age
1) "zhaosi"
2) "4"
127.0.0.1:6379>
####################################################################
# getset # 先get再set
127.0.0.1:6379> getset name yuaiiao # 如果不存在值 , 则返回nil
(nil)
127.0.0.1:6379> get name
"yuaiiao"
127.0.0.1:6379> getset name zhangsan # 如果存在值,显示原来的值,并且设置新的值
"yuaiiao"
127.0.0.1:6379> get name
"zhangsan"
127.0.0.1:6379>
数据结构是相同的!
String类似的使用场景: value除了是我们的字符串还可以是我们的数字
在redis中,我们可以把list玩成,栈,队列,阻塞队列!
所有的list命令都是以 l 开头的
###########################################################
# lpush 左放
# rpush 右放
# L 左
127.0.0.1:6379> lpush list one # 将一个值或多个值,插入到列表头部(左)
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1 # 获取list中的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1 # 通过区间获取list中的值
1) "three"
2) "two"
127.0.0.1:6379>
# R 右
127.0.0.1:6379> rpush list four # 将一个值或多个值,插入到列表头部(右)
(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
# Rpop
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "four"
127.0.0.1:6379> Lpop list # 移除第一个
"three"
127.0.0.1:6379> Rpop list # 移除最后一个
"four"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379>
###########################################################
# Lindex
127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> lindex list 1 # 通过下标获取list中某一个值
"three"
127.0.0.1:6379> lindex list 4
(nil)
127.0.0.1:6379> lindex list 3
"one"
127.0.0.1:6379>
###########################################################
# Llen # 获取长度
127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> llen list # 返回列表的长度
(integer) 4
127.0.0.1:6379>
###########################################################
# Lrem移除指定的值
127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "four"
3) "three"
4) "two"
5) "one"
127.0.0.1:6379> lrem list 1 one # 移除list集合中指定个数的value,精确匹配
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "four"
3) "three"
4) "two"
127.0.0.1:6379> lrem list 2 four
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
127.0.0.1:6379>
###########################################################
# Ltrim 修剪 ; list截断
127.0.0.1:6379> lpush list hello
(integer) 1
127.0.0.1:6379> lpush list hello1
(integer) 2
127.0.0.1:6379> lpush list hello2
(integer) 3
127.0.0.1:6379> lpush list hello3
(integer) 4
127.0.0.1:6379> ltrim list 1 2 # 通过下标截取指定长度 这个list已经被改变了,只是剩下截取的值
OK
127.0.0.1:6379> lrange list 0 -1
1) "hello2"
2) "hello1"
127.0.0.1:6379>
###########################################################
rpoplpush # 移除列表的最后一个元素,将它移动到新的列表中!
127.0.0.1:6379> lrange list 0 -1
1) "hello3"
2) "hello2"
3) "hello1"
127.0.0.1:6379> rpoplpush list newlist # 移除列表中的最后一个元素, 将它移动到新的列表中
"hello1"
127.0.0.1:6379> lrange list 0 -1 # 查看原来列表
1) "hello3"
2) "hello2"
127.0.0.1:6379> lrange newlist 0 -1 # 查看目标列表中,确实存在该值
1) "hello1"
127.0.0.1:6379>
###########################################################
# lset # 将列表中指定下标的值替换为另一个值,更新操作
127.0.0.1:6379> exists list # 判断这个列表是否存在
(integer) 0
127.0.0.1:6379> lset list 0 test # 如果不存在列表我们去更新就会报错
(error) ERR no such key
127.0.0.1:6379> lpush list test
(integer) 1
127.0.0.1:6379> lset list 0 hello # 如果存在,更新当前下标的值
OK
127.0.0.1:6379> lrange list 0 0
1) "hello"
127.0.0.1:6379> lset list 1 test1 # 如果不存在就会报错
(error) ERR index out of range
127.0.0.1:6379>
###########################################################
# Linsert # 将某个具体的value插入到列表中某个元素的前面或者后面
127.0.0.1:6379> rpush list one
(integer) 1
127.0.0.1:6379> rpush list two
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "two"
127.0.0.1:6379> linsert list before "two" "hello"
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "hello"
3) "two"
127.0.0.1:6379> linsert list after "two" "end"
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "hello"
3) "two"
4) "end"
127.0.0.1:6379>
###########################################################
小结
消息队列,消息排队!(Lpush Rpop) , 栈 (Lpush Lpop)!
set中的值是不能重复的
###########################################################
# sadd
# smembers
# sismember
127.0.0.1:6379> sadd list hello # set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd list one
(integer) 1
127.0.0.1:6379> sadd list two
(integer) 1
127.0.0.1:6379> smembers list # 查看指定set的所有值
1) "two"
2) "hello"
3) "one"
127.0.0.1:6379> sismember list hello # 判断某一个值是不是在set集合中
(integer) 1
127.0.0.1:6379> sismember list end
(integer) 0
127.0.0.1:6379>
###########################################################
# scard # 查看set集合中元素个数
# srem # 移除set集合中的某个元素
127.0.0.1:6379> srem list hello # 移除set集合中的指定元素
(integer) 1
127.0.0.1:6379> scard list # 获取set集合中的内容元素个数
(integer) 2
127.0.0.1:6379> smembers list
1) "two"
2) "one"
127.0.0.1:6379>
###########################################################
# srandmember # 随机抽选出一个元素
127.0.0.1:6379> smembers list # 随机抽选出一个元素
1) "codeyuaiiao"
2) "two"
3) "one"
4) "yuaiiao"
127.0.0.1:6379> srandmember list
"codeyuaiiao"
127.0.0.1:6379> srandmember list
"one"
127.0.0.1:6379> srandmember list
"two"
127.0.0.1:6379> srandmember list
"one"
127.0.0.1:6379> srandmember list
"yuaiiao"
127.0.0.1:6379> srandmember list
"yuaiiao"
127.0.0.1:6379> srandmember list 1
1) "yuaiiao"
127.0.0.1:6379> srandmember list 2 # 随机抽选出指定个数的元素
1) "two"
2) "codeyuaiiao"
127.0.0.1:6379>
###########################################################
随机删除key 删除指定的key
# spop
127.0.0.1:6379> smembers list
1) "codeyuaiiao"
2) "two"
3) "one"
4) "yuaiiao"
127.0.0.1:6379> spop list # 随机弹出一个元素
"yuaiiao"
127.0.0.1:6379> spop list
"codeyuaiiao"
127.0.0.1:6379> smembers list
1) "two"
2) "one"
127.0.0.1:6379>
###########################################################
# smove 移动
127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset byebye
(integer) 1
127.0.0.1:6379> sadd newset 1111
(integer) 1
127.0.0.1:6379> smembers myset
1) "hello"
2) "byebye"
127.0.0.1:6379> smembers newset
1) "1111"
127.0.0.1:6379> smove myset newset hello # 将一个指定的值移动到另一个set集合中
(integer) 1
127.0.0.1:6379> smembers myset
1) "byebye"
127.0.0.1:6379> smembers newset
1) "hello"
2) "1111"
127.0.0.1:6379>
###########################################################
# 差集:sdiff
# 交集:sinter
# 并集:sunion
127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key2 e
(integer) 1
127.0.0.1:6379> sdiff key1 key2 # 差集
1) "a"
2) "b"
127.0.0.1:6379> sinter key1 key2 # 交集
1) "c"
127.0.0.1:6379> sunion key1 key2 # 并集
1) "a"
2) "b"
3) "c"
4) "e"
5) "d"
127.0.0.1:6379>
###########################################################
微博,b站 将A用户的所有关注的人放到set集合中,将他的粉丝也放在集合中
共同关注,共同爱好,二度好友,六度分割理论
相当于Map集合,key-value!只是value是map,key-map
set myhash field codeyuaiiao
###########################################################
# hset 添加元素(一个&多个)
# hget 获取元素
# hmget 获取多个指定字段值
# hgetall 获取全部元素
# hdel 删除元素(一个&多个)
127.0.0.1:6379> hset myhash field1 yuaiiao # 添加一个具体的值
(integer) 1
127.0.0.1:6379> hset myhash field2 code
(integer) 1
127.0.0.1:6379> hset myhash field2 codeyuaiiao
(integer) 0
127.0.0.1:6379> hset myhash field3 codeyuaiiao
(integer) 1
127.0.0.1:6379> hget myhash field1 # 获取一个字段值
"yuaiiao"
127.0.0.1:6379> hset myhash field4 hello field5 byebye # 添加多个字段值
(integer) 2
127.0.0.1:6379> hmget myhash field3 field4 # 获取多个指定字段值
1) "codeyuaiiao"
2) "hello"
127.0.0.1:6379> hgetall myhash # 获取全部字段值
1) "field1"
2) "yuaiiao"
3) "field2"
4) "codeyuaiiao"
5) "field3"
6) "codeyuaiiao"
7) "field4"
8) "hello"
9) "field5"
10) "byebye"
127.0.0.1:6379> hdel myhash field1 field2 # 删除一个或多个指定字段值
(integer) 2
127.0.0.1:6379> hgetall myhash
1) "field3"
2) "codeyuaiiao"
3) "field4"
4) "hello"
5) "field5"
6) "byebye"
127.0.0.1:6379>
###########################################################
# hlen 获取字段数量
127.0.0.1:6379> hset myhash field1 hello
(integer) 1
127.0.0.1:6379> hset myhash field2 byebye
(integer) 1
127.0.0.1:6379> hset myhash field3 yuaiiao
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "byebye"
3) "field2"
4) "byebye"
5) "field3"
6) "yuaiiao"
127.0.0.1:6379> hlen myhash # 获取hash表的字段数量
(integer) 3
127.0.0.1:6379>
###########################################################
# hexists 判断字段是否存在
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "byebye"
3) "field2"
4) "byebye"
5) "field3"
6) "yuaiiao"
127.0.0.1:6379> hexists myhash field1 # 判断hash表中指定字段是否存在
(integer) 1
127.0.0.1:6379> hexists myhash field4
(integer) 0
127.0.0.1:6379>
###########################################################
# hkeys # 获取所有field
# hvals # 获取所有value
127.0.0.1:6379> hkeys myhash # 获取所有field
1) "field1"
2) "field2"
3) "field3"
127.0.0.1:6379> hvals myhash # 获取所有value
1) "byebye"
2) "byebye"
3) "yuaiiao"
127.0.0.1:6379>
###########################################################
# hinceby # 自增
127.0.0.1:6379> hincrby myhash field3 2 # 指定增量
(integer) 8
127.0.0.1:6379> hincrby myhash field3 -2 # 指定减量
(integer) 6
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "hello"
3) "field2"
4) "byebye"
5) "field3"
6) "6"
127.0.0.1:6379> hsetnx myhash field4 yuaiiao # 如果不存在可以设置
(integer) 1
127.0.0.1:6379> hsetnx myhash field4 code # 如果存在则不可设置
(integer) 0
127.0.0.1:6379>hgetall myhash
1) "field1"
2) "hello"
3) "field2"
4) "byebye"
5) "field3"
6) "6"
7) "field4"
8) "yuaiiao"
127.0.0.1:6379>
###########################################################
hash变更的数据user name age ,尤其是用户信息之类的,经常变动的信息! hash更适合于对象的存储,String更加适合字符串存储
在set的基础上,增加了一个值,set k1 v1 zset k1 score1 v1
###########################################################
# zset 添加有序元素
# zrange 查看所有元素
127.0.0.1:6379> zadd myzset 1 one # 添加一个元素
(integer) 1
127.0.0.1:6379> zadd myzset 2 two 3 three # 添加两个元素
(integer) 2
127.0.0.1:6379> zrange myzset 0 -1 # 查看所有元素
1) "one"
2) "two"
3) "three"
127.0.0.1:6379>
###########################################################
# 排序如何实现
# zrangebyscore
# zrevrange
# -inf 负无穷
# -inf 正无穷
127.0.0.1:6379> zadd salary 2500 zhangsan # 添加三个用户
(integer) 1
127.0.0.1:6379> zadd salary 5000 lisi
(integer) 1
127.0.0.1:6379> zadd salary 500 yuaiiao
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf # 显示全部用户,从小到大
1) "yuaiiao"
2) "zhangsan"
3) "lisi"
127.0.0.1:6379> zrevrange salary 0 -1 # 显示全部用户,从大到小
1) "lisi"
2) "zhangsan"
3) "yuaiiao"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores # 显示全部的用户并且附带成绩
1) "yuaiiao"
2) "500"
3) "zhangsan"
4) "2500"
5) "lisi"
6) "5000"
127.0.0.1:6379> zrevrange salary 0 -1 withscores # 显示全部的成绩从高到底
1) "lisi"
2) "5000"
3) "zhangsan"
4) "2500"
5) "yuaiiao"
6) "500"
127.0.0.1:6379> zrangebyscore salary -inf 2500 withscores # 显示工资小于2500员工的升序排序
1) "yuaiiao"
2) "500"
3) "zhangsan"
4) "2500"
127.0.0.1:6379>
###########################################################
# zrem # 移除元素
127.0.0.1:6379> zrange salary 0 -1
1) "yuaiiao"
2) "zhangsan"
3) "lisi"
127.0.0.1:6379> zrem salary lisi # 移除有序集合中的指定元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "yuaiiao"
2) "zhangsan"
127.0.0.1:6379> zcard salary # 获取有序集合中的个数
(integer) 2
127.0.0.1:6379>
###########################################################
# zcount 查看区间内的元素个数
127.0.0.1:6379> zadd test 1 one 2 two 3 three
(integer) 3
127.0.0.1:6379> zcount test 1 2 # 获取指定区间的成员个数
(integer) 2
127.0.0.1:6379> zcount test 1 3
(integer) 3
127.0.0.1:6379>
###########################################################
其余的一些API,工作中遇到就到官方文档查询
案例思路: set 排序 ,存储班级成绩表,工资表排序
普通消息,1 , 重要消息 2 带权重进行判断!
排行榜应用实现,取 Top N 测试!
朋友的定位,附近的人,打车距离计算?
redis的 Geo 在redis3.2版本就推出了,这个功能可以推算地理位置的信息, 两地之间的距离,方圆几里的人
可以查询一些测试数据:http://www.jsons.cn/lngcode/
只有六个命令
# geoadd 添加地理位置
# 有效地经度和纬度
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 113.28 23.12 guangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 114.08 22.54 shenzhen
(integer) 1
127.0.0.1:6379> geoadd china:city 120.15 30.28 hangzhou 106.50 29.53 chongqing
(integer) 2
127.0.0.1:6379>
获得当前定位:一定是一个坐标值!
# 查询
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city beijing shenzhen
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
2) 1) "114.08000081777572632"
2) "22.53999903789756587"
127.0.0.1:6379>
两人之间的距离
单位 m km
127.0.0.1:6379> geodist china:city beijing shanghai
"1067378.7564"
127.0.0.1:6379> geodist china:city beijing shanghai km # 北京到上海的直线距离
"1067.3788"
127.0.0.1:6379> geodist china:city beijing shenzhen km # 北京到深圳的直线距离
"1943.2550"
127.0.0.1:6379>
withcoord 显示经纬度
withdist 显示距离
count 显示数量
127.0.0.1:6379> georadius china:city 110 30 1000 km
1) "chongqing"
2) "shenzhen"
3) "guangzhou"
4) "hangzhou"
127.0.0.1:6379> georadius china:city 110 30 900 km
1) "chongqing"
2) "guangzhou"
127.0.0.1:6379> georadius china:city 110 30 900 km withcoord # 查找范围内的城市并显示经纬度
1) 1) "chongqing"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "guangzhou"
2) 1) "113.27999979257583618"
2) "23.1199990030198208"
127.0.0.1:6379> georadius china:city 110 30 900 km withhash
1) 1) "chongqing"
2) (integer) 4026042091628984
2) 1) "guangzhou"
2) (integer) 4046533745880732
127.0.0.1:6379> georadius china:city 110 30 900 km withcount
(error) ERR syntax error
127.0.0.1:6379> georadius china:city 110 30 900 km withdist
1) 1) "chongqing"
2) "341.9374"
2) 1) "guangzhou"
2) "831.7713"
127.0.0.1:6379> georadius china:city 110 30 900 km withcoord count 1
1) 1) "chongqing"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
127.0.0.1:6379> georadius china:city 110 30 900 km withcoord count 2
1) 1) "chongqing"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "guangzhou"
2) 1) "113.27999979257583618"
2) "23.1199990030198208"
127.0.0.1:6379>
127.0.0.1:6379> georadiusbymember china:city beijing 1000 km
1) "beijing"
127.0.0.1:6379> georadiusbymember china:city shanghai 1000 km
1) "hangzhou"
2) "shanghai"
127.0.0.1:6379>
# 将二维的经纬度,换为了一维的字符串,
# 字符串越像,越接近
127.0.0.1:6379> geohash china:city beijing shanghai
1) "wx4fbxxfke0"
2) "wtw3sj5zbj0"
127.0.0.1:6379>
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqing"
2) "shenzhen"
3) "guangzhou"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> zrem china:city chongqing # 删除
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "shenzhen"
2) "guangzhou"
3) "hangzhou"
4) "shanghai"
5) "beijing"
127.0.0.1:6379>
什么是基数?
A{1,3.,5,7,9}
B{1,3,5,7,9,9}
基数(不重复的元素) =AB合并=5 , 可以接受误差
简介
Redis 2.8.9 版本就跟新了Hyperloglog数据结构!
Redis Hyperloglog 基数统计的算法!
优点: 占用的内存是固定的, 2^64 不同的元素的基数,只需要12kb的内存即可,如果从内存角度来比较,且允许有误差(0.81%)Hyperloglog首选
网页的UV(一个人访问一个网站多次,但是还是算作一个人!)
传统的方式,set保存用户的id ,然后就可以统计set中元素数量作为标准!
set集合(无序,不重复)
这个方式如果保存大量的用户id,就会比较麻烦! 我们的目的是为了技术,而不是保存用户id
测试使用
# pfadd 添加
# pfcount 计数
# pfmerge 合并
127.0.0.1:6379> pfadd key a b c g s k a l o i u # 添加元素
(integer) 1
127.0.0.1:6379> pfadd key1 s k i y o l a k v f s
(integer) 1
127.0.0.1:6379> pfcount key # 查看数量
(integer) 10
127.0.0.1:6379> pfcount key1
(integer) 9
127.0.0.1:6379> pfmerge key2 key key1 # key key1 合并到key2中
OK
127.0.0.1:6379> pfcount key2
(integer) 13
如果允许容错要用 hyperloglog
不允许就用 set 或者自己的数据类型
统计用户信息(活跃,不活跃) (登录,未登录)
打卡(打卡,未打卡)
两个状态的都可以使用bitmap
bitmap 位图 , 数据结构! 都是操作二进制位来进行记录的,就只有0和1两个状态
1字节 = 8bit
测试
使用bitmaps 来记录一周的打卡情况!
127.0.0.1:6379> setbit login 0 1
(integer) 0
127.0.0.1:6379> setbit login 1 0
(integer) 0
127.0.0.1:6379> setbit login 2 1
(integer) 0
127.0.0.1:6379> setbit login 3 1
(integer) 0
127.0.0.1:6379> setbit login 4 0
(integer) 0
127.0.0.1:6379> setbit login 5 1
(integer) 0
127.0.0.1:6379> setbit login 6 0
(integer) 0
127.0.0.1:6379>
查看某一天是否打卡
127.0.0.1:6379> getbit login 2
(integer) 1
127.0.0.1:6379> getbit login 6
(integer) 0
127.0.0.1:6379>
统计操作,统计打卡的天数
127.0.0.1:6379> bitcount login # 统计为1的次数
(integer) 4
127.0.0.1:6379>
这些在生活中或开发中,都有十分多的应用场景,学习了,就是多一个思路.技多不压身
Redis事务本质: 一组命令的集合! 一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行!
一次性,顺序性,排他性! 执行一系列的命令!
------队列 set get set 执行-----
Redis 事务没有隔离级别的概念!
所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会执行! Exec
Redis单条命令是保证原子性的,但是事务不保证原子性!
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set key hello # 执行命令
QUEUED
127.0.0.1:6379> set key1 yuaiiao
QUEUED
127.0.0.1:6379> get key
QUEUED
127.0.0.1:6379> get key1
QUEUED
127.0.0.1:6379> exec # 执行事务
1) OK
2) OK
3) "hello"
4) "yuaiiao"
127.0.0.1:6379>
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key 1
QUEUED
127.0.0.1:6379> set key2 3
QUEUED
127.0.0.1:6379> discard # 放弃事务
OK
127.0.0.1:6379> get key2
(nil)
127.0.0.1:6379>
编译型异常(diamante有问题! 命令错了!) ,事务中所有的命令都不会被执行
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set k1 v1 # 执行命令
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> getset k3 v3
QUEUED
127.0.0.1:6379> getset k3 # 错误命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> exec # 执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k2 # 事务执行失败,得不到值
(nil)
127.0.0.1:6379>
运行时异常(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 # 字符串 自增1 错误
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec # 错误的命令报错但是其余命令都能执行
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "v2"
127.0.0.1:6379> get k2 # 其余命令正常执行
"v2"
127.0.0.1:6379> get k3
"v3"
127.0.0.1:6379>
悲观锁
乐观锁
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
127.0.0.1:6379>
测试多线程修改值,使用watch可以当做redis 的乐观锁操作
127.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 # 执行之前,另一个线程,修改了我们的值,就会导致事务执行失败!
# 127.0.0.1:6379> get money
"80"
# 127.0.0.1:6379> set money 1000
# OK
(nil)
127.0.0.1:6379>
如果修改失败,获取最新的就好
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 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 980
2) (integer) 40
127.0.0.1:6379>
我们要使用java来操作redis, 知其然并知其所以然,授人以鱼不如授人以渔,学习不能急躁,慢慢来会很快
什么是jedis?
jedis 是redis官方推荐的java连接开发工具! 使用java操作Redis的中间件 ! 如果你要使用java 操作redis, 那么一定要对jedis十分熟悉
1.导入依赖
<dependencies>
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>3.3.0version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.70version>
dependency>
dependencies>
2.编码测试
SpringBoot操作数据 :Spring-data jpa jdbc mongodb redis !
SpringData 也是和 SpringBoot 齐名的项目!
说明 :在 SpringBoot2.x之后, 原来使用的jedis 被替换为了 lettuce ?
jedis :采用的直连, 多个线程操作的话 ,是不安全的, 如果想要避免不安全, 使用jedis pool 连接池 ! 更像 BIO模式.
lettuce : 采用netty , 实例可以再多个线程中进行共享,不存在线程不安全的情况 ! 可以减少线程数据了, 更像NIO模式.
源码分析
整合测试
导入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
配置连接
# 配置redis
spring.redis.host=192.168.189.128
spring.redis.port=6379
spring.redis.password=123456
测试
测试test
@SpringBootTest
class RedisSpringbootApplicationTests {
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate redisTemplate;
@Autowired
private RedisUtil redisUtil;
@Test
void contextLoads() {
//redisTemplate 操作不同的数据类型 , API和我们的指令一样
//opsForValue 操作字符串 类似Spring
//opsForList 操作List
//opsForSet 操作Set
//opsForHash
//opsForvZset
//opsForGeo
//opsForHyperLogLog
//除了基本的操作,我们常用的方法都可以直接通过redisTemplate操作, 比如事务 ,和基本的CRUD
//获取redis的操作对象
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();
redisTemplate.opsForValue().set("mykey","hello yuaiiao");
System.out.println(redisTemplate.opsForValue().get("mykey"));
}
}
关于对象的保存
RedisConfig
// RedisConfig
@Configuration
public class RedisConfig {
public RedisConfig() {
}
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
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);
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;
}
}
// RedisUtil
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
* @param key 键
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
//测试test
@Test
void test1(){
redisUtil.set("name","yuaiiao");
System.out.println(redisUtil.get("name"));
}
所有的redis操作,其实对于java开发人员来说,十分的简单,更重要的是要去理解redis的思想和每一种数据结构的用处和作用场景.
启动的时候就通过配置文件启动的
Redis 中这些小的配置 才会让你脱颖而出
单位
1.对大小写不敏感
包含
包含多个配置文件
网络
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
快照
持久化,在规定的时间内,执行了多少次操作,则会持久化到文件,.rdb.aof
redis是内存数据库,如果没有持久化,那么数据断电及失!
# 如果900s内,如果至少有一个1 key进行了修改,我们及进行持久化操作
save 900 1
# 如果300s内,如果至少10 key进行了修改,我们及进行持久化操作
save 300 10
# 如果60s内,如果至少10000 key进行了修改,我们及进行持久化操作
save 60 10000
# 我们之后学习持久化,会自己定义这个测试!
stop-writes-on-bgsave-error yes # 持久化如果出错,是否还需要继续工作!
rdbcompression yes # 是否压缩rdb文件.需要消耗一些cpu资源!
rdbchecksum yes # 保存rdb文件的时候,进行错误的检查!
dir ./ # rdb 文件保存大的目录
REPLICATION 复制 , 我们后面讲解主从复制,时候再进行讲解
SECURITY 安全
限制 CLIENTS
maxclients 10000 # 设置能连接上tedis 的最大客户端的数量
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 : 永不过期,返回错误
APPEND ONLY MODE 模式 aof配置
appendonly no # 默认是不开启
appendfilename "appendonly.aof" # 持久化的文件的名字
#appendfsync always # 每次修改都会 sync(同步). 消耗性能
appendfsync everysec # 美妙执行一侧 sync, 可能会丢失这1s的数据
# appendfsync no # 不执行sync ,这个时候操作系统自己同步数据,速度最快!
具体的配置,我们在Redis持久化中解决
在主从复制中,rdb就是备用了! 从机上面!
什么是RDB
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里.
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中, 带持久化过程都结束了,再用这个临时文件替换上次持久化好的文件. 整个过程中,主进程是不进行任何IO操作的.这就确保了极高的性能.如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感.那RDB方式要不AOF方式更加的高效.RDB的缺点是最后一次持久化后的数据可能丢失. 我们默认的就是RDB, 一般情况下不需要修改这个配置!
RDB保存的文件是,dump.rdb都是在我们的配置文件中快照中进行配置的!
触发机制
备份就会自动生成一个dump.rdb
如何恢复rdb文件!
只需要将rdb 文件放在我们redis启动目录就可以,redis启动的时候会自动检查dump.rdb恢复其中的数据!
查看需要存在的位置
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin" # 入伙在这个目录下存在dump.rdb 文件,启动就会自动恢复其中的数据
默认的配置就可以了
优点&缺点
优点:
适合大规模的数据恢复!
对数据的完整性要求不高!
缺点:
需要一定的时间间隔进行操作! 如果redis意外宕机了,这个最后一个修改的数据就没有了!
fork进程的时候,会占用一定的内容空间!
将我们的所有命令都记录下来,history , 恢复的时候就把这个文件全部在执行一遍
是什么
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录) , 只许追加文件但不可以改写文件,redis 启动之初会读取改文件重新构建数据,换言之,redis重启的话就跟据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
Aof保存的是 appendonly.aof文件
append
默认是不开启的,我们需要手动进行配置! 我们只需要将appendonly 改为yes就开启了aof!
重启,redis 就可以生效了!
如果这个aof文件有错位,这时候 redis是启动不起来的,我们需要修复这个aof文件
redis 给我提供了一个工具redis-check-aof --fix
如果文件正常,重启就可以直接恢复了!
重写规则说明
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 客户端可以订阅任意数量的频道.
订阅/发布消息图:
第一个: 消息发送者, 第二个 :频道 第三个 :消息订阅者!
命令
这些命令被广泛用于构建即时通信应用,比如网络聊天室和实时广播,实时提醒
订阅端
127.0.0.1:6379> subscribe codeyuaiiao # 订阅一个频道 codeyuaiiao
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "codeyuaiiao"
3) (integer) 1
# 等待读取推送
1) "message" # 消息
2) "codeyuaiiao" # 那个频道的消息
3) "hello world" # 消息的具体内容
1) "message"
2) "codeyuaiiao"
3) "hello yuhaijiao"
发送端:
127.0.0.1:6379> publish codeyuaiiao "hello world" # 发布者发布消息到频道
(integer) 1
127.0.0.1:6379> publish codeyuaiiao "hello yuhaijiao" # 发布者发布消息到频道
(integer) 1
127.0.0.1:6379>
原理
通过publish命令向订阅者发送消息,redis-server会使用给定的频道作为键,在它所维护的channel字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者.
使用场景:
稍微复杂的场景我们就会使用 消息中间件MQ
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器.前者称为主节点(master/leader),后者称为从节点(slave/follower);==数据的复制是单向的,只能由主节点到从节点.==Master以写为主,Slave以读为主.
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点.
主从复制的作用主要包括:
一般来说,要将Redis运用于工程项目中,只是用一台Redis是万万不能的(宕机 ,一主二从),原因如下:
这种场景,我们可以使用如下这种架构:
只要在公司中,主从复制就是必须要使用的,因为在真实的项目中不可能单机使用Redis !
复制3个配置文件,然后修改对应的信息
修改完毕之后,启动我们的3个redis服务器,可以通过进程信息查
默认情况下, 每台Redis 服务器都是主节点 ; 我们一般情况下只用配置从机就好了!
slaveof 127.0.0.1 6379 # slaveof host 6379 设定谁是主机
info replication 查询信息
slaveof 主机ip 端口号 # 设置从机 slaveof 127.0.0.1 6379
如果两个都配置完了,就有两个从机了
真实的从主配置应该在配置文件中配置,这样的话是永久的, 我们这里使用的是命令,暂时的!
细节
主机可以写, 从机不能写只能读! 主机中的所有信息和数据,都会自动被从机保存.
主机写:
从机读:
测试: 主机断开连接,从机依旧连接到主机的, 但是没有写操作, 这个时候, 主机如果回来了,从机依旧可以直接直接获取到主机写的信息!
如果是使用命令行, 来配置的主从,这个时候如果重启了,就会变成主机 ! 只要变为从机, 立马就会从主机中获取值!
复制原理
Slave启动成功连接到master后会发送一个sync同步命令
Master接到命令,启动后台的存盘进程,同时手机所有接受到的用于修改数据集命令, 在后台进程完毕之后,master将传送整个数据问价难道slave,并完成一次完全同步.
全量复制: 二slave服务在接收到数据库文件数据后, 将其存盘并加载到内存中.
增量复制: Master继续将新的所有收集到的修改命令依次传给slave,完成同步.
但是只要是重新连接master, 一次完全同步(全量复制)将被自动执行! 我们的数据一定可以在从机中看到!
层层链路
上一个M连接下一个S!
这时候也可以完成我们的主从复制!
如果没有老大了,这个时候能不能选择一个老大出来呢? 手动!
谋朝篡位
如果主机断开了连接, 我们可以使用slaveof no one
让自己变成主机! 其他的节点就可以手动连接到最新的这个主节点(手动)! 如果这个时候老大修复了, 那就重新连接!
(自动选举老大)
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费时费力,还会造成一段时间内服务不可用. 这不是一个推荐的方式.更多时候,我们优先考虑哨兵模式.Redis从2.8开始正式提供了Sentinel(哨兵)架构来解决这个问题.
谋朝篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动从库转换为主库
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行.其原理是哨兵通过他送命令,等待Redis服务器响应,从而监控运行的多个Redis实例.
这里的哨兵有两个作用:
然而一个烧饼进程对Redis服务器进行监控,可能会出现问题,为此我们可以使用多个哨兵进行监控.各个哨兵之间还会进行监控,这样就形成了多哨兵模式.
假设主服务器宕机,哨兵1先检测到这个结果,系统不会马上进行failover过程,仅仅是哨兵1主管的认为主服务器不可用,这个现象成为主观下线.当后面的哨兵也检测到主服务器不可用,并且数量到大一定值时,那么哨兵之间就会进行一个投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作.切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线.
测试
我们目前的状态是 一主二从
# sentinel monitor 被监控的名称 host port 1
sentinel monitor myredis 127.0.0.1 6379 1
后面的这个数字1,代表主机挂了,slave投票看让谁接替称为主机,票数最多的,就会成为主机
启动
redis-sentinel YuConfig/sentinel.conf
[root@localhost bin]# redis-sentinel YuConfig/sentinel.conf
5748:X 04 Nov 2020 15:59:17.801 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
5748:X 04 Nov 2020 15:59:17.801 # Redis version=6.0.8, bits=64, commit=00000000, modified=0, pid=5748, just started
5748:X 04 Nov 2020 15:59:17.801 # Configuration loaded
5748:X 04 Nov 2020 15:59:17.803 * Increased maximum number of open files to 10032 (it was originally set to 1024).
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.0.8 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 5748
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
5748:X 04 Nov 2020 15:59:17.804 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
5748:X 04 Nov 2020 15:59:17.821 # Sentinel ID is f876de823361a0896cdd4c39d29dd9eb189c4c7c
5748:X 04 Nov 2020 15:59:17.821 # +monitor master myredis 127.0.0.1 6379 quorum 1
如果Master节点断开了,这个时候就会从从机中随机选择一个服务器(这里面有一个投票算法底层)
哨兵日志!
如果主机此时回来了,只能诡兵道心得主机下,当做从机,这就是哨兵模式的规则!
哨兵模式
优点:
缺点:
哨兵模式的 全部配置
port 26379
dir /tmp
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel auth-pass mymaster MySUPER--secret-0123password
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
sentinel notification-script mymaster /var/redis/notify.sh
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh # 一般运维配置
在这里我们不会详细的分析解决方案的底层!
Redis 缓存的使用,极大地提升了应用程序的性能和效率,特别是数据查询方面. 但同时, 他也带来了一些问题,其中, 最重要的问题就是数据的一致性问题,从严格意义上来讲,这个问题无解. 如果对数据的一致性要求很高,那么就不能使用缓存.
另外的一些典型问题就是, 缓存穿透 , 缓存击穿和缓存雪崩.目前业界也都有比较流行的解决方案.
概念
缓存穿透的概念很简单 , 用户想要查询一个数据, 发现redis 内存数据库中没有, 也就是缓存没有命中, 于是向持久层数据库查询 .发现也没有, 于是本次查询失败. 当用户很多的时候, 缓存都没有命中(秒杀…) , 于是都去请求持久层数据库 . 这会给持久层数据库造成很大的压力, 这时候就相当于出现了缓存穿透.
解决方案两种
1.布隆过滤器
布隆过滤器是一种数据结构 , 对所有可能查询的参数以hash形式存储, 在控制层先进行校验, 不符合则丢弃, 从而避免不了对底层存储系统的查询压力;
2.缓存空对象
当存储层不命中后 , 即使返回的空对象也将其缓存起来, 同时会设置一个过期时间, 之后再访问这个数据将会从缓存中获取,保护了后端数据源;
但是这种方法会存在两个问题:
概念
这里需要注意和缓存击穿的区别, 缓存击穿, 是指一个key非常热点, 在不停的扛着大并发,大并发集中对这一个点进行访问, 当这个key在失效的的瞬间, 持续的大并发就穿破缓存, 直接请求数据库, 就像在一个屏障上凿开了一个洞.
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据, 由于魂村过期, 会同时访问数据库来查询最新数据, 并且回写缓存, 会导致数据库瞬间压力过大.
解决方案
设置热点数据永不过期
从缓存层面来看, 没有设置过期时间,所以不会出现热点key过期后产生的问题.
加互斥锁
分布式锁: 使用分布式锁,保证对于每个key 同时只有一个线程去查询后端服务, 其他线程没有获得分布式锁的权限, 因此只需要等待即可. 这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大.
缓存雪崩, 是指在摸一个时间段, 缓存集中过期失效. Redis宕机!
产生雪崩的原因之一, 比如马上就要到双十二零点 , 很快就会迎来一波抢购, 这波商品时间比较集中的放入了缓存,假设缓存一个小时,那么到了凌晨一点中的时候, 这批商品的缓存就都过期了. 而对这批商品的访问查询,都落到了数据库上, 对于数据库而言, 就会产生周期性的压力波峰. 于是所有的请求都会到达存储层, 存储层的调用量会暴增, 造成存储层也会挂掉的情况.
其实集中过期, 倒不是非常致命的, 比较致命的缓存雪崩, 是缓存服务器某个节点宕机或断网 . 因为自然形成的缓存雪崩, 一定是在某个时间段集中创建缓存, 这个时候 , 数据库也是可以顶住压力的. 无非就是对数据库数据库产生周期性的压力而已. 而缓存服务节点的宕机, 对数据库服务器造成的压力是不可遇见的. 很有可能瞬间就把数据库压垮的.
解决方案
redis高可用
这个思想的含义是, 既然redis有可能挂掉, 那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群. (异地多活!)
限流降级
这个解决方案的思想是, 在缓存失效后 , 通过枷锁或者队列来控制读数据库写缓存的线程数量. 比如对某个key只允许一个线程查询数据和写缓存, 其他线程等待.
数据预热
数据加热的含义就是在正式部署前, 我先把可能的数据预先访问一遍, 这样可能大量访问的数据就会加载到缓存中. 在即将发生大并发访问前手动触发加载不同的key , 设置不同的过期时间, 让缓存失效的时间点尽量均匀.