为什么用Nosql
1.单机SQL的时代
网站的瓶颈:
- 数据量太大,一个机器放不下。
- 数据的索引(B + Tree),一个机器内存放不下。
- 访问量(读写混合),一个服务器承受不了。
如果出现以上情况,需要升级。
2.Memcached(缓存)+ MySQL + 垂直拆分
网站80%的情况都是在读,每次都要去查询数据库的话就会十分麻烦!如果希望减轻数据的压力,可以使用缓存来保证效率。
发展过程:优化数据结构与索引—>文件缓存(IO)—> Memcached(当时最热门的的技术)
数据访问层(Data Access Layer)
3.分库分表 + 水平拆分 + Mysql集群
技术和业务在发展的同时,门槛会逐渐提高。
本质:数据库(读、写)
早些年MyISAM:表锁,十分影响效率!高并发下会出现严重锁问题。
后期Innodb:行锁
逐渐使用分库分表来解决写的压力。MySQL当时推出了表分区,并没有得到大范围使用。
MySQL的集群很好地满足了当时的需求。
4.当今时代
技术爆炸
图型数据库 JSON
MySQL存储博客、图片等较大的文件时。数据库表就会很大,效率就降低了。需要有一种新型数据库来专门处理这些数据。
基本互联网项目
为什么用NoSQL
用户的个人信息、社交网络、地理位置。用户自己产生的数据,用户日志等等爆发式增长!
NoSQL可以很好地处理以上问题。
NoSQL不仅仅是SQL(Not only SQL)
Map<String,Object> 使用键值对来控制。
NoSQL特点
解耦
- 方便扩展(数据之间没有关系,方便扩展)
- 大数据量高性能(Redis一秒写8万次,读取11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能比较高。)
- 数据类型是多样型的(不需要实现设计数据库!随取随用)
NoSQL
- 不仅仅是数据
- 没有固定的额查询语言
- 键值对存储,列存储,文档存储,图像数据库(社交关系)
- 最终一致性
- CAP定理和BASE(异地多活)
- 高性能、高可用、高扩展
NoSQL的四大分类
KV键值对
- 新浪:redis
- 阿里:redis + memcache
文档型数据库(bson和json一样)
- MongoDB
- MongoDB是一个基于分布式文件存储的数据库,c++编写,主要用于处理大量的文档
- MongoDB是一个介于关系型数据库和非关系型数据库中中间的产品! MongoDB 是非关系型数据库中功能最丰富、最像关系型数据库的。
- ConthDB
列存储数据库
- HBase
- 分布式文件系统
图关系数据库
Neo4J、InfoGrid
图结构 存储是各种关系,专注于构建关系图谱。
Redis入门
Redis是什么
Redis(Remote Dictionary Server) 远程字典服务。是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API
Redis 能干啥?
- 内存存储、持久化、内存中是断电即失、持久化很重要(rdb、aof)。
- 效率高,可用于高速缓存。
- 发布订阅系统
- 地图信息分析
- 计时器、计数器(浏览量)
- …
特性
- 多样的数据类型
- 持久化
- 集群
- 事务
官网:https://redis.io/docs/getting-started/
默认端口 6379
Windows使用方法:
- 先开启redis-server.exe
- 再打开redis-cli.exe
基础知识
一共有16个数据库,默认使用第0个数据库。
flushall //清除所有数据库
flushdb //清除当前数据库
keys * //查看全部数据库的内容
Redis是单线程
Redis是很快的,Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis性能瓶颈是根据机器的内存和网络带宽,可以使用单线程来实现。
Redis是将所有的数据放在内存中,使用单线程操作效率高,多线程(CPU会上下文切换:耗时),对于内存系统而言,没有上下文切换效率高。
Redis-Key
127.0.0.1:6379[3]> keys *
(empty list or set) //查看库中所有内容
127.0.0.1:6379[3]> set name kang //存入内容
OK
127.0.0.1:6379[3]> exists name //判断元素是否存在
(integer) 1
五大数据类型
String字符串
基础操作
127.0.0.1:6379[3]> set key1 v1 #设置值
OK
127.0.0.1:6379[3]> get key1
"v1"
127.0.0.1:6379[3]> keys *
1) "key1"
2) "name"
127.0.0.1:6379[3]> exists key1 #判断一个key是否存在
(integer) 1
127.0.0.1:6379[3]> append key1 "hello" #添加字符串 如果当前key不存在,相当于 set key
(integer) 7
127.0.0.1:6379[3]> get key1 #读取某一个key
"v1hello"
127.0.0.1:6379[3]> strlen key1 #获取一个key的长度
(integer) 7
自增操作
127.0.0.1:6379> set views 0 #设置浏览量
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views #浏览量自增1
(integer) 1
127.0.0.1:6379> 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> incrby views 10 #设置步长 指定增量10
(integer) 11
127.0.0.1:6379> decrby views 10 #设置步长 指定减量10
(integer) 1
字符串范围
127.0.0.1:6379> set key1 hello,worid
OK
127.0.0.1:6379> get key1 #读取字符串
"hello,worid"
127.0.0.1:6379> getrange key1 0 3 #截取字符串[0,3]
"hell"
127.0.0.1:6379> getrange key1 0 -1 #获取全部的字符串 和 get key是一样的
"hello,worid"
替换字符串
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> setrange key2 2 xx #从指定的位置开始替换
(integer) 7
127.0.0.1:6379> get key2
"abxxefg"
过期时间
#setnx SET if Not eXists
#1 如果key被设置了
#0 如果key没有被设置
redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
127.0.0.1:6379> setex key3 20 dd #20s后过期
OK
127.0.0.1:6379> ttl key3 返回key剩余的过期时间
(integer) 13
读写多个值
127.0.0.1:6379> mset key1 v1 key2 v2 key3 v3
OK
127.0.0.1:6379> mget key1 key2 key3
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx key1 key2 key3 key4
(integer) 0
#对应给定的keys到他们相应的values上。只要有一个key已经存在,MSETNX一个操作都不会执行
存入json格式
127.0.0.1:6379> set user:1 {name:kk,age:4} #设置一个user:1对象 值为json字符来保存一个对象
OK
127.0.0.1:6379> get user:1
"{name:kk,age:4}"
127.0.0.1:6379> mset user:1:name kkk user:1:age 2 #
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "kkk"
2) "2"
#getset
127.0.0.1:6379> getset key1 kk
"v1"
127.0.0.1:6379> get key1
"kk"
#自动将key对应到value并且返回原来key对应的value。如果key存在但是对应的value不是字符串,就返回错误。
String类似的使用场景:value除了是我们的字符串还可以是我们的数字
- 计数器
- 统计多单位的数量
- 粉丝数
List列表
Redis中的List其实是链表(Redis使用双端链表实现List)
可以通过List实现栈、队列、阻塞队列。
lpush
127.0.0.1:6379> lpush list 1 #将给定值推入到列表左端
(integer) 1
127.0.0.1:6379> lpush list 2
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "1"
127.0.0.1:6379> rpush list 4
(integer) 4
127.0.0.1:6379> lrange list 0 -1 #获取列表在给定范围上的所有值 从左边向右边读取
1) "3"
2) "2"
3) "1"
4) "4"
127.0.0.1:6379> rpop list #从列表的右端弹出一个值,并返回被弹出的值
"4"
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
3) "1"
lindex
通过索引获取列表元素
#通过索引获取列表中的元素。从0开始计算,也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
127.0.0.1:6379> lindex list 1
"2"
127.0.0.1:6379> llen list #返回列表的长度
(integer) 3
rpoprpush #移除列表的最后一个元素(从列表的右端弹出一个值),将它移动到新的列表中
127.0.0.1:6379> lrange list 0 -1 #将列表中指定下标的值替换为指定的值 不存在更新会报错
1) "3"
2) "2"
3) "1"
127.0.0.1:6379> lset list 1 dd
OK
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "dd"
3) "1"
linsert
在值后边插入
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "dd"
3) "1"
127.0.0.1:6379> linsert list before dd qq #在某一个值前面插入
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "qq"
3) "dd"
4) "1"
小结:
- 如果key不存在,创建新的链表
- 如果key存在,新增内容
- 移除了所有的值,空链表,也代表不存在
- 在两边插入或者改动值,效率最高!中间元素,效率相对低一点
Set集合
Redis的set是string类型的无序集合。集合成员是唯一的,集合中不含有重复数据。
Redis中的集合是通过哈希表实现的,添加、删除、查找的时间复杂度都是 O(1)。
sadd
127.0.0.1:6379> sadd myset "hello" 2
(integer) 2
127.0.0.1:6379> smembers myset
1) "hello"
2) "2"
scard
127.0.0.1:6379> scard myset #获取set集合中的元素个数
(integer) 2
srem
127.0.0.1:6379> srem myset 2 #移除set集合中的指定元素
(integer) 1
127.0.0.1:6379> smembers myset
1) "hello"
127.0.0.1:6379>
spop
随机删除一些set集合中的元素
127.0.0.1:6379> smembers myset
1) "ddd"
2) "dada"
3) "hello"
4) "world"
127.0.0.1:6379> spop myset
"world"
127.0.0.1:6379> spop myset
"hello"
127.0.0.1:6379> spop myset
"ddd"
数字集合类
- 交集 sinter 共同好友
- 差集 sdiff
- 并集 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 key2 a
(integer) 1
127.0.0.1:6379> sdiff key1 key2 #差集
1) "b"
127.0.0.1:6379> sunion key1 key2 #并集
1) "a"
2) "b"
127.0.0.1:6379> sinter key1 key2 #交集 共同好友
1) "a"
Hash(哈希)
Redis hash是一个String类型的field(字段)和value(值)的映射表,hash特别适合用于存储对象。
hset
127.0.0.1:6379> hset myhash field1 kang
(integer) 1
127.0.0.1:6379> hget myhash field1
"kang"
hmset
设置多个元素的 field value
127.0.0.1:6379> hmset myhash field2 dada field3 mam #set多个 key-value
OK
127.0.0.1:6379> hmget myhash field1 field2 field3
1) "kang"
2) "dada"
3) "mam"
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "kang"
3) "field2"
4) "dada"
5) "field3"
6) "mam"
hdel
删除 field 后,value 也会被删掉
127.0.0.1:6379> hdel myhash field1
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "dada"
3) "field3"
4) "mam"
127.0.0.1:6379> hlen myhash #获取hash表的字段数量!
(integer) 2
127.0.0.1:6379> hkeys myhash #只获取 field 字段
1) "field2"
2) "field3"
127.0.0.1:6379> hvals myhash #只获取 value 值
1) "dada"
2) "mam"
Zset有序集合
Redis有序集合和集合一样都是String类型元素的集合,不允许重复元素的出现。不同的是每个元素都会关联一个
double类型的分数。redis通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,double类型的分数可以重复。集合通过哈希表实现,删除、查找、添加的复杂度为 O(1)。
zadd
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"
排序的实现
zrangebyscore xxx 负无穷 正无穷
127.0.0.1:6379> zadd salary 2500 hong
127.0.0.1:6379> zadd salary 3000 kang
127.0.0.1:6379> zadd salary 300 wu
127.0.0.1:6379> zrangebyscore salary -inf +inf #显示全部用户 从小到大
1) "wu"
2) "hong"
3) "kang"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores #显示全部用户并附带成绩
1) "wu"
2) "300"
3) "hong"
4) "2500"
5) "kang"
6) "3000"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores limit 1 2
1) "hong"
2) "2500"
3) "kang"
4) "3000"
127.0.0.1:6379> zrangebyscore salary -inf 2500 withscores #显示工资小于2500的员工的升序排序
1) "wu"
2) "300"
3) "hong"
4) "2500"
zrem
127.0.0.1:6379> zrem salary kang #移除指定的元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "wu"
2) "hong"
zcard
127.0.0.1:6379> zcard myset #返回有序集的元素个数
(integer) 3
zrevrange
返回有序集key中,指定区间内的成员。其中成员的位置按score值递减(从大到小)来排列
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores
1) "wu"
2) "300"
3) "hong"
4) "2500"
127.0.0.1:6379> zrevrange salary 0 -1
1) "hong"
2) "wu"
127.0.0.1:6379> zrevrange salary 0 -1 withscores
1) "hong"
2) "2500"
3) "wu"
4) "300"
zcount
返回指定分数范围内的元素个数
127.0.0.1:6379> zcount salary -inf +inf
(integer) 3
三种特殊的数据类型
geospatial 地理位置
朋友的定位,附近的人,打车距离计算?
geoadd
# getadd 添加地理位置
# 规则:两级无法直接添加,一般下载城市数据,通过java程序导入。
# 有效的经度从-180度到180度。
# 有效的纬度从-85.05112878度到85.05112878度。
127.0.0.1:6379> geoadd china:city 116.41005 39.9049 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.472644 31.231706 shanghai
(integer) 1
geopos
获取当前的定位,一定是一个坐标
127.0.0.1:6379> geopos china:city beijing # 获取指定城市的经度和纬度
1) 1) "116.410051882267"
2) "39.904899707671923"
127.0.0.1:6379> geodist china:city beijing shanghai km #获取两个经纬度的距离
"1067.4049"
georadius
类似附近的人功能 (获取附近所有的人的地址,定位)通过半径来查询
GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" # 添加经纬度坐标
(integer) 2
127.0.0.1:6379> GEORADIUS Sicily 15 37 200 km WITHDIST WITHCOORD # 西西里岛200km范围内的坐标 withdist 距离 withcoord 坐标 coordinate (坐标)
1) 1) "Palermo"
2) "190.4424"
3) 1) "13.361389338970184"
2) "38.115556395496299"
2) 1) "Catania"
2) "56.4413"
3) 1) "15.087267458438873"
2) "37.50266842333162"
georadiusbymember
#指定成员的位置被用作查询的中心。
hyperloglog
什么是基数
基数(不重复的元素)
A {1,3,5,7,8,7}
B{1,3,5,7,8}
B是A的基数集
简介
redis2.8.9版本更新了Hyperloglog,是用来做基数统计的算法
应用场景:一个人访问一个网站多次,还是算作一个人,类似于公众号阅读量
但是需要一定的容错率
测试使用
127.0.0.1:6379> pfadd mykey a b c d e f g # 创建第一组元素
(integer) 1
127.0.0.1:6379> pfadd mykey2 i j z b n m # 创建第二组元素
(integer) 1
127.0.0.1:6379> pfcount mykey
(integer) 7
127.0.0.1:6379> pfcount mykey2
(integer) 6
127.0.0.1:6379> pfmerge mykey3 mykey mykey2 # 合并两组
OK
127.0.0.1:6379> pfcount mykey3
(integer) 12
bitmaps 位存储
位存储
统计用户信息,活跃,不活跃。登录,未登录。
Bitmaps,数据结构。都是操作二进制位来记录,只有0和1两个状态。
# 可以用0 1 代替是否打开 然后查看某天是否打卡
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 0
(integer) 0
127.0.0.1:6379> getbit sign 1
(integer) 0
127.0.0.1:6379> bitcount sign # 查看某个区间数量 可实现判断是否全勤
(integer) 1
事务
Redis事务本质:一组命令的集合。一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行。
一次性、顺序性、排他性!执行一些列的命令。
Redis事务没有隔离级别的概念!
所有的命令在事务中并没有直接被执行!只有发起执行命令的时候才会执行。
Redis单条命令是保存原子性的,但是事务不保证原子性。
Redis的事务:
- 开启事务(multi)
- 命令入队
- 执行事务(exec)
正常执行事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) "v2"
4) OK
放弃事务
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> discard # 取消事务
OK
127.0.0.1:6379> get k2 # 事务队列中命令都不会被执行
(nil)
编译型异常(代码有问题,命令有错)事务中所有的命令都不会被执行。
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set 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)
运行时异常(1/0),如果事务队列中存在语法性,执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常。
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1 # 代码结构没有问题 只不过执行时失败
QUEUED
127.0.0.1:6379> set k2 v2
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) "v2"
监控 watch
悲观锁:
- 很悲观,认为什么时候都会出问题,无论做什么都加锁。
乐观锁
- 很乐观,认为什么时候都不会出问题,所以不上锁。更新数据的时候去判断一下,在此期间是否有人修改过这个数据。
- 获取version
- 更新的时候比较version
Redis测监视测试
正常执行成功
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money # 监视money
OK
127.0.0.1:6379> multi # 事务正常结束,数据期间没有发生变动,这个时候就正常执行成功。
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
如果多线程修改值,使用watch可以当做redis的乐观锁操作!
执行之前如果另一个线程修改了值,事务就会失败。
watch的功能:
如果在 WATCH 执行之后, 事务开始之前, 有其他客户端修改了被监视的 key 的值, 那么当前客户端的事务就会失败。如果 unwatch 取消监视,事务可以正常执行成功。
Jedis
Jedis是使用java操作 redis 的中间件。
- 导入对应的依赖
<!--导入jedis包-->
<dependencies>
<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.79</version>
</dependency>
- 编码测试
public class TestPing {
public static void main(String[] args) {
//1.new Jedis 对象
Jedis jedis = new Jedis("127.0.0.1", 6379);
//2.jedis 所有的命令和redis相同
System.out.println(jedis.ping());
}
}
jedis执行事务
public class TestPing {
public static void main(String[] args) {
//1.new Jedis 对象
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello", "world");
jsonObject.put("name", "kang");
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
try {
multi.set("user1", result);
multi.set("user2", result);
int i = 1 / 0; //代码抛出异常事务,执行失败
multi.exec(); //执行事务
} catch (Exception e) {
multi.discard();
e.printStackTrace();
}finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close();
}
}
}
java.lang.ArithmeticException: / by zero
at com.kang.TestPing.main(TestPing.java:25)
null
null
Springboot整合
lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况。可以减少线程数据,更像NIO模式
整合测试
源码分析
RedisAutoConfiguration
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//默认的 RedisTemplate 没有过多的设置,redis 对象都是需要序列化
//两个泛型是 Object 的类型,后期需要使用强制转化<String, Object>
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean //string 是redis中最常使用的类型,单独提出来了一个bean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
- 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 配置连接
spring.redis.host=127.0.0.1
server.port=6379
- 测试
@Test
void contextLoads() {
//redisTemplate
//opsForValue() 操作字符串 类似String
//opsForList() 操作List 类似List
redisTemplate.opsForValue().set("mykey","kangkang");
System.out.println(redisTemplate.opsForValue().get("mykey"));
}
序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程
Redis序列化配置的固定模板
@Configuration
public class RedisConfig {
//编写自己的 redisTemplate
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
//序列化配置
Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectJackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//String的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
//hash的key采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//value序列化方式采用jackson
template.setValueSerializer(objectJackson2JsonRedisSerializer);
//hash的value序列化采用jackson
template.setHashValueSerializer(objectJackson2JsonRedisSerializer);
return template;
}
}
Redis持久化
Redis是个基于内存的数据库。服务器宕机,内存中的数据将会全部丢失,所以Redis需要实现持久化功能。
RDB(Redis DataBase)
什么是RDB
RDB中文名为快照,RDB持久化是把当前进程数据生成快照保存到磁盘上的过程,由于是某一时刻的快照,快照中的值要早于或等于内存中的值。
触发机制
- save的规则满足的情况下,会自动触发rdb规则。
- 执行 flushall 命令,也会触发我们的rdb规则!
- 退出 redis,也会产生rdb文件。
优点:
- 适合大规模的数据恢复。
- 对数据的完整性要求不高。
缺点:
- 需要一定的时间间隔进行操作!如果redis宕机了,最后一次修改的数据就不存在了。
- fork进程的时候,会占用一定的内容空间。
有时候在生产环境中会备份。
AOF(Append Only File)
Redis是“写后”日志,Redis先执行命令,把数据写入内存,然后才记录日志。日志里记录的是Redis收到的每一条命令,这些命令以文本形式保存。PS:大多数数据库采用的是写前日志(WAL),例如MySQL,通过写前日志和两阶段提交,实现数据和逻辑的一致性。
将所有的命令都记录下来
保存的文件是 appendonly.aof文件
优点:
- 每一次修改都同步,文件的完整会更好。 appendfsync always
- 每秒同步一次,可能会丢失一秒的数据。 appendfsync everysec
- 从不同步,效率最高。 appendfsync no
缺点:
- 相对于数据文件来说,aof远远大于rdb,修复速度也比rdb慢。
- aof运行效率也比rdb慢,redis默认的配置就是rdb持久化。
Redis发布订阅
Redis发布订阅(pub/sub)是一种消息通信模式:发送者发送消息,订阅者接收消息。
Redis客户端可以订阅任意数量的频道。
127.0.0.1:6379> subscribe kang # 订阅频道 接收消息
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "kang"
3) (integer) 1
1) "message"
2) "kang"
3) "666"
127.0.0.1:6379> publish kang 666 # 向指定频道发送消息
(integer) 1
Redis主从复制
主从复制,读写分离!80%的情况都是在进行读操作!减缓服务器的压力!
默认情况下,每台redis服务器都是主节点。
127.0.0.1:6379> info replication # 查看当前库的信息
# Replication
role:master # 角色 master
connected_slaves:0 # 没有从机
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
复制配置文件,修改对应信息
- 端口
- pid名字
- log文件名字
- dump.rdb 名字
主机可以写,从机不能写,只能读。
主机断开连接,从机依旧连接到主机的,但是没有写操作,如果主机回来了,从机依旧可以直接获取到主机写的信息!
如果使用命令行来配置的主从,这个时候如果重启了,从机就会变成主机!但是变为从机时,立刻就可以从主机中获取值。
哨兵模式
Redis Sentinel,即Redis哨兵。哨兵的核心功能就是主节点的自动故障转移。谋朝篡位的自动版,自动称为主节点。
Redis缓存穿透和雪崩
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求。(查不到)
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。(量太大,缓存过期)
参考视频:https://www.bilibili.com/video/BV1S54y1R7SB?spm_id_from=333.999.0.0