Redis

什么是NoSQL

NoSQL = Not Only SQL (不仅仅是sql) 泛指非关系型数据库,例如Redis。

这些数据不需要多余的操作,就可以横向扩展的。

关系型数据库:行,列,代表:mysql 。

NoSQL特点:

解耦。

1.方便扩展(数据之间没有关系,很好扩展)

2.大数据量高性能(Redis 一秒写8w次,读写11w,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)

3.数据类型是多样型的!(不需要事先设计数据库!随取随用!如果是数据量十分大的表,很多人就无法设计了!)

4.传统的RDBMS和NoSQL

传统的RDBMS
	结构化组织
	SQL
	数据和关系都存在单独的表中。
	严格的事务
	基础的事务。
NoSQL
    不仅仅是数据
    没有固定的查询语言
    键值对储存,列存储,文档存储,图形数据库(社交关系)
    最终一致性,
    CAP定理和BASE (异地多活)
    高性能,高可用,高可扩。

NoSQL的四大分类

KV键值对:

Redis

文档型数据库(bson格式和json一样):

MongoDB(一般必须掌握)

MongoDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档!

MongoDB是一个介于关系型数据库和非关系型数据库中间的产品!MongoDB是非关系型数据库中,功能最丰富,最像关系型数据库的。

ConthDB

列存储数据库

HBase

分布式文件系统

图关系数据库

他不是存图形,放的是关系,比如:朋友圈的社交网络,广告推荐!

Neo4j,infoGrid;

四者的对比

Redis是什么?

Redis(Remote Dictionary Server),即远程字典服务。

是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型,key-value数据库,并提供多种语言的api。

redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在基础上实现了master-slave(主从)同步。是免费和开源的,是当下最热门的Nosql技术之一,也被人们称之为结构化数据库。

redis能干嘛?

1.内存储存、持久化,内存中的数据是断电即失,所以说持久化很重要(rdb,aof)

2.效率高,可以用于高速缓存。

3.发布订阅系统。

4,地图信息分析。

5.计时器,计数器。

特性:1.多样的数据类型;2.持久化;3.集群;4.事务….

学习redis

官网:https://redis.io/

中文网:www.redis.cn/

github上边下载win的地址:https://github.com/tporadowski/redis/releases

win测试连接

redis推荐使用linux下使用。

linux安装

在官网下载安装包,将安装包拖到xftp传到opt目录下,然后解压安装包

tar -zxvf redis-6.0.6.tar.gz 

基本环境安装,安装gcc-c++

yum install gcc-c++

redis版本高,要升级gcc

#第一步
sudo yum install centos-release-scl
#第二步
sudo yum install devtoolset-7-gcc*
#第三步
scl enable devtoolset-7 bash
make 命令 (必须)
make
    make install 做一个确认。
    

redis默认安装路径:/usr/local/bin

将redis配置文件复制到当前目录下

先创建一个superConfig 自己的配置文件
    然后复制过去
    cp /opt/redis-6.0.6/redis.conf  superConfig

重启:

redis默认不是后台启动的,修改配置文件

/requirepass 也能找到
vim redis.conf  进去配置文件 
    可以配置密码
    先通过grep -rn "requirepass" *命令找到修改密码在多少行 386
    然后再进去vim redis.conf 配置文件, :386 找到这行,然后取消注释,输入密码。123456

然后cd../ 返回bin目录,接着在指定的文件启动服务

启动服务:redis-server superConfig/redis.conf
    然后redis-cli -p 6379 连接
    ping
    出现
    (error) NOAUTH Authentication required.错误
    输入 
    auth 123456
     ok
    成功连接
    

查看所有的key

keys * 

查看redis进程是否开启

ps -ef|grep redis

关闭redis进程

shutdown  //关闭进程
    exit  //退出 
    

后面使用单机多redis启动集群测试。

测试性能

redis-benchmark 是一个压力测试工具

官方自带的性能测试工具!

redis-benchmark 命令参数!

redis 基础知识

redis默认16个数据库,默认使用的是第0个数据库。

可以使用select来切换数据库。

127.0.0.1:6379> select 3  切换到第三个数据库
OK
127.0.0.1:6379[3]> dbsize  数据库长度为0
(integer) 0
127.0.0.1:6379[3]> 
keys *   查看数据库所有的key
flushdb   清除当前数据库
flushall  清空所有数据库内容

redis是单线程的!

image-20220528125002238

五大数据类型

Redis-Key

String:

常用命令
    set name "super"  //设置key set key
    keys *   //查看所有的key。
    exists name // 判断当前的key是否存在。
    move name 1  //移除当前的key
    expire name  //设置key的过期时间,单位为秒
    ttl name    //查看当前key的剩余时间
    type name   //查看当前key的类型
    #################################################################################
    append name "super" //追加字符串,如果当前的key不存在,就相当于set key。
    strlen name //获取字符串的长度
    #################################################################################
    incr views //自增1
    decr views //自减1
    incrby  views 10 设置步长,指定增量
 #################################################################################
 字符串范围
    127.0.0.1:6379[3]> getrange name 2 5  //截取字符串
    "perb"
    127.0.0.1:6379[3]> getrange name 0 -1  //获取所有的字符串
    "superbrother"
    127.0.0.1:6379[3]> setrange name 4 ge  //替换指定位置开始的字符串
    (integer) 12
    127.0.0.1:6379[3]> get name
    "supegerother"
   ###############################################################################
# setex(set with expire) #设置过期时间
# setnx(set if not exist) #不存在设置时间    
127.0.0.1:6379[3]> setex key 30 "hello"  #设置key的值为hello,30秒后过期
OK
127.0.0.1:6379[3]> ttl key
(integer) 25
127.0.0.1:6379[3]> get key
"hello"
127.0.0.1:6379[3]> setnx mykey "redis"   #如果mykey不存在,创建mykey
(integer) 1
127.0.0.1:6379[3]> setnx mykey "redis1" #如果mykey,存在,创建失败
(integer) 0
127.0.0.1:6379[3]> get mykey
"redis"
    ############################################################################### 
   mset k1 v1 k2 v2 k3 v3  //同时设置多个值。
   mget k1 k2 k3 name      //同时获取多个值
   msetnx k1 v1 k6 v6      msetnx是一个原子性的操作,要么一起成功,要么一起失败!
  ############################################################################### # 对象
  set user:1 {name:zhangsan,age:3} #设置一个user:1 对象 值为json字符来保存一个对象!
  # 这里的key是一个巧妙的设计: user:{id}:{filed},如此设计在redis是完全ok的!
 127.0.0.1:6379[3]> mset user:1:name zhangsan user:1:age 13
 OK
 127.0.0.1:6379[3]> mget user:1:name user:1:age
 1) "zhangsan"
 2) "13"
   ############################################################################### 
   # getset 先get,后set
   
 127.0.0.1:6379[3]> getset dp redis  #如果不存在值,则返回nil
 (nil)
 127.0.0.1:6379[3]> get dp
 "redis"
 127.0.0.1:6379[3]> getset dp mogodb #如果存在值,获取原来的值,并设置新的值。
 "redis"
 127.0.0.1:6379[3]> get dp
 "mogodb"

List

在redis里面,我们可以把list玩成,栈,队列,阻塞队列。

所有的list命令都是以l开头的,redis不区分大小写命令

127.0.0.1:6379[3]> lpush list one   #将一个值或多个值插入到列表头部,(L:左)
(integer) 1
127.0.0.1:6379[3]> lpush list rwo
(integer) 2
127.0.0.1:6379[3]> lpush list three
(integer) 3
127.0.0.1:6379[3]> lrange list 0 -1  #获取list中的值!
1) "three"
2) "rwo"
3) "one"
127.0.0.1:6379[3]> lrange list 0 1  #通过区间获取具体的值!
1) "three"
2) "rwo"
127.0.0.1:6379[3]> 
127.0.0.1:6379[3]> rpush list four  #将一个值或多个值插入到列表尾部,(L:右)
(integer) 4
127.0.0.1:6379[3]> lrange list 0 -1
1) "three"
2) "rwo"
3) "one"
4) "four"
127.0.0.1:6379[3]> rrange list 0 -1
  ###############################################################################
  127.0.0.1:6379[3]> lpop list  #移除list的第一个元素
 "three"
 127.0.0.1:6379[3]> rpop list   #移除list的最后一个元素
 "four"
 127.0.0.1:6379[3]> lrange list 0 -1
 1) "rwo"
 2) "one"
 127.0.0.1:6379[3]> lrange list 0 -1
 1) "rwo"
 2) "one"
 127.0.0.1:6379[3]> lindex list 0  #通过下标获取list中的某一个值
 "rwo"
 127.0.0.1:6379[3]> lindex list 1
 "one"
   ###############################################################################
127.0.0.1:6379> lpush lsit one
(integer) 1
127.0.0.1:6379> lpush lsit two
(integer) 2
127.0.0.1:6379> lpush lsit three
(integer) 3
127.0.0.1:6379> llen lsit  #返回列表的长度
(integer) 3
 ###############################################################################
 #移除指定的值!
 127.0.0.1:6379> lpush lsit three
(integer) 4
127.0.0.1:6379> llen lsit
(integer) 4
127.0.0.1:6379> lrem lsit 1 one #移除lsit集合中指定个数的value,精确匹配
(integer) 1
127.0.0.1:6379> get one
(nil)
127.0.0.1:6379> lrange lsit 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379> lrem lsit 2 three #移除lsit集合中的2个three。
(integer) 2
127.0.0.1:6379> lrange lsit 0 -1
1) "two"
 ###############################################################################
trim 修剪。 list 截断
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 0 2 剪裁指定位置长度的元素,返回剪裁的元素。
OK
127.0.0.1:6379> lrange list 0 -1
(empty array)
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "hello1"
3) "hello2"
rpoplpush mylist otherlist 移除列表的最后一个元素,将他移动到新的列表中。
 ###############################################################################
 #lset 将列表指定下标的值替换为另一个值,更新操作
127.0.0.1:6379> rpush list "hello"
(integer) 1
127.0.0.1:6379> lset list 0 hhh 将下标为0的值:hello替换为hhh。
OK
127.0.0.1:6379> get list
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> lrange list 0 -1
1) "hhh"

127.0.0.1:6379> exists mylist #判断这个列表是否存在,
(integer) 0
127.0.0.1:6379> lset mylist 0 hhh #如果不存在列表,更新就会报错
(error) ERR no such key
127.0.0.1:6379> 
 ###############################################################################
 #linsert 将某个具体的值插入到列表中某个元素的前面或者后面。
127.0.0.1:6379> lpush mylist world
(integer) 2
127.0.0.1:6379> linsert mylist after hello java #将java插在hello元素的后面
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "world"
2) "hello"
3) "java"
127.0.0.1:6379> linsert mylist before hello java1 #将Java1插在hello元素的前面
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "world"
2) "java1"
3) "hello"
4) "java"
 

Set(集合)

set里面的值是不能重复的。

127.0.0.1:6379> sadd myset "hello"  #set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd myset "superbrother"
(integer) 1
127.0.0.1:6379> sadd myset "love superbrother"
(integer) 1
127.0.0.1:6379> smembers myset    #查看指定set的所有值
1) "love superbrother"
2) "hello"
3) "superbrother"
127.0.0.1:6379> sismember myset hello #判断一个集合是不是在set集合中
(integer) 1
127.0.0.1:6379> sismember myset world
(integer) 0
 ###############################################################################
127.0.0.1:6379> scard myset
(integer) 3   #获取set集合中的内容元素个数!
###############################################################################
127.0.0.1:6379> srem myset hello #移除set集合中的指定元素
(integer) 1
127.0.0.1:6379> smembers myset   #
1) "love superbrother"
2) "superbrother"
###############################################################################
set无序不重复集合,抽随机
127.0.0.1:6379> srandmember myset 2  随机抽取出指定个数的元素
1) "love superbrother"
2) "superbrother"
###############################################################################
删除指定key,随机删除key。
127.0.0.1:6379> spop myset 随机删除一些set集合中的元素
"superbrother"
###############################################################################
127.0.0.1:6379> sadd myset2 "set2"
(integer) 1
127.0.0.1:6379> smove myset myset2 "love superbrother" #将一个指定的值,移动到另一个set                                                         集合。
(integer) 1
127.0.0.1:6379> smembers myset
(empty array)
127.0.0.1:6379> smembers myset2
1) "love superbrother"
2) "set2"
###############################################################################
微博,b站,共同关注(并集)
数字集合类
差集 #sdiff
交集 #sinter
并集 #sunion
127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key2 c d e
(integer) 3
127.0.0.1:6379> smembers key2
1) "e"
2) "d"
3) "c"
127.0.0.1:6379> sdiff key1 key2 #差集
1) "b"
2) "a"
127.0.0.1:6379> sinter key1 key2 #交集
1) "c"
127.0.0.1:6379> sunion key1 key2 #并集
1) "c"
2) "a"
3) "b"
4) "d"
5) "e"
应用:微博,A用户将所有关注的人放在一个set集合中,将它的粉丝也放在一个集合中!
共同关注,共同爱好,二度好友,推荐好友。

Hash

Map集合,key-Map集合!本质和String类型没有太大的区别,还是一个简单的key-value!

127.0.0.1:6379> hset myhash field1 superbrother #set一个具体 key-value
(integer) 1
127.0.0.1:6379> hget myhash field1   #获取一个字段值
"superbrother"
127.0.0.1:6379> hmset myhash field1 hello field2 world #set多个 key-value
OK
127.0.0.1:6379> hmget myhash field1 field2  #获取多个字段值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash #获取全部数据
1) "field1"
2) "hello"
3) "field2"
4) "world"
###############################################################################
127.0.0.1:6379> hdel myhash field1  #删除hash指定key字段!对应value值也就消失了!
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
###############################################################################
127.0.0.1:6379> hmset myhash field1 hello field2 world
OK
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
3) "field1"
4) "hello"
127.0.0.1:6379> hlen myhash #获取哈希表的字段数量
(integer) 2
###############################################################################
127.0.0.1:6379> hexists  myhash field1
(integer) 1
127.0.0.1:6379> hexists myhash3 field1 #判断哈希表中指定字段是否存在
(integer) 0
127.0.0.1:6379> hkeys myhash #只获取所有的字段
1) "field2"
2) "field1"
127.0.0.1:6379> hvals myhash #只获取所有的值
1) "world"
2) "hello"
###############################################################################
127.0.0.1:6379> hset myhash field3 5  #指定增量
(integer) 1
127.0.0.1:6379> hincrby myhash field3 1 #给field3字段自加1
(integer) 6
127.0.0.1:6379> hincrby myhash field3 -1 #给field3字段自减1,相当于decr
(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变更的数据user name age, 尤其是用户信息之类的,经常变动的信息!hash更适合于对象的存储,String更加适合字符串的存储。

Zset(有序集合)

在set的基础上,增加了一个值,set k1 v1 zset k1 score1 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 salary 2500 xiaohong #添加三个用户
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 500 superbrother
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf #显示全部的用户 从小到大
1) "superbrother"
2) "zhangsan"
127.0.0.1:6379> zrevrange salary 0 -1 #从大到小
1) "zhangsan"
2) "superbrother"
127.0.0.1:6379> zrangebyscore salary -inf +inf
1) "superbrother"
2) "xiaohong"
3) "zhangsan"
###############################################################################
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores #显示全部的用户并且附带成绩
1) "superbrother"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
###############################################################################
127.0.0.1:6379> zrangebyscore salary -inf 2500 withscores #显示工资小于2500员工的升序                                                           排序!
1) "superbrother"
2) "500"
3) "xiaohong"
4) "2500"
127.0.0.1:6379> zrange salary 0 -1
1) "superbrother"
2) "xiaohong"
3) "zhangsan"
###############################################################################
127.0.0.1:6379> zrem salary xiaohong  #移除有序集合中指定的元素。
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "superbrother"
2) "zhangsan"
127.0.0.1:6379> zcard salary #获取有序集合中的个数
(integer) 2
127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 kuangshen
(integer) 2
127.0.0.1:6379> zcount myset 1 3
(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2
###############################################################################
127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 kuangshen
(integer) 2
127.0.0.1:6379> zcount myset 1 3  #获取指定区间的成员数量
(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2

案例思路:set排序 存储班级 成绩表,工资表排序!

普通消息,1.重要消息,2.普通消息。带权重进行判断。

排行榜应用实现,取top n测试。

三种特殊数据类型

geospatial (地理位置)

朋友的定位,附近的人,打车距离计算

redis的geo在redis3.2版本就推出了!这个功能可以推算出地理位置的信息,两地之间的距离,方圆几里的人。

只有六个命令

getadd

添加地理位置#规则 两级无法直接添加,我们一般会下载城市数据,直接通过java程序导入!

有效的经度从-180度到180度。

有效的维度从-85.05112878度到85.05112878度。

当坐标位置超出上述指定范围时,该命令将会返回一个错误。

如果经纬度写反,就会报错。

127.0.0.1:6379> geoadd china:city 39.90 116.40 beijin
(error) ERR invalid longitude,latitude pair 39.900000,116.400000
###############################################################################
#参数 key 值() 添加
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
geopos
127.0.0.1:6379> geopos china:city beijing #获取指定城市的经度和维度
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
127.0.0.1:6379> 
geodist

两个之间的距离
单位:

m 表示单位为米。

km表示单位为千米。

mi表示单位为英里。

ft表示单位为英尺。

127.0.0.1:6379> geodist china:city beijing shanghai km 北京到上海的直线距离,单位为km。
"1067.3788"
georadius

以给定的经纬度为中心,找出某一半径内的元素
我附近的人?(获取所有附近的人的地址,定位!)通过半径来查询!
获取指定数量的人,200
所有的数据都应该录入:china:city,才会让结果更加精确。

127.0.0.1:6379> georadius china:city 120 30 1000 km #以120,30这个经纬度为中心,寻找方圆1000km以内的城市
1) "shanghai"
127.0.0.1:6379> georadius china:city 120 30 1000 km withdist #显示到中间距离的位置
1) 1) "shanghai"
   2) "196.2512"
127.0.0.1:6379> georadius china:city 120 30 1000 km withcoord #显示他人的定位信息
1) 1) "shanghai"
   2) 1) "121.47000163793563843"
      2) "31.22999903975783553"
127.0.0.1:6379> georadius china:city 120 30 1000 km withcoord count 1 #筛选出指定的结果(数量)
1) 1) "shanghai"
   2) 1) "121.47000163793563843"
      2) "31.22999903975783553"
gearadiusbymember

找出位于指定元素周围的其他元素

127.0.0.1:6379> georadiusbymember china city beijing 1000 km

geohash

返回一个或多个位置元素的geohash表示

该命令将返回11个字符的geohash字符串!

将二维的经纬度转化为一维的字符串,如果两个字符串越接近,那么则距离越近!

127.0.0.1:6379> geohash china:city beijing shanghai
1) "wx4fbxxfke0"
2) "wtw3sj5zbj0"

geo底层的实现原理其实就是Zset!,我们可以使用Zset命令来操作geo!

127.0.0.1:6379> zrange china:city 0 -1 #查看地图中全部元素
1) "shanghai"
2) "beijing"
127.0.0.1:6379> zrem china:city beijing #移除指定元素
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "shanghai"

hyperloglog

什么是基数

A {1,3,5,6,7,8,9}

B {1,2,4,5,7}

基数(不重复的元素) = 3 ,可以接受误差!

统计基数的算法

优点:占用的内存是固定的,2^64不同的元素基数,只需要费112kb内存,如果从内存的角度来比较的话,他就是首选。

应用:

网页的uv(访问量:一个人访问一个网站多次,但还是算作 一个人!)

传统的方式,set保存用户id,然后就可以统计set中的元素数量作为标准判断!

这个方式如果保存大量的用户id,就会比较麻烦,我们的目的是为了计数,而不是保存用户id;

0.81%错误率,统计UV任务,可以忽略不计。

127.0.0.1:6379> pfadd mykey a b c d e f g h #创建第一组元素 mykey
(integer) 1
127.0.0.1:6379> pfcount mykey #统计mykey元素的基数数量
(integer) 8
127.0.0.1:6379> pfadd mykey2 i j z x b d f g  #创建第二组元素mykey2
(integer) 1
127.0.0.1:6379> pfmerge mykeye mykey mykey2 #合并两组mykey mykey2 =>mykeye 并集
OK
127.0.0.1:6379> pfcount mykey3
(integer) 0
127.0.0.1:6379> pfcount mykeye #看并集的数量
(integer) 12

bitmaps

位存储

统计用户信息,活跃,不活跃。登录,未登录。365打卡,未打卡。两个状态的,都可以使用bitmaps。

bitmaps位图,数据结构。都是操作二进制位来进行记录,就只有0和1两个状态。

365天 = 365bit 1字节 = 8bit 46个字节左右。

使用bitmap来记录周一到周日的打卡

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 1
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(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 3
(integer) 1
127.0.0.1:6379> getbit sign 6
(integer) 0
127.0.0.1:6379> 

统计操作,统计打卡的天数!

127.0.0.1:6379> bitcount sign #统计这周的打卡记录,就可以看到是否全勤
(integer) 3

事务

Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行的过程中,会按照顺序执行!

一次性、顺序性、排他性,执行一些列的命令。

Redis单条命令式保存原子性的,但是事务不保存原子性。

Redis事务没有隔离级别的概念。

所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会执行!

redis事务

开启事务(multi)

命令入队(……)

执行事务(exec)

正常执行事务

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1 #命令入队
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec #执行事务
1) OK
2) OK
3) "v2"
4) OK

放弃事务

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get k4
(nil)

编译型异常(代码有问题!命令有错),事务中所有的命令都不会被执行!

运行时异常(1/0),如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常!

Watch

悲观锁:

很悲观,认为什么时候都会出问题,无论做什么都会加锁!

乐观锁:

很乐观,认为什么时候都不会出问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据。

mysql :获取version,更新的时候比较version。

Redis监视测试

正常执行!

127.0.0.1:6379> watch money  #监视money对象
OK
127.0.0.1:6379> multi #事务正常结束,数据期间没有发生变动,这个时候就正常执行成功。
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20

测试多线程修改值,使用watch可以当做redis的乐观操作。

执行事务的时候,另外一个线程,如果修改了我们的值,这个时候,就会导致事务执行失败。

如果事务执行失败,就先解锁(unwatch),获取最新值。

Jedis

我们要使用java来操作Redis。

Jedis是redis官方推荐的java连接开发工具!使用java操作redis中间件!如果你要使用java操作redis,那么一定要对jedis十分熟悉。

首选点击file,改变版本

第一步:

第二步:

第三步:

导入相应的依赖

    <dependencies>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>4.2.3</version>
        </dependency>
<!--        fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.79</version>
        </dependency>
<!-- 报如下错误,导入下边依赖即可。
     SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defa-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-nop</artifactId>
            <version>1.7.2</version>
            <type>jar</type>
        </dependency>
    </dependencies>

2.编码测试:

连接数据库

package com.wyc;

import redis.clients.jedis.Jedis;

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.ping());
    }
}

操作命令

断开连接

Springboot整合

springboot操作数据:spring-data jpa jdbc mongodb redis

springData也是和springboot齐名的项目

说明:在springboot2.x之后,原来使用的jedis被替换为了lettuce。

jedis:采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用jedis pool连接池,更像BIO模式。

lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况,可以减少线程数据,更像NIO模式。

源码分析

    @Bean
    @ConditionalOnMissingBean( 
        name = {"redisTemplate"}  //我们可以自己定义一个redisTemplate来替换这个默认的!
    )
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        
        //默认的RedisTemplate没有过多的设置,redis对象是需要序列化的!
        //两个泛型都是<Object,Object>的类型,我们后面使用需要强制转化<String,Object>
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean  //由于string是redis中最常用的类型,所以说单独提出来了一个bean.
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        return new StringRedisTemplate(redisConnectionFactory);
    }

1.导入依赖

        <dependency>
<!--            操作redis-->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

2.配置连接

spring.redis.host=127.0.0.1
spring.redis.port=6379

3.测试

第一步:注入redisTemplate.

第二步:测试连接。

package com.wyc;

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.core.RedisTemplate;

@SpringBootTest
class SpringbootRedisApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        //opsForValue 操作字符串  类似于String

        //除了基本的操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务,和基本的crud

        //获取redis的连接对象
//        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//        connection.flushDb();
//        connection.flushAll();
         redisTemplate.opsForValue().set("mykey","狂神说java");
        System.out.println(redisTemplate.opsForValue().get("mykey"));
    }

}

Redis.conf详解

启动的时候,就通过配置文件来启动。

cd/usr/local/bin/superConfig 进入到自己的redis配置文件的目录。

vim redis.conf 进入到自己的redis配置文件

bind 127.0.0.1 #绑定的ip
protected-mode yes #保护模式
port 6379 #端口设置

通用general

daemonize yes #以守护进程的方式运行,默认是no,我们需要指定一个pid文件
logfile "" #日志的文件位置名
databases 16 #数据库的数量,默认是16个数据库
always-show-logo yes #是否总是显示logo

快照

持久化,在规定的时间内,执行了多少次操作,则会持久化到文件。rdb.aof

redis是内存数据库,如果没有持久化,那么数据断电即失。

#如果900s内,如果至少有一个1 key进行了修改,我们即进行持久化操作
sava 900 1
#如果300s内,如果至少10 key进行了修改,我们即进行持久化操作
save 300 10
#如果300s内,如果至少1000 key进行了修改,我们即进行持久化操作
save 60 10000
# 我们之后学习持久化,会自己定义这个测试。
stop-writes-on-bgsave-error yes #持久化如果出错,是否还需要继续工作
rdbcompression yes #是否压缩rdb文件,需要消耗一些cpu资源
rdbchecksum yes #保存rdb文件的时候,进行错位的检查校验
dir ./ #rdb 文件保存的目录

SECURITY 安全

127.0.0.1:6379> config set requirepass "123456"  #设置redis密码
OK
127.0.0.1:6379> config get requirepass #获取redis密码
1) "requirepass"
2) "123456"

限制

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持久化

rdb

1.save的规则满足的情况下,会自动触发rdb规则

2.执行flushall命令,也会触发我们的rdb规则

3.退出redis,也会产生rdb文件

备份就会自动生成一个dump.rdb

恢复rdb文件

1.只需要将rdb文件放在我们redis启动目录就可以,redis启动的时候会自动检查dump.rdb恢复其中的数据

2.查看需要存在的位置

127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin" #如果在这个目录下存在dump.rdb文件,启动就会自动恢复其中的数据

优点:

1.适合大规模的数据恢复

2.对数据的完整性要求不高

缺点:

1.需要一定的时间间隔进程操作,如果redis意外宕机了,这个最后一次修改数据就没有了。

2.fork进程的时候,会占用一定的内容空间。

AOF

默认是不开启的,我们需要手动进行配置,我们只需要将appendonly改为yes就开启了aof!

重启,redis就可以就可以生效了。

如果这个aof文件有错,这时候redis是重启不起来的,我们需要修复这个aof文件

redis给我们提供了一个工具 redis-check-aof –fix

如果修改之后,文件正常,重启就可以直接恢复了

优点

1.每一次修改都同步,文件的完整性会更加好。

  1. 每秒同步一次,可能会丢失一秒的数据
  2. 从不同步,效率最高的。

缺点

1.相对于数据文件来说,aof远远大于rdb,修复的速度也比rdb慢。

2.aof运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化。

Redis发布订阅

redis发布订阅是一种消息通信模式:发送者发送信息,订阅者接收消息。微信,微博,关注系统。

redis客户端可以订阅任意数量的频道

订阅/发布消息图:

第一个:消息发送者,第二个:频道 第三个:消息订阅者。

订阅端

127.0.0.1:6379> subscribe kuangshenshuo  #订阅一个频道kuangshenshuo
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "kuangshenshuo"
3) (integer) 1
#等待读取推送的信息
1) "message" #消息
2) "kuangshenshuo"   #那个频道的消息
3) "hello,kuangshen" #消息的具体内容
1) "message"
2) "kuangshenshuo"
3) "hello,redis"

发送端

127.0.0.1:6379> publish kuangshenshuo "hello,kuangshen" #发布者发布消息到频道
(integer) 1
127.0.0.1:6379> publish kuangshenshuo "hello,redis" #发布者发布消息到评到
(integer) 1

使用场景:

1.实时消息系统

2.事实聊天(频道当作聊天室,将信息回显给所有人即可)

3.订阅,关注系统都是可以的。

稍微复杂的场景我们就会使用消息中间件MQ。

Redis主从复制

主从复制,是指将一台redis服务器的数据,复制到其他的redis服务器。前者称为主节点,后者称为从节点;数据的复制是单向的,只能由主节点到从节点。Master以写为主,slave以读为主。

默认情况下,每台redis服务器都是主节点;

且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。

主从复制的作用主要包括

1.数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。

2.故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。

3.负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其时在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。

4.高可用(集群)基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的(宕机),原因如下:

1.从结构上,单个redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大;

2.从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G,也不能将所有内存用作Redis存储内存,一般来说,单台Redis最大使用内存不应该超过20G.

电商网站上的商品,一般都是一次上传,无数次浏览的,说专业点也就是“多读少写”。

主从复制,读写分离,80%的情况下都是在进行读操作,减缓服务器压力,架构中经常使用,一主二从。

只要在公司中,主从复制就是必须要使用的,因为在真实的项目不可能单机使用Redis.

环境配置

只配置从库,不用配置主库。

127.0.0.1:6379> info replication #查看当前库的信息
# Replication
role:master #角色 master
connected_slaves:0  #没有从机
master_replid:581f38a35816c6aa3d3627a141e676b33e450e17
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

复制3个配置文件,然后修改对应的信息

1.端口

2.pid名字

3.log文件名字

4.dump.rdb名字

修改完毕之后,启动我们的3个redis服务器,可以通过进程信息查看。

一主二从

默认情况下,每台redis服务器都是主节点。我们一般情况下只用配置从机就好了

认老大,一主(79)二从(80,81)

命令配置
#slaveof 127.0.0.1 6379

真实的主从配置应该在配置文件中配置,这样的话是永久的。如果使用命令配置是暂时的

从机只能读取内容。

层层链路

上一个M连接一个S!

这个时候也可以完成我们的主从复制

如果没有老大了,这个时候能不能选择一个老大出来呢。

谋朝篡位

如果主机断开了连接,我们可以使用SLAVEOF no one 让自己变成主机,其他的节点就可以手动连接到最新的这个主节点(手动)如果这个时候老大修复了,那就重新连接。

哨兵模式

主从切换技术的方法是:当主服务器宕机后,需要手动把一台服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。Redis从2.8开始正式提供了Sentinel(哨兵)架构来解决这个问题。

谋朝篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库

哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行,其原理是哨兵通过发送命令,等待Redis服务器相应,从而监控运行的多个Redis实例。

Redis缓存穿透和雪崩

布隆过滤器

缓存空对象

缓存击穿

缓存雪崩

posted on 2022-08-09 16:00  super-brother  阅读(34)  评论(0编辑  收藏  举报