文章目录
NoSQL概述
背景:
- 单机MySQL时期,一般的网站访问量不大。网站瓶颈:
- 数据量太大,一个机器放不下
- 数据索引(B+Tree),一个机器内存放不下
- 访问量(读写混合),一个机器承受不了
- Memcached(缓存)+ MySQL + 垂直拆分(读写分离)
- 发展过程:优化数据结构呵索引 -> 文件缓存IO -> Memcached
- 分库分表 + 水平拆分 + MySQL集群
- NoSQL数据库
什么是NoSQL
Not Only SQL 泛指非关系型数据库。传统关系型数据库已经无法应对web2.0时代。尤其是超大规模的高并发社区。
特点:解耦
- 方便扩展(数据之间没有关系)
- 大数据量高性能(1秒写8万次,读11万次。缓存是记录级的,是一种细粒度的缓存,性能更高)
- 数据类型多样性(无需事件设计数据库)
- 传统的RDBMS 和 NoSQL
传统的RDBMS关系型数据库
- 结构化组织
- SQL
- 数据和关系都存放在单独的表中
- 数据操作,数据定义语音
- 严格的一致性
- 基本的实务操作
NoSQL
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库
- 最终一致性
- CAP定理 和 BASE理论(异地多活)
- 高性能,高可用,高可扩
了解:大数据时代的3V(问题) + 3高(要求):
大数据时代的3V:主要是描述问题的
- 海量Volume
- 多样Variety
- 实时Velocity
大数据时代的3高:主要是对程序的要求
- 高并发
- 高可扩(随时水平拆分)
- 高性能(保证用户体验)
NoSQL四大分类
K:V键值对: Redis + Tair / Memecache
文档型数据库(bson格式(类似JSON)): MongoDB(MongoDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量文档。是一个介于关系型数据库和非关系型数据库中间的产品)。ConthDB。
列存储数据库: HBase,分布式文件系统
图形关系数据库: 不是存储图形,而是存储关系。eg:朋友圈社交网络。Neo4j。 InfoGrid。
Redis入门
概述
Redis是什么?
Redis(Remote Dictionary Server ),即远程字典服务。C语言,支持网络,基于内存,可持久化的日志型的key-value数据库。能够周期性的把数据写入磁盘,并实现了主从同步。
Redis应用场景:
- 内存存储、持久化
- 效率高,可用于告诉缓存
- 发布订阅系统
- 地图信息分析
- 计时器、计数器(浏览量)
特性:
- 多样的数据类型
- 持久化
- 集群
- 事务
Linux安装Redis
# 安装gcc依赖
`[root@localhost local]# yum install -y gcc `
# 下载并解压安装包
`[root@localhost local]# wget http://download.redis.io/releases/redis-5.0.3.tar.gz`
`[root@localhost local]# tar -zxvf redis-5.0.3.tar.gz`
# cd切换到redis解压目录下,执行编译
`[root@localhost local]# cd redis-5.0.3`
`[root@localhost redis-5.0.3]# make`
## 安装并指定安装目录
[root@localhost redis-5.0.3]# make install PREFIX=/usr/local/redis
# 启动服务
## 前台启动
`[root@localhost redis-5.0.3]# cd /usr/local/redis/bin/`
`[root@localhost bin]# ./redis-server`
## 后台启动
从 redis 的源码目录中复制 redis.conf 到 redis 的安装目录
[root@localhost bin]# cp /usr/local/redis-5.0.3/redis.conf /usr/local/redis/bin/
后台启动
[root@localhost bin]# ./redis-server redis.conf
引用: Centos7安装Redis
测试性能
redis-benchmark 是一个官方的压力测试工具。
# 测试:100个并发,100000个请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
基础知识
# 切换数据库
127.0.0.1:6379> select 3
OK
# 查看当前数据库大小
127.0.0.1:6379[3]> dbsize
(integer) 0
# 设值
127.0.0.1:6379[3]> set name huathy
OK
# 取值
127.0.0.1:6379[3]> get name
"huathy"
127.0.0.1:6379[3]> dbsize
(integer) 1
# 查看数据库所有的key
127.0.0.1:6379[3]> keys *
1) "name"
# 清空当前数据库
127.0.0.1:6379[3]> flushdb
OK
# 清空所有数据库
127.0.0.1:6379[3]> flushall
OK
Redis是单线程的!
Redis基于内存操作,是很快的。CPU不是redis的性能瓶颈。Redis的性能瓶颈受机器内存以及带宽的影响。
Redis为什么单线程还是那么快?
Redis的所有数据放在内存中,由于多线程上下文切换耗时,故单线程操作效率最高。
Redis五大基本数据类型
Redis-Key
127.0.0.1:6379> set name huathy
OK
127.0.0.1:6379> set age 22
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"
# 判断key是否存在
127.0.0.1:6379> exists name
(integer) 1
127.0.0.1:6379> exists name1
(integer) 0
# 移除key
127.0.0.1:6379> move name 1
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> set name huathy
OK
# 设置过期时间
127.0.0.1:6379> expire name 15
(integer) 1
# 查询当前key的剩余时间
127.0.0.1:6379> ttl name
(integer) 11
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)
# 查看当前key的类型
127.0.0.1:6379> type name
string
String(字符串)
# 设置值
127.0.0.1:6379> set k1 v1
OK
# 获取值
127.0.0.1:6379> get k1
"v1"
# 获得所有的key
127.0.0.1:6379> keys *
1) "k1"
# 判断key是否存在
127.0.0.1:6379> exists k1
(integer) 1
# 追加字符串,如果key不存在,则相当于set key
127.0.0.1:6379> append k1 hello
(integer) 7
127.0.0.1:6379> get k1
"v1hello"
# 获取字符串长度
127.0.0.1:6379> strlen k1
(integer) 7
127.0.0.1:6379> append k2 xixi
(integer) 4
127.0.0.1:6379> keys *
1) "k2"
2) "k1"
自增自减
127.0.0.1:6379> set views 0
OK
# 自增1
127.0.0.1:6379> incr views
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
# 自减1
127.0.0.1:6379> decr views
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
# 自增步长
127.0.0.1:6379> incrby views 10
(integer) 10
# 自减步长
127.0.0.1:6379> decrby views 5
(integer) 5
127.0.0.1:6379> get views
"5"
字符串范围range,替换
127.0.0.1:6379> set k1 "hello,huathy"
OK
127.0.0.1:6379> get k1
"hello,huathy"
# 截取字符串[0,3]
127.0.0.1:6379> getrange k1 0 4
"hello"
# 获取全部字符串,和get key相同
127.0.0.1:6379> getrange k1 0 -1
"hello,huathy"
127.0.0.1:6379> set k2 abcdefg
OK
# 替换指定位置开始的字符串
127.0.0.1:6379> setrange k2 1 xx
(integer) 7
127.0.0.1:6379> get k2
"axxdefg"
# setex(set with expire) #设置过期时间
# setnx(set if not exist) # 不存在再设置
#设置k3的值并指定30秒过期
127.0.0.1:6379> setex k3 30 hello
OK
127.0.0.1:6379> ttl k3
(integer) 26
127.0.0.1:6379> get k3
"hello"
# 如果k4不存在,则创建k4
127.0.0.1:6379> setnx k4 redis
(integer) 1
127.0.0.1:6379> keys *
1) "k2"
2) "k1"
3) "k4"
# 如果k4存在,则创建失败
127.0.0.1:6379> setnx k4 "mongoDB"
(integer) 0
127.0.0.1:6379> get k4
"redis"
批量设值,取值
# 批量设值
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"
# 如果不存在,则批量设置。mset是一个原子性的操作。
127.0.0.1:6379> msetnx k1 v1 k4 v4
(integer) 0
127.0.0.1:6379> get k4
(nil)
对象
set user:1 {name:xixi,age:3} #设hi一个user:1对象,值为json格式的字符串来保存一个对象。
127.0.0.1:6379> mset user:1:name xixi user:1:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "xixi"
2) "2"
getset
# 如果不存在,则返回null。并设置值
127.0.0.1:6379> getset db redis
(nil)
# 如果存在,则先取值,再设值
127.0.0.1:6379> getset db mongodb
"redis"
127.0.0.1:6379> get db
"mongodb"
String类型的使用场景:
value除了是字符串还可以是数字。
- 计数器
- 统计多单位的数量。
List
在redis中,List可以实现栈,队列,阻塞队列。
所有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
# 获取list中的值
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
# 通过区间获取list中的值
127.0.0.1:6379> lrange list 0 1
1) "three"
2) "two"
# 将一个或多个值,插入到列表尾部(右)
127.0.0.1:6379> Rpush list Rpush1
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "Rpush1"
############################################################
# 移除列表第一个元素
127.0.0.1:6379> lpop list
"three"
# 移除列表最后一个元素
127.0.0.1:6379> rpop list
"Rpush1"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
# 通过索引下标来获取list中的某一个值。
127.0.0.1:6379> lindex list 0
"two"
############################################################
# 获取列表的长度
127.0.0.1:6379> llen list
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lpush list three
(integer) 4
# 移除list集合中指定个数的value
127.0.0.1:6379> lrem list 1 one
(integer) 1
127.0.0.1:6379> lrem list 2 three
(integer) 2
############################################################
# trim截断截取
127.0.0.1:6379> rpush mylist 0
(integer) 1
127.0.0.1:6379> rpush mylist 2
(integer) 2
127.0.0.1:6379> rpush mylist 3
(integer) 3
127.0.0.1:6379> rpush mylist 4
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "0"
2) "2"
3) "3"
4) "4"
# 通过下标截取指定长度,不在范围内的元素全部删除。
127.0.0.1:6379> ltrim mylist 2 3
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "3"
2) "4"
############################################################
#移除列表最后一个元素,并将他移动到一个新的list
127.0.0.1:6379> rpoplpush mylist otherlist
"4"
127.0.0.1:6379> lrange mylist 0 -1 # 查看原来的list
1) "2"
2) "1"
3) "3"
127.0.0.1:6379> lrange otherlist 0 -1 #查看新的list
1) "4"
###########################################################
# lset:将列表中指定下标的值,替换为另一个值。更新操作
127.0.0.1:6379> exists list # 判断这个list是否存在
(integer) 0
127.0.0.1:6379> lset list 0 item # 不存在则报错
(error) ERR no such key
127.0.0.1:6379> lpush list value1
(integer) 1
127.0.0.1:6379> lset list 0 item # 如果存在,则更新当前下标的值
OK
127.0.0.1:6379> lrange list 0 -1 # 如果不存在,则报错
1) "item"
127.0.0.1:6379> lset list 1 other
(error) ERR index out of range
###########################################################
# linsert 将某个具体的value插入到列表中某个元素的前面/后面
127.0.0.1:6379> rpush mylist hello world
(integer) 2
127.0.0.1:6379> linsert mylist before word xixi
(integer) -1
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "world"
127.0.0.1:6379> linsert mylist before world xixi
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "xixi"
3) "world"
127.0.0.1:6379> linsert mylist after xixi haha
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "xixi"
3) "haha"
4) "world"
小结:
list实际上是一个链表,before Node after,left ,right都可以插入值。
如果key不存在,则创建新的链表。如果key存在,新增内容。如果移除了所有值,空链表也表示不存在。在两边插入或者改动值,效率最高。中间的元素改动相对较慢。
消息队列:Lpush,Rpop;栈:Lpush,Lpop
Set(集合)
set中的值是不能重复读。
# set集合中添加一个或多个元素
127.0.0.1:6379> sadd myset xixi huathy haha
(integer) 3
# 查看指定set的所有值
127.0.0.1:6379> smembers myset
1) "haha"
2) "huathy"
3) "xixi"
# 判断元素是否存在
127.0.0.1:6379> sismember myset xixi
(integer) 1
127.0.0.1:6379> sismember myset hello
(integer) 0
127.0.0.1:6379> scard myset
(integer) 3
# 移除set中的指定元素
127.0.0.1:6379> srem myset haha
(integer) 1
127.0.0.1:6379> scard myset
(integer) 2
###########################################################
# 随机抽选一个元素
127.0.0.1:6379> srandmember myset
1) "hello"
# 随机抽选指定个数的元素
127.0.0.1:6379> srandmember myset 2
1) "haha"
2) "xixi"
# 随机删除一些set集合中的元素
127.0.0.1:6379> spop myset
"hello"
127.0.0.1:6379> smembers myset
1) "huathy"
2) "world"
3) "xixi"
4) "haha"
###########################################################
# 将一个指定的值,移动到另外一个set集合中
127.0.0.1:6379> sadd myset world huathy xixi haha
(integer) 4
127.0.0.1:6379> smove myset myset2 world # 将一个指定的值,移动到另外一个set集合中
(integer) 1
127.0.0.1:6379> smembers myset
1) "haha"
2) "xixi"
3) "huathy"
127.0.0.1:6379> smembers myset2
1) "world"
###########################################################
微博,B站:共同关注。(并集)
数字集合类:
- 差集:SDIFF
- 交集:SINTER
- 并集:SUNION
127.0.0.1:6379> sadd k1 a b f e
(integer) 4
127.0.0.1:6379> sadd k2 b c d e
(integer) 4
127.0.0.1:6379> sdiff k1 k2 #差集
1) "a"
2) "f"
127.0.0.1:6379> sinter k1 k2 #交集
1) "e"
2) "b"
127.0.0.1:6379> sunion k1 k2 #并集
1) "f"
2) "a"
3) "b"
4) "c"
5) "e"
6) "d"
Hash(哈希)
类比Map集合,key-map!这里的值是一个map集合。本质和String类型没有太大的区别,还是一个key-value。
127.0.0.1:6379> hset myhash f1 v1 #set一个具体的key-value
(integer) 1
127.0.0.1:6379> hget myhash f1 #获取一个字段的值
"v1"
127.0.0.1:6379> hmset myhash f1 hello f2 world #set多个具体的key-value
OK
127.0.0.1:6379> hmget myhash f1 f2 #获取多个字段的值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash #获取全部的数据
1) "f1" #key
2) "hello" #value
3) "f2"
4) "world"
127.0.0.1:6379> hdel myhash f1 #删除hash指定的key字段。
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "f2"
2) "world"
###########################################################
# 获取hash表的字段数量
127.0.0.1:6379> hmset myhash f1 hello f2 world
OK
127.0.0.1:6379> hlen myhash
(integer) 2
###########################################################
# 判断hash中指定的key是否存在
127.0.0.1:6379> hexists myhash f1
(integer) 1
127.0.0.1:6379> hexists myhash f3
(integer) 0
###########################################################
# 只获取所有的field
127.0.0.1:6379> hkeys myhash
1) "f2"
2) "f1"
# 只获取所有的value
127.0.0.1:6379> hvals myhash
1) "world"
2) "hello"
###########################################################
127.0.0.1:6379> hset myhash f3 5 #指定增量
(integer) 1
127.0.0.1:6379> hincrby myhash f3 1 #自增1
(integer) 6
127.0.0.1:6379> hincrby myhash f3 -1 #自增-1
(integer) 5
127.0.0.1:6379> hsetnx myhash f4 hello #如果不存在则可以新增
(integer) 1
127.0.0.1:6379> hsetnx myhash f4 world # 如果不存在则不能新增
(integer) 0
Hash变更的数据,user name age。做用户信息的保存,经常变更的数据。更适合存储对象。
127.0.0.1:6379> hmset user:1 name xixi age 22
OK
127.0.0.1:6379> hgetall user:1
1) "name"
2) "xixi"
3) "age"
4) "22"
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 xixi 5000 haha 500 huathy #添加三个记录
(integer) 3
127.0.0.1:6379> zrangebyscore salary -inf +inf #按照从小到大排序
1) "huathy"
2) "xixi"
3) "haha"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores #按照从小到大排序,并且附带值
1) "huathy"
2) "500"
3) "xixi"
4) "2500"
5) "haha"
6) "5000"
127.0.0.1:6379> zrevrange salary 0 -1 #按照从大到小排序
1) "xixi"
2) "huathy"
###########################################################
# 移除元素
127.0.0.1:6379> zrange salary 0 -1
1) "huathy"
2) "xixi"
3) "haha"
127.0.0.1:6379> zrem salary haha
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "huathy"
2) "xixi"
###########################################################
# 获取集合中的个数
127.0.0.1:6379> zcard salary
(integer) 2
###########################################################
# 获取指定区间的成员数量
127.0.0.1:6379> zadd myset 1 hello 2 world 3 huathy
(integer) 3
127.0.0.1:6379> zcount myset 1 3 # 获取指定区间的成员数量
(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2
应用场景:set排序,可存储班级成绩表,工资表排序。带权重的普通消息,重要消息。排行榜应用。
三种特殊的数据类型
geospatital 地理位置
朋友圈定位,附近的人,打车距离计算
Redis的Geo可以推算地理位置信息,两地间的距离,方圆几里的人。
GEOADD:
GEODIST:
GEOHASH:
GEOPOS:
GEORADIUS:
GEORADIUSBYMEMBER:
# geoAdd添加地理位置
# 规则:两极是无法直接添加的,一般的下载城市数据,通过java导入
# 参数:key value(经度 维度 名称)
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 113.27 23.16 guangzhou
(integer) 2
127.0.0.1:6379> geoadd china:city 114.05 22.50 shenzhen 120.16 30.24 hangzhou
(integer) 2
###################################################################
# geoPos 获取指定城市的地理位置
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.39999896287918"
2) "39.900000091670925"
127.0.0.1:6379> geopos china:city shenzhen guangzhou
1) 1) "114.04999762773514"
2) "22.500001138003192"
2) 1) "113.27000051736832"
2) "23.159999437635349"
###################################################################
# geoDist 获取两地之间的直线距离
单位:m 米;km 千米;mi 英里;ft 英尺
127.0.0.1:6379> geodist china:city beijing shanghai
"1067378.7564"
127.0.0.1:6379> geodist china:city beijing shanghai km #以km为单位
"1067.3788"
127.0.0.1:6379> geodist china:city hangzhou shenzhen km
"1053.9526"
###################################################################
# 附近的人(获取所有附近的人的定位,通过半径查询)
# geoRadios 以给定的经纬度为中心,找出某一半径内的元素
127.0.0.1:6379> georadius china:city 110 30 1000 km
1) "shenzhen"
2) "guangzhou"
3) "hangzhou"
127.0.0.1:6379> georadius china:city 110 30 1000 km withdist #显示到中心位置的距离
1) 1) "shenzhen"
2) "926.6568"
2) 1) "guangzhou"
2) "827.2683"
3) 1) "hangzhou"
2) "977.5143"
127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord #显示到中心位置的位置信息
1) 1) "shenzhen"
2) 1) "114.04999762773514"
2) "22.500001138003192"
2) 1) "guangzhou"
2) 1) "113.27000051736832"
2) "23.159999437635349"
3) 1) "hangzhou"
2) 1) "120.16000002622604"
2) "30.240000322949022"
127.0.0.1:6379> georadius china:city 110 30 1000 km withdist count 1 #筛选出一条记录
1) 1) "guangzhou"
2) "827.2683"
###################################################################
# geoRadios:找出位于指定元素周围的其他元素
127.0.0.1:6379> geoRadiusByMember china:city shanghai 1000 km
1) "hangzhou"
2) "shanghai"
###################################################################
# geoHash:返回11个字符的geoHash字符串。将二维的经纬度,转为一维的字符串。若两个字符串越像,则距离越近。
127.0.0.1:6379> geoHash china:city shanghai hangzhou
1) "wtw3sj5zbj0"
2) "wtmkn31bfb0"
GEO底层的实现原理是zSet。可以使用zSet命令来操作GEO。
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> zrem china:city beijing
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "shenzhen"
2) "guangzhou"
3) "hangzhou"
4) "shanghai"
HyperLogLog
基数(不重复的元素)A{1,3,5} B{1,2,4} 基数=
网页的UV(用户浏览量)(一个人访问多次,还是算作一次)。传统的方式,set保存用户id,然后统计set元素数。如果保存大量用户id,浪费空间。目的:计数。
HyperLogLog:占用内存固定。有一定的错误率。
127.0.0.1:6379> pfadd mykey a b c d e f # 添加元素
(integer) 1
127.0.0.1:6379> pfcount mykey # 统计元素数量
(integer) 6
127.0.0.1:6379> pfadd mykey2 c d e f j h i
(integer) 1
127.0.0.1:6379> pfcount mykey2
(integer) 7
127.0.0.1:6379> pfmerge mykey3 mykey mykey2 #合并两组元素
OK
127.0.0.1:6379> pfcount mykey3
(integer) 9
BitMaps
位存储:统计用户信息(活跃-不活跃;登录-未登录)两种状态的情况。
# 周一到周五的打卡状态信息
127.0.0.1:6379> setbit sign 0 0
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
#######################################################
127.0.0.1:6379> getbit sign 3 #查看周四是否打卡
(integer) 1
#######################################################
127.0.0.1:6379> bitcount sign # 统计打卡记录天数
(integer) 3
Redis事务
MySQL:ACID。原子性。
区别:
Redis的单条命令保证原子性的,但事务不保证原子性。一个事务中的所有的命令都会被序列化,在事务执行的过程中,会按照顺序执行。
Redis事务没有隔离级别的概念,所有的命令在事务中,并没有直接执行,只有发起执行命令的时候才会执行。
一次性,顺序性,排他性。
Redis的事务:
- 开启事务(multi)
- 命令入队
- 执行事务(exec)
- 放弃事务(discard)
# 开启事务
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 k4 v4
QUEUED
127.0.0.1:6379> discard # 放弃事务
OK
127.0.0.1:6379> get k4
(nil)
异常
编译型异常(命令错误)。事务中所有命令都不执行。
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 k2 #错误命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec #执行命令报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1
(nil)
运行时异常(1/0),错误命令抛出异常,其他命令正常执行。
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 "v1"
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"
监控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 money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
#############################
# 线程2修改数据
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> set money 1000
OK
############################
127.0.0.1:6379> exec
(nil)
######################################################
# 解决方法
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 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec
1) (integer) 990
2) (integer) 30
Jedis
- 什么是Jedis:Jedis是官方推荐的Java连接开发工具。
- 编码测试:连接数据库 - 操作命令 - 断开连接
说明:在SpringBoot 2.X之后,原来使用的jedis被替换为了lettuce?
jedis:采用的直连,多线程操作不安全。为避免不安全可采用jedis pool连接池。
Lettuce:采用netty,实例可以在多个线程中国共享,不存在线程不安全的情况。可以减少线程数量。
SpringBoot整合Redis
源码分析:
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class) //可以自定义一个RedisTemplate来替换
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//默认的RedisTemplate没有太多的配置。redis对象都是需要序列化的。
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
- 导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
- 配置连接
spring: ###### Redis配置 ##### redis: port: 6379 url: 127.0.0.1
- 测试
package com.hx.redis;
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.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class RedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
/**
* api和指令是相同的
* opsForValue() 操作字符串,类似String。
* opsForList() 操作List
* opsForSet() 操作set
*/
// redisTemplate.opsForValue().set("","");
//
// //除了基本操作,常用的可以使用redisTemplate方法直接操作。如事务和基本的CRUD
// redisTemplate.multi();
// redisTemplate.exec();
//
// //获取redis的连接对象
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();
redisTemplate.opsForValue().set("name","xixi");
redisTemplate.opsForValue().set("姓名","嘻嘻");
System.out.println(redisTemplate.opsForValue().get("name"));
System.out.println(redisTemplate.opsForValue().get("姓名"));
}
}
序列化配置。
默认的序列化方式,采用JDK序列化。可能会有转义。采用JSON序列化。
```java
// org.springframework.data.redis.core.RedisTemplate
@Nullable
private RedisSerializer keySerializer = null;
@Nullable
private RedisSerializer valueSerializer = null;
@Nullable
private RedisSerializer hashKeySerializer = null;
@Nullable
private RedisSerializer hashValueSerializer = null;
if (this.defaultSerializer == null) {
this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}
redis.conf 配置文件
-
units are case insensitive so 1GB 1Gb 1gB are all the same.
配置文件对大小写不敏感。 -
可以引入配置文件。
-
NETWORK 网络
bind 127.0.0.1 #绑定的IP protected-mode yes #开启保护模式 port 6379 #端口号
-
GENERAL 通用配置
#以守护进程方式运行。默认no不开启,需要手动开启为yes deamonize yes
#如果以后台方式运行,则需要指定一个pid文件
pidfile /var/run/redis_6379.pid
日志配置
# 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 ""
# 数据库数量配置,默认16
databases 16
- SNAPSHOTTING 快照配置
持久化,在规定时间内,执行了多少次操作,则会持久化到一个文件(.rdb .aof)。
redis是内存数据库,如果没有持久化,断电则会丢失。
# 如果在X秒内有Y个key发生修改,则进行持久化操作。
save 900 1
save 300 10
save 60 10000
# 持久化出错,是否继续工作
stop-writes-on-bgsave-error yes
# 是否雅俗RDB文件,需要消耗CPU的资源
rdbcompression yes
# 保存rdb文件时,进行错误校验
rdbchecksum yes
# rdb文件的保存目录
dir ./
- REPLICATION 复制,主从复制
- SECURITY 安全配置
8. 0.0.1:6379> config set requirepass 123456 #设置redis密码
OK
9. 0.0.1:6379> config get requirepass #尝试获取redis密码
(error) NOAUTH Authentication required. #没有权限
10. 0.0.1:6379> auth 123456 #验证密码
OK
11. 0.0.1:6379> config get requirepass #获取redis密码
12. "requirepass"
13. "123456"
```
```Properties
requirepass 123456
```
14. CLIENTS 客户端限制
```properties
# 最大客户端连接数量限制
# maxclients 10000
```
15. MEMORY MANAGEMENT 内存管理配置
```properties
# redis最大内存容量
# maxmemory <bytes>
# 内存达到上限的处理策略
# maxmemory-policy noeviction
```
# noeviction: 不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信息。(默认值)
# allkeys-lru: 所有key通用; 优先删除最近最少使用(less recently used ,LRU) 的 key。
# volatile-lru: 只限于设置了 expire 的部分; 优先删除最近最少使用(less recently used ,LRU) 的 key。
# allkeys-random: 所有key通用; 随机删除一部分 key。
# volatile-random: 只限于设置了 expire 的部分; 随机删除一部分 key。
# volatile-ttl: 只限于设置了 expire 的部分; 优先删除剩余时间(time to live,TTL) 短的key。
# https://www.cnblogs.com/yueerya/p/11507898.html
- APPEND ONLY MODE 模式 AOF配置
# 默认不开启AOF模式,默认采用RDB持久化。在大多数情况下,RDB已经够用。
appendonly no
# 持久化的文件名
appendfilename "appendonly.aof"
# 同步策略
# appendfsync always #每次修改都会同步
appendfsync everysec #每秒同步一次,但可能丢失这一秒的数据
# appendfsync no #不同步,这时操作系统自己同步数据,速度最快。
Redis持久化
Redis是内存数据库,如果不将内存中的数据保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。故Redis提供了持久化功能。
RDB(Redis DataBase)
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork(创建)一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。整个过程中,主进程不进行任何IO操作。确保极高的性能。若需要进行大规模的恢复,且数据完整性不敏感,则RDB较AOF更高效。RDB缺点:最后一次的持久化数据可能丢失。
RDB保存的文件是dbfilename dump.rdb
触发机制
-
save的规则满足的情况下,自动触发rdb规则。
-
执行了flushAll,会触发rdb规则。
-
退出redis,也会产生rdb文件。
备份会自动生成一个dump.rdb文件。如何恢复rdb文件
是需要将rdb文件放在redis的指定目录下即可。redis启动时会自动检查dump.rdb文件,并恢复其中的数据。# 查看备份文件指定的存放路径
-
0.0.1:6379> config get dir
-
“dir”
-
“/usr/local/bin” #如果该目录下存在dump.rdb文件,启动就会自动恢复其中的数据
优点:
-
适合大规模的数据恢复
-
对数据的完整性要求不高
缺点: -
需要一定的时间间隔进行操作。如果redis意外宕机,那么最后一次修改的数据就没有记录。
-
fork进程的时候,会占用一定的内存空间。
AOF(Append Only File)
将所有的命令记录下来,history,恢复时再将所有的命令全部执行一遍.