NoSql入门和概述
入门概述
1.互联网时代背景下大机遇, 为什么用nosql
单机MySQL的美好年代
Memcached(缓存)+MYSQL+垂直拆分
MySQL主从读写分离
写的操作放在主库, 读的操作放在从库
分表分库+水平拆分+mysql集群
MySQL的扩展性瓶颈
今天是什么样子
为什么用NoSQL
2.是什么
3.能干嘛
易扩展
大数据量高性能
redis: 一秒读11万写8万
多样灵活的数据模型
传统RDBMS VS NOSQL
4.去哪下
5.怎么玩
KV: 键值对
Cache: 缓存
Persistemce: 持久化
3V + 3高
大数据时代的3V
海量Volume
多样Variety
实时Velocity
互联网需求的3高
高可扩(横向扩展, 加服务器进行扩展)
高并发
高性能(高可用,避免单点故障等)
当下的NoSQL经典应用
当下的应用是sql和nosql一起使用
NoSQL数据模型简介
以一个电商客户, 订单, 订购, 地址模型来对比下关系型数据库和非关系型数据库
关系型ER图
NOSQL(MongoDB)的BSON模型
聚合模型
-
KV键值对
-
Bson(mongodb中类似于json的串)
-
列族
-
图形
NoSQL数据库的四大分类
KV键值对: 典型介绍
- 新浪: BerkeleyDB + redis
- 美团: redis + tair
- 阿里, 百度: memcache + redis
文档型数据库(bson格式比较多):典型介绍
CouchDB
MongoDB
列存储数据库
Cassandra, HBase
分布式文件系统
图关系数据库
他不是放图形的, 放的是关系, 比如: 朋友圈社交网络, 广告推荐系统
社交网络, 推荐系统等. 专注于构建关系图谱
Neo4J,InfoGrid
四者对比
分布式数据库中CAP原理CAP+BASE
传统的ACID分别是什么
A(Atomicity)原子性
C(Consistency)一致性
I(Isolation)独立性
D(Durability)持久性
CAP
C: Consistency(强一致性)
A: Availability(可用性)
P: Partition tolerance(分区容错性)
CAP的3进2
CAP理论就是说在分布式存储系统中, 最多只能实现上面的两点,
而由于当前的网络硬件肯定会出现延迟丢包等问题, 所以分区容忍性是我们必须要实现的
所以我们只能在一致性和可用性之间进行权衡, 没有NoSQL系统能同时保证这三点
C: 强一致性 A: 高可用性 P:分布式容忍性
CA: 传统Oracle数据库
AP: 大多数网站架构的选择
CP: Redis MongoDB
注意: 分布式架构的时候必须做出取舍
一致性和可用性的抉择
对于web2.0网站来说, 关系数据库的很多主要特征却往往无用武之地
数据库事务一致性需求
很多web实时系统不要求严格的数据库事务, 对读一致性的要求很低, 有些场合对写一致性要求并不高, 允许实现最终一致性
数据库的写实时性和读实时性需求
对关系数据库来说, 插入一条数据之后立刻查询, 是肯定可以读出来这条数据的, 但是对于很多web应用来说, 并不要求这么高的实时性, 比如说发一条消息之后, 过几秒乃至十几秒后, 我的订阅者才看到这条动态是完全可以接受的
对复杂的SQL查询, 特别是多表关联查询的需求
任何大数据量的web系统, 都非常忌讳多个大表的关联查询, 以及复杂的数据分析类型的报表查询, 特别是SNS类型的网站, 从需求以及产品设计角度, 就避免了这种情况的发生. 往往更多的只是单表的主键查询, 以及单表的简单条件分页查询, SQL的功能被极大的弱化了
经典CAP图
CAP理论的核心是: 一个分布式系统不可能同事很好的满足一致性, 可用性和分区容错性这三个需求, 最多只能同时较好的满足两个.
因此, 根据CAP原理将NoSQL数据库分成了满足CA原则, 满足CP原则和满足AP原则三类
CA: 单点集群, 满足一致性, 可用性的系统, 通常在可扩展性上不太强大
CP: 满足一致性, 分区容忍性的系统, 通常性能不是特别高
AP: 满足可用性, 分区容忍性的系统, 通常可能对一致性要求低一些
Base是什么
分布式+集群简介
Redis入门介绍
入门概述
1.是什么
2.能干嘛
3.去哪儿下
4.怎么玩
Redis是单线程的, 为什么单线程还这么快
Redis的安装
Windows版安装
重要提示
Linux版安装
redis安装参考这篇博客
https://www.cnblogs.com/heqiuyong/p/10463334.html
需要在linux环境中安装好gccyum install gcc-c++
make命令时故障解析
再cd /opt/redis5.0.5下把redis.conf拷贝到/myredis/目录下(需要先建立一下)
再vim编辑这个复制的文件
把daemonize设置成yes 表示以后台进程方式运行
再cd /usr/local/bin(redis的默认安装目录)
启动redis-server redis-server /myredis/redis.conf
指定配置文件启动redisserver
再启动redis客户端连接redisserverredis-cli -p 6379
在redis-cli中终止redis-server服务SHUTDOWN
退出redis-cli exit
Redis启动后杂项基础知识讲解
对单进程的说明
Redis数据类型
Redis的五大数据类型
那里去获得redis常见数据类型操作命令
Redis键(key)
案例
keys *
查询所有keyexists key的名字
判断某个key是否存在move key db
剪切, 当前库就没有了, 被移动到另外的库了expire key 秒数
为给定的key设置过期时间ttl key
查看还有多少秒过期, -1表示永不过期, -2表示已过期type key
查看你的key是什么类型del key
删除keyrandomkey
随机返回key空间中的一个key
设置过期时间常用于保存cookie session以及单点登录
Redis字符串(String) 单值单value
常用
案例
-
set/get/append/strlen
set k1 v1
设置值
get k1
获取值
append k1 12345
追加,如果k1不存在, 相当于新建 返回的是追加以后的字符串长度
strlen k1
获取k1对应的值的长度 -
Incr/decr/incrby/decrby
一定要是数字才能进行加减,
incr k2
k2加1
decr k2
k2减1
incrby k2 3
k2加3
decrby k2 2
k2减2 -
getrange/setrange
截取/替换字符串getrange key start end
注意 start和end都是闭区间的
-
setex(set with expire) 键秒值/setnx(set if not exist)
常用于分布式锁
setex k4 10 v4
设置k4=v4 并设置10秒过期setnx k1 v11
如果k1不存在则设置成v11 如果存在, 不会覆盖其值 -
mset/mget/msetnx
mset k1 v1 k2 v2 k3 v3
一次性设置多个键值对mget k1 k2 k3
一次性取多个值
msetnx k1 v1 k2 v2 k11 v11
一次性设置多个值, 不存在才设置, 但是只要有一个存在, 则全部不生效了,保证原子性操作, 要么一起成功, 要么一起失败
-
getset(先get再set)
常见使用场景
Redis列表(List)单值多value
常用
案例, 在redis里面, 可以把list玩成栈, 队列, 阻塞队列
lpush/rpush/lrange
lpush list01 1 2 3 4 5
从左边依次给list01入栈5个value 现在集合从做到右的顺序是5 4 3 2 1
lrange list01 0 -1
从左边获取list01全部的value 显示的数据是
rpush list02 1 2 3 4 5
从右边压栈12345 现在list02从左到右的顺序是 12345 再lrange 0 -1 一下出现的是12345
lpop/rpop
lpop list01
去掉左数第一个value
rpop list02
去掉右数第一个lindex
按照索引下标获得元素(从上到下即从左到右)llen
llen list01
获取长度lrem key 删N个value
lrem list03 2 3
从左边删除list03 2个value=3的数据ltrim key 开始index 结束index
截取指定范围的值后再赋值给key,闭区间
ltrim list01 0 4
截取list01的0到4一共5个value 重新赋值给list01rpoplpush 源列表 目的列表
rpoplpush list01 list02
从list01的最右边出栈, 并入栈到list02的最左边lset key index value
lset list01 1 5
向list01中左数第二个数据重新赋值为5, lset一个不存在的list会报错linsert key before/after 值1 值2
linsert list01 before 5 java
向list01的数据5之前插入java字符串 after即为向后插exists list的key
判断该list是否存在
性能总结
小结与常见使用场景
Redis集合(Set) 单值多value,set中的值不能重复
常用
案例
sadd/smembers/sismember
sadd set01 1 1 2 2 3 3
set里面不能有重复数据, 所以sadd重复数据只会添加一次
smembers set01
查看set01的全部内容
sismember set01 1
查询1是否在set01集合中 返回1证明存在, 0代表不存在scard
获取集合里面的元素个数
scard set01
获取set01元素个数srem key value
删除集合中元素
srem set01 3
删除集合中的元素3srandmember key 某个整数(随机出几个集合中的value)
srandmember set01 3
让set01中随机出3个元素并返回spop key 随机出栈
spop set01
从set01中随机出栈一个元素, 元素在set中删除smove key1 key2 在key1里某个值
作用是将key1里的某个值赋给key2
smove set01 set02 5
把set01中的5移动到set02中
数学集合类
差集 sdiff
交集 sinter 可以实现查看共同好友
并集 sunion(兼顾联合去重功能)
常用场景
Redis哈希(Hash)KV模式不变, 但V是一个键值对
常用
案例
hset/hget/hmset/hmget/hgetall/hdel
hset user id 11
设置key为user 值为一个键值对 键值对的key为id 值为11
hget user id
获取user的id的值
hmset customer id 11 name li4 age 26
把value的kv一起set进去
hmget customer id name age
一口气读取customer的多个kv的v
hgetall customer
读取customer的全部kv
hdel user name
把user下的name对应的kv删除hlen
hlen user
获取user的kv的个数hexists key 在key里面的某个值的key
hexists customer id
判断该key对应的kv中的key是否存在, 1代表存在, 0代表不存在hkeys/hvals
hkeys customer
输出customer的所有kv的key
hvals customer
输出customer的所有kv的valuehincrby/hincrbyfloat
hincrby customer age 2
给customer的age的value加2 如果想要减的话, 要加上一个负数,因为没有hdecrby这个命令
hincybyfloat customer score 0.5
对小数可以加上一个小数hsetnx
hsetnx customer age 26
如果customer下的age不存在就创建, 存在的话就不做任何操作
常见使用场景
Redis有序集合Zset(sorted set)
常用
案例
zadd/zrange
zadd zset01 60 v1 70 v2 80 v3 90 v4 100 v5
新建zset key 分数 值 分数 值…
zrange zset 0 -1
查询全部的值
zrange zset01 0 -1 withscores
查询全部的值带scorezrangebyscore key 开始score 结束score
zrangebyscore zset01 60 90
查询zset01 60到90分之间(包括60和90)的值
zrangebyscore zset01 -inf +inf
inf代表无穷, 这个命令是zset01中的数据从小到大排序
zrevrangebyscore zset01 +inf -inf
从大到小排序
zrangebyscore zset01 60 (90
包括60但不包括90查找zset01的值
zrangebyscore zset01 60 90 limit 2 2
如果查找出来的数据太多, 可以再次进行截取 从查询出的所有数据中的下标为2(即第三个)开始, 截取两个 即截取了下标为2和3的两个数据zrem key 某score下对应的value值
作用是删除元素
zrem zset01 v5
删除zset01中的v5zcard/zcount key score区间/zrank key values值 作用是获得下标值/zscore key 对应分
zcard zset01
获取个数,注意分数和value是一体的
zcount zset01 60 80
统计60分到80分的个数
zrank zset01 v4
获取该元素的下标
zscore zset01 v4
获得v4元素对应的分数zrevrank key values值
作用是逆序获得下标值
zrevrank zset01 v4
v4对应的下标从左向右数应该是3 但是此命令是返回逆序的下标, 所以返回0zrevrange
zrevrange zset01 0 -1
倒序输出所有值zrevangebyscore key 结束score 开始score
zrevrangebyscore zset01 90 60
获取小于等于90 大于等于60的值做倒序输出
常用场景
三种特殊数据类型
geospatial 地理位置
朋友的定位, 附近的人, 打车距离计算?
Redis的Geo在Redis3.2版本推出! 这个功能可以推算地理位置的信息, 两地之间的距离, 方圆几里的人!
可以查询一些测试数据:查询城市经纬度
只有6个命令
- GEOADD
- GEODIST
- GEOHASH
- GEOPOS
- GEORADIUS
- GEORADIUSBYMEMBER
geoadd
# geoadd key 经度 纬度 城市名
# 规则: 两极(南北极)无法直接添加, 我们一般会下载城市数据, 直接通过java程序一次性导入
# 参数 key 值(经度, 纬度, 名称)
> geoadd china:city 116.40 39.90 beijing
> geoadd china:city 121.47 31.23 shanghai
# 可以一次性添加多个
# geoadd china:city 121.47 31.23 shanghai 116.40 39.90 beijing
geopos 获得当前定位: 一定是一个坐标值
# 获取指定城市的经度和纬度
GEOPOS china:city beijing 获取北京的经度纬度
GEOPOS china:city beijing shanghai 获取多个
GEODIST 获取两个城市之间的距离
> GEODIST china:city beijing shanghai # 查看北京到上海的直线距离
# 获取单位为km的距离
> GEODIST china:city beijing shanghai km
georadius 以给定的经纬度为中心, 找出某一半径内的元素
我附近的人? (获取所有附近的人的地址, 定位!)通过半径来查询范围
# georadius key 经度 纬度 半径 单位 以给定的经度和纬度为中心, 给的半径找到key中存的元素
GEORADIUS china:city 110 30 1000 km
# 找到元素并显示到中心点的直线距离
GEORADIUS china:city 110 30 1000 km withdist
# 找到元素并显示经纬度
GEORADIUS china:city 110 30 1000 km withcoord
# 设置查询的个数
GEORADIUS china:city 110 30 500 km withdist withcoord count 1
GEORADIUSBYMEMBER 根据给定的元素确定中心点, 再根据半径进行查询, 如果两个字符串越接近, 那么距离越近
GEORADIUSBYMEMBER china:city beijing 1000 km
GEOHASH 该命令将返回11个字符的GeoHash字符串,把经纬度通过算法转换成字符串
Geo底层的实现原理其实就是Zset! 我们可以使用Zset命令来操作geo
Hyperloglog
什么是基数? 集合中不重复元素的个数
A{1,3,5,7,8,7} B{1,3,5,7,8}
A集合中有两个7 所以基数是5 B集合的基数也是5
Redis2.8.9版本中就更新了Hyperloglog数据结构
RedisHyperloglog基数统计的算法!
优点: 占用的内存是固定的, 2^64不同的元素的基数, 只需要费12kb内存! 如果要从内存角度来比较的话, Hyperloglog首选 0.81%错误率! 统计UV任务是可以忽略不计的
网页的UV(一个人访问一个网站多次, 但是还是算作一个人的访问量)
传统的方式, set保存用户的id, 然后就可以统计set中的元素数量作为标准判断!
这个方式如果保存大量的用户id , 就会比较麻烦, 我们的目的是为了计数, 而不是保存用户id
Bitmap
位存储 0101
统计用户信息, 活跃, 不活跃! 登录, 未登录! 打卡,365打卡! 这些两种状态的, 都可以使用Bitmap位图数据结构
都是操作二进制位来进行记录, 就只有个0和1两个状态!
解析配置文件redis.conf
它在哪儿
默认在/opt/redisxxx下面 备份一份, 然后拷贝到别的地方
Units单位
INCLUDES包含
NETWORK 网络
bind 127.0.0.1 默认只能通过本机进行连接, 可以设置为bind *
或者直接注释掉 可以通过外部进行访问
protected-mode yes 开启保护模式, 一般会配置为yes 但是我配置成yes后使用Jedis进行访问会报错, 所以这里我写成no
port 6379 访问的端口号
GENERAL通用
-
daemonize 出厂时为no 表示redisserver是否以后台方式启动, 设置为true时表示后台启动
-
pidfile 进程pid文件路径, 如果以后台方式运行, 就需要指定一个pid文件
-
port 端口号
-
tcp-backlog 默认511
-
timeout 默认0 客户端空闲多少秒后会被关闭 0代表不会关闭
-
bind 绑定ip 默认为本机 搭建集群时会用到
-
tcp-keeplive 每隔多少秒检测一下连接是否断开
-
loglevel 日志级别 默认notice redis有4个日志级别 debug> verbose>notice>warning
-
logfile 默认为空串 日志文件位置, 可以设置为"/opt/redis/logs/6379.log", 也可以不设置走默认配置
-
syslog-enabled 系统日志是否启用 默认为关闭
-
syslog-ident 系统日志以什么开头 默认为redis
-
syslog-facility
-
databases redis存在的数据库 默认16个
SNAPSHOTTING快照(执行rdb持久化操作会用到, 执行了多少次操作会触发持久化配置)
Save
save 秒数 写操作次数 配置rdb触发方式, 多少秒内写了多少个key 才触发rdb持久化
save的禁用
如果有一个数据特别重要, 想要告诉redisserver立即备份, 不要等着触发条件了, 直接使用save指令
Stop-writes-on-bgsave-error 持久化如果出错, 是否要继续工作
写入某一条数据到rdb文件失败后, 是否继续, 默认是yes表示继续
rdbcompression 是否压缩rdb文件, 需要消耗一些cpu资源
rdbchecksum 保存rdb时是否进行错误校验
dbfilename
生成rdb文件的名字, 默认为dump.rdb
dir
生成rdb文件的位置 默认为./ 默认为redis安装位置
REPLICATION复制
SECURITY安全
可以在配置文件中指定访问密码, 不需要通过命令指定
访问密码的查看, 设置和取消
通过redis-cli连接上redisserver后 使用
config get requirepass
获取访问密码
config get dir
查看当前启动redis-server的路径, 很大可能为配置文件路径, 因为修改完配置文件就要启动redis
config set requirepass "123456"
设置redis连接密码
这时要输入密码
auth 123456
再输入其他命令
CLIENTS
maxclients 10000 最大客户端连接数
Maxmemory最大内存使用
Maxmemory-policy 缓存过期策略 默认noeviction 永不过期
LRU算法, 最近最少使用的key
LIMITS限制
Maxclients
最大客户端连接数默认10000
Maxmemory
最大内存使用
Maxmemory-policy
缓存过期策略
默认noeviction 永不过期
Maxmemory-samples
默认选取5个样本
APPEND ONLY MODE 追加
appendonly 开启aof持久化
appendfilename 保存aof的文件名
Appendfsync
默认是everysec
No-appendfsync-on-rewrite
重写时是否可以运用Appendfsync, 用默认no即可, 保证数据安全性
Auto-aof-rewrite-min-size设置重写的基准值
Auto-aof-rewrite-percentage 设置重写的基准值
常见配置redis.conf介绍
Redis的持久化
RDB(Redis DataBase)
是什么
恢复时是从dump.rdb文件直接读取数据, 大规模数据恢复时, 对数据精度要求不高的时候, 建议使用
Fork
Fork的作用是复制一个与当前进程一样的进程. 新进程的所有数据(变量, 环境变量, 程序计数器等)数值都和原进程一致, 但是是一个全新的进程, 并作为原进程的子进程
Rdb保存的是dump.rdb文件
最好把备份的rdb文件复制到备用linux服务器上. 以免主服务器宕机
配置位置
详细设置见上边
如何触发RDB快照
-
配置文件中默认的快照配置
-
命令save或者是bgsave
-
执行flushall命令,也会产生dumo.rdb文件, 但里面是空的, 无意义
-
在redis-cli执行SHUTDOWN操作关闭RedisServer时, 会立即触发生成RDB文件,并覆盖之前的
同样kill 和 kill -9 强制关闭redis-server时也会触发
如何恢复
将备份文件(dump.rdb)移动到redis安装目录并启动服务即可
可以使用redis-cli客户端输入config get dir获取目录
优势
适合大规模的数据恢复
对数据完整性和一致性要求不高
劣势
在一定时间间隔做一次备份, 所以如果redis意外down掉的话, 就会丢失最后一次快照后的所有修改
Fork的时候, 内存中的数据被克隆了一份, 大致两倍的膨胀性需要考虑
如何停止RDB备份
动态所有停止RDB保存规则的方法: redis-cli 中config set save ""
小总结
AOF(Append Only File)
是什么
以日志的形式来记录每个写操作, 将redis执行过的所有写指令记录下来(读操作不记录), 只许追加文件但不可以改写文件, redis启动之初会读取该文件重新构建数据, 换言之, redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
AOF保存的是appendonly.aof文件
配置位置
见上面配置文件,首先需要把appendonly设置成yes(现在默认是no) 来开启aof持久化
AOF启动/修复/恢复
启动, 设置配置文件appendonly 改成yes
正常恢复: 将有数据的aof文件复制一份保存到对应目录下, 查看目录用config get dir
恢复: 重启redis后自动重新加载aof文件
异常恢复:
启动配置文件: 设置appendonly为yes
修复: 执行redis-check-aof --fix appendonly.aof
恢复: 重启redis然后自动重新加载
Rewrite
是什么
重写原理
触发机制
优势
每修改同步: appendfsync always 同步持久化 每次发生数据变更会被立即记录到磁盘, 性能较差但数据完整性比较高
每秒同步: appendfsync everysec 异步操作, 每秒记录 如果一秒内宕机, 有数据丢失
不同步: appendfsync no 从不同步
劣势
相同数据集的数据而言aof文件要远大于rdb文件, 恢复速度慢于rdb
aof运行效率要慢于rdb, 每秒同步策略效率较好, 不同步效率和rdb相同
小总结
总结(Which One)
Redis的事务
是什么
可以一次执行多个命令, 本质是一组命令的队列, 一个事务中的所有命令都会序列化(串行化), 按顺序地串行化执行而不会被其他命令插入, 不许加塞, Redis单条命令保存是原子性的, 但是事务不保证原子性且Redis事务没有隔离级别的概念 Redis的命令在事务的队列中, 并没有直接被执行! 只有发起执行命令(Exec)后才会被执行
能干嘛
事务的本质相当于一个命令队列
一个队列中, 一次性, 顺序性, 排他性的执行一系列命令
怎么玩
开启事务(multi)
命令入队(set k1 v1 …多条命令)
执行事务(exec)
锁: Redis 可以实现乐观锁 watch命令
常用命令
Case1: 正常放行
Case2: 放弃事务
Case3: 全体连坐, 命令还没加入到queue就报错,会全体连坐, 类似于编译报错
Case4: 冤头债主,命令已经成功加入到队列中, 执行时报错, 会冤头债主, 类似于运行报错
Case5: watch监控
悲观锁/乐观锁/CAS(Check And Set)
悲观锁
认为一定会出事儿, 一定会有人改
乐观锁
认为不一定会出事儿, 每条记录后面加一个version版本号, 每个人修改时, 修改之前要读一次, 修改过后要提交的时候, 还要读一次, 如果两次读取的版本号不一致, 则不能提交, 要把最新的数据读出来以后再这个基础上进行修改后, 才能再次提交(也要进行version比较,成功后才能提交)
CAS check and set
比较并设置, 先比较是否相同, 相同了再进行设置
初始化信用卡可用余额和欠额
余额-20 欠额要+20
无加塞篡改, 先监控再开启multi, 保证两笔金额变动在同一个事物内
有加塞篡改
unwatch, 取消对所有key的监视
如果监控到的变量被改变了, 先unwatch解锁后 再重新watch加锁, 直到事务被正常执行为止
一旦执行了exec之前加的监控锁都会被取消掉了
小结
3阶段
3特性
Redis的发布订阅
是什么
进程间的一种消息通信模式, 发送者(pub)发送消息, 订阅者(sub)接受消息
订阅/发布消息图
命令
案例
Redis的复制(Master/Slave)
是什么
也就是我们所说的主从复制, 主机数据更新以后根据配置和策略, 自动同步到备机的master/slaver机制, Master以写为主, Slave以读为主
能干嘛
读写分离
容灾恢复
怎么玩
1.配从(库)不配主(库)
2.从库配置: slaveof 主库ip 主库端口
启用redis-cli 后 slaveof 主库ip 主库端口, 但是每次与master断开以后, 都需要重新连接, 除非你配置进redis.conf文件
连接上redis-cli后使用info replication查看主从复制状态
3.修改配置文件细节操作
拷贝多个redis.conf文件
开启daemonize yes
Pid文件名字
指定端口
log文件名字
dump.rdb名字
分别修改3个conf文件对应的daemonize, pidname, logname, dump.rdb为不同的, 在一台虚拟机上模拟3个redis-server
其余两台机器修改方式同上, 但是要加以区分
4.常用3招
一主二仆(一台主机, 两台备机)
Init
一个master 两个slave
日志查看
在linux中tail -100f 6379.log
主从问题演示
薪火相传
反客为主
首先恢复成一主二仆配置, 主 down的情况下, 在从机上执行slaveof no one
使当前数据库停止与其他数据库的同步, 转成主数据库
复制原理
哨兵模式(sentinel)
简单一句话, 反客为主的自动版
是什么
反客为主的自动版, 能够后台监控主机是否故障, 如果故障了根据投票数自动将从库转换为主库
怎么玩
- 调整结构, 6379带着80, 81
- 自定义的 /myredis目录下新建sentinel.conf文件, 名字绝不能错
- 配置哨兵, 添写内容
vim sentinel.conf
- sentinel monitor 被监控redis-server名字(自己起名字) 127.0.0.1 6379 1
- 上面最后一个数字1, 表示主机挂掉后slave投票看让谁接替成为新主机, 得票数多少后成为主机
- 启动哨兵
- redis-sentinel /myredis/sentinel.conf
- 上述目录依照各自的实际情况配置, 可能目录不同
- 正常主从演示
- 原有的master挂了
- 投票新选(哨兵工作,主库切换)
- 重新主从继续开工, info replication查看
- 问题: 如果之前的master重启回来, 会不会双master冲突 不会, 它会被设置成slave
一组sentinel能同时监控多个master
复制的缺点
Redis的Java客户端Jedis
是什么?
Jedis是Redis官方推荐的java连接开发工具, 在使用java操作Redis的中间件,如果要使用java操作redis, 那么一定要对Jedis十分的熟悉
Jedis测试联通
- 导入依赖
<dependencies>
<!--导入Jedis的jar包-->
<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>
- 编码测试
- 连接数据库
public class TestPing {
public static void main(String[] args) {
// 1. new Jedis对象即可
Jedis jedis = new Jedis("192.168.0.5",6379);
// jedis所有的命令就是我们之前学习的所有指令
System.out.println(jedis.ping());
}
}
出现问题1: Connection refused
ip地址值正确, 端口正确, 访问连接异常, 原因是在redis的conf文件中指定了bind 127.0.0.1了, 注释这一行即可
出现问题2: DENIED Redis is running in protected mode because protected mode is enabled
因为redis的conf文件中设置了protected-mode yes开启受保护模式, 把他设置成no即可
- 操作命令
- 断开连接
jedis.close();
Jedis常用API
Key
public class TestPing {
public static void main(String[] args) {
// 1. new Jedis对象即可
Jedis jedis = new Jedis("192.168.0.5",6379);
System.out.println("清空数据: "+ jedis.flushDB());
System.out.println("判断某个键是否存在: "+ jedis.exists("username"));
System.out.println("新增String类型的k-v username-j1的键值对" + jedis.set("username","j1"));
System.out.println("新增String类型的k-v password-hh的键值对" + jedis.set("password","hh"));
System.out.println("获取所有的key");
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.randomKey());
System.out.println("重命名key" + jedis.rename("username","name"));
System.out.println("取出改名后的name对应的值" + jedis.get("name"));
System.out.println("根据索引选择数据库" + jedis.select(0));
System.out.println("删除当前选择库中的所有key" + jedis.flushDB());
System.out.println("返回当前库中key的数目" + jedis.dbSize());
System.out.println("删除所有数据库中的所有key" + jedis.flushAll());
}
}
String
public class TestPing {
public static void main(String[] args) {
// 1. new Jedis对象即可
Jedis jedis = new Jedis("192.168.0.5", 6379);
System.out.println("========新增数据========");
System.out.println(jedis.set("key1","value1"));
System.out.println(jedis.set("key2","value2"));
System.out.println(jedis.set("key3","value3"));
System.out.println("删除键key2 "+ jedis.del("key2"));
System.out.println("获取键key2" + jedis.get("key2"));
System.out.println("修改key1的值" + jedis.set("key1","value1Changed"));
System.out.println("获取key1的值" + jedis.get("key1"));
//append返回追加后生成字符串的长度
System.out.println("在key3后面加入值" + jedis.append("key3","End"));
System.out.println("获取key3的值" + jedis.get("key3"));
System.out.println("增加多个键值对" + jedis.mset("key01","value01","key02","value02","key03","value03"));
System.out.println("获取多个键值对" + jedis.mget("key01","key02","key03"));
System.out.println("获取多个键值对" + jedis.mget("key01","key02","key03","key04"));
System.out.println("删除多个键值对" + jedis.mget("key01","key02"));
System.out.println("获取多个键值对" + jedis.mget("key01","key02","key03"));
jedis.flushDB();
System.out.println("========新增键值对防止覆盖原先值========");
System.out.println("========不存在再创建setnx===================");
System.out.println(jedis.setnx("key1","value1"));
System.out.println(jedis.setnx("key2","value2"));
System.out.println(jedis.setnx("key2","value2-new"));
System.out.println(jedis.get("key1"));
System.out.println(jedis.get("key2"));
System.out.println("========新增键值对并设置有效时间========");
System.out.println(jedis.setex("key3",2,"value3"));
System.out.println(jedis.get("key3"));
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(jedis.get("key3"));
System.out.println("==========获取原值, 更新为新值==========");
System.out.println(jedis.getSet("key2","key2GetSet"));
System.out.println(jedis.get("key2"));
System.out.println("获得key2的值的子串" + jedis.getrange("key2",2,4));
}
}
List
Set
Hash
Jedis事务
正常执行事务
// 1. new Jedis对象即可
Jedis jedis = new Jedis("192.168.0.5", 6379);
JSONObject jsonObject = new JSONObject();
jsonObject.put("j1","handsome");
jsonObject.put("hh","beautiful");
String str = jsonObject.toJSONString();
//可以在这里加一个监控
jedis.watch("name1");
//开启事务
Transaction multi = jedis.multi();
try {
multi.set("name1",str);
multi.set("name2",str);
//执行事务
multi.exec();
} catch (Exception e) {
//放弃事务
multi.discard();
e.printStackTrace();
} finally {
//查询一下是否set进去了
System.out.println(jedis.get("name1"));
System.out.println(jedis.get("name2"));
//关闭连接
jedis.close();
}
Jedis主从复制
JedisPool
SpringBoot集成Redis
- 导入依赖
- 配置连接
- 测试
springboot使用redisTemplate
说明: 在SpringBoot2.x之后, 原来使用的jedis被替换成了lettuce
jedis: 采用的是直连, 多个线程操作的话, 是不安全的, 如果想要避免不安全的, 使用jedis pool连接池,像是BIO模式
lettuce: 采用netty, 实例可以在多个线程中进行共享, 不存在线程不安全的情况, 可以减少线程数据了, 更像NIO模式
pom文件
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<dependencies>
<!--导入Jedis的jar包-->
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
源码分析
@ConditionalOnMissingBean(name = “redisTemplate”)代表如果自定义了redisTemplate类的话, 就不用下面的类了
默认的RedisTemplate 没有过多的设置redis对象都是需要序列化!
两个反省都是Object, Object的类型, 我们后续使用需要强制转换
我们可以自己定义一个redisTemplate来替换这个默认的
由于string 类型是redis中最常使用的类型, 所以单独提出了一个Bean叫stringRedisTemplate
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
//默认的RedisTemplate 没有过多的设置redis对象都是需要序列化!
//两个反省都是Object, Object的类型, 我们后续使用需要强制转换<String, Object>
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
yml
spring:
redis:
host: 192.168.0.5
port: 6379
# 配置连接池需要配置lettuce连接池
测试
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestPing {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void test1(){
//redisTemplate都是opsForxxx例如opsForValue就是操作k-v类型的,
//opsForSet就是操作Set类型
//除了基本的操作, 我们常用的方法都可以直接redisTemplate.出来, 比如事务和基本的CRUD
redisTemplate.opsForValue().set("myKey","杨俊毅");
System.out.println(redisTemplate.opsForValue().get("myKey"));
//获取连接, 可以清空库
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();
}
}
RedisTemplate的序列化(自定义RedisTemplate)
不添加序列化的错误
在PoJo类中一般都会实现序列化, 加入implements Serializable后 再用redisTemplate直接set对象再取就不会报错了
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class 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;
}
}
RedisTemplate工具类RedisUtil
package com.kuang.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@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;
}
}
}
Redis各个数据类型的使用
sortedSet做延时任务
https://www.cnblogs.com/catcher1994/p/12496319.html
Redis的过期策略以及内存淘汰机制
Redis的渐进式ReHash
渐进式ReHash相关文章
https://blog.csdn.net/belalds/article/details/93713491