文章目录
Redis
1、Nosql概述
为什么要用Nosql
大数据时代,一般的数据库无法进行分析处理!springBoot;springCloud;
**适者生存,一定要逼自己学习!**身存法则。
1、单机MySQL的年代
90年代网站的瓶颈是什么?
1、数据量太大、一个机器放不下!
2、数据的索引(B+tree),一个机器内存也放不下~
3、访问量(读写混合),一个服务器承受不了
只要出现以上三种情况之一,就必须要晋级!
2、Memcached(缓存)+ MySQL+垂直拆分(读写分离)
网站80%的操作都是在读操作。 每次都要查数据库非常麻烦。
所以说我们希望减轻数据库的压力,可以使用缓存!
3、分库分表+水平拆分+MySQL集群
本质:数据库:读+写
4、如今的时代
2010—2020 十年之间,世界已经发生了翻天覆地的变化!(定位:也是数据)
MySQL的关系型数据库已经不够用了!数据量很多,变化很快!
目前一个基本的互联网项目!
为什么要用NoSQL!
用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式的数据。
这个时候NoSQL可以很好处理以上情况!
什么是NoSQL
NoSQL
NoSQL = Not Only SQL
关系型数据库:表格,行,列
泛指非关系型数据库 ,随着web2.0互联网的诞生!尤其是超大规模的高并发的社区 ! 站长!
Redis是最发展最快的非关系型数据库!
很多信息,如用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志,这些数据类型不需要一个固定的格式!不需要多余的操作就可以横向拓展的!Map<String,Object> 使用键值对来控制
NoSQL特点
1、方便扩展(数据之间没有关系,就可以很好扩展)!
2、大数据量高性能 (Redis 一秒写8万次,读取11万次 ,性能比较高)
3、数据类型是多样型的!(不需要事现设计数据库,随取随用!)
4、传统的RDBMS和NoSQL(关系型数据库与非关系型数据库)
传统的RDBMS
- 结构化组织
- SQL
- 数据和关系都存在单独的表中
- 操作操作,数据定义语言
- 严格的一致性
- 事务
...
NoSQL
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库
- CAP定理 和 BASE (异地多活) (狂神理念:只要学不死,就往死里学)
- 高性能,高可用,高可扩
- ...
了解:3V+3高
大数据时代的3V:主要是描述问题的
- 海量
- 多样
- 实时
大数据时代的3高:
- 高并发
- 高可拓(随时水平拆分)
- 高性能(保证用户体验和性能)
阿里巴巴演进分析
不同数据库存不同的信息
# 1、商品的基本信息
名称、价格、商家信息;
关系型数据库就可以解决了!MySQL / Oracle(淘宝早年就去IOE了!-王坚:推荐文章:阿里云的这群疯子)
# 2、商品的描述、评论(文字比较多)
文档型数据库中,MongoDB
# 3、图片
分布式文件系统FastDFS
- 淘宝自己的 TFS
- Google的 GFS
- Hadoop HDFS
- 阿里云 oss
# 4、关键字
- 搜索引擎 solr elasticsearch
- 淘宝自己的 ISearch 多隆(多去了解这些技术大佬)
所有牛逼的人都有一段苦逼的岁月!但是你只要像SB一样的去坚持,终将牛逼!
# 5、商品热门的波段信息
- 内存数据库
- Redis Tair Memche...
# 6、商品的交易,外部的支付接口
- 三方应用
要知道,一个简单的网站背后的技术一定不是大家所想的那么简单!
大型互联网应用问题:
- 数据类型太多了
- 数据源繁多,经常重构!
- 数据要改造
NoSQL的四大分类
KV键值对
- Redis
- memecache
- tair
文档型数据库
- MongoDB
- MongoDB是一个基于分布式存储的数据库,主要用来处理大量的文档!
- MongoDB是一个介于关系型数据库和非关系型数据中间的产品!MongoDB是非关系型数据库中功能最丰富的,最像关系型数据库的
- ConthDB
列存储数据库
- HBase
- 分布式文件系统
图关系数据库
- 他不是存图形的,放的是关系。比如:朋友圈社交网络,广告推荐!
- Neo4j、Graph
革命是块砖,哪里需要哪里搬
敬畏之心可以使人进步!
2、Redis入门
概述
Redis是什么
Remote Dictionary Server,远程字典服务。
免费和开源!是当下最热门的NoSQL技术之一。
Redis能干嘛
1、内存存储、持久化,内存是断电即失的,所以说持久化很重要(rdb、aof)
2、效率高,可以用来高速缓存
3、发布订阅系统
4、地图信息分析
5、计时器、计数器(浏览量)
…
特征
1、多样的数据类型你
2、持久化
3、集群
4、事务
…
学习中要用到的东西
1、官网:https://redis.io/
2、中文官网:http://www.redis.cn/
注意:Window在Github上下载
Windows安装启动
redis默认端口6379
1、打开服务 server
2、打开客户端
记住一句话,Windows下使用确实简单,推荐使用Lunux开发
测试性能
redis-benchmark是一个压力测试工具
简单测试一下
# 测试 100个并发连接 100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 1000
基础的知识
redis默认有16个数据库,不同的数据库可以存不同的数据
默认使用的是第0个
可以使用select进行切换
127.0.0.1:6379> select 3 # 切换到第三个数据库
OK
127.0.0.1:6379[3]> dbsize # 查看大小
(integer) 0
127.0.0.1:6379[3]> keys * # 查看数据库所有的key
1) "name"
127.0.0.1:6379[3]> flushdb # 清空当前库
OK
127.0.0.1:6379> flushall # 清空全部数据库内容
OK
思考:为什么redis默认端口是6379
答:是作者的偶像名字
Redis是单线程的
明白redis是很快的,官方表示,Redis是基于内存操作,CPU不是Redis性能的瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了!
Redis为什么单线程还这么快
误区1、高性能的服务器一定是多线程的
误区2、多线程(CPU会上下文切换)一定比单线程效率高
先去CPU->内存->硬盘的速度
核心:redis是将所有的数据放在内存中,所以说使用单线程操作效率就是最高的。多线程(CPU会上下文切换,是耗时的操作!),对于内存系统来说,如果没有上下文切换效率就是最高的!多次读写就是在一个CPU上的,在内存情况下,这个就是最佳的方案!、
3、五大数据类型
官网
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
127.0.0.1:6379> EXISTS name # 判读当前key是否存在
(integer) 1
127.0.0.1:6379> move name 1 # 从库1移除name
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> keys * # 查看所有的key
1) "name"
2) "age"
127.0.0.1:6379> EXPIRE name 10 # 设置过期时间,单位是秒
(integer) 1
127.0.0.1:6379> ttl name # 查看当前key的剩余时间
(integer) 7
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> type age # 查看当前key的类型
string
127.0.0.1:6379>
String(字符串)
90%的java程序员使用redis只会使用一个string类型!
################################################################
127.0.0.1:6379> set key1 v1
OK
127.0.0.1:6379> keys *
1) "key1"
127.0.0.1:6379> APPEND key1 "hello" # append对value进行字符串拼接
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> STRLEN key1 # 获得key的长度
(integer) 7
127.0.0.1:6379> APPEND key1 ",julian"
(integer) 14
127.0.0.1:6379> get key1
"v1hello,julian"
################################################################
127.0.0.1:6379> set views 0 # 设置浏览量0
OK
127.0.0.1:6379> incr views # 自增1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views # 自减1
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> incrby views 10 # 可以设置指定增量
(integer) 11
127.0.0.1:6379> incrby views 10
(integer) 21
127.0.0.1:6379> decrby views 15
(integer) 6
################################################################
# 字符串范围 range
127.0.0.1:6379> set key1 "hello,Julian" # 设置key1值
OK
127.0.0.1:6379> get key1
"hello,Julian"
127.0.0.1:6379> getrange key1 0 3 # 截取字符串[0,3]
"hell"
127.0.0.1:6379> getrange key1 0 -1 # 获取全部的字符串 和 get key是一样的
"hello,Julian"
# 替换
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> setrange key2 1 xx # 替换指定位置开始的字符串
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"
################################################################
# setex(set with expire) # 设置过期时间
# setnx(set if not exist) # 不存在在设置(在分布式锁中经常使用!)
127.0.0.1:6379> setex key3 30 "hello" # 设置key3的值为hello,过期时间为30秒
OK
127.0.0.1:6379> ttl key3
(integer) 26
127.0.0.1:6379> get key3
"hello"
127.0.0.1:6379> setnx mykey "redis" # 如果mykey不存在,创建mykey
(integer) 1
127.0.0.1:6379> keys *
1) "mykey"
2) "key1"
3) "key2"
127.0.0.1:6379> setnx mykey "MongoDB" # 如果mykey存在,创建失败!
(integer) 0
127.0.0.1:6379> get mykey
"redis"
################################################################
# mset
# mget
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # 同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k1"
3) "k2"
127.0.0.1:6379> mget k1 k2 k3 # 同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 # msetnx 是一个原子性操作,要么一起成功,要么一起失败!
(integer) 0
127.0.0.1:6379> get key4
(nil)
# 对象
set user:1 {name:zhangsan,age:3} # 设置一个user:1对象,值为json字符串来保存一个对象。
# 这里的key是一个巧妙的设计:user{id}:{filed},如此设计在redis中是完全ok了!
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 21
OK
127.0.0.1:6379> mget user:1:name uer:1:age
1) "zhangsan"
2) (nil)
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "21"
################################################################
getset # 先get然后再set
127.0.0.1:6379> getset db redis # 如果不存在值,则返回nil
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb # 如果存在值,获取原来的值,并设置新的值
"redis"
127.0.0.1:6379> get db
"mongodb"
数据结构是相同的!
String类型的使用场景!value除了是我们的字符串还可以是数字!
- 计数器
- 统计多单位的数量
- 粉丝数
- 对象缓存储存
List (列表)
基本的数据类型,列表
在redis里面,我们可以把redis玩成栈、队列、阻塞队列!
所有的list命令都是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 # Lrange 通过区间获取list的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1
1) "three"
2) "two"
127.0.0.1:6379> rpush list right # 将一个值或多个值,插入到列表尾部(右边)
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
########################################################
127.0.0.1:6379> LRANGE list1 0 -1
1) "three"
2) "two"
3) "one"
4) "demo"
127.0.0.1:6379> Lpop list1 # 移除左边元素(第一个元素)
"three"
127.0.0.1:6379> Rpop list1 # 移除右边元素(最后一个元素)
"demo"
127.0.0.1:6379> Lrange list1 0 -1
1) "two"
2) "one"
127.0.0.1:6379> Lindex list1 1 #Lindex 通过下标获取值
"one"
127.0.0.1:6379> Lindex list1 0
"two"
########################################################
# 移除指定的值 Lrem
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> Lrem list 1 three # 移除指定元素three
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> ltrim list 0 1 # Ltrim截取指定长度
OK
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
########################################################
# Lset 将列表中指定下标的值替换为另外一个值,更新操作
127.0.0.1:6379> Lpush list value1
(integer) 1
127.0.0.1:6379> Lset list 0 item # Lset:替换当前下标的值,如果不存在,报错!
OK
127.0.0.1:6379> lrange list 0 -1
1) "item"
########################################################
小节
- 实际上是一个链表
消息队列 Lpush Rpop
栈 Lpush Lpop
Set (集合)
set中的值不能重复
# 命令以s开头
########################################################
127.0.0.1:6379> sadd myset hello # sadd 向set中添加
(integer) 1
127.0.0.1:6379> sadd myset zhu
(integer) 1
127.0.0.1:6379> sadd myset LianWei
(integer) 1
127.0.0.1:6379> smembers myset # smembers 查看指定set的所有值
1) "LianWei"
2) "zhu"
3) "hello"
127.0.0.1:6379> Sismember myset hello # sismember 判断某一个值是否在set集合中
(integer) 1
########################################################
127.0.0.1:6379> scard myset # scard获取set集合中的个数
(integer) 3
127.0.0.1:6379> srem myset zhu # srem移除指定元素
(integer) 1
########################################################
set 无序不重复集合。抽随机!
127.0.0.1:6379> srandmember myset # 抽取随机成员
"hello"
########################################################
删除随机的key
127.0.0.1:6379> spop myset # 随机删除set中的元素
"zhu"
127.0.0.1:6379> spop myset
"hello"
127.0.0.1:6379> spop myset
"LianWei"
########################################################
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) "b"
2) "a"
127.0.0.1:6379> sinter key1 key2 # 交集 共同好友可以这样实现
1) "c"
127.0.0.1:6379> sunion key1 key2 # 并集
1) "a"
2) "c"
3) "e"
4) "d"
5) "b"
########################################################
Hash(哈希)
Map集合,key-map!
########################################################
127.0.0.1:6379> hset myhash field1 Julian # hset往哈希中存hashmap
(integer) 1
127.0.0.1:6379> hget myhash field1 # 获取一个字段值
"Julian"
127.0.0.1:6379> hdel myhash field1 # 删除hash指定key字段,对应的value也消失
(integer) 1
# hlen 获取字段长度
# hexists 判断hash中的指定字段是否存在
# hkeys 只获得所有的field
# hvals 只获得所有value
hash可以存变更的数据, user name age,尤其是用户信息之类的,经常变动的数据!hash更适合对象 的存储。
Zset(有序集合)
在set的基础上,增加了一个值,表示优先级!
127.0.0.1:6379> zadd myset 1 one # 添加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three # 添加多个值
(integer) 2
127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "two"
3) "three"
########################################################
127.0.0.1:6379> zadd salary 2500 xiaohong # 给用户添加薪水
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 500 lisi
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf # 小到大排序
1) "lisi"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores # 小到大排序并显示scores
1) "lisi"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
########################################################
移除元素
127.0.0.1:6379> zrem salary xiaohong # 移除有序集合中的指定元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "lisi"
2) "zhangsan"
127.0.0.1:6379> zcard salary # 获取有序集合中的个数
(integer) 2
4、三种特殊数据类型
geospatial地理位置
朋友的定位,附近的人,打车距离计算?
Redis的Geo,在Redis3.2版本就推出了。这个功能可以推算地理位置的信息,两地之间的距离,方圆几里的人。
只有6个命令
geoadd
# 添加地理位置
# 规则:南北极无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入
# 参数 key 值(经度 纬度)
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijin
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing
(integer) 1
127.0.0.1:6379> geoadd china:city 114.05 22.52 shenzhen
(integer) 1
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 108.96 34.26 xian
(integer) 1
geopos
127.0.0.1:6379> geopos china:city beijin # 获取指定的城市的经纬度!
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city chongqing
1) 1) "106.49999767541885376"
2) "29.52999957900659211"
geodist
127.0.0.1:6379> geodist china:city beijin shanghai #计算 北京-上海 之间的距离
"1067378.7564"
127.0.0.1:6379> geodist china:city beijin shanghai km # 单位转为km
"1067.3788"
georadius 以给定的地址为中心,找出半径内的元素
我附近的人?(获取所有附近的人的地址,定位!)通过半径来查询!
127.0.0.1:6379> georadius china:city 110 30 1000 km # 查看附近1000km的地区
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
127.0.0.1:6379> georadius china:city 110 30 500 km
1) "chongqing"
2) "xian"
Hyperloglog
Bitmap
5、事务
MySQL:ACID,要么都成功,要么都失败(原子性)
Redis单条命令是保证原子性的,但是事务不保证原子性
Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行。
一次性、顺序性、排他性
--- 队列 set set set 执行 ---
Redis事务没有隔离级别的概念!
所有的命令在事务中并没有全部执行,只有发起命令才会执行。
redis的事务:
- 开启事务(multi)
- 命令入队( … )
- 执行事务(exec)
锁:Redis可以实现乐观锁
1)正常执行事务
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set k1 v1 # 命令入队
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec # 执行事务
1) OK
2) OK
3) "v2"
2)放弃事务
127.0.0.1:6379> multi # 开始事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> discard # 取消事务
OK
127.0.0.1:6379> get k4 # 事务队列中的命令都不会被执行
(nil)
3)编译型异常 (代码有问题!命令有错)
事务中所有的命令都不会被执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> 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> set k5 v5
QUEUED
127.0.0.1:6379> exec # 执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5 # 所有的命令都不会被执行
(nil)
4)运行时异常(1/0)
如果在事务队列中存在语法性,那么执行命令的时候,其他命令可以正常执行,错误命令抛出异常
所以Redis事务没有一致性
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 "v1" # 给字符串自增1(错误的语法)
QUEUED
127.0.0.1:6379> incr k1 # 执行的时候会失败
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range # 虽然命令报错了,但是后面的命令正常执行成功了
3) OK
4) "v2"
127.0.0.1:6379> get k2
"v2"
监控 Watch
悲观锁
- 很悲观,无论做什么都加锁
乐观锁
- 很乐观,无论做什么都不加锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据
Redis的监视测试
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money # 监视money对象
OK
127.0.0.1:6379> multi # 事务正常结束,事务期间没有发生变动,这个时候就正常执行成功
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
127.0.0.1:6379>
测试多线程修改值,使用watch可以当作Redis的乐观锁操作!
6、Jedis
使用Java来操作Redis
1)什么是Jedis
Redis官方推荐的java连接开发工具!使用Java操作Redis中间件!
要知其然并知其所以然,学习不能急躁,慢慢来!慢慢来会很快!
2)测试
- 导入对应的依赖
<dependencies><!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<!--导入jedis的包-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
</dependencies>
- 编码测试
- 连接数据库
- 操作命令
- 断开连接!
Jedis jedis = new Jedis("127.0.1",6379);
System.out.println(jedis.ping());
3) 常用API
String
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.1",6379);
System.out.println("清空数据:"+jedis.flushDB());
System.out.println("判断某个键是否存在:"+jedis.exists("username"));
System.out.println("新增<username,Julian>的键值对:"+jedis.set("username","Julian"));
System.out.println("新增<password,123>的键值对:"+jedis.set("password","123"));
System.out.println("系统中的所有键:");
Set<String> keys = jedis.keys("*");
System.out.println(keys);
System.out.println("删除键password:"+jedis.del("password"));
System.out.println("判断password是否存在:"+jedis.exists("password"));
System.out.println("查看username存储的类型:"+jedis.type("username"));
System.out.println("重命名key:"+jedis.rename("username","name"));
System.out.println("返回当前数据库中key的数目:"+jedis.dbSize());
System.out.println("删除所有数据库中的key:"+jedis.flushAll());
}
4) Jedis操作事务
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello", "world");
jsonObject.put("name", "Julian");
// 开启事务
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
try {
multi.set("key1", result);
multi.set("key2", result);
multi.exec(); // 执行事务
} catch (Exception e) {
multi.discard(); // 事务的放弃
e.printStackTrace();
} finally {
System.out.println(jedis.get("key1"));
System.out.println(jedis.get("key2"));
jedis.close(); // 关闭连接
}
}
7、SpringBoot整合
lettuce
说明:在SpringBoot2.x之后,原来使用的jedis被替换为了lettuce
- jedis:采用的是直连,多个线程操作的话,是不安全的,如果想要避免不安全,就要采用jedis pool连接池! BIO模式
- lettuce:采用的netty(高性能网络…),实例可以在多个线程中共享,不存在线程安全的情况,可以减少线程数量。 NIO模式
RedisAutoConfiguration类
1、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、 配置连接
# 配置redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
3、 测试一下
package com.zhu;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class Spring03SpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
// redisTemplate
// opsForValue 操作字符串
// opsForList 操作List
// redisTemplate.opsForValue().set("name","Julian");
// 获取连接
// 除了基本的操作,我们常用的方法可以直接通过redisTemplate操作,比如事务、CRUD
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
redisTemplate.opsForValue().set("name","Julian");
System.out.println(redisTemplate.opsForValue().get("name"));
}
}
配置类 == xml配置
package com.zhu.config;
import com.zhu.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
// Configuration代表这是一个配置类,相当于之前的beans.xml
@Configuration
@ComponentScan("com.zhu.pojo")
public class MyConfig {
// 注册一个bean,相当于之前的bean标签
// 这个方法的名字,相当于bean标签的id属性
// 这个方法的返回值,相当于bean标签的class属性
// 相当于<bean id="getUser" class="com.zhu.pojo.User"></bean>
@Bean
public User getUser1(){
User user = new User();
user.setName("xiaomengyu");
return user;
}
}
RedisTemplate和StringRedisTemplate区别
-
RedisTemplate使用JdkSerializationRedisSerializer序列化
-
StringRedisTemplate使用StringRedisSerializer序列化
总结
当你的redis数据库里面本来存的是字符串数据或者你要存取的数据就是字符串类型数据的时候,那么你就使用StringRedisTemplate即可,
但是如果你的数据是复杂的对象类型,而取出的时候又不想做任何的数据转换,直接从Redis里面取出一个对象,那么使用RedisTemplate是更好的选择。
8、Redis.conf 详解
NETWORK
bind 127.0.0.1 # 绑定的ip
protected-mode yes # 保护模式:开启
port 6379 # 端口设置
GENERAL
loglevel notice # 日志打印级别
logfile "" # 日志的文件名
databases 16 # 数据库的数量,默认是16个
SNAPSHOTTING
持久化,在规定的时间内,执行了多少次操作,则会持久化到文件,.rdb/.aof
save 900 1 # 900s内key至少有1个key进行了修改,就持久化操作
save 300 10 # 300s内key至少有10个key进行了修改,就持久化操作
save 60 10000 # 60s内key至少有10000个key进行了修改,就持久化操作
stop-writes-on-bgsave-error yes # 持久化出错,是否还需要继续工作!
rdbcompression yes # 是否压缩rdb文件,需要消耗一些cpu资源
dir ./ # rdb保存的目录
REPLICATION(主从复制)
SECURITY(安全)
# 可以设置redis的密码,默认是没有密码的
config set requirepass "123456" # 设置密码
auth 123456 # 登录,密码验证
LIMITS
maxclients 10000 # 设置能连上redis最大客户端数量
maxmemory-policy noeviction # 内存达到上限之后的处理策略
APPEND ONLY MODE (aof)
appendonly no # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分情况下,rdb完全够用了
appendfilename "appendonly.aof" # 持久化的文件的名字
# appendfsync always # 每次修改都会写入,速度比较慢
appendfsync everysec # 每秒执行一次,同步,可能会丢失这1s的数据
# appendfsync no # 不执行同步,操作系统自己同步数据,速度最快
9、 Redis持久化★★★★★
rdb
Redis会单独创建一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主线程是不进行IO操作的,这就确保了极高的性能。
save 900 1 # 900s内key至少有1个key进行了修改,就持久化操作
save 300 10 # 300s内key至少有10个key进行了修改,就持久化操作
save 60 10000 # 60s内key至少有10000个key进行了修改,就持久化操作
触发机制:
- save的规则满足的情况下,会自动触发rdb规则
- 执行flushall命令,也会触发rdb规则
- 退出redis,也会产生rdb文件!
如何恢复rdb文件
只需要将rdb文件放在redis的启动目录下即可。
优点:
- 适合大规模的数据恢复!
- 对数据的完整性要求不高!
缺点:
- 需要一定的时间间隔操作!如果redis意外宕机,最后一次修改的数据就没了
- fork进程的时候,会占用一定的内存空间!
aof (append only file)
将我们所有的命令都记录下来,恢复的时候就把这个文件全部再执行一遍。
以日志的形式记录每个写操作,将Redis执行过的所有指令都记录下来(读操作不记录),只许追加文件不许改文件,redis启动之初会读取该文件重新构建数据。
Aof保存的是 appendonly.aof 文件
appendonly no # 默认是不开启的,要手动改成yes
重启,redis就可以生效了。
如果这个aof 文件有错位,这时候redis是启动不起来的,我们需要修复这个文件。
redis提供了reis-check-aof --fix
如果文件正常,即可修复!
# appendfsync always
appendfsync everysec
# appendfsync no
优点:
- 每一次修改都同步,文件的完整性更好
- 每秒同步一次,可能丢失1s的数据
- 从不同步,效率最高!
缺点:
- 相对于数据文件来说,aof远远大于rdb,修复速度也比rdb慢!
- aof的运行效率比rdb慢,redis默认配置是rdb
10、 Redis发布/订阅
# 订阅端
127.0.0.1:6379> SUBSCRIBE zhulianwei # 订阅zhulianwei的频道
Reading messages... (press Ctrl-C to quit) # 等待推送的信息
1) "subscribe"
2) "zhulianwei"
3) (integer) 1
1) "message"
2) "zhulianwei"
3) "hello" # 接收到消息的具体内容
1) "message"
2) "zhulianwei"
3) "helloRedis"
# 发送端
127.0.0.1:6379> PUBLISH zhulianwei hello # 发布者发布消息到频道
(integer) 1
127.0.0.1:6379> PUBLISH zhulianwei helloRedis
(integer) 1
127.0.0.1:6379>
使用场景:
1、实时消息系统
2、 实时聊天
3、 订阅、关注系统
稍微复杂的场景我们就会使用消息中间件来做MQ
11、主从复制★★★★★
1. 概念
指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的! 只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。
默认情况下,每台Redis服务器都是主节点
架构中经常使用!一主二从。公司中必用!
2. 主要作用
1、 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
2、故障恢复
3、负载均衡:可从主节点提供写操作,从节点提供读操作,分担服务器均衡。
4、高可用(集群)
3. 环境配置
127.0.0.1:6379> info replication # 查看当前库的信息
# Replication
role:master # 角色:master
connected_slaves:0 # 从机:0个
master_replid:82900d25c62aea1af32aa3f047446e1216e70827
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379>
复制3个配置文件,修改对应信息
- 端口port
- pidfile的名字
- 日志名字
- dump.rdb的名字
启动服务端
redis-server.exe redis.conf79.conf
redis-server.exe redis.conf80.conf
redis-server.exe redis.conf81.conf
启动客户端
D:\Program Files\Redis-x64-5.0.10>redis-cli.exe -p 6379
127.0.0.1:6379> exit
D:\Program Files\Redis-x64-5.0.10>redis-cli.exe -p 6380
127.0.0.1:6380> exit
D:\Program Files\Redis-x64-5.0.10>redis-cli.exe -p 6381
127.0.0.1:6381> exit
4. 一主二从
默认情况下,每个节点都是主节点,一般情况下只用配从机就可以了!
一主6379
二从6380、6381
6380
127.0.0.1:6380> slaveof 127.0.0.1 6379 # 认6379端口为主机
OK
127.0.0.1:6380> info replication
# Replication
role:slave # 角色变为从机
master_host:127.0.0.1 # 主机地址
master_port:6379 # 主机端口
master_link_status:up
master_last_io_seconds_ago:7
master_sync_in_progress:0
slave_repl_offset:14
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:7cfd81c204730a527c3b55503e45853f2fcf0d4a
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:14
6381
D:\Program Files\Redis-x64-5.0.10>redis-cli.exe -p 6381
127.0.0.1:6381> slaveof 127.0.0.1 6379
OK
6379
D:\Program Files\Redis-x64-5.0.10>redis-cli.exe -p 6379 # 查看主机状态
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2 # 2台从机配置
slave0:ip=127.0.0.1,port=6380,state=online,offset=294,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=294,lag=0
master_replid:7cfd81c204730a527c3b55503e45853f2fcf0d4a
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:294
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:294
127.0.0.1:6379>
主机可以写,从机只能读!
主机中的所有信息和数据,都会自动保存到从机保存!
测试:
当主机断开连接(shutdon)之后,从机依旧连接到主机的,只不过不能进行写操作。
复制原理
Slave启动成功连接到master后会发送一个sync同步命令
Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集合命令,在后台进程执行完毕之后,master将传送整个数据文件到salve,并完成一次完全同步。
全量复制:slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制:master继续将新的所有收集到的修改命令一次传给slave,完成同步。
只要重新连接到master,一次完全同步(全量复制)将被自动执行。
slaveof no one # 自动变master
12、哨兵模式
(自动选老大模式)
当主服务器宕机后,需要手动把一台从机却换为主机,手动模式很费力!
Redis从2.8开始正式提供了==Sentinel(哨兵)==架构来解决这个问题
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程它会独立运行,其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
为了防止哨兵死亡, 我们一般也会为哨兵配置哨兵集群
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yZkz1vIx-1618840999053)(C:%5CUsers%5CJulian%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20210410162856255.png)]
测试
1、 新增sentinel.conf文件
# sentinel monitor 被监控的名称 host port
sentinel monitor myredis 127.0.0.1 6379 1
2、配置
如果master节点宕机了,这个时候就会在从机中通过投票算法选择出一个服务器
哨兵模式
优点:
-
基于主从复制模式,所有的主从配置优点,它都有
-
主从可以切换,故障可以转移,系统可用性更好
缺点:
- Redis不好在线扩容,集群容量一旦达到上线,在线扩容就很麻烦
- 实现哨兵模式的配置很麻烦,里面有很多选择
13、缓存穿透和雪崩
真实开发中,请求一般先请求缓存,缓存中有,就直接返回。缓存中没有,就会去数据库中查询。
1. 缓存穿透(请求量太大,缓存过期导致的)
缓存中无,数据库中有
缓存中没有但数据库中有的数据(一般是缓存时间到期)。此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
解决方案
- 设置热点数据永不过期
- 加互斥锁(保证只有一个线程可以查询数据库,其余线程等待状态)
2. 缓存穿透(查不到导致的)
缓存中无,DB中也无。
大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。
解决方法
- 增加参数校验,过滤器,一些不合法的参数直接抛异常返回客户端。如拦截id<0的
- 缓存空对象。从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击。
3. 缓存雪崩
当缓存服务器重启或者大量缓存集中在某一个时间段失效,或者Redis宕机,这样在失效的时候,瞬间把数据库压垮。
比如:双十一,淘宝会停掉一些服务(退款),保证主要服务可用!
解决方案
- 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
- 设置热点数据永远不过期。
- Redis高可用:多增Redis服务器,搭建集群!
过期数据的删除策略
-
惰性删除:只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。
-
定期删除:每隔一段时间抽取一批 key 执行删除过期 key 操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。
两者各有千秋,所以 Redis 采用的是 定期删除+惰性/懒汉式删除 。
但是,仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的情况。这样就导致大量过期 key 堆积在内存里,然后就 Out of memory 了。
怎么解决这个问题呢?答案就是: Redis 内存淘汰机制
Redis内存淘汰机制
相关问题:MySQL 里有 2000w 数据,Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?