Redis
什么是NoSQL
关系型数据库:表格,行,列
很多的数据类型的个人信息,社交网络,地理位置。这些数据类型的存储不需要一个固定的格式。不需要多余的操作就可以横向扩展。 (Map)使用键值对来控制。
NoSQL特点
- 方便扩展(数据之间没有关系,很好扩展)
- 大数据量高性能(Redis一秒写8万次,读取11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能很高)
- 数据类型是多样的
- 不需要事先设计数据库,随去随用
传统RDBMS和NoSQL的区别
传统的 RDBMS
- 结构化组织
- SQL
- 数据和关系都存在单独表中
- 严格的一致性
- 事务
NoSQL
- 不仅仅是数据
- 没有固定的查询语言
- 有很多存储方式(键值对,列存储,文档存储,图形数据库)
- 最终一致性
- CAP定理和BASE定理(异地多活)
- 高性能,高可用,高可扩
NoSQL的四大分类
KV键值对
1. 新浪:Redis
2. 美团:Redis + Tair
3. 阿里、百度:Redis + memecache
文档数据类型
1. MongoDB
MongoDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档
MongoDB是一个介于关系型数据库和非关系型数据库中间的产品。MongoDB是非关系型数据库中功能最丰富,最像关系型数据库的。
2. ConthDB
列存储数据库
1. HBase
2. 分布式文件系统
图形关系数据库
他不是存图形,存的是关系,比如:朋友圈社交网络,广告推荐
Redis入门
概述
Redis是什么?
Redis(Remote Dictionary Server),即远程字典服务。
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
免费和开源!是当下最热门的NoSQL技术之一。
redis能干吗?
1. 内存存储、持久化,内存中是断电即失的、所以说持久化很重要(rdb、aof)
2. 效率高,可以用于高速缓存
3. 发布订阅系统
4. 地图信息分析
5. 计时器、计数器(浏览量)
特性
- 多样的数据类型
- 持久化
- 集群
- 事务
官网:https://redis.io/
中文网:http://www.redis.cn/
Linux下安装Redis
- 下载并解压redis
- 安装基本的环境
yum install gcc-c++ gcc环境
make 安装基本的文件
redis默认安装路径
usr/local/bin
- 拷贝配置文件 以免修改出现问题无法还原
[root@majiawei bin]# cp /opt/redis-6.2.1/redis.conf mjw_config/
[root@majiawei bin]# cd mjw_config/
[root@majiawei mjw_config]# ls
redis.conf
- redis默认不是后台启动的
修改redis-conf文件 将daemonize的no改为yes
- 启动redis服务并测试连接
[root@majiawei bin]# redis-server mjw_config/redis.conf
[root@majiawei bin]# redis-cli -p 6379 测试连接
127.0.0.1:6379> ping
PONG 成功
127.0.0.1:6379> set name mjw
OK
127.0.0.1:6379> get name
"mjw"
127.0.0.1:6379>
- 查看Redis的进程是否开启
[root@majiawei /]# ps -ef|grep redis
root 25621 1 0 22:24 ? 00:00:00 redis-server 127.0.0.1:6379
root 25697 19113 0 22:25 pts/1 00:00:00 redis-cli -p 6379
root 25939 18843 0 22:29 pts/0 00:00:00 grep --color=auto redis
- 关闭Redis服务
127.0.0.1:6379> shutdown
not connected> exit
[root@majiawei bin]#
测试性能
redis-benchmark 一个官方自带的性能测试工具
参数如下
测试:50个并发连接 10000个请求
[root@majiawei bin]# redis-server mjw_config/redis.conf
[root@majiawei bin]# redis-benchmark -h localhost -p 6379 -n 10000
Redis基本命令
- redis默认有16个数据库 默认使用的是第0个 可以使用select 进行切换数据库
127.0.0.1:6379> select 2 #切换数据库
OK
127.0.0.1:6379[2]> dbsize #查看数据库大小
(integer) 0
127.0.0.1:6379[2]>
127.0.0.1:6379> set name mjw
OK
127.0.0.1:6379> set age 20
OK
127.0.0.1:6379> keys * #查看所有的key
1) "age"
2) "name"
127.0.0.1:6379> flushdb #清空当前数据库
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> flushall #清空所有数据库
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> EXISTS name #用于判断是否存在某个key
(integer) 1
127.0.0.1:6379> EXISTS namename
(integer) 0
127.0.0.1:6379> get name
"mjw"
127.0.0.1:6379> EXPIRE name 10 #设置过期时间10秒
(integer) 1
127.0.0.1:6379> ttl name #查看当前key的剩余时间
(integer) 6
127.0.0.1:6379> ttl name
(integer) 3
127.0.0.1:6379> ttl name
(integer) -2 #-2表示已经没有了
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> set name mjw
OK
127.0.0.1:6379> type name #查看key的数据类型
string
Redis是单线程的!
Redis是基于内存操作的,CPU不是Redis的性能瓶颈,Redis的瓶颈是机器的内存和网络的带宽。
Redis是C写的。
Redis为什么单线程还这么快?
- 高性能的服务器不一定是多线程的
- 多线程不一定比单线程效率搞(因为有CPU上下层切换)
核心:Redis是将所有的数据都存放在内存中,所以说使用单线程去操作效率是最高的,多线程(CPU上下文会切换:耗时),对于内存系统来说如果没有上下文切换效率是最高的。
Redis基础知识
Redis是一个开源的,内存中的数据结构存储系统,它可以用作`数据库`、`缓存`和`消息中间件`。它支持多种类型的数据结构,如果字符串、散列、集合、有序集与范围查询,bitmaps、hyperloglogs和地理空间索引半径查询。Redis内置了复制(replication)、LUA脚本(LUA ascripting)、LRU驱动事件(LRU eviction)、事务(transactions)和不同级别的磁盘持久化,并通过Redis哨兵和自动分区提供高可用性。
Redis数据类型
五大数据类型 String、List、Set、Hash、Zset
String类型基本命令
127.0.0.1:6379> get name
"mjw"
127.0.0.1:6379> APPEND name zp #追加字符串
(integer) 5
127.0.0.1:6379> get name
"mjwzp"
127.0.0.1:6379> STRLEN name #获取字符串的长度
(integer) 5
127.0.0.1:6379> set num 0
OK
127.0.0.1:6379> incr num #自增加1
(integer) 1
127.0.0.1:6379> incr num
(integer) 2
127.0.0.1:6379> get num
"2"
127.0.0.1:6379> decr num #自减减1
(integer) 1
127.0.0.1:6379> decr num
(integer) 0
127.0.0.1:6379> get num
"0"
127.0.0.1:6379> incrby num 5 #自增加5 设置步长
(integer) 5
127.0.0.1:6379> get num
"5"
127.0.0.1:6379> set key1 "abcdefghijkl" #截取字符串
OK
127.0.0.1:6379> get key1
"abcdefghijkl"
127.0.0.1:6379> GETRANGE key1 0 5 #截取字符串
"abcdef"
127.0.0.1:6379> set key1 abcdef
OK
127.0.0.1:6379> SETRANGE key1 0 mjw #替换指定位置开始的字符串
(integer) 6
127.0.0.1:6379> get key1
"mjwdef"
127.0.0.1:6379> setex key1 20 "mjw" #设置过期时间为20秒 值为mjw
OK
127.0.0.1:6379> ttl key1
(integer) 15
127.0.0.1:6379> ttl key1
(integer) -2
127.0.0.1:6379> setnx key2 "mjw" #如果key不存在设置
(integer) 1
127.0.0.1:6379> setnx key2 "zp"
(integer) 0
127.0.0.1:6379> mset key1 v1 key2 v2 key3 v3 #一次性设置多个
OK
127.0.0.1:6379> keys *
1) "key1"
2) "key2"
3) "key3"
127.0.0.1:6379> mget key1 key2 key3 #一次性获取多个
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx key1 v1 key4 v4 #如果不存在设置 msetnx 是原子性操作
(integer) 0
127.0.0.1:6379> mset user:1:name mjw user:1:age 20 #设置对象
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "mjw"
2) "20"
127.0.0.1:6379> get key1 #get set组合命令
(nil)
127.0.0.1:6379> getset key1 mjw # 如果不存在值 返回null
(nil)
127.0.0.1:6379> get key1
"mjw"
127.0.0.1:6379> getset key1 zp #如果存在值获取原来的值,并设置新的值
"mjw"
127.0.0.1:6379> get key1
"zp"
List类型基本命令
所有的List命令都是以L开头的!
127.0.0.1:6379> LPUSH list a #将一个值插入列表 分为头插/尾插 LPUSH/RPUSH
(integer) 1
127.0.0.1:6379> LPUSH list b
(integer) 2
127.0.0.1:6379> LPUSH list c
(integer) 3
127.0.0.1:6379> keys *
1) "list"
127.0.0.1:6379> LRANGE list 0 -1 #获取值 可用根据区间获取具体的值
1) "c"
2) "b"
3) "a"
127.0.0.1:6379> LRANGE list 0 0
1) "c"
127.0.0.1:6379> LPOP list #移除第一个元素
"c"
127.0.0.1:6379> RPOP list #移除最后一个元素
"a"
127.0.0.1:6379> lindex list 0 #通过下标获取值
"myq"
127.0.0.1:6379> llen list #返回列表长度
(integer) 3
127.0.0.1:6379> lrem list 1 aaa #删除指定个数 指定的值
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "aaa"
2) "aaa"
3) "myq"
4) "zp"
5) "mjw"
127.0.0.1:6379> lrem list 2 aaa
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "myq"
2) "zp"
3) "mjw"
1) "myq"
2) "zp"
3) "mjw"
127.0.0.1:6379> ltrim list 0 1 #截取 类似于 subtring
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "myq"
2) "zp"
127.0.0.1:6379> lrange mjw 0 -1
1) "ccc"
2) "bbb"
3) "aaa"
127.0.0.1:6379> rpoplpush mjw zp #移除列表的最后一个元素,并将他移动到新的列表中
"aaa"
127.0.0.1:6379> lrange mjw 0 -1
1) "ccc"
2) "bbb"
127.0.0.1:6379> lrange zp 0 -1
1) "aaa"
127.0.0.1:6379> EXISTS mjw #判断列表是否有值
(integer) 0
127.0.0.1:6379> lset mjw 0 aaa #如果不存在报错
(error) ERR no such key
127.0.0.1:6379> lpush mjw aaa
(integer) 1
127.0.0.1:6379> lset mjw 1 bbb
(error) ERR index out of range
127.0.0.1:6379> lset mjw 0 bbb #如果存在 则修改
OK
127.0.0.1:6379> LRANGE mjw 0 -1
1) "bbb"
127.0.0.1:6379> lrange mjw 0 -1
1) "bbb"
2) "aaa"
127.0.0.1:6379> LINSERT mjw before bbb zzz #插入在前
(integer) 3
127.0.0.1:6379> LINSERT mjw after aaa xxx #插入在后
(integer) 4
127.0.0.1:6379> lrange mjw 0 -1
1) "zzz"
2) "bbb"
3) "aaa"
4) "xxx"
实际上是一个链表
如果key不存在,创建新链表
如果key存在,新增内容
如果移除了所有值,空链表代表,不存在
Set(集合)
set中的值是不能重复的。
127.0.0.1:6379> sadd name mjw #集合中添加元素
(integer) 1
127.0.0.1:6379> sadd name mjw
(integer) 0
127.0.0.1:6379> sadd name zp
(integer) 1
127.0.0.1:6379> SMEMBERS name #获取集合中的所有元素
1) "zp"
2) "mjw"
127.0.0.1:6379> SISMEMBER name mjw #判断集合中有没有这个元素
(integer) 1
127.0.0.1:6379> SISMEMBER name aaa
(integer) 0
127.0.0.1:6379> scard name #获取集合大小
(integer) 2
127.0.0.1:6379> SREM name mjw #移除某个元素
(integer) 1
127.0.0.1:6379> SMEMBERS name
1) "zp"
127.0.0.1:6379> SRANDMEMBER name #随机抽取一个元素
"mjw"
127.0.0.1:6379> SRANDMEMBER name
"ddd"
127.0.0.1:6379> SRANDMEMBER name
"zp"
127.0.0.1:6379> SRANDMEMBER name 2 #随机抽取指定个数的元素
1) "ddd"
2) "ccc"
127.0.0.1:6379> SRANDMEMBER name 3
1) "mjw"
2) "bbb"
3) "ccc"
127.0.0.1:6379> SMEMBERS name
1) "zp"
2) "mjw"
3) "bbb"
4) "ddd"
5) "ccc"
6) "aaa"
127.0.0.1:6379> spop name #随机删除元素
"mjw"
127.0.0.1:6379> spop name
"bbb"
127.0.0.1:6379> spop name
"aaa"
127.0.0.1:6379> SMEMBERS name
1) "zp"
2) "ddd"
3) "ccc"
127.0.0.1:6379> sadd name1 mjw
(integer) 1
127.0.0.1:6379> sadd name2 zp
(integer) 1
127.0.0.1:6379> SMOVE name1 name2 mjw #将一个集合中的元素添加到另一个集合中
(integer) 1
127.0.0.1:6379> SMEMBERS name2
1) "zp"
2) "mjw"
127.0.0.1:6379> SMEMBERS name1
(empty array)
127.0.0.1:6379> sadd ts1 a
(integer) 1
127.0.0.1:6379> sadd ts1 b
(integer) 1
127.0.0.1:6379> sadd ts1 c
(integer) 1
127.0.0.1:6379> sadd ts2 c
(integer) 1
127.0.0.1:6379> sadd ts2 d
(integer) 1
127.0.0.1:6379> sadd ts2 e
(integer) 1
127.0.0.1:6379> SDIFF ts1 ts2 #差集 ts1相对于ts2的差集
1) "b"
2) "a"
127.0.0.1:6379> SINTER ts1 ts2 #交集
1) "c"
127.0.0.1:6379> sunion ts1 ts2 #并集
1) "e"
2) "a"
3) "c"
4) "b"
5) "d"
HASH(哈希)
Map集合,key-map类似与java的 Map<String,Map<String,String>>
这样的结构。
127.0.0.1:6379> HSET one key1 value1 #set一个key-value
(integer) 1
127.0.0.1:6379> hget one key1
"value1"
127.0.0.1:6379> HMSET one key2 value2 key3 value3 #设置多个
OK
127.0.0.1:6379> HMGET one key2 key3
1) "value2"
2) "value3"
127.0.0.1:6379> HGETALL one #获取hash中全部的数据
1) "key1"
2) "value1"
3) "key2"
4) "value2"
5) "key3"
6) "value3"
127.0.0.1:6379> HDEL one key3 #删除hash中指定的key
(integer) 1
127.0.0.1:6379> HGETALL one
1) "key1"
2) "value1"
3) "key2"
4) "value2"
127.0.0.1:6379> HLEN one #获取hash长度
(integer) 2
127.0.0.1:6379> HEXISTS one key1 #判断hash中的key是否存在
(integer) 1
127.0.0.1:6379> HEXISTS one key3
(integer) 0
127.0.0.1:6379> HKEYS one #获取hash中全部key
1) "key1"
2) "key2"
127.0.0.1:6379> HVALS one #获取hash中全部value
1) "value1"
2) "value2"
127.0.0.1:6379> HSET one key3 3
(integer) 1
127.0.0.1:6379> HINCRBY one key3 1 #自增/自减
(integer) 4
127.0.0.1:6379> HINCRBY one key3 -2
(integer) 2
Zset(有序集合)
在set的基础上,增加了一个值,set key1 v1 zset key1 score v1
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 age 22 mjw
(integer) 1
127.0.0.1:6379> zadd age 18 zp
(integer) 1
127.0.0.1:6379> zadd age 40 gjy
(integer) 1
127.0.0.1:6379> zadd age 5 myq
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE age -inf +inf #升序 ZRANGEBYSCORE key min max
1) "myq"
2) "zp"
3) "mjw"
4) "gjy"
127.0.0.1:6379> ZRANGEBYSCORE age -inf +inf withscores #附带年龄
1) "myq"
2) "5"
3) "zp"
4) "18"
5) "mjw"
6) "22"
7) "gjy"
8) "40"
127.0.0.1:6379> ZRANGEBYSCORE age -inf 18 withscores #取值范围 age小于等于18的
1) "myq"
2) "5"
3) "zp"
4) "18"
127.0.0.1:6379> ZREVRANGE age 0 -1 withscores #降序
1) "mjw"
2) "22"
3) "zp"
4) "18"
5) "myq"
6) "5"
127.0.0.1:6379> zrange age 0 -1
1) "myq"
2) "zp"
3) "mjw"
4) "gjy"
127.0.0.1:6379> zrem age gjy #删除
(integer) 1
127.0.0.1:6379> zrange age 0 -1
1) "myq"
2) "zp"
3) "mjw"
127.0.0.1:6379> zcard age #获取集合中的个数
(integer) 3
127.0.0.1:6379> zrange age 0 -1 withscores
1) "myq"
2) "5"
3) "zp"
4) "18"
5) "mjw"
6) "22"
127.0.0.1:6379>
127.0.0.1:6379> zcount age 0 5
(integer) 1
127.0.0.1:6379> zcount age 0 20 #获取指定区间的成员变量
三种特殊数据类型
geospatial 地理位置
Redis3.2版本退出,可用推算地理位置。 目前Redis官网有8个相关命令。
GEOADD
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 32.23 shanghai
(integer) 1
127.0.0.1:6379> GEOADD china:city 106.50 29.53 chongqin
(integer) 1
127.0.0.1:6379> GEOADD china:city 114.05 22.52 shenzheng
(integer) 1
127.0.0.1:6379> GEOADD china:city 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 2
127.0.0.1:6379> GEOPOS china:city beijin #获取指定城市的经度/维度
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
127.0.0.1:6379> GEOPOS china:city shanghai xian
1) 1) "121.47000163793563843"
2) "32.22999976626139329"
2) 1) "108.96000176668167114"
2) "34.25999964418929977"
m 表示 mi
km 表示 千米
mi 表示 英里
ft 表示 英尺
127.0.0.1:6379> GEODIST china:city beijin shanghai km #查看两地距离 直线距离
"966.8143"
127.0.0.1:6379> GEODIST china:city beijin xian km
"910.0565"
127.0.0.1:6379> GEODIST china:city beijin chongqin ft
"4803381.9063"
127.0.0.1:6379> GEODIST china:city beijin chongqin m
"1464070.8051"
GEORADIUS --以给定的经纬度为中心,找出某一半径内的元素
GEORADIUS key 经度 维度 半径 半径单位 可选带参
127.0.0.1:6379> GEORADIUS china:city 110 30 100 km
(empty array)
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km
1) "chongqin"
2) "xian"
3) "shenzheng"
4) "hangzhou"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km #以110 30为中心,寻半径1000km以内的城市
1) "chongqin"
2) "xian"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist #直线距离
1) 1) "chongqin"
2) "341.9374"
2) 1) "xian"
2) "483.8340"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord #经纬度
1) 1) "chongqin"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) "483.8340"
3) 1) "108.96000176668167114"
2) "34.25999964418929977"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord count 1 #限制只查一个
1) 1) "chongqin"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
#以指定元素为中心找出其他元素
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijin 1000 km
1) "shanghai"
2) "beijin"
3) "xian"
#简单来讲就是将经纬度用字符串来表示了
127.0.0.1:6379> GEOHASH china:city beijin
1) "wx4fbxxfke0"
# 以给定的经纬度为中心,找出某一半径/矩形内的元素 并加以排序
127.0.0.1:6379> GEOSEARCH china:city fromlonlat 120 30 byradius 500 km desc
1) "shanghai"
2) "hangzhou"
127.0.0.1:6379> GEOSEARCH china:city fromlonlat 120 30 byradius 500 km asc
1) "hangzhou"
2) "shanghai"
127.0.0.1:6379> GEOSEARCH china:city fromlonlat 120 30 bybox 500 500 km asc
1) "hangzhou"
2) "shanghai"
127.0.0.1:6379> GEOSEARCH china:city fromlonlat 120 30 bybox 500 500 km desc
1) "shanghai"
2) "hangzhou"
127.0.0.1:6379> ZRANGE china:city 0 -1 #查看全部元素
1) "chongqin"
2) "xian"
3) "shenzheng"
4) "hangzhou"
5) "shanghai"
6) "beijin"
127.0.0.1:6379> ZREM china:city chongqin #删除指定元素
(integer) 1
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "xian"
2) "shenzheng"
3) "hangzhou"
4) "shanghai"
5) "beijin"
Hyperloglog
Redis 2.8.9 版本更新了Hyperloglog数据结构,用作于基数统计算法。
网页的UV(一个人访问一个网站多次,但是还是算作是一个人)。
传统方式,set保存用户的id,让后统计set中元素数量。
这样就会保存大量的数据
#只有count 不能查看其中的元素
127.0.0.1:6379> PFADD key1 a b c d e f g #创建元素
(integer) 1
127.0.0.1:6379> PFCOUNT key1
(integer) 7
127.0.0.1:6379> PFADD key2 c g l i o p b
(integer) 1
127.0.0.1:6379> PFCOUNT key2
(integer) 7
127.0.0.1:6379> PFMERGE key3 key1 key2 #对两个元素进行并集操作
OK
127.0.0.1:6379> PFCOUNT key3
(integer) 11
Bitmaps
位存储
Bitmap位图,数据结构!都是操作二进制位来进行记录,就只有0和1两个状态。
365天=365bit 1字节=8bit 46个字节左右。
127.0.0.1:6379> setbit key 0 1 #设置
(integer) 0
127.0.0.1:6379> setbit key 1 0
(integer) 0
127.0.0.1:6379> setbit key 2 1
(integer) 0
127.0.0.1:6379> setbit key 3 0
(integer) 0
127.0.0.1:6379> getbit key 2 #获取
(integer) 1
127.0.0.1:6379> getbit key 1
(integer) 0
127.0.0.1:6379> bitcount key #统计有效次数
(integer) 2
事务
要么同时成功,要么同时失败:原子性!
Redis事务本质:一组命令的集合,一个事务的所有命令都会被序列化,在事务执行过程中,会按照顺序执行。
特性:一次性、顺序性、排他性
---队列 set set set 执行---
Redis单条命令是保证原子性的,但是事务是不保证原子性的!
Redis事务是没有隔离级别概念的!
所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会被执行! Exec
Redis的事务:
- 开启事务(MULTI)
- 命令入队(就是平常用的命令)
- 执行事务(EXEC)
正常执行事务
127.0.0.1:6379> MULTI #开启事务
OK
127.0.0.1:6379(TX)> set key1 v1 #命令入队
QUEUED
127.0.0.1:6379(TX)> set key2 v2
QUEUED
127.0.0.1:6379(TX)> get key1
QUEUED
127.0.0.1:6379(TX)> set key3 v3
QUEUED
127.0.0.1:6379(TX)> exec #执行事务
1) OK
2) OK
3) "v1"
4) OK
放弃事务
127.0.0.1:6379> keys *
1) "key1"
2) "key2"
3) "key3"
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set key4 v4
QUEUED
127.0.0.1:6379(TX)> DISCARD #取消事务
OK
127.0.0.1:6379> get key4 #事务中的命令没有被执行
(nil)
编译型异常(命令有错),事务中所有的命令都不会被执行
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set key1 v1
QUEUED
127.0.0.1:6379(TX)> set key2 v2
QUEUED
127.0.0.1:6379(TX)> setget key3 v3 #错误命令
(error) ERR unknown command `setget`, with args beginning with: `key3`, `v3`,
127.0.0.1:6379(TX)> EXEC #执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get key1 #所有的命令都不会被执行
(nil)
运行时异常(1/0),如果事务队列中存在语法错误,那么执行命令的时候,其他命令可以正常执行,错误命令抛出异常
127.0.0.1:6379> set key1 "v1"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incr key1
QUEUED
127.0.0.1:6379(TX)> set key2 v2
QUEUED
127.0.0.1:6379(TX)> set key3 v3
QUEUED
127.0.0.1:6379(TX)> exec
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
127.0.0.1:6379> get key2
"v2"
监控
悲观锁
- 很悲观,认为什么时候都会出问题,无论什么时候都会上锁
乐观锁
-
很乐观,认为什么时候都不会出问题,所以不会上锁,更新数据的时候进行判断,在此期间是否有人修改数据。
-
获取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(TX)> DECRBY money 20
QUEUED
127.0.0.1:6379(TX)> INCRBY out 20
QUEUED
127.0.0.1:6379(TX)> EXEC
1) (integer) 80
2) (integer) 20
模拟执行失败,开启两个客户端
#客户端一
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> get out
"20"
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY money 40
QUEUED
127.0.0.1:6379(TX)> INCRBY out 40
QUEUED
#线程一不执行EXEC命令,此时修改线程二
#客户端二
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> set money 200
OK
#切换为线程一
#客户端一
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> get out
"20"
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY money 40
QUEUED
127.0.0.1:6379(TX)> INCRBY out 40
QUEUED
127.0.0.1:6379(TX)> EXEC #执行之前,另外一个线程,修改了我们的值,这时候就会导致事务执行失败
(nil)
测试多线程修改值,使用watch 可以当作redis的乐观锁操作。
那事务执行失败了要怎么办呢?
127.0.0.1:6379> unwatch #如果发现事务执行失败,就先解锁
OK
127.0.0.1:6379> watch money #获取最新的值,再次监视
OK
乐观锁结束 监控执行事务前先监控你要修改的值,然后在你要执行命令的时候 去获取这个值,判断她和你之前获取的是否相同,如果不同就说明此期间被改动了,那事务就执行失败。
Jedis
使用Java操作Redis
什么是Jedis :是Redis官方推荐的Java连接开发工具,使用Java操作Redis中间件。
- 创建Maven项目,导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mjw</groupId>
<artifactId>redis_01_jedis</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--导入Jedis的包-->
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.6.0</version>
</dependency>
<!--fastjson-->
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
</dependencies>
</project>
- 搭建Redis及测试
- 连接数据库
- 操作命令
- 断开连接
jedis.close();
注意如果是连接远程的话需要修改redis配置1. 修改防火墙开放6379端口 2.cong配置文件中 将bind注释掉 3. 将protected-mode 中 yes 改为 no。
常用的Api命令
String、List、Set、Hash、Zset
System.out.println("清空数据:"+jedis.flushDB());
System.out.println("判断某个键是否存在:"+jedis.exists("username"));
System.out.println("新增键值对:"+jedis.set("username","mjw"));
System.out.println("获取所有键值对:"+jedis.keys("*"));
System.out.println("删除键:"+jedis.del("username"));
System.out.println("新增键值对:"+jedis.set("username","mjw"));
System.out.println("判断键是否存在"+jedis.exists("username"));
System.out.println("查看key的类型:"+jedis.type("username"));
System.out.println("随机返回一个key:"+jedis.randomKey());
System.out.println("重命名key:"+jedis.rename("username","name"));
System.out.println("按索引查询:"+jedis.select(0));
System.out.println("返回当前数据库中的key的数目:"+jedis.dbSize());
System.out.println("删除所有数据库中所有key:"+jedis.flushAll());
//java中的命令和之前linux中是一模一样的
Jedis使用事务
成功执行
执行失败
SpringBoot整合Redis
创建SpringBoot项目
pom中引入的依赖为
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
值得注意的是进入spring-boot-starter-data-redis中查看依赖项,发现Jedis没有了。查阅资料后:
在SpringBoot2.x之后,原来使用的Jedis被替换为了Lettuce
Jedis:采用直连,多个线程操作的话是不安全的,如果想避免需要使用Jedis Pool连接池。
Lettuce:采用Netty,实例可以在多个线程中共享,不存在线程不安全的情况。
# SpringBoot所有的配置类,都有一个自动配置类 RedisAutoConfiguration
# 自动配置类都会绑定一个properties配置文件 RedisProperties
想到得到这些信息直接去spring-boot-autoconfigure
中检索即可
RedisAutoConfiguration类分析
@Bean
@ConditionalOnMissingBean(name = {"redisTemplate"})
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
//ConditionalOnMissingBean不存在才生效,我们可以自定义一个redisTemplate
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//默认的redisTemplate没有对对象进行序列化
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
//由于String是redis中最常用的类型,所以单独提出来了一个bean。
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
- 配置连接
spring.redis.host=112.124.110.85
spring.redis.port=6379
- 使用
//redisTemplate 操作不同的数据类型,api和指令相同
redisTemplate.opsForValue();//类似字符串
redisTemplate.opsForList();//类似List
redisTemplate.opsForHash();
redisTemplate.opsForSet();
redisTemplate.opsForZSet();
//除了基本的操作,我们常用的方法可以直接通过redisTemplate操作,比如事务
redisTemplate.multi();![在这里插入图片描述](https://img-blog.csdnimg.cn/c401b91e0e974363b2c75363cbe47072.png#pic_center)
//获取redis连接对象
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.close();
connection.flushDb();
所以我们在Java中set 之后 在命令行中取的时候key都是序列化之后的结果。
所以我们需要自己取定义redisTemplate,一般序列化都用Json进行序列化。
如果使用对象不对其序列化的话就会报错。
当User实现了Serializable后就可以成功执行了。
自定义redisTmplate
@Configuration
public class RedisConfig {
//编写我们自己的RedisTemplate
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(redisConnectionFactory);
//配置具体的序列化方式
Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
template.setKeySerializer(objectJackson2JsonRedisSerializer);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectJackson2JsonRedisSerializer.setObjectMapper(om);
//String的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key采取String的序列化方式
template.setKeySerializer(stringRedisSerializer);
//value序列化方式采用jackson
template.setValueSerializer(objectJackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
测试
###################################
@Test
void testRedis(){
User user = new User("mjw",20);
redisTemplate.opsForValue().set("user",user);
System.out.println(redisTemplate.opsForValue().get("user"));
}
##################################客户端中查看结果
127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x04user" #这是使用默认的
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys * #使用自定义的 key是 String序列化
1) "user"
Redis.conf 详解
网络
bind 0.0.0.0 # 绑定IP
protected-mode no # 保护模式,如果你外网想访问服务器的话得设置为no
port 6379 # 端口设置
通用 GENERAL
daemonize yes # 以守护进程的方式运行,默认是no,我们要自己改为YES
idfile /var/run/redis_6379.pid #如果以后台的方式运行,我们就需要指定一个pid文件。
#日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel notice
logfile "" #日志文件位置
databases 16 #数据库的数量,默认是16个数据库
always-show-logo no #是否总是显示LOGO 启动Redis时的LOGO
快照
持久化,在规定的时间内,执行了多少次操作,则会持久到文件.rdb.aof中
redis是内存数据库,如果没有持久化,那么数据断电及失!
# 如果900s内,至少有一个key进行了修改,就进行持久化操作
save 3600 1
# 如果300s内,至少100个key进行了修改,就进行持久化操作
save 300 100
# 如果60s内,至少10000个key进行了修改,就进行持久化操作
save 60 10000
stop-writes-on-bgsave-error yes #持久化如果出错,是否需要继续工作
rdbcompression yes #是否压缩rdb文件,需要消耗一些CPU资源
rdbchecksum yes #保存rdb文件的时候,进行错误的检查校验
dir ./ #rdb 文件保存的目录
REPLICATIONf复制
SECURITY 安全
可以设置redis的密码,默认是没有密码的
127.0.0.1:6379> config get requirepass # 获取redis密码
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass root # 设置redis密码
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "root"
127.0.0.1:6379> auth root # 使用密码进行登录
OK
限制 CLIENTS
maxclients 10000 # 设置能连接上redis的最大客户端的容量
maxmemory <bytes> # redis 配置最大的内存容量
maxmemory-policy noeviction # 内存达到上限之后的处理策略
1. # volatile-lru 只对设置了过期时间的key进行LRU(默认值)
2. # allkeys-lru 删除lru算法的key
3. # volatile-random 随机删除即将过期的key
4. # allkeys-random 随机删除
5. # volatile-ttl 删除即将过期的
6. # noeviction 永不过期,返回错误
APPEND ONLY模式 aof配置
appendonly no # 默认是不开启aof模式,默认是使用rdb方式的,在大部分情况下,rdb完全够用
appendfilename "appendonly.aof" # 持久化的文件的名字
# appendfsync always # 每次修改都会 sync,消耗性能
appendfsync everysec # 每秒执行一次 sync,可能会丢失这1s的数据
# appendfsync no # 不执行 sync,操作系统自己同步数据
Redis持久化
Redis是内存数据库,如果不将内存中的数据状态保存到磁盘,那么一旦服务器宕机,服务器中的数据就会消逝(断电即失)。所以Redis提供了持久化。
RDB操作
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是快照,他回复是将快照文件读到内存中。
Redis会单独创建(fork)一个子进程来进行持久化,会将数据写入到一个临时文件中,待这个文件持久化过程结束,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能,如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOP方式更加的高效,RDB的缺点是最后一次持久化后的数据可能会丢失,我们默认的结束RDB,一般情况下不需要修改配置。
RDB默认保存的文件是 dump.rdb
现在我们配置的RDB规则是
如果在60秒内有5次修改操作则持久化。
测试 先将dump.rdb删除
set key1 v1 ~key5 v5 添加五条数据 查看结果
dump.rdb有出现了看来规则执行了
如果要测试是否持久化成功可以将退出redis进程之后再重启redis看是否存在相应的key
shutdown之后再重新连接redis 如何查看是否有恢复数据
RDB触发机制
- save 的规则满足的情况下,会自动触发rdb规则
- 执行flushall 命令,也会触发rdb
- 退出redis 也会触发rdb
如何恢复rdb文件
-
只需要将rdb文件放在我们redis启动目录就可以,redis启动的时候会自动检查dump.rdb并恢复其中的数据。
-
查看需要存在的位置
127.0.0.1:6379> config get dir 1) "dir" 2) "/usr/local/bin" # 如果在整个目录下存在 dump.rdb文件,启动就会自动恢复其中的数据
优点:
- 适合大规模的数据恢复
- 对数据的完整性要求不高
缺点:
- 需要一定的时间间隔进行操作,如果redis以外宏机了,那最后一次修改的数据就没有了
- fork进程的适合,会占用一定的内存空间。
AOF操作
将我们的所有命令都记录下来,恢复的时候就把这个文件全部执行。
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以修改文件,redis启动之初就会读取该文件重新构建数据,redis重启的话就根据日志文件的内容将写指令从前到后执行一次完成数据的恢复。
AOP保存的是 appendonly.aof文件
默认是不开启的,我们需要手动进行配置。我们只需要将appendonly改为yes并重启redis即可生效。
重写规则
AOF默认结束文件的无线追加,文件会越来越大
如果AOF文件大于64m,太大了 fork一个新的进程来将我们的文件进行重写。
错误修复
如果这个AOF文件出现错误,这时候redis是启动不起来的,我们可以使用redis的工具来修复aof文件。 redis-check-aof --fix
如果文件正常,就可启动redis了。
优点
- 每一次修改都同步,文件的完整会更加好
- 每秒同步一次,可能会丢失一秒的数据
- 从不同步,效率最高的 (appendonly no)
缺点
- 相对于数据文件来说,aof远远大于rdb,修复的速度比rdb慢
- AOF运行效率比rdb慢,所以我们redis默认的配置是rdb持久化。
总结
- RDN持久化方式能够在指定时间间隔内对你的数据进行快照存储
- AOF持久化方式记录每次服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis协议追加保存每次写的操作到文件尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
- 只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化。
- 同时开启两种持久化方式
- 在这种情况下,当Redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集比RDB文件保存的数据集要完整。
- RDB的数据不实时,同时使用两者的时候服务器重启也只会找AOF文件,那要不要只使用AOF呢?建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能存在的Bug,留着作为一个万一的手段。
- 性能建议
- 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。
- 如果Enable AOF,好处是在最恶劣情况下也只会丢失不超过2秒的数据,启动脚本比较简单只load自己的AOF文件就可以了,代价一是带来持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据都写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应尽量减少AOF rewrite的频率,AOF重写的基础大小默认值是64M大小,可以设到5G以上,默认超过原大小100%大小。
- 如果不Enable AOF,仅靠Master-Slave Repllcation 实现高可用性也可以,能省略一大笔IO,也减少了rewrite时带来的系统波动,代价是如果Master/Slave 同时挂掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入比较新的那个。
从前到后执行一次完成数据的恢复。
AOP保存的是 appendonly.aof文件
[外链图片转存中…(img-gZWHvqkC-1630594413416)]
默认是不开启的,我们需要手动进行配置。我们只需要将appendonly改为yes并重启redis即可生效。
重写规则
[外链图片转存中…(img-MyJhfNch-1630594413418)]
AOF默认结束文件的无线追加,文件会越来越大
如果AOF文件大于64m,太大了 fork一个新的进程来将我们的文件进行重写。
错误修复
如果这个AOF文件出现错误,这时候redis是启动不起来的,我们可以使用redis的工具来修复aof文件。 redis-check-aof --fix
[外链图片转存中…(img-FDQzUUGC-1630594413420)]
如果文件正常,就可启动redis了。
优点
- 每一次修改都同步,文件的完整会更加好
- 每秒同步一次,可能会丢失一秒的数据
- 从不同步,效率最高的 (appendonly no)
缺点
- 相对于数据文件来说,aof远远大于rdb,修复的速度比rdb慢
- AOF运行效率比rdb慢,所以我们redis默认的配置是rdb持久化。
总结
- RDN持久化方式能够在指定时间间隔内对你的数据进行快照存储
- AOF持久化方式记录每次服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis协议追加保存每次写的操作到文件尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
- 只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化。
- 同时开启两种持久化方式
- 在这种情况下,当Redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集比RDB文件保存的数据集要完整。
- RDB的数据不实时,同时使用两者的时候服务器重启也只会找AOF文件,那要不要只使用AOF呢?建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能存在的Bug,留着作为一个万一的手段。
- 性能建议
- 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。
- 如果Enable AOF,好处是在最恶劣情况下也只会丢失不超过2秒的数据,启动脚本比较简单只load自己的AOF文件就可以了,代价一是带来持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据都写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应尽量减少AOF rewrite的频率,AOF重写的基础大小默认值是64M大小,可以设到5G以上,默认超过原大小100%大小。
- 如果不Enable AOF,仅靠Master-Slave Repllcation 实现高可用性也可以,能省略一大笔IO,也减少了rewrite时带来的系统波动,代价是如果Master/Slave 同时挂掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入比较新的那个。