zoukankan      html  css  js  c++  java
  • Redis 狂神说 笔记

    1.Redis

    Nosql概述

    为什么使用noSql

    因为数据的访问量越来越大,单靠关系型数据库已经无法支撑用户需求,所以架构也在用户的需求下一步步进行演进。

    1、单机Mysql时代

    在这里插入图片描述

    90年代,一个网站的访问量一般不会太大,单个数据库完全够用。随着用户增多,网站出现以下问题

    • 数据量增加到一定程度,单机数据库就放不下了

    • 数据的索引(B+ Tree),一个机器内存也存放不下

    • 访问量变大后(读写混合),一台服务器承受不住。

    2、Memcached(缓存) + Mysql + 垂直拆分(读写分离)

    网站80%的情况都是在读,每次都要去查询数据库的话就十分的麻烦!所以说我们希望减轻数据库的压力,我们可以使用缓存来保证效率!

    在这里插入图片描述

    优化过程经历了以下几个过程:

    优化数据库的数据结构和索引(难度大)

    文件缓存,通过IO流获取比每次都访问数据库效率略高,但是流量爆炸式增长时候,IO流也承受不了

    MemCache,当时最热门的技术,通过在数据库和数据库访问层之间加上一层缓存,第一次访问时查询数据库,将结果保存到缓存,后续的查询先检查缓存,若有直接拿去使用,效率显著提升。

    3、分库分表 + 水平拆分 + Mysql集群

    在这里插入图片描述

    4、如今最近的年代

    如今信息量井喷式增长,各种各样的数据出现(用户定位数据,图片数据等),大数据的背景下关系型数据库(RDBMS)无法满足大量数据要求。Nosql数据库就能轻松解决这些问题。

    目前一个基本的互联网项目

    在这里插入图片描述

    为什么要用NoSQL ?

    用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式增长!
    这时候我们就需要使用NoSQL数据库的,Nosql可以很好的处理以上的情况!

    什么是Nosql
    NoSQL = Not Only SQL(不仅仅是SQL)

    Not Only Structured Query Language

    关系型数据库:列+行,同一个表下数据的结构是一样的。

    非关系型数据库:数据存储没有固定的格式,并且可以进行横向扩展。

    nosql特点

    NoSQL泛指非关系型数据库,随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区,暴露出来很多难以克服的问题,NoSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的。Nosql特点

    1. 方便扩展(数据之间没有关系,很好扩展!)
    2. 大数据量高性能(Redis一秒可以写8万次,读11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)
    3. 数据类型是多样型的!(不需要事先设计数据库,随取随用)
    4. 传统的 RDBMS 和 NoSQL
    传统的 RDBMS(关系型数据库)
    - 结构化组织
    - SQL
    - 数据和关系都存在单独的表中 row col
    - 操作,数据定义语言
    - 严格的一致性
    - 基础的事务
    - ...
    
    Nosql
    - 不仅仅是数据
    - 没有固定的查询语言
    - 键值对存储,列存储,文档存储,图形数据库(社交关系)
    - 最终一致性
    - CAP定理和BASE
    - 高性能,高可用,高扩展
    - ...
    

    了解:3V + 3高

    大数据时代的3V :主要是描述问题

    1. 海量Velume
    2. 多样Variety
    3. 实时Velocity

    大数据时代的3高 : 主要是对程序的要求

    1. 高并发
    2. 高可扩
    3. 高性能

    真正在公司中的实践:NoSQL + RDBMS 一起使用才是最强的。

    nosql四大分类

    KV键值对

    1. redis
    2. tair
    3. memcache

    文档型数据库

    1. mongoDB
      • 基于分布式文件存储的数据库。C++编写,用于处理大量文档。
      • MongoDB是RDBMS和NoSQL的中间产品。MongoDB是非关系型数据库中功能最丰富的,NoSQL中最像关系型数据库的数据库。
    2. ConthDB

    列存储数据库

    • HBase(大数据必学)
    • 分布式文件系统

    图关系数据库

    用于广告推荐,社交网络,实现知识图谱等

    • Neo4j、InfoGrid

    以阿里巴巴网站为例子,SQL和NOSQL的应用场景如下:

    # 商品信息
    - 一般存放在关系型数据库:Mysql,阿里巴巴使用的Mysql都是经过内部改动的。
    
    # 商品描述、评论(文字居多)
    - 文档型数据库:MongoDB
    
    # 图片
    - 分布式文件系统 FastDFS
    - 淘宝:TFS
    - Google: GFS
    - Hadoop: HDFS
    - 阿里云: oss
    
    # 商品关键字 用于搜索
    - 搜索引擎:solr,elasticsearch
    - 阿里:Isearch 多隆
    
    # 商品热门的波段信息
    - 内存数据库:Redis,Memcache
    
    # 商品交易,外部支付接口
    - 第三方应用
    

    Redis数据库是KV键值对型数据库。

    主要优点是:查找快

    缺点是:数据无结构化通常被当做字符串或者二进制数据。

    针对他的主要优点,最典型的应用场景是内容缓存,主要用于处理大量数据的高访问负载和一些日志系统等。

    推荐阅读:阿里云的这群疯子https://yq.aliyun.com/articles/653511

    Redis基本知识

    windows安装redis

    官网:https://redis.io/

    中文官网:http://www.redis.cn/

    推荐使用Linux服务器学习。windows版本的Redis已经停更很久了

    image-20210518094246396

    windows 在 https://github.com/MicrosoftArchive/redis/releases/tag/win-3.2.100 下载安装包

    下载解压后只有5M。说明redis很小

    image-20210518094410232

    beanmark是测试性能的,aof后面会讲

    开启redis-server.exe,打开redis-cli测试

    测试结果

    image-20210518094428759

    image-20210518094438309

    linux安装redis

    1. 在官网下载安装包 https://redis.io/

    2. 把安装包移动到服务器上

    3. 移动安装包到 /opt/ 目录,

    4. 解压安装包 tar -zxvf xxx

      image-20210518104014551

    5. 安装运行环境 gcc-c++

      yum install gcc-c++
      
      make
      
      make install
      
    6. 进入/usr/local/bin,类似于windows的默认安装路径,把解压后的配置文件复制到/usr/local/bin/cyogurt目录下,修改配置文件,因为redis默认不是后台运行的

      image-20210518104248787

    7. 运行redis测试

      [root@centos1 bin]# redis-server cyogurt/redis.conf #没有提示
      

      image-20210518104350070

      image-20210518104458035

    8. 退出redis

      image-20210518104713961

    测试Redis性能

    使用redis-benchmark进行性能测试

    image-20210518112547762

    image-20210518112343462

    redis的一些基础命令

    redis默认有16个数据库,默认选择的是第0个

    image-20210518201122583

    16个数据库为:DB 0~DB 15,数据库之间键是隔离的不会产生同名冲突。

    1. 使用 select n 命令切换,n: 0~15
    2. 使用 dbsize 查看数据库大小,和key数量相关
    3. 使用 keys * 查看当前数据库所有的键
    4. 使用 flushdb 清空当前数据库
    5. 使用 flushall 清空所有的数据库

    测试如下

    127.0.0.1:6379> set name iandf
    OK
    127.0.0.1:6379> keys *
    1) "name"
    127.0.0.1:6379> dbsize
    (integer) 1
    127.0.0.1:6379> select 1
    OK
    127.0.0.1:6379[1]> keys *
    (empty array)
    127.0.0.1:6379[1]> set name yogurt
    OK
    127.0.0.1:6379[1]> get name
    "yogurt"
    127.0.0.1:6379[1]> flushdb
    OK
    127.0.0.1:6379[1]> get name
    (nil)
    127.0.0.1:6379[1]> select 0
    OK
    127.0.0.1:6379> keys *
    1) "key:__rand_int__"
    2) "counter:__rand_int__"
    3) "mylist"
    4) "myhash"
    5) "name"
    127.0.0.1:6379> select 1
    OK
    127.0.0.1:6379[1]> flushall
    OK
    127.0.0.1:6379[1]> select 0
    OK
    127.0.0.1:6379> keys *
    (empty array)
    127.0.0.1:6379> exit
    

    基本命令补充

    1. move key n 把key移动到第n个数据库

      image-20210518203905128

    2. expire key second 设置key的过期时间 ttl key 查看key还有多少存活时间

      image-20210518204118007

    3. type key 查看key的类型

      127.0.0.1:6379> keys age
      1) "age"
      127.0.0.1:6379> type age
      string
      
    4. exists key... 查看当前key是都存在,如果有多个key,只要有一个key存在就返回 1 0表示不存在,1表示存在

      127.0.0.1:6379> exists name
      (integer) 0
      127.0.0.1:6379> exists age
      (integer) 1
      127.0.0.1:6379> exists age name
      (integer) 1
      127.0.0.1:6379> exists name age
      (integer) 1
      
    5. del key... 删除键值对

      127.0.0.1:6379> keys *1) "age"127.0.0.1:6379> del age(integer) 1127.0.0.1:6379> keys *(empty array)
      

    redis是单线程的

    Redis是单线程的,Redis是基于内存操作的。所以Redis的性能瓶颈不是CPU,而是机器内存和网络带宽。

    为什么单线程的Redis的速度如此快呢,性能这么高呢?QPS达到10W+

    首先多线程之间的切换是需要时间的,也就是说一般需要切换线程都是需要进行一些I/O操作的,但是Redis数据都是放在内存的,所以CPU不会因为Redis和硬盘打交道,所以对于Redis来说,没有上下文的切换效率是最高的。

    redis的基本使用

    简介

    image-20210519095327851

    REDIS支持的5种类型

    • strings
    • hashes
    • lists
    • sets
    • sorted sets

    5种基本数据类型

    Strings 的使用

    • appen key value 向指定的key的value后追加字符串

      127.0.0.1:6379> set key name
      OK
      127.0.0.1:6379> get key
      "name"
      127.0.0.1:6379> append key iandf
      (integer) 9
      127.0.0.1:6379> get key
      "nameiandf"
      
    • STRLEN key 获取key保存值的字符串长度

    • GETRANGE key start end 按起止位置获取字符串(闭区间,起止位置都取)

    • SETRANGE key offset value 用指定的value 替换key中 offset开始的值

      127.0.0.1:6379> set name abcdef
      OK
      127.0.0.1:6379> strlen name 
      (integer) 6
      127.0.0.1:6379> getrange name 0 3
      "abcd" 
      127.0.0.1:6379> setrange name 2 xx
      (integer) 6
      127.0.0.1:6379> get name
      "abxxef"
      
    • GETSET key value 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。

      127.0.0.1:6379> get name
      "abxxef"
      127.0.0.1:6379> getset name aa
      "abxxef"
      127.0.0.1:6379> get name
      "aa"
      127.0.0.1:6379> keys *
      1) "name"
      127.0.0.1:6379> getset name1 iandf  #如果没有就创建key并返回空
      (nil)
      127.0.0.1:6379> get name1
      "iandf"
      
    • SETNX key value 仅当key不存在时进行set

      127.0.0.1:6379> get name1
      "iandf"
      127.0.0.1:6379> setnx name1 aa
      (integer) 0 #fail 存在的时候返回0 
      127.0.0.1:6379> setnx name2 yogurt
      (integer) 1 #sucess 不存在的时候返回1
      127.0.0.1:6379> get name2
      "yogurt"
      
    • SETEX key seconds value set 键值对并设置过期时间

      127.0.0.1:6379> get name2
      "yogurt"
      127.0.0.1:6379> setex name2 20 yogurt
      OK
      127.0.0.1:6379> ttl name2
      (integer) 14
      
    • MSET/MGET key1 value1 [key2 value2..] 批量set/get键值对

      127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
      OK
      127.0.0.1:6379> mget k1 k2 k3
      1) "v1"
      2) "v2"
      3) "v3"
      
    • MSETNX key1 value1 [key2 value2..] 批量设置键值对,仅当参数中所有的key都不存在时执行

      127.0.0.1:6379> MSETNX k5 v5 k1 v1
      (integer) 0
      127.0.0.1:6379> keys *
      1) "k2"
      2) "name"
      3) "k4"
      4) "k3"
      5) "name1"
      6) "k1"
      
    • incr/decr key value 将指定key的value数值进行+1/-1(仅对于数字)

      127.0.0.1:6379> set views 0
      OK
      127.0.0.1:6379> get views
      "0"
      127.0.0.1:6379> incr views
      (integer) 1
      127.0.0.1:6379> incr views
      (integer) 2
      127.0.0.1:6379> get views
      "2"
      127.0.0.1:6379> decr views
      (integer) 1
      127.0.0.1:6379> get views
      "1"
      
    • INCRBY/DECRBY key n 按指定的步长 n 对数值进行加减

      127.0.0.1:6379> incrby views 5(integer) 6127.0.0.1:6379> decrby views 4(integer) 2127.0.0.1:6379> incrbyfloat views 0.5 #为数值加上浮点型数值"2.5"
      

    String的应用场景

    • 文章的阅读数

    • 对象存储缓存 set XX:{id}:xx

      127.0.0.1:6379> set blog:140:views 0 #初始化id为140博客的观看量OK127.0.0.1:6379> incr blog:140:views #有人来看这篇博客,浏览量加一(integer) 1 #views的当前值
      

    lists 的使用

    和数据结构里面的LIST一样,都有增删改查等方法,只是命令不同。

    Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)

    一个列表最多可以包含 2^32 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。

    首先我们列表,可以经过规则定义将其变为队列、栈、双端队列等

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VPvbIltc-1597890996518)(狂神说 Redis.assets/image-20200813114255459.png)]

    LPUSH/RPUSH key value1[value2..] 从左边/右边向列表中PUSH值(一个或者多个)。

    127.0.0.1:6379> LPUSH list v1 #向队列的左边添加一个元素。类似于头插(integer) 1127.0.0.1:6379> LPUSH list v2(integer) 2127.0.0.1:6379> LPUSH list v3(integer) 3127.0.0.1:6379> LRange list 0 -1 #输出list中指定范围的元素 -1表示末尾1) "v3"2) "v2"3) "v1"127.0.0.1:6379> RPUSH list v4 #向list的末尾添加一个元素,类似于尾插(integer) 4127.0.0.1:6379> LRange list 0 -1 1) "v3"2) "v2"3) "v1"4) "v4"
    

    LINSERT key BEFORE|AFTER pivot value 在指定列表元素的前/后 插入value

    127.0.0.1:6379> lrange list 0 -11) "v3"2) "v2"3) "v1"127.0.0.1:6379> linsert list before v2 v2.5(integer) 4127.0.0.1:6379> lrange list 0 -11) "v3"2) "v2.5"3) "v2"4) "v1"
    

    127.0.0.1:6379> LPOP list 1 #从list的头部开始删除1个预算内宿1) "v3"127.0.0.1:6379> RPOP list 2 #从list的尾部开始删除2个元素1) "v4"2) "v1"127.0.0.1:6379> LRange list 0 -11) "v2"
    

    LREM key count value List中是允许value重复的 count > 0:从头部开始搜索 然后删除指定的value 至多删除count个 count < 0:从尾部开始搜索删除一个指定的值 count = 0:删除列表中所有的指定value。

    image-20210519113154346

    LSET key index value 通过索引为元素设值

    LTRIM key start end 通过下标截取(改变list原有的值)指定范围内的列表 闭区间

    127.0.0.1:6379> lrange list 0 -11) "v3"2) "v2"3) "v1"127.0.0.1:6379> clear127.0.0.1:6379> ltrim list 1 2 OK127.0.0.1:6379> lrange list 0 -11) "v2"2) "v1"127.0.0.1:6379> LSET list 0 votherOK127.0.0.1:6379> lrange list 0 -11) "vother"2) "v1"
    

    LRANGE key start end 获取list中的起止元素

    LINDEX key index 通过索引获取列表元素

    LLEN key 查看列表长度

    127.0.0.1:6379> lrange list 0 -11) "vother"2) "v1"127.0.0.1:6379> lindex list 1"v1"127.0.0.1:6379> llen list(integer) 2
    

    其他

    RPOPLPUSH source destination 将列表的尾部(右)最后一个值弹出,并返回,然后加到另一个列表的头部

    image-20210519114052073

    小结

    • list实际上是一个链表,before Node after , left, right 都可以插入值
    • 如果key不存在,则创建新的链表
    • 如果key存在,新增内容
    • 如果移除了所有值,空链表,也代表不存在
    • 在两边插入或者改动值,效率最高!修改中间元素,效率相对较低

    应用

    消息排队!消息队列(Lpush Rpop),栈(Lpush Lpop)

    set 的使用

    set是一个无序的不能有重复元素的集合

    Redis 中 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。

    集合中最大的成员数为 2^32 - 1 (4294967295, 每个集合可存储40多亿个成员)。

    SADD key member1[member2..] 向集合中无序增加一个/多个成员

    127.0.0.1:6379> sadd set v1(integer) 1127.0.0.1:6379> sadd set v1 #重复插入元素失败(integer) 0127.0.0.1:6379> sadd set v2(integer) 1127.0.0.1:6379> sadd set v3(integer) 1127.0.0.1:6379> smembers set #获取的元素顺序和插入顺序无关1) "v2"2) "v1"3) "v3"
    

    SPOP key [count] 随机移除并返回集合中的成员

    image-20210519121122405

    SREM key member1[member2..] 移除集合中一个/多个成员

    image-20210519121434883

    SISMEMBER key member 查询member元素是否是集合的成员,结果是无序的, 0表示无,1表示有

    SMEMBERS key 返回集合中所有的成员

    image-20210519122234126

    SCARD key 获取集合的成员数

    image-20210519131421584

    其他

    SRANDMEMBER key [count] 随机返回集合中count个成员,count缺省值为1

    127.0.0.1:6379> SRANDMEMBER set"v3"127.0.0.1:6379> SRANDMEMBER set"v2"127.0.0.1:6379> SRANDMEMBER set 21) "v2"2) "v10"127.0.0.1:6379> SRANDMEMBER set 21) "v1"2) "v4"
    

    SMOVE source destination member 将source集合的成员member移动到destination集合

    127.0.0.1:6379> sadd set1 v10(integer) 1127.0.0.1:6379> SMEMBERS set1) "v2"127.0.0.1:6379> SMEMBERS set11) "v10"127.0.0.1:6379> smove set set1 v10(integer) 0127.0.0.1:6379> smove set set1 v2(integer) 1127.0.0.1:6379> SMEMBERS set(empty array)127.0.0.1:6379> SMEMBERS set11) "v2"2) "v10"
    

    差集,交集,并集

    SDIFF key1[key2..] 返回所有集合的差集 key1- key2 - …

    SINTER key1 [key2..] 返回所有集合的交集

    SUNION key1 [key2..] 返回所有集合的并集

    image-20210519131208420

    hash 的使用

    Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。

    Set就是一种简化的Hash,只变动key,而value使用默认值填充。可以将一个Hash表作为一个对象进行存储,表中存放对象的信息。

    他的数据结构相当于 key - {field:value}

    HSET key field1 value1 [field2 value2..] 同时将多个 field-value (域-值)对设置到哈希表 key 中。

    127.0.0.1:6379> hset myhash field1 value1(integer) 1127.0.0.1:6379> hset myhash field1 value2 #返回的是0,说明hash里面存在该字段,但会覆盖vlaue值(integer) 0127.0.0.1:6379> hset myhash f2 v2 f3 v3 #可以一次性添加多个(integer) 2127.0.0.1:6379> hgetall myhash #获取hash里面所有的键值对1) "field1"2) "value2"3) "f2"4) "v2"5) "f3"6) "v3"
    

    HSETNX key field value 只有在字段 field 不存在时,设置哈希表字段的值。

    image-20210519150333319

    HDEL key field1 [field2..] 删除哈希表key中一个/多个field字段

    127.0.0.1:6379> hgetall myhash1) "field1"2) "value2"3) "f2"4) "v2"5) "f3"6) "v3"7) "f4"8) "v4"127.0.0.1:6379> hdel myhash field1 (integer) 1127.0.0.1:6379> hdel myhash field f10 f2 #只有f2是存在的(integer) 1 #返回删除的键值对数量
    

    HSET key field value 可以修改已经存在的field的值

    HINCRBY key field n 为哈希表 key 中的指定字段的整数值加上增量n,并返回增量后的结果 只适用于整数型字段

    127.0.0.1:6379> hset myhash f5 1(integer) 1127.0.0.1:6379> hget myhash f5"1"127.0.0.1:6379> HINCRBY myhash f5 6(integer) 7127.0.0.1:6379> hget myhash f5"7"127.0.0.1:6379> hset myhash f5 0(integer) 0127.0.0.1:6379> hget myhash f5"0"
    

    HEXISTS key field 查看哈希表 key 中,指定的字段是否存在。

    HGET key field 获取存储在哈希表中指定字段的值

    HGETALL key 获取在哈希表key 的所有字段和值

    HKEYS key 获取哈希表key中所有的字段

    HLEN key 获取哈希表中字段的数量

    HVALS key 获取哈希表中所有值

    127.0.0.1:6379> hvals myhash1) "v3"2) "v4"127.0.0.1:6379> hkeys myhash1) "f3"2) "f4"127.0.0.1:6379> hgetall myhash1) "f3"2) "v3"3) "f4"4) "v4"127.0.0.1:6379> hlen myhash(integer) 2127.0.0.1:6379> hget myhash f4"v4"127.0.0.1:6379> hget myhash f2(nil) #不存在返回空127.0.0.1:6379> hexists myhash f4(integer) 1127.0.0.1:6379> hexists myhash f0(integer) 0 #不存在返回0
    

    Zset 有序集合的使用

    不同的是每个元素都会关联一个double类型的分数(score)。redis正是通过分数来为集合中的成员进行从小到大的排序。

    score相同:按字典顺序排序

    有序集合的成员是唯一的,但分数(score)却可以重复。

    ZADD key score member1 [score2 member2] 向有序集合添加一个或多个成员,或者更新已存在成员的分数

    127.0.0.1:6379> zadd salary 200 p1(integer) 1127.0.0.1:6379> zadd salary 1000 p2(integer) 1127.0.0.1:6379> zadd salary 20000 p3 #score必须是浮点数(error) ERR value is not a valid float127.0.0.1:6379> zadd salary 20000 p3(integer) 1127.0.0.1:6379> zcard salary(integer) 3
    

    ZREM key member1 [member2..] 移除有序集合中一个/多个成员

    127.0.0.1:6379> zrem salary p1(integer) 1 #返回移除的数量127.0.0.1:6379> zrange salary 0 -1 withscores1) "p2"2) "1000"3) "p3"4) "20000"
    

    ZINCRBY key n member 有序集合中对指定成员的分数加上增量 n

    127.0.0.1:6379> ZINCRBY salary 500 p2"1500" #返回的是增加后key对应的score127.0.0.1:6379> zrange salary 0 -1 withscores1) "p2"2) "1500"3) "p3"4) "20000"
    

    ZRANK key member 返回有序集合中指定成员的索引

    ZRANGE key start end 通过索引区间返回有序集合成指定区间内的成员

    ZRANGEBYSCORE key min max 通过分数返回有序集合指定区间内的成员-inf 和 +inf分别表示最小最大值,闭区间[] 按分数升序排列

    ZREVRANGEBYSCORRE key max min 返回有序集中指定分数区间内的成员,分数从高到低排序,按分数降序排列

    127.0.0.1:6379> zrange salary 0 -1 withscores 1) "p2"2) "1500"3) "p3"4) "20000"127.0.0.1:6379> zrangebyscore salary 1500 20000 withscores #闭区间1) "p2"2) "1500"3) "p3"4) "20000"127.0.0.1:6379> zrevrangebyscore salary +inf -inf withscores1) "p3"2) "20000"3) "p2"4) "1500"127.0.0.1:6379> zrank salary p3(integer) 1 #zset 里面的索引
    

    应用案例:

    • set排序 存储班级成绩表 工资表排序!
    • 普通消息,1.重要消息 2.带权重进行判断
    • 排行榜应用实现,取Top N测试

    三种特殊类型

    geospatial 的使用

    使用经纬度定位地理坐标并用一个有序集合zset保存,所以zset命令也可以使用

    geoadd key longitud(经度) latitude(纬度) member [..] 将一个或多个具体经纬度的坐标存入一个有序集合

    127.0.0.1:6379> geoadd china:city 114.31 30.52 wuhan(integer) 1127.0.0.1:6379> geoadd china:city 121.48 31.22 shanghai(integer) 1127.0.0.1:6379> geoadd china:city 113 28.21 changsha(integer) 1127.0.0.1:6379> geoadd china:city 113.23 23.16 guangzhou 114.07 22.62 shenzhen(integer) 2127.0.0.1:6379> geoadd china:city 116.46 39.92 beijing(integer) 1
    

    geopos key member [member..] 获取集合中的一个/多个成员坐标

    127.0.0.1:6379> geopos china:city beijing1) 1) "116.45999997854232788"   2) "39.9199990416181052"
    

    geodist key member1 member2 [unit] 返回两个给定位置之间的距离。默认以米作为单位。

    127.0.0.1:6379> geodist china:city beijing shanghai km"1068.4581"
    

    georadius key longitude latitude radius m|km|mi|ft [WITHCOORD][WITHDIST] [WITHHASH] [COUNT count]

    以给定的经纬度为中心, 返回集合包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。

    • [WITHCOORD] 显示符合元素的经度纬度
    • [WITHDIST] 显示符合元素与给定经纬度的直线距离,单位与半径单位相同
    • [COUNT count] 当有count的时候获取到的元素会按照直线距离升序排序
    127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord withdist 1) 1) "guangzhou"   2) "825.7153"   3) 1) "113.22999805212020874"      2) "23.1599994376353493"2) 1) "shenzhen"   2) "915.4546"   3) 1) "114.07000154256820679"      2) "22.61999990712862285"3) 1) "changsha"   2) "353.0147"   3) 1) "112.99999862909317017"      2) "28.21000044502112303"4) 1) "wuhan"   2) "418.0605"   3) 1) "114.31000024080276489"      2) "30.52000083053655999"127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord withdist count 2 #按照直线距离升序排序1) 1) "changsha"   2) "353.0147"   3) 1) "112.99999862909317017"      2) "28.21000044502112303"2) 1) "wuhan"   2) "418.0605"   3) 1) "114.31000024080276489"      2) "30.52000083053655999"
    

    GEORADIUSBYMEMBER key member radius...

    功能与GEORADIUS相同,只是中心位置不是具体的经纬度,而是使用集合中已有的成员作为中心点。符合的元素中也包含自己本身

    127.0.0.1:6379> GEORADIUSBYMEMBER china:city wuhan 500 km withcoord withdist count 21) 1) "wuhan"   2) "0.0000"   3) 1) "114.31000024080276489"      2) "30.52000083053655999"2) 1) "changsha"   2) "286.5926"   3) 1) "112.99999862909317017"      2) "28.21000044502112303"
    

    geohash key member1 [member2..]

    返回一个或多个位置元素的Geohash表示。使用Geohash位置52点整数编码。

    127.0.0.1:6379> GEOHASH china:city wuhan changsha1) "wt3m95ypbh0"2) "wt027rkp4v0"
    

    注意

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

    指定单位的参数 unit 必须是以下单位的其中一个:

    • m 表示单位为米。

    • km 表示单位为千米。

    • mi 表示单位为英里。

    • ft 表示单位为英尺。

    hyperloglog 基数统计

    Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。

    因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。其底层使用string数据类型

    什么是基数?

    ​ 数据集中不重复的元素的个数。

    应用场景:

    ​ 网页的访问量(UV):一个用户多次访问,也只能算作一个人。

    传统实现,存储用户的id,然后每次进行比较。当用户变多之后这种方式及其浪费空间,而我们的目的只是计数,Hyperloglog就能帮助我们利用最小的空间完成。

    PFADD key element1 [elememt2..] 添加指定元素到 HyperLogLog 中

    PFCOUNT key [key] 返回给定 HyperLogLog 的基数估算值。

    PFMERGE destkey sourcekey [sourcekey..] 将多个 HyperLogLog 合并为一个 HyperLogLog

    127.0.0.1:6379> PFADD blog:01:views a b c d (integer) 1127.0.0.1:6379> PFADD blog:01:views a b c d  e(integer) 1127.0.0.1:6379> PFCOUNT blog:01:views #只计算不同元素的个数(integer) 5127.0.0.1:6379> PFADD blog:02:views b e f g a (integer) 1127.0.0.1:6379> PFCOUNT blog:02:views(integer) 5127.0.0.1:6379> PFMERGE test blog:01:views blog:02:viewsOK127.0.0.1:6379> PFCOUNT test #取并集,重复元素不算(integer) 7
    

    但是使用 HYPERLOGLOG 做基数统计会有一定的误差,如果允许容错,那么一定可以使用Hyperloglog ! 如果不允许容错,就使用set或者自己的数据类型即可 !

    bitmaps 位图

    使用位存储,信息状态只有 0 和 1

    Bitmap是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset),在bitmap上可执行AND,OR,XOR,NOT以及其它位操作。

    应用场景

    签到统计、状态统计

    setbit key offset value 为指定key的offset位设置值

    getbit key offset 获取offset位的值

    bitcount key [start end] 统计字符串被设置为1的bit数,也可以指定统计范围按字节

    一周签到测试

    127.0.0.1:6379> setbit sign 0 1 #输入周一0 ~ 周日6 的签到数据(integer) 0127.0.0.1:6379> setbit sign 1 0(integer) 0127.0.0.1:6379> setbit sign 2 0(integer) 0127.0.0.1:6379> setbit sign 3 1(integer) 0127.0.0.1:6379> setbit sign 4 0(integer) 0127.0.0.1:6379> setbit sign 5 1(integer) 0127.0.0.1:6379> setbit sign 6 1(integer) 0127.0.0.1:6379> setbit sign 7 0(integer) 0127.0.0.1:6379> getbit sign 7 #获取当天签到的信息(integer) 0127.0.0.1:6379> getbit sign 6(integer) 1127.0.0.1:6379> bitcount sign #获取一周签到的次数(integer) 4
    

    redis 事务

    简介

    Redis事务本质:一组命令的集合。

    ----------------- 队列 set set set 执行 -------------------

    事务中每条命令都会被序列化,执行过程中按顺序执行,不允许其他命令进行干扰。事务特性

    • 一次性 执行exec命令时,队列中的所有命令一起执行
    • 顺序性 执行过程中按顺序执行
    • 排他性 不允许其他命令进行干扰

    NOTE:

    1. Redis事务没有隔离级别的概念
    2. Redis单条命令是保证原子性的,但是事务不保证原子性!

    Redis事务操作过程

    • 开启事务(multi
    • 命令入队
    • 执行事务(exec

    所以事务中的命令在加入时都没有被执行,直到提交时才会开始执行(Exec)一次性按顺序执行完成。

    测试正常事务执行流程

    127.0.0.1:6379> multi #开启事务OK127.0.0.1:6379(TX)> sadd set 1QUEUED #命令入队127.0.0.1:6379(TX)> sadd set 2QUEUED127.0.0.1:6379(TX)> SMEMBERS setQUEUED127.0.0.1:6379(TX)> sadd set 3QUEUED127.0.0.1:6379(TX)> exec #执行事务1) (integer) 12) (integer) 13) 1) "1"   2) "2"4) (integer) 1
    

    取消事务

    127.0.0.1:6379> multiOK127.0.0.1:6379(TX)> sadd set1 1QUEUED127.0.0.1:6379(TX)> sadd set1 2QUEUED127.0.0.1:6379(TX)> SMEMBERS set1QUEUED127.0.0.1:6379(TX)> DISCARD #取消事务OK127.0.0.1:6379> keys * #没有set11) "myset"2) "set"
    

    事务错误

    代码语法错误(编译时异常)所有的命令都不执行

    127.0.0.1:6379> multiOK127.0.0.1:6379(TX)> set k1 v1QUEUED127.0.0.1:6379(TX)> set k2 v2QUEUED127.0.0.1:6379(TX)> error k3 #代码语法错误(error) ERR unknown command `error`, with args beginning with: `k3`, # 会报错但是不影响后续命令入队127.0.0.1:6379(TX)> set k3 v3QUEUED127.0.0.1:6379(TX)> exec(error) EXECABORT Transaction discarded because of previous errors. # 执行报错127.0.0.1:6379> get k1(nil) # 其他命令并没有被执行
    

    代码逻辑错误 (运行时异常) **其他命令可以正常执行 ** >>> 所以不保证事务原子性

    127.0.0.1:6379> multiOK127.0.0.1:6379(TX)> set test "v1"QUEUED127.0.0.1:6379(TX)> incr test #发生运行时异常,代码逻辑错误,其他命令可以被执行QUEUED127.0.0.1:6379(TX)> sadd set 1QUEUED127.0.0.1:6379(TX)> SMEMBERS setQUEUED127.0.0.1:6379(TX)> exec #其他命令正常执行1) OK2) (error) ERR value is not an integer or out of range3) (integer) 14) 1) "1"
    

    watch 实现乐观锁

    悲观锁:

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

    乐观锁:

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

    mysql实现乐观锁过程

    • 获取version
    • 更新的时候比较version

    redis使用watch key监控指定数据,相当于乐观锁加锁。

    如果指定的key在一个事务的执行过程中,被其他事务修改了,那么该事务就会执行失败

    正常测试

    127.0.0.1:6379> set money 100OK127.0.0.1:6379> set out 0OK127.0.0.1:6379> WATCH moneyOK127.0.0.1:6379> multiOK127.0.0.1:6379(TX)> DECRBY money 10QUEUED127.0.0.1:6379(TX)> INCRBY out 10QUEUED127.0.0.1:6379(TX)> exec1) (integer) 902) (integer) 10127.0.0.1:6379> UNWATCH #事务执行完后解锁,以便于再次加锁获取最新的值OK
    

    UNWATCH 事务执行完后解锁,以便于再次加锁获取最新的值

    UNWATCH刷新一个事务中已被监视的所有key。如果执行 EXEC 或者 DISCARD , 则不需要手动执行 UNWATCH。

    异常测试

    我们启动另外一个客户端模拟插队线程。测试多线程修改值,使用watch可以当做redis的乐观锁操作

    线程1:

    127.0.0.1:6379> watch moneyOK127.0.0.1:6379> multiOK127.0.0.1:6379(TX)> DECRBY money 10QUEUED127.0.0.1:6379(TX)> INCRBY out 10QUEUED#模拟执行到这里的时候线程二获得了执行权限
    

    模拟线程插队,线程2:

    127.0.0.1:6379> INCRBY money 1000(integer) 1100
    

    回到线程1,执行事务:

    127.0.0.1:6379(TX)> exec #线程一拿到了执行权限,但是执行事务失败,因为money被线程二修改了(nil)127.0.0.1:6379> get money # 线程2 修改生效,线程1事务执行失败,数值没有被修改"1100"127.0.0.1:6379> get out"0"
    

    操作redis

    Jedis操作redis

    连接redis

    使用jedis操作redis,首先让win连接上redis

    redis连接超时解决办法

    1. 导入依赖

      <!--导入jredis的包-->
      <dependency>
          <groupId>redis.clients</groupId>
          <artifactId>jedis</artifactId>
          <version>3.2.0</version>
      </dependency>
      
    2. 连接redis

      public static void main(String[] args) {
          System.out.println("hello jedis");
          Jedis jedis = new Jedis("10.116.80.5", 6379);
          System.out.println(jedis.ping());
      }
      
    3. 测试结果

      image-20210521095053515

    值得一提的是jedis的函数和redis命令都是一样的,以后自己测试即可

    redis事务

    事务正常执行

    public static void main(String[] args) {
        Jedis jedis = new Jedis("10.116.80.5", 6379);
        jedis.flushDB();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello","world");
        jsonObject.put("name","yogurt");
        String result = jsonObject.toJSONString();
        System.out.println("result -> " + result);
        //开启事务
        Transaction transaction = jedis.multi();
        try {
            //命令入队
            transaction.set("user1",result);
            transaction.set("user2",result);
            //事务执行
            transaction.exec();
        } catch (Exception e) {
            //发生异常事务回滚
            transaction.discard();
            System.out.println("user1 -> " +jedis.get("user1"));
            System.out.println("user2 -> " +jedis.get("user2"));
            e.printStackTrace();
        } finally {
            System.out.println("user1 -> " +jedis.get("user1"));
            System.out.println("user2 -> " +jedis.get("user2"));
            jedis.close();
        }
    }
    

    测试结果

    image-20210521100233121

    springboot操作redis

    导入依赖

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

    springboot 2.x后 ,原来使用的 Jedis 被 lettuce 替换。

    **jedis和lettuce的区别 : **

    • jedis:采用的直连,多个线程操作的话,是不安全的。如果要避免不安全,使用jedis pool连接池!更像BIO模式
    • lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况!可以减少线程数据了,更像NIO模式

    SpringData-Redis配置分析

    redis也是使用springboot的自动配置的,可以在自动配置的包里面找到redis的自动配置类

    image-20210521104446830

    那么就一定还存在一个RedisProperties类,所有在application.properties文件,配置相关redis的数据都会被绑定到这个类中

    image-20210521104925096

    SpringBoot2.x后默认使用Lettuce来替换Jedis,所以这里只有LettuceConnectionConfiguration生效,这里底层是使用lettuce来操作redis的。原因如下:

    image-20210521105226136

    RedisAutoConfiguratio 类中还有两个 Template bean

    • RedisTemplate
    • StringRedisTemplate

    当看到xxTemplate时可以对比RestTemplat、SqlSessionTemplate,通过使用这些Template来间接操作组件。那么这俩也不会例外。分别用于操作Redis和Redis中的String数据类型。

    在RedisTemplate上也有一个条件注解@ConditionalOnMissingBean(name = "redisTemplate"),说明我们是可以对其进行定制化的

    到这里为止,可以看RedisProperties类的定义来编写配置文件,编写配置文件的时候,有时候要配置lettuce而不是jedis,比如连接池。

    springboot中使用redis

    1. 导入依赖,上一节讲了

    2. 编写配置文件

      #配置redis
      spring.redis.host=10.116.80.5
      spring.redis.port=6379
      
    3. 使用RedisTemplate操作redis

      @Autowired
      private RedisTemplate redisTemplate;
      @Test
      void contextLoads() {
          /*
      
              redisTemplate 操作不同的数据类型,api和我们的指令是一样的
              opsForValue 操作字符串 类似String
              opsForList 操作List 类似List
              ...
      
              除了基本的操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务和基本的CRUD
      
              获取连接对象,进行一些对数据库进行清空的操作和关闭数据库连接的操作
              RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
              connection.flushDb();
              connection.flushAll();
              connection.close();
           */
          redisTemplate.opsForValue().set("k1","v1");
          System.out.println(redisTemplate.opsForValue().get("k1"));
      }
      
    4. 测试结果

      image-20210521110832001

    自定义Template 解决乱码

    以上的测试结果是正常的,但在控制台看数据的时候发现是乱码

    image-20210521112610815

    这时候就关系到存储对象的序列化和反序列化的问题,在网络中传输的对象也是一样需要序列化,否者就全是乱码

    看一下默认的 RedisTemplate 是怎么序列化的

    image-20210521112857932

    这里看到是有一个String的序列化器的但是没有赋值给keySerializer

    默认的序列化器是采用JDK序列化器

    image-20210521113058529

    所以上面说了是序列化器的问题,我们就可以自定义一个RedisTemplate

    RedisSerializer提供了多种序列化方案:直接调用RedisSerializer的静态方法来返回序列化器

    image-20210521113855278

    自定义RedisTemplate

    @Configuration
    public class RedisConfig {
    
        @Bean
        @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
        //返回的应该是RedisTemplate<String, Object>,因为key都是string类型的,方便后续操作
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<String, Object> template = new RedisTemplate<>();
    
            // key、hash的key 采用 String序列化方式
            template.setKeySerializer(RedisSerializer.string());
            template.setHashKeySerializer(RedisSerializer.string());
            // value、hash的value 采用 Jackson 序列化方式
            template.setValueSerializer(RedisSerializer.json());
            template.setHashValueSerializer(RedisSerializer.json());
            
            // 连接工厂,不必修改
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
    }
    

    再进行测试发现就不会乱码了

    image-20210521114543744

    自定义Redis工具类

    使用RedisTemplate需要频繁调用.opForxxx然后才能进行对应的操作,这样使用起来代码效率低下,工作中一般不会这样使用,而是将这些常用的公共API抽取出来封装成为一个工具类,然后直接使用工具类来间接操作Redis,不但效率高并且易用。

    工具类过长,放在 E:markDown文件后端基础常用的工具类和基类 edis.md 中


    typora-copy-images-to: upload


    redis进阶

    redis 配置文件详解

    容量单位不区分大小写,G和GB有区别

    # 1k => 1000 bytes
    # 1kb => 1024 bytes
    # 1m => 1000000 bytes
    # 1mb => 1024*1024 bytes
    # 1g => 1000000000 bytes
    # 1gb => 1024*1024*1024 bytes
    

    可以使用 include 引入更多的配置文件

    # If instead you are interested in using includes to override configuration
    # options, it is better to use include as the last line.
    #
    # include /path/to/local.conf
    # include /path/to/other.conf
    

    网络配置

    # IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES
    # JUST COMMENT OUT THE FOLLOWING LINE.
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    bind 127.0.0.1 -::1
    
    # you are sure you want clients from other hosts to connect to Redis
    # even if no authentication is configured, nor a specific set of interfaces
    protected-mode no #保护模式,如果想要其他的主机连接该主机的Redis就关闭它
    
    # Accept connections on the specified port, default is 6379 (IANA #815344).
    # If port 0 is specified Redis will not listen on a TCP socket.
    port 6379 #默认端口
    
    # By default Redis does not run as a daemon. Use 'yes' if you need it.
    # Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
    # When Redis is supervised by upstart or systemd, this parameter has no impact.
    daemonize yes #yes表示redis后台进程运行
    

    日志

    # 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 #默认日志级别
    
    # Specify the log file name. Also the empty string can be used to force
    # Redis to log on the standard output. Note that if you use standard
    # output for logging but daemonize, logs will be sent to /dev/null
    logfile "" #指定日志文件名字
    

    SNAPSHOTTING 快照

    由于Redis是基于内存的数据库,需要将数据由内存持久化到文件中

    持久化方式:

    • RDB
    • AOF

    持久化规则

    # Unless specified otherwise, by default Redis will save the DB:#   * After 3600 seconds (an hour) if at least 1 key changed#   * After 300 seconds (5 minutes) if at least 100 keys changed#   * After 60 seconds if at least 10000 keys changed## You can set these explicitly by uncommenting the three following lines.## save 3600 1    1小时至少有1个key被修改过 就持久化# save 300 100   5分钟至少有100个key被修改过 就持久化# save 60 10000  1分钟至少有10000个key被修改过 就持久化#持久化错误是否继续工作stop-writes-on-bgsave-error yes
    

    RDB相关

    #是否对rdb文件进行压缩rdbcompression yes#校验rdb文件rdbchecksum yes# rdb的持久化文件名dbfilename dump.rdb# rdb的文件路径dir ./
    

    Security模块

    # requirepass foobared requirepass xxx #配置密码
    

    客户端连接相关

    maxclients 10000  最大客户端数量maxmemory <bytes> 最大内存限制maxmemory-policy noeviction # 内存达到限制值的处理策略
    

    redis 中的默认的过期策略是 volatile-lru

    设置方式

    config set maxmemory-policy volatile-lru 
    

    maxmemory-policy 六种方式
    1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)

    2、allkeys-lru : 删除lru算法的key

    3、volatile-random:随机删除即将过期key

    4、allkeys-random:随机删除

    5、volatile-ttl : 删除即将过期的

    6、noeviction : 永不过期,返回错误

    AOF相关部分

    appendonly no #默认关闭aof模式,使用rdb模式appendfilename "appendonly.aof" #aof持久化文件名# appendfsync always #每次修改同步appendfsync everysec #每秒同步# appendfsync no     #不同步,由操作系统控制同步
    

    设置配置文件的方法

    • 直接对配置文件修改

    • 在命令行里面修改 重启redis后配置就会失效

      config set key value
      

    测试 修改密码

    image-20210521143158485

    redis持久化

    RDB redis database

    什么是RDB

    在指定时间间隔后,将内存中的数据集快照写入数据库 ;在恢复时候,直接读取快照文件,进行数据的恢复 ;

    在这里插入图片描述

    默认情况下, Redis 将数据库快照保存在名字为 dump.rdb的二进制文件中。文件名可以在配置文件中进行自定义。

    RDB工作原理

    在进行 RDB 的时候,redis 的主线程是不会做 io 操作的,主线程会 fork 一个子线程来完成该操作;

    • Redis 调用forks。同时拥有父进程和子进程。
    • 子进程将数据集写入到一个临时 RDB 文件中。
    • 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。

    这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益(因为是使用子进程进行写操作,而父进程依然可以接收来自客户端的请求。)

    在这里插入图片描述

    持久化触发机制

    1. save的规则满足的情况下,会自动触发rdb原则
    2. 执行flushall命令,也会触发我们的rdb原则
    3. 退出redis,也会自动产生rdb文件

    save

    使用 save 命令,会立刻对当前内存中的数据进行持久化 ,但是会阻塞,也就是不接受其他操作了;

    由于 save 命令是同步命令,会占用Redis的主进程。若Redis数据非常多时,save命令执行速度会非常慢,阻塞所有客户端的请求。

    在这里插入图片描述

    bgsave

    bgsave 是异步进行,进行持久化的时候,redis 还可以将继续响应客户端请求 ;

    bgsave和save对比

    命令 save bgsave
    IO类型 同步 异步
    阻塞? 是(阻塞发生在fock(),通常非常快)
    复杂度 O(n) O(n)
    优点 不会消耗额外的内存 不阻塞客户端命令
    缺点 阻塞客户端命令 需要fock子进程,消耗内存

    优缺点

    优点:

    1. 适合大规模的数据恢复
    2. 对数据的完整性要求不高

    缺点:

    1. 需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次修改的数据就没有了。
    2. fork进程的时候,会占用一定的内容空间。

    AOF append only file

    简介

    ​ AOF以日志的形式来记录每个写的操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

    	快照功能(RDB)并不是非常耐久(durable): 如果 Redis 因为某些原因而造成故障停机, 那么服务器将丢失最近写入、以及未保存到快照中的那些数据。 从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化。
    

    如果要使用AOF,需要修改配置文件:

    image-20210521151642587

    aof 原理

    image-20210521152644727

    如果appendonly.aof文件被人恶意篡改,那么可以使用redis给我们提供了一个工具redis-check-aof,使用命令如下redis-check-aof --fix

    重写规则

    aof默认就是文件的无限追加,文件会越来越大

    如果aof文件大于64m,太大了!fork一个新的进程来将我们的文件进行重写!

    image-20210521153111980

    优点和缺点

    appendonly no # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分所有的情况下,rdb完全够用!appendfilename "appendonly.aof" # 持久化的文件的名字# appendfsync always # 每次修改都会 sync。消耗性能appendfsync everysec # 每秒执行一次 sync,可能会丢失这1s的数据!# appendfsync no # 不执行 sync,这个时候操作系统自己同步数据,速度最快!
    

    优点:

    1、每一次修改都同步,文件的完整会更好!

    2、每秒同步一次,可能会丢失一秒的数据

    3、从不同步,效率最高的!

    缺点:

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

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

    rdb 和 aof 对比

    RDB AOF
    启动优先级
    体积
    恢复速度
    数据安全性 丢数据 根据策略决定

    扩展

    1、RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储
    2、AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis 协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
    3、只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化
    4、同时开启两种持久化方式

    • 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
    • RDB 的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的Bug,留着作为一个万一的手段。

    5、性能建议

    • 因为RDB文件只用作后备用途,建议只在Save上持久化RDB文件,而且只要15分钟备份一次就够了,只保留 save 900 1 这条规则。
    • 如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值。
    • 如果不Enable AOF ,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO,也减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个 Master/Slave 中的 RDB文件,载入较新的那个,微博就是这种架构。

    Redis 发布与订阅

    简介

    Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

    image-20210522094209713

    下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

    image-20210522094331561

    当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

    image-2021052204353851

    基本命令

    命令 描述
    PSUBSCRIBE pattern [pattern..] 订阅一个或多个符合给定模式的频道。
    PUNSUBSCRIBE pattern [pattern..] 退订一个或多个符合给定模式的频道。
    PUBSUB subcommand [argument[argument]] 查看订阅与发布系统状态。
    PUBLISH channel message 向指定频道发布消息
    SUBSCRIBE channel [channel..] 订阅给定的一个或多个频道。
    UNSUBSCRIBE channel [channel..] 退订一个或多个频道

    测试

    订阅者

    127.0.0.1:6379> SUBSCRIBE iandfReading messages... (press Ctrl-C to quit)#订阅iandf频道1) "subscribe"2) "iandf"3) (integer) 1#等待发布者发布信息1) "message"2) "iandf"3) "welcome to iandf channel"
    

    另外开启一个客户端当做发布者

    127.0.0.1:6379> publish iandf "welcome to iandf channel"(integer) 1127.0.0.1:6379> PUBSUB channels #查看活跃的频道1) "iandf"
    

    原理

    每个 Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer 结构, 结构的 pubsub_channels 属性是一个字典, 这个字典就用于保存订阅频道的信息,其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。

    在这里插入图片描述

    客户端订阅,就被链接到对应频道的链表的尾部,退订则就是将客户端节点从链表中移除。

    缺点

    1. 如果一个客户端订阅了频道,但自己读取消息的速度却不够快的话,那么不断积压的消息会使redis输出缓冲区的体积变得越来越大,这可能使得redis本身的速度变慢,甚至直接崩溃。
    2. 这和数据传输可靠性有关,如果在订阅方断线,那么他将会丢失所有在短线期间发布者发布的消息。

    应用

    1. 消息订阅:公众号订阅,微博关注等等(起始更多是使用消息队列来进行实现)
    2. 多人在线聊天室。

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

    Redis 主从复制

    概念

    主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。

    默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。

    image-20210522101111754

    作用

    • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
    • 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
    • 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
    • 高可用基石:主从复制还是哨兵和集群能够实施的基础。

    为什么使用集群?

    • 从结构上,单个 Redish服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较
      大,单台服务器故障率高,系统崩坏概率大
    • 从容量上来说,单个 Redise服务器内存容量有限,就算一台 Redish服务器内存容量为256G,也不能将所有
      内存用作 Redis存储内存,一般来说,单台 Redis最大使用内存不应该超过20G

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

    一主二从环境配置

    配置文件中有一个replication模块 ,可以使用 info replication 查看当前库的信息

    127.0.0.1:6379> info replication #查看当前库的信息# Replicationrole:master #角色:主机connected_slaves:0 #从机数:0master_failover_state:no-failovermaster_replid:5e24d5e5409ecfcab3304caf9df9572de1beb942master_replid2:0000000000000000000000000000000000000000master_repl_offset:0second_repl_offset:-1repl_backlog_active:0repl_backlog_size:1048576repl_backlog_first_byte_offset:0repl_backlog_histlen:0
    

    配置从机

    1. 使用命令行配置 slaveof 127.0.0.1 6379 重启失效,需要重新配置

      image-20210522103934886

    2. 使用配置文件配置 永久生效

      image-20210522104222215

      image-20210522104401647

    3. 查看主机信息

      127.0.0.1:6379> info replication# Replicationrole:master connected_slaves:2 #从机数slave0:ip=127.0.0.1,port=6380,state=online,offset=560,lag=1 #从机信息slave1:ip=127.0.0.1,port=6381,state=online,offset=560,lag=1master_failover_state:no-failovermaster_replid:c2ea36e7d18f41423b503046042b6e63b8f3de0dmaster_replid2:0000000000000000000000000000000000000000master_repl_offset:560second_repl_offset:-1repl_backlog_active:1repl_backlog_size:1048576repl_backlog_first_byte_offset:1repl_backlog_histlen:560127.0.0.1:6379> 
      

    主从规则

    1. 从机只能读,不能写,主机可读可写但是多用于写。

      image-20210522104842697

    2. 当主机断电宕机后,默认情况下从机的角色不会发生变化 ,集群中只是失去了写操作,当主机恢复以后,又会连接上从机恢复原状。

      image-20210522105029419

    3. 如果是使用命令行,来配置的主从,这个时候如果重启了,就会变回主机!只要变为从机,立马就会从主机中获取值!

    4. 第二条中提到,默认情况下,主机故障后,不会出现新的主机,有两种方式可以产生新的主机:

      • 从机手动执行命令slaveof no one,这样执行以后 从机 会独立出来成为一个主机。这样即使主机重启了,主机也没有从节点了,需要重新配置
      • 使用哨兵模式(自动选举)一般都使用哨兵模式,不手动配置

    主机从机的连接方式

    方式一:刚刚的那种连接方式如图

    image-20210522110244261

    方式二: 但是也可以向链路一样把主机和从机连接起来

    image-20210522111938570

    image-2021052211130536 image-2021052211190356

    主从复制原理

    **复制原理 : **

    • Slave启动成功连接到 master后会发送一个sync同步命令
    • Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后, master将传送整个数据文件到 slave,并完成一次完全同步。
    • 全量复制:而 slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
    • 增量复制: Master继续将新的所有收集到的修改命令依次传给 slave,完成同步

    但是只要是重新连接 master,一次完全同步(全量复制)将被自动执行!我们的数据一定可以在从机中看到!

    哨兵模式(重要)

    简介

    哨兵模式是一种自动选举一个作为主机的主从切换模式,而不需要手动配置

    ​ 主从切換技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。 Redis从2.8开始正式提供了Sentinel(哨兵)架构来解决这个问题。哨兵模式能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。哨兵模式是一种特殊的模式,首先 Redist提供了哨兵的命令哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待 Redis服务器响应,从而监控运行的多个 Redis实例。

    image-20210522114220832

    这里的哨兵有两个作用

    • 通过发送命令,让 Redis服务器返回监控其运行状态,包括主服务器和从服务器。
    • 当哨兵监测到 master2宕机,会自动将 slave切换成 master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机

    然而一个哨兵进程对 Redise服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。

    image-20210522114341568

    假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行 failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行 failover故障转移操作。切換成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线

    就是说一个哨兵发现主机不可用了,后面的哨兵也发现了问题,当数量超过需先设定的阈值时(阈值在sentinel.conf里面设置),就会进行failover操作。

    简单实践~一哨兵一主二从

    编写哨兵模式下的配置文件 sentinel.conf

    # 哨兵sentinel监控的redis主节点的 ip port # master-name  可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了# sentinel monitor <master-name> <ip> <redis-port> <quorum>sentinel monitor mymaster 127.0.0.1 6379 1
    

    测试结果

    image-2021052212018441

    就算79主机重新上线了,也只能成为80的从机

    哨兵的完整配置文件

    # Example sentinel.conf # 哨兵sentinel实例运行的端口 默认26379port 26379 # 哨兵sentinel的工作目录dir /tmp # 哨兵sentinel监控的redis主节点的 ip port # master-name  可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了# sentinel monitor <master-name> <ip> <redis-port> <quorum>sentinel monitor mymaster 127.0.0.1 6379 1 # 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码# sentinel auth-pass <master-name> <password>sentinel auth-pass mymaster MySUPER--secret-0123passw0rd  # 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒# sentinel down-after-milliseconds <master-name> <milliseconds>sentinel down-after-milliseconds mymaster 30000 # 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。# sentinel parallel-syncs <master-name> <numslaves>sentinel parallel-syncs mymaster 1   # 故障转移的超时时间 failover-timeout 可以用在以下这些方面: #1. 同一个sentinel对同一个master两次failover之间的间隔时间。#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。#3.当想要取消一个正在进行的failover所需要的时间。  #4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了# 默认三分钟# sentinel failover-timeout <master-name> <milliseconds>sentinel failover-timeout mymaster 180000 # SCRIPTS EXECUTION #配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。#对于脚本的运行结果有以下规则:#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。 #通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,#这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,#一个是事件的类型,#一个是事件的描述。#如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。#通知脚本# sentinel notification-script <master-name> <script-path>  sentinel notification-script mymaster /var/redis/notify.sh # 客户端重新配置主节点参数脚本# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。# 以下参数将会在调用脚本时传给脚本:# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port># 目前<state>总是“failover”,# <role>是“leader”或者“observer”中的一个。 # 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的# 这个脚本应该是通用的,能被多次调用,不是针对性的。# sentinel client-reconfig-script <master-name> <script-path>sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
    

    缓存穿透、击穿和雪崩

    **Reids作为MYSQL的缓存是,用户请求流出如下图所示: **

    image-20210522133056842

    缓存穿透

    简介

    在默认情况下,用户请求数据时,会先在缓存(Redis)中查找,若没找到即缓存未命中,再在数据库中进行查找,数量少可能问题不大,可是一旦大量的请求数据(例如秒杀场景)缓存都没有命中的话,就会全部转移到数据库上,造成数据库极大的压力,就有可能导致数据库崩溃。网络安全中也有人恶意使用这种手段进行攻击被称为洪水攻击,这个时候就会发生缓存穿透。

    解决方案

    布隆过滤器

    对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力。

    image-20210522133304965

    缓存空对象

    当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访可这个数据将会从缓存中获取,保护了后端数据源。

    image-20210522133359518

    但是这种方法会存在两个问题
    1、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键。
    2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。

    缓存击穿(量太大,缓存过期)

    简介

    ​ 相较于缓存穿透,缓存击穿的目的性更强,一个存在的key,在缓存过期的一刻,同时有大量的请求请求这个key,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。

    解决方案

    • 设置热点数据永不过期
      • 这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。
    • 加互斥锁(分布式锁)
      • 在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。

    image-20210522133745552

    缓存雪崩

    简介

    ​ 缓存雪崩,是指在某一个时间段,缓存集中过期失效。 Redis宕机!
    ​ 产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴増,造成存储层也会挂掉的情况。

    image-20210522133916402

    ​ 其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。

    解决方案

    • redis高可用
      • 这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群
    • 限流降级
      • 这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
    • 数据预热
      • 数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
  • 相关阅读:
    Coursera机器学习week1 单元测试
    Coursera机器学习week1 笔记
    Apache服务器
    yum软件包安装
    linux察看安装包有那些
    虚拟上怎么挂载镜像
    netstat -altp
    MongoDB——》聚合查询(project、match、limit、skip、unwind、group、sort)
    jdk8流list转Map
    Spring Boot 和 Spring Cloud Feign调用服务及传递参数踩坑记录(转)
  • 原文地址:https://www.cnblogs.com/iandf/p/14798673.html
Copyright © 2011-2022 走看看