1、NoSQL概述
NoSQL(Not Only SQL),泛指非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在处理web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,出现了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,特别是大数据应用难题。
Redis是发展最快的一个关系型数据库
NoSQL特点
- 方便扩展(数据之间没有关系,很好扩展)
- 大数据量高性能(Redis一秒可以写8w次,可以读取11w次)
- 数据类型是多样型的(不需要事先设计数据库,如果是数据量十分大的表,很多人无法设计了)
2、NoSQL的四大分类
KV键值对
- Redis
- Oracle
文档型数据库(bson和json):
- mongoDB:是一个基于分布式文件存储的数据库,主要用来处理大量的文档,MongoDB是一个介于关系型数据和非关系型数据库中间的产品
- ConthDB
列存储数据库:
- HBase
- 分布式文件系统
图关系数据库
- 不是存图形的,存放的是关系:朋友圈社交网络
- Neo4j,InfoGrid
3、Redis入门
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库数据库/103728),并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。
redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
Redis也被人们称为结构性数据库
3.1、Redis作用
- 内存存储持久化,内存中是断电即失,所以说持久化很重要(rdb,aof)
- 效率高,可以用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器,计数器(浏览量)
3.2、Redis的特性
- 免费开源
- 持久化
- 集群
- 事务
4、安装Redis
4.1、Windows下安装
下载安装包:https://github.com/microsoftarchive/redis/releases/tag/win-3.2.100
安装完成压缩包之后解压
开启Redis
redis默认端口是6379
使用客户端连接redis
使用ping命令检查连接
出现闪退情况
win+r ==>> cmd 进入Redis文件夹依次执行以下命令
1、redis-cli.exe
2、shutdown
3、exit
4、redis-server.exe redis.windows.conf
4.2、Linux下安装
下载安装包
进入mnt目录,将redis的linux安装包移动到当前目录,之后进行解压
把redis解压后的文件移动到 /usr/local目录下
安装c语言编译环境 yum install gcc-c++
进入redis安装目录 cd /usr/local/redis
编译文件 make
进入到 ./src 目录
进行redis安装 make install
在 redis目录中创建bin和etc目录 mkdir bin mkdir etc
将redis.conf文件移动到etc文件夹下 mv reds.conf ./etc
将src下的将mkreleasehdr.sh、redis-benchmark、redis-check-aof、redis-cli、redis-server 移动到 ../bin/ 目录下
对redis.conf文件进行编辑 vim redis.conf
之后将 daemonize 属性的no改为yes
使用redis-server /usr/local/redis-6.2.6/etc/redis.conf命令启动redis
之后使用ps -ef|grep redis命令检查是否启动
使用redis-cli -p 6379命令进入redis服务器
关闭服务的方法 进入redis服务器之后直接shutdown之后使用exit命令即可退出
5、测试redis性能
redis-benchmark是一个压力测试工具,官方自带的性能测试工具
redis-benchmark 性能参数
序号 | 选项 | 描述 | 默认值 |
---|---|---|---|
1 | -h | 指定服务器主机名 | 127.0.0.1 |
2 | -p | 指定服务器端口 | 6379 |
3 | -s | 指定服务器 socket | |
4 | -c | 指定并发连接数 | 50 |
5 | -n | 指定请求数 | 10000 |
6 | -d | 以字节的形式指定 SET/GET 值的数据大小 | 2 |
7 | -k | 1=keep alive 0=reconnect | 1 |
8 | -r | SET/GET/INCR 使用随机 key, SADD 使用随机值 | |
9 | -P | 通过管道传输 请求 | 1 |
10 | -q | 强制退出 redis。仅显示 query/sec 值 | |
11 | –csv | 以 CSV 格式输出 | |
12 | *-l*(L 的小写字母) | 生成循环,永久执行测试 | |
13 | -t | 仅运行以逗号分隔的测试命令列表。 | |
14 | *-I*(i 的大写字母) | Idle 模式。仅打 |
6、基础知识
redis默认有16个数据库,默认使用的是第0个
可以使用select切换数据库
使用dbsize查看当前数据库的空间
keys * 查看当前所有的key
flush db 清空当前的数据库
flushall 清空全部的数据库
6.1、redis是单线程的
redis是很快的,redis是基于内存操作,CPU不是redis性能瓶颈,redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了
redis是c语言编写的,官方提供的数据为100000+的QPS,这个不比Memecache差
redis为什么单线程这么快?
误区1:高性能的服务器并一定是多线程的
误区2:多线程不一定比单线程效率高
核心:redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的
7、五大基本类型
Redis是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件MQ。它支持多种类型的数据结构如字符串(strings),散列(hashes)、列表(lists)、集合(sets)、有序结合(sorted sets)与范围查询,bitmaps,hyperloglogs和地理空间(geospatial)索引半径查询,Redis内置了赋值(replication)、LUA脚本(lua scripting)、LRU驱动事件(LRU eviction)、事务(transactions)和不同级别的磁盘持久化(persistence),并通过Redis哨兵(Sectinel)和自动分区(Cluster)提高可用性(high availability)
7.1、Redis-Key
使用set 属性名 属性值 的方法可以设置键值对
使用EXISTS 属性名 的方式可以判断一个属性名是否存在
使用move 属性名 数据库名 的方式可以移除一个键值对
使用 get 属性名可以得到对应的值
设置过期时间 expire name 时间(单位秒)
查看当前key的剩余时间 ttl 属性名
查看属性的类型 type 属性名
7.2、String
字符串类型
使用set 属性名 属性值 的方法可以设置键值对
使用 get 属性名可以得到对应的值
使用EXISTS 属性名 的方式可以判断一个属性名是否存在
append 属性名 字符串 向原来的字符串后面进行追加,如果当前属性名不存在就创建
strlen 属性名 获取字符串的长度
redis自动实现了i++的操作 使用命令
incr 属性名的方式就可以实现 即使这个属性是一个字符串类型,但是这个字符串类型必须是一个数字不能是其他的字符
i–的操作
decr 属性名
设置设置自增和自减步长
incrby 属性名 步长
decrby 属性名 步长
截取一部分的字符串
getrange key1 0 3
属性名 开始下标 结束下标
注:redis的下标也是从0开始,使用getrange key1 0 -1
可以查看全部的字符串
修改字符串的方法
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> setrange key2 1 xx #1表示起始下标 xx表示更改后的值
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"
setex key seconds value
将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。
setnx key value
只有在 key 不存在时设置 key 的值。如果key存在就不会设置
mset 可以一次性设置多个值
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> keys *
1) "k2"
2) "k1"
3) "k3"
mget 可以一次性得到多个键值对
127.0.0.1:6379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"
msetnx 一次性设置多个键值对,其中如果某个键存在就不会设置这个键
设置一个对象
#设置一个user:1对象 冒号没有意义就是一个字符 值为json格式字符串
127.0.0.1:6379> set user:1 {name:zhangsan,age:3}
OK
127.0.0.1:6379> keys *
1) "user:1"
127.0.0.1:6379> set user2 {name:lisi,age:2} #设置一个user2对象
OK
127.0.0.1:6379> keys *
1) "user2"
2) "user:1"
高阶用法
#可以一次性设置多个值
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 20
OK
#这里的key是一个巧妙的设计 user:{id}:{json字符串}
127.0.0.1:6379> mget user:1:name
1) "zhangsan"
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "20"
组合命令
getset
先get然后再set
127.0.0.1:6379> getset db redis
(nil)
127.0.0.1:6379> get db
"redis"
7.3、List
在redis中可以将list编程栈、队列、阻塞队列
在redis中所有的list命令都是以l开头
127.0.0.1:6379> lpush list one #将一个值插入到列表的头部(左)
(integer) 1
127.0.0.1:6379> LPUSH list two
(integer) 2
127.0.0.1:6379> LPUSH list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1 #获取全部的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1 #我们应该得到是 one和two,由此可以看出list默认是一个栈
1) "three"
2) "two"
#################################################################
127.0.0.1:6379> RPUSH list right #使用rpush就可以插入到列表的底部
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
#################################################################
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> lpop list 1 #从列表的最左边移除(顶部)
1) "three"
127.0.0.1:6379> rpop list 1 #从列表的最右边移除(底部)
1) "right"
#################################################################
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex list 0 #利用索引访问列表
"two"
127.0.0.1:6379> lindex list 1
"one"
#################################################################
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> llen list #返回列表的长度
(integer) 2
#################################################################
127.0.0.1:6379> LRANGE list 0 -1
1) "one"
2) "two"
3) "one"
127.0.0.1:6379> lrem list 1 one #移除指定值 1表示移除的数量
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
#################################################################
127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "hello1"
(integer) 2
127.0.0.1:6379> rpush mylist "hello2"
(integer) 3
127.0.0.1:6379> rpush mylist "hello3"
(integer) 4
127.0.0.1:6379> ltrim mylist 1 2 #对列表元素进行截取,取出需要的部分
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello1"
2) "hello2"
#################################################################
127.0.0.1:6379> RPUSH mylist hello
(integer) 1
127.0.0.1:6379> RPUSH mylist hello1
(integer) 2
127.0.0.1:6379> RPUSH mylist hello2
(integer) 3
127.0.0.1:6379> RPUSH mylist hello3
(integer) 4
127.0.0.1:6379> rpoplpush mylist newlist #从前一个列表的尾部删除从后一个列表的头部插入
"hello3"
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "hello1"
3) "hello2"
127.0.0.1:6379> LRANGE newlist 0 -1
1) "hello3"
#################################################################
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "hello1"
3) "hello2"
#对mylist的0号元素进行更新 被更新的值或者列表必须存在,不存在会报错
127.0.0.1:6379> lset mylist 0 item
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "item"
2) "hello1"
3) "hello2"
#################################################################
127.0.0.1:6379> lrange mylist 0 -1
1) "item"
2) "hello1"
3) "hello2"
127.0.0.1:6379> linsert mylist before "item" "other" #向指定的地方插入一个值
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "other"
2) "item"
3) "hello1"
4) "hello2"
小结
- list实际是一个链表
- 如果key不存在,创建新的链表
- 如果key存在,新增内容
- 如果移除了key,所有的value就消失了
- 在两边插入或者改动值效率最高,中间元素效率低
7.4、Set
集合类型,set中的值不能重复
127.0.0.1:6379> sadd myset hello #向set集合中添加值
(integer) 1
127.0.0.1:6379> SMEMBERS myset #查看set中所有元素
1) "hello"
127.0.0.1:6379> SISMEMBER myset hello #判断某个元素是否在set中
(integer) 1
#################################################################
127.0.0.1:6379> SMEMBERS myset
1) "nihao"
2) "hello"
127.0.0.1:6379> scard myset #统计set集合中元素个数
(integer) 2
#################################################################
127.0.0.1:6379> SMEMBERS myset
1) "nihao"
2) "hello"
127.0.0.1:6379> srem myset hello #移除set集合中的元素
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "nihao"
#################################################################
127.0.0.1:6379> SMEMBERS myset
1) "nihao"
2) "nihao4"
3) "nihao3"
4) "nihao2"
127.0.0.1:6379> SRANDMEMBER myset #获取一个随机的元素
"nihao"
127.0.0.1:6379> SRANDMEMBER myset
"nihao2"
127.0.0.1:6379> SRANDMEMBER myset
"nihao3"
#################################################################
127.0.0.1:6379> spop myset #随机删除一个元素
"nihao3"
127.0.0.1:6379> SMEMBERS myset
1) "nihao"
2) "nihao4"
3) "nihao2"
#################################################################
127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset hello1
(integer) 1
127.0.0.1:6379> sadd myset hello2
(integer) 1
127.0.0.1:6379> sadd myset2 set2
(integer) 1
127.0.0.1:6379> smove myset myset2 hello #将一个set集合中的元素移动到另一个集合中
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello2"
2) "hello1"
127.0.0.1:6379> SMEMBERS myset2
1) "hello"
2) "set2"
#################################################################
127.0.0.1:6379> SMEMBERS key1
1) "a"
2) "c"
3) "b"
127.0.0.1:6379> SMEMBERS key2
1) "e"
2) "c"
3) "d"
127.0.0.1:6379> sdiff key1 key2 #获取两个集合的差集
1) "a"
2) "b"
#################################################################
127.0.0.1:6379> sinter key1 key2 #求两个集合的交集
1) "c"
#################################################################
127.0.0.1:6379> SUNION key1 key2 #求两个集合的并集
1) "e"
2) "c"
3) "b"
4) "a"
5) "d"
7.5、Hash
map集合,Map<key,Map<key,value>> 本质和String类型没有太大区别,还是一个简单的key-value
127.0.0.1:6379> hset myhash field1 hello #添加元素到myhash中 key为field1 value为hello
(integer) 1
127.0.0.1:6379> hget myhash field1 #获取hash中的元素
"hello"
#################################################################
127.0.0.1:6379> hmset myhash field1 hello field2 world #同时添加多个值
OK
127.0.0.1:6379> hmget myhash field1 field2 #同时获取多个值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash #获取set集合中全部的元素
1) "field1" #key
2) "hello" #value
3) "field2" #key
4) "world" #value
#################################################################
127.0.0.1:6379> hdel myhash field1 #删除一个元素
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
#################################################################
127.0.0.1:6379> hlen myhash #获取元素数量
(integer) 1
#################################################################
127.0.0.1:6379> HEXISTS myhash field1 #判断某一个key是否存在
(integer) 1
127.0.0.1:6379> HEXISTS myhash field10
(integer) 0
#################################################################
127.0.0.1:6379> hkeys myhash #获取所有的key
1) "field2"
2) "field1"
127.0.0.1:6379> hvals myhash #获取所有的value
1) "world"
2) "hello"
#################################################################
127.0.0.1:6379> hset myhash field3 5
(integer) 1
127.0.0.1:6379> HINCRBY myhash field3 1 #增加1
(integer) 6
127.0.0.1:6379> HINCRBY myhash field3 -1 #减少1
(integer) 5
#################################################################
127.0.0.1:6379> hsetnx myhash field4 hello #若不存在就创建存在就不创建
(integer) 1
127.0.0.1:6379> hsetnx myhash field4 world
(integer) 0
小结
hash更适合对象的存储,String更适合字符串的存储
7.6、Zset
有序集合,在set的基础上增加了一个值
#添加一个值 myset是key 1表示其分数,排序就是按照这个分数来进行排序的 one就是value
127.0.0.1:6379> zadd myset 1 one
(integer) 1
127.0.0.1:6379> zadd myset 2 two
(integer) 1
127.0.0.1:6379> zadd myset 3 three 4 four #添加多个值
(integer) 2
127.0.0.1:6379> zrange myset 0 -1 #获取集合中所有的元素
1) "one"
2) "two"
3) "three"
4) "four"
#################################################################
127.0.0.1:6379> zadd salary 2500 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 500 hty
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf #升序排序
1) "hty"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZREVRANGE salary 0 -1 #降序排序
1) "zhangsan"
2) "xiaohong"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores #将后面的value也输出出来
1) "hty"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores #输出负无穷到2500区间的排序好的所有元素
1) "hty"
2) "500"
3) "xiaohong"
4) "2500"
#################################################################
127.0.0.1:6379> zrange salary 0 -1
1) "hty"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrem salary hty #移除一个元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "xiaohong"
2) "zhangsan"
#################################################################
127.0.0.1:6379> zcard salary #获取元素个数
(integer) 2
#################################################################
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores
1) "xiaohong"
2) "2500"
3) "zhangsan"
4) "5000"
127.0.0.1:6379> zcount salary 0 3000 #统计一个区间内元素的个数
(integer) 1
一些应用
存储班级成绩表,工资表排序
排行榜每日更新
8、三种特殊数据类型
8.1、geospatial
地理位置,朋友定位,附近的人,打车距离计算。redis的geospaticl在redis3.2版本就推出了。这个功能可以推算地理位置的信息,两地之间的距离,方圆几里的人
#规则:地球两极无法直接添加,一般都是下载城市数据,直接通过java程序一次性导入
#参数为 key 纬度 精度 名称
#(error) ERR invalid longitude,latitude pair 39.900000,116.400000 这个错误就是经纬度的大小超出限制
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing #添加城市数据
(integer) 1
127.0.0.1:6379> geoadd chind:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd chind:city 106.50 29.53 chongqing
(integer) 1
127.0.0.1:6379> geoadd chind:city 114.05 22.52 shenzhen
(integer) 1
127.0.0.1:6379> geoadd chind:city 120.16 30.24 hangzhou
(integer) 1
127.0.0.1:6379> geoadd chind:city 108.96 34.26 xian
(integer) 1
#################################################################
127.0.0.1:6379> GEOPOS Sicily Palermo #获取城市经纬度
1) 1) "13.36138933897018433"
2) "38.11555639549629859"
127.0.0.1:6379> GEOPOS Sicily Catania
1) 1) "15.08726745843887329"
2) "37.50266842333162032"
#################################################################
127.0.0.1:6379> GEODIST Sicily Palermo Catania #获取两个坐标之间的距离
"166274.1516"
127.0.0.1:6379> GEODIST Sicily Palermo Catania m #可以设置单位
"166274.1516"
127.0.0.1:6379> GEODIST Sicily Palermo Catania km
"166.2742"
#################################################################
#显示当前位置周围200km内的地理位置,15和37是当前的经纬度 withdist是输出距离的意思
127.0.0.1:6379> GEORADIUS Sicily 15 37 200 km WITHDIST
1) 1) "Palermo"
2) "190.4424"
2) 1) "Catania"
2) "56.4413"
#################################################################
127.0.0.1:6379> zrange Sicily 0 -1 #查看所有元素
1) "Palermo"
2) "Catania"
#################################################################
#geo的底层是zset所以说可以使用zset的命令操作geo
127.0.0.1:6379> zrem Sicily Catania #移除一个元素
(integer) 1
8.2、Hyperloglogs
==基数:==就是在一个集合中元素的种类数量
Hyperloglogs基数统计的算法,统计网页的访问量(一个人访问一个网站多次,但是还是算作一个人)
传统的方式是set方式来保存用户ID,这个方式如果保存大量的用户id就会比较麻烦,因为目的不是保存id而是计数
127.0.0.1:6379> PFADD mykey a b c d e f g h i j #向集合中添加元素
(integer) 1
127.0.0.1:6379> PFCOUNT mykey #统计元素数量
(integer) 10
127.0.0.1:6379> PFADD mykey2 i j z x c v b n m
(integer) 1
127.0.0.1:6379> PFCOUNT mykey2
(integer) 9
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2 #合并两个集合并去除重复值
OK
127.0.0.1:6379> pfcount mykey3
(integer) 15
Hyperloglogs有0.81%的错误率,如果需要统计像访客量这种的数据,我们就可以使用Hyperloglogs,因为这种错误使我们可以接收的,如果不允许出错就使用set或者自己的数据类型就行
8.3、Bitmaps
位存储,只有两个状态的统计量就可以使用bitmaps
使用bitmaps记录从周一到周日的打卡
#sign就是key 第一个数字就是星期 第二个数字就是是否打卡
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> SETBIT sign 2 0
(integer) 0
127.0.0.1:6379> SETBIT sign 3 0
(integer) 0
127.0.0.1:6379> SETBIT sign 4 0
(integer) 0
127.0.0.1:6379> SETBIT sign 5 0
(integer) 0
127.0.0.1:6379> SETBIT sign 6 0
(integer) 0
查看某一天是否打卡
127.0.0.1:6379> GETBIT sign 0 #获取value
(integer) 1
127.0.0.1:6379> GETBIT sign 1
(integer) 0
统计打卡的天数
127.0.0.1:6379> BITCOUNT sign
(integer) 1
9、事务操作
Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:
- 批量操作在发送 EXEC 命令前被放入队列缓存。
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
- 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
一个事务从开始到执行会经历以下三个阶段:
- 开始事务。
- 命令入队。
- 执行事务。
Redis可以实现乐观锁
正常执行事务
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379(TX)> set k1 v1 #设置命令
QUEUED #命令入队
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec #执行事务
1) OK
2) OK
3) "v2"
4) OK
放弃事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v3
QUEUED
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> discard #放弃事务
OK
127.0.0.1:6379> get k4
(nil)
编译型异常(命令有错),事务中所有的命令都不会被执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> getset k3 #这个命令是错的
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> set k5 v5
QUEUED
127.0.0.1:6379(TX)> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
运行时异常(1/0),如果事务队列中存在逻辑错误,执行命令的时候,其他命令可以正常执行,错误命令会抛异常
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incr k1 #不是整数不能自增
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> get k3
QUEUED
127.0.0.1:6379(TX)> EXEC
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k3
"v3"s
监控—Watch(面试常问)
悲观锁:
- 认为什么时候都会出问题,无论干什么都会加锁
乐观锁:
- 很乐观,认为什么手都不会出问题,所以不会上锁,更新数据的时候去判断一下,在此期间是否有人修改过这个数据
事务执行成功
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> watch money #监视这个money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY money 10
QUEUED
127.0.0.1:6379(TX)> INCRBY out 10
QUEUED
#此时事务没有执行,但是我们使用另外一个线程修改了这个money的值
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> set money 1000
OK
#现在再次执行事务
127.0.0.1:6379(TX)> exec
(nil) #事务就会执行失败
#此时想要执行事务就必须要再次对money这个值进行监视
10、Jedis
需要使用java来操作redis,就需要Jedis
Jedis是官方推荐的java连接开发工具,是使用java操作redis的中间件,如果要使用java操作redis,那么一定要对jedis十分熟悉
导入依赖
<dependencies>
<!--导入jedis的包-->
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.1.1</version>
</dependency>
<!--导入fastjson的包-->
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.79</version>
</dependency>
</dependencies>
测试
- 连接数据库
- 操作命令
- 断开连接
//测试连接
//1.new一个jedis对象
Jedis jedis = new Jedis("127.0.0.1",6379);
//jedis所有的命令就是之前学习的所有指令
String ping = jedis.ping();
System.out.println(ping);
//测试
import redis.clients.jedis.Jedis;
import java.util.Set;
public class TestPing {
public static void main(String[] args) {
//1.new一个jedis对象
Jedis jedis = new Jedis("127.0.0.1", 6379);
System.out.println("清空数据:"+jedis.flushDB());
System.out.println("判断某个键是否存在:"+jedis.exists("username"));
System.out.println("新增<username,hty>键值对:"+jedis.set("username","hty"));
System.out.println("新增<password,password>键值对:"+jedis.set("password","password"));
System.out.println("系统中所有的键:");
Set<String> keys = jedis.keys("*");
System.out.println(keys);
System.out.println("删除键password:"+jedis.del("password"));
System.out.println("判断键password是否存在:"+jedis.exists("password"));
System.out.println("查看键username所存储的值的类型:"+jedis.type("username"));
System.out.println("随即返回key空间的一个:"+jedis.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());
jedis.close();
}
}
事务操作
public class Testtx {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
Transaction multi = jedis.multi();
multi.set("k1","v1");
multi.get("k1");
List<Object> exec = multi.exec();
System.out.println(exec);
jedis.close();
}
}
事务的操作仍然是与api的操作是一样的
11、springboot整合
在springboot2.x之后,原来使用的jedis被替换成了lettuce
jedis:采用的是直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用jedis pool连接池
lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况,可以减少线程数据,更像NIO
12、Redis.conf详解
启动的时候,就通过配置文件来启动
配置文件对大小写不敏感
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
可以导入其他配置文件
# include /path/to/local.conf
# include /path/to/other.conf
绑定的ip
bind 127.0.0.1 -::1 192.168.0.112
保护模式
protected-mode no
端口设置
port 6379
################################# 通用配置 #####################################
以守护进程的方式运行 默认为no要手动开启为yes
daemonize yes
如果以后台方式运行,我们就需要指定一个pid文件
pidfile /var/run/redis_6379.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 ""
数据库的数量,默认是16个数据库
databases 16
是否总是显示logo
always-show-logo no
################################ 快照 ################################
#持久化会用到,在规定时间内,执行了多少次操作,则会持久化到文件,redis是内存数据库,如果没有持久化,那么数据断电即失
如果3600s内至少有一个key进行了修改,我们就进行持久化操作
save 3600 1
如果300s内至少有一个key进行了修改,我们就进行持久化操作
save 300 100
如果60s内至少有一个key进行了修改,我们就进行持久化操作
save 60 10000
持久化如果出错了,是否还需要继续工作
stop-writes-on-bgsave-error yes
是否压缩rdb文件(持久化文件) 需要消耗一些cpu资源
rdbcompression yes
是否校验rdb文件
rdbchecksum yes
rdb文件保存的目录
dir ./
################################# REPLICATION #################################
#复制
################################## SECURITY ###################################
#安全
可以在这里设置密码,默认是没有密码的,一般采用命令来设置密码
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get requirepass #获取密码
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "123456" #设置密码
OK
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"
127.0.0.1:6379> auth 123456
OK
################################### CLIENTS ####################################
#限制
设置能连接上redis的最大客户端的数量
maxclients 10000
redis最大的内存容量
maxmemory <bytes>
内存达到上限之后的处理策略
maxmemory-policy noeviction
############################## APPEND ONLY MODE ###############################
#AOF配置
默认不开启aof模式,默认使用rdb方式持久化,在大部分情况下,rdb够用
appendonly no
持久化文件的名字
appendfilename "appendonly.aof"
每次修改都会sync同步
# appendfsync always
每秒执行一次sync同步 可能会丢失一秒的数据
appendfsync everysec
不执行同步
# appendfsync no
13、Redis持久化
Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失,所以Redis提供了持久化功能
13.1、RDB(Redis DataBase)
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的snapshot快照,它恢复时是将快照文件直接读到内存中
Redis会单独创建一个子进程来持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件,整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能,如果需要进行大规模数据的恢复且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加高效,RDB的缺点是最后一次持久化后的数据可能丢失,默认情况下就是RDB,一般情况下不需要修改配置
rdb保存的文件是 dump.rdb
可以在配置文件中进行修改
dbfilename dump.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/redis-6.2.6/bin"#如果这个目录下存在dump.rdb文件,启动就会自动恢复其中的数据
优点:
- 适合大规模数据恢复
- 对数据完整性要求不高
缺点:
- 需要一定的时间间隔进行操作,如果redis意外宕机了,最后一次修改的数据就没了
- fork进程的时候会占用一定的空间
13.2、AOF(Append Only File)
将所有的命令都记录下来,恢复的时候就把这个文件全部再执行一遍
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只需追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
AOF保存的是appendonly.aof文件
在配置文件中进行配置
appendonly no #默认为不开启,需要手动开启配置
重启redis就可以生效
如果这个aof文件有错误,这时候redis无法启动,我们需要修复这个配置文件,redis提供了一个工具redis-check-aof
执行如下命令就可以修复
redis-check-aof --fix
aof优缺点
优点:
- 每一次修改都同步,文件的完整性会更好
- 每秒同步一次,可能会丢失一秒的数据
- 从不同步,效率最高
缺点:
- 相对于数据文件来说,aof大小远远大于rdb,修复的速度也比rdb慢
- AOF运行效率也要比rdb慢,所以redis默认配置就是rdb持久化
重写
默认配置中,如果一个aof文件大于64mb,就会产生一个新的进程来将文件重写
14、Redis发布订阅
Redis发布订阅(pub/sub)是一种消息通信模式,发送者(pub)发送消息,订阅者(sub)接收消息。
Redis客户端可以订阅任意数量的频道
订阅/发布消息图:
我们需要一个消息发送者,一个频道和消息订阅者
序号 | 命令及描述 |
---|---|
1 | [PSUBSCRIBE pattern [pattern …] 订阅一个或多个符合给定模式的频道。 |
2 | [PUBSUB subcommand [argument [argument …]查看订阅与发布系统状态。 |
3 | [PUBLISH channel message] 将信息发送到指定的频道。 |
4 | [PUNSUBSCRIBE [pattern [pattern …]退订所有给定模式的频道。 |
5 | [SUBSCRIBE channel [channel …] 订阅给定的一个或多个频道的信息。 |
6 | [UNSUBSCRIBE [channel [channel …]指退订给定的频道。 |
案例:创建一个autunomy频道并进行消息发布
127.0.0.1:6379> SUBSCRIBE autunomy #创建频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "autunomy"
3) (integer) 1
127.0.0.1:6379> PUBLISH autunomy "hello hty" #消息发送
(integer) 1
127.0.0.1:6379> SUBSCRIBE autunomy #另一边的客户端就会接收到消息
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "autunomy"
3) (integer) 1
1) "message"
2) "autunomy"
3) "hello hty"
使用场景
- 实时消息系统
- 实时聊天
- 订阅,关注系统
稍微复杂的场景就会使用消息中间件MQ
15、Redis主从复制
将一台Redis服务器的数据复制到其他Redis服务器,前者称为主节点,后者称为从节点;数据的复制是单向的,只能由主节点到从节点,主节点以写为主,从节点以读为主
主要作用:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复,实际上是一种服务的冗余
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载,尤其是在写少读多的场景下,通过多个节点分担读负载,可以打打提高Redis服务器的并发量
- 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础
通常在公司中,主从复制就是必须要使用的,因为在真实的项目中不可能单机使用Redis
15.1、集群环境的搭建
只配置从库,不用配置主库
127.0.0.1:6379> info replication #查看当前库的信息
# Replication
role:master #角色
connected_slaves:0 #连接的从机
master_failover_state:no-failover
master_replid:f8dfe42935294ebe44aad1c6a82d7b43fed2c509
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
首先在主机中复制三个配置文件
[root@CentOS etc]# cp redis.conf redis79.conf
[root@CentOS etc]# cp redis.conf redis80.conf
[root@CentOS etc]# cp redis.conf redis81.conf
然后分别修改每个配置文件的port pid Log rdb的属性值
之后分别启动然后查看是否启动成功
[root@CentOS bin]# ps -ef|grep redis
root 9677 1 0 21:21 ? 00:00:00 redis-server 127.0.0.1:6379
root 9686 1 0 21:22 ? 00:00:00 redis-server 127.0.0.1:6380
root 9702 1 0 21:22 ? 00:00:00 redis-server 127.0.0.1:6381
root 9726 9515 0 21:23 pts/8 00:00:00 grep --color=auto redis
15.2、一主(79)二从(80,81)
默认情况下,每台Redis服务器都是主节点一般情况下只需要配置从机就行
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 #使用这个命令可以配置主机地址
OK
127.0.0.1:6379> info replication #查看当前redis的信息
# Replication
role:master #是主机
connected_slaves:2 #有两个从机
slave0:ip=127.0.0.1,port=6380,state=online,offset=70,lag=0 #从机1的信息
slave1:ip=127.0.0.1,port=6381,state=online,offset=70,lag=1 #从机2的信息
master_failover_state:no-failover
master_replid:55eb71e11b78add5af6af12a3eeb2ad01b408d1f
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:70
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:70
真实的主从配置应该是在配置文件中配置,这样的话是永久的,我们使用的是命令,是暂时的
主机可以写,从机不能写只能读,主机中的数据和信息都会自动被从机保存
测试
当主机断开后,从机仍然还是正常工作的,当主机重新连接之后,从机依然可以读取到主机的内容
当从机断开后,主机写入数据之后,从机重新连接后无法读取到主机在从机断开时写入的数据,因为我们使用的是命令行进行配置,一旦从机断开重连之后,从机就会自动变为主机,如果重新配置了主机的话就可以拿到任何数据
复制的原理
从机启动成功连接master后会发送一个sync命令
主机接到命令之后,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,主机将传送整个数据文件到从机,并完成一次完全同步
全量复制:从机服务在接收到数据库文件数据后,将其存盘并加载到内存中
增量复制:主机继续将新的所有收集到的修改命令一次传给从机完成同步
但是只要重新连接主机,一次完全同步(全量赋值)将被自动执行
主从复制的模式
正常模式
另一种模式
其中一个既当主机也当从机,但是如果主机没有宕机的话,这个仍然是一个从机,不能进行写数据,当主机宕机之后,如果不进行配置他仍然是从机
配置方式
SLAVEOF no one
配置之后第一个从机就变为了主机
15.3、哨兵模式*
可以自动的进行选取主机
概述
主从切换技术的方法是:当主机服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费时费力,还会造成一段时间内服务不可用,这不是一种推荐的方式,更多时候我们优先考虑哨兵模式,Redis从2.8开始正式提供了Sentinel架构来解决这个问题
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程它会独立运行,其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行多个Redis实例
哨兵的作用
- 通过发送命令,让Redis服务器返回监控器运行状态,包括主服务器和从服务器
- 当哨兵检测到master宕机,然后通过发布定云模式通知其他的从服务器修改配置文件,让他们切换主机
然后一个哨兵进程对Redis进行监控也可能会出现问题,于是就有了多哨兵模式,各个哨兵之间还会进行监控
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象称为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。
哨兵的配置
首先编写配置文件 sentinel.conf
sentinel monitor myredis 127.0.0.1 6379 1 #myredis是起的名字 1表示开启投票模式,让票数最多的成为主机
启动哨兵
redis-sentinel sentinel.conf
如果主机崩了,其中一个从机就会称为主机,就算原来的主机连接回来也只能称为现在主机的从机
哨兵模式的优缺点
优点:
- 哨兵集群,基于主从复制模式,所有的主从配置优点,它全有
- 主从可以切换,故障可以转移,系统的可用性就会更好
- 哨兵模式就是主从模式的升级,手动到自动,更加健壮!
缺点:
- Redis 不好啊在线扩容的,集群容量一旦到达上限,在线扩容就十分麻烦!
- 实现哨兵模式的配置其实很麻烦,里面有很多选择
16、缓存穿透和雪崩
Redis缓存的使用,极大提升了应用程序的性能和效率,特别是数据查询方面,但同时他也带来一些问题,其中,最要害的问题就是数据的一致性问题,从严格意义上讲,这个问题无解,如果对数据的一致性要求很高,那么就不能使用缓存
另外一些典型问题就是,缓存穿透,缓存雪崩喝缓存击穿,目前业界也都有比较流行的解决方案
16.1、缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案1–布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力
解决方案2–缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源
问题:
- 如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键
- 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响
16.2、缓存击穿
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
解决方案1–设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题
解决方案2–加互斥锁
分布式锁:使用分布式锁,保证对于每一个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可,这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大
16.3、缓存雪崩
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案1–redis高可用
这个思想的含义是,既然redis有可能挂掉,那就多增加几台redis,这样一台挂掉还有其他的可以继续工作,其实就是搭建集群
解决方案2–限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,比如对某个key只允许一个线程查询数据和写缓存,其他线程等待
解决方案3–数据预热
数据加热的含义就是在正式部署前,我先把可能的数据预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中,在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期,让缓存失效的时间点尽量均匀
现在主机的从机
哨兵模式的优缺点
优点:
- 哨兵集群,基于主从复制模式,所有的主从配置优点,它全有
- 主从可以切换,故障可以转移,系统的可用性就会更好
- 哨兵模式就是主从模式的升级,手动到自动,更加健壮!
缺点:
- Redis 不好啊在线扩容的,集群容量一旦到达上限,在线扩容就十分麻烦!
- 实现哨兵模式的配置其实很麻烦,里面有很多选择