• redis基础


    NoSQL概述

    数据存储瓶颈是什么

    数据量总大小,一个机器放不下

    数据索引一个机器内存放不下

    访问量一个服务器不能承受

    优化数据结构–文件缓存IO

    后来,随着访问量的上升,几乎大部分使用MySQL架构的网站在数据库上都开始出现了性能问题,web 程序不再仅仅专注在功能上,同时也在追求性能。程序猿们开始大量使用缓存技术来缓解数据库的压 力,优化数据库的结构和索引,开始比较流行的是通过文件缓存来缓解数据库压力,但是当访问量继续 增大的时候,多台web机器通过文件缓存不能共享,大量的小文件缓存也带了比较高的IO压力,在这个 时候,Memcached就自然的成为一个非常时尚的技术产品。
    

    Memcached +MySQL +垂直拆分

    image-20220826161043362

    Mysql主从读写分离

    由于数据库的写入压力增加,Memcached只能缓解数据库的读取压力,读写集中在一个数据库上让数 据库不堪重负,大部分网站开始使用主从复制技术来达到读写分离,以提高读写性能和读库的可扩展 性,MySQL的master-slave模式成为这个时候的网站标配了
    

    image-20220826161437140

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

    在Memcached的高速缓存,MySQL的主从复制,读写分离的基础之上,这时MySQL主库的写压力开始 出现瓶颈,而数据量的持续猛增,由于MyISAM使用表锁,在高并发下会出现严重的锁问题,大量的高 并发MySQL应用开始使用InnoDB引擎代替MyISAM。 同时,开始流行使用分表分库来缓解写压力和数据增长的扩展问题,这个时候,分表分库成了一个热门 技术,是面试的热门问题,也是业界讨论的热门技术问题。也就是在这个时候,MySQL推出了还不太稳 定的表分区,这也给技术实力一般的公司带来了希望。虽然MySQL推出了MySQL Cluster集群,但性能 也不能很好满足互联网的需求,只是在高可靠性上提供了非常大的保证。
    

    image-20220826162141401

    NoSQL 特点

    方便扩展

    NoSQL 数据库种类繁多,但是一个共同的特点都是去掉关系数据库的关系型特性。
    数据之间无关系,这样就非常容易扩展,也无形之间,在架构的层面上带来了可扩展的能力
    

    性能高

    NoSQL数据库都具有非常高的读写性能,尤其是在大数据量下,同样表现优秀。这得益于它的非关系
    性,数据库的结构简单。
    一般MySQL使用Query Cache,每次表的更新Cache就失效,是一种大力度的Cache,在针对Web2.0的
    交互频繁应用,Cache性能不高,而NoSQL的Cache是记录级的,是一种细粒度的Cache,所以NoSQL
    在这个层面上来说就要性能高很多了。
    官方记录:Redis 一秒可以写8万次,读11万次!
    

    数据类型多样

    泛指非关系型的数据库,随着互联网Web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别 是超大规模和高并发的社交网络服务类型的Web2.0纯动态网站已经显得力不从心,暴露了很多难以克服 的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展,NoSQL数据库的产生就是为 了解决大规模数据集合多种数据种类带来的挑战,尤其是大数据应用难题,包括超大规模数据的存储。 (例如谷歌或Facebook每天为他们的用户收集万亿比特的数据)。这些类型的数据存储不需要固定的模 式,无需多余操作就可以横向扩展。
    

    多样灵活的数据模型

    NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式,而在关系数据库里,增删
    字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是噩梦。
    
    
    4、传统的RDBMS VS NoSQL
    拓展:3V+3高
    大数据时代的3V : 主要是对问题的描述
    海量 Volume
    多样 Variety
    实时 Velocity
    互联网需求的3高 : 主要是对程序的要求
    高并发
    高可用
    高性能
    当下的应用是 SQL 和 NoSQL 一起使用,技术没有高低之分,就看你怎么用,对吧!
    经典应用分析
    聊聊阿里巴巴中文网站的商品信息如何存放,以女装、包包为例:
    
    1、商品的基本信息
    2、商品描述、详情、评价信息(多文字类)
    3、商品的图片
    4、商品的关键字
    名称、价格、出厂日期、生产厂商等
    关系型数据库:mysql、oracle目前淘宝在去O化(也即,拿掉Oracle)
    注意,淘宝内部用的MySQL是里面的大牛自己改造过的。
    为什么去IOE:
    2008年,王坚博士加入阿里巴巴,成为首席架构师。把云计算植入阿里IT基因。
    2013年5月17日,阿里集团最后一台IBM小机在支付宝下线。这是自2009年“去IOE”战略透露以来,“去
    IOE”非常重要的一个节点。“去 IOE”指的是摆脱掉IT部署中原有的IBM小型机、Oracle数据库以及EMC
    存储的过度依赖。告别最后一台小机,意味着整个阿里集团尽管还有一些Oracle数据库和EMC存储,但是
    IBM小型机已全部被替换。2013年7月10日,淘宝重中之重的广告系统使用的Oracle数据库下线,也是整
    个淘宝最后一个 Oracle数据库。这两件事合在一起是阿里巴巴技术发展过程中的一个重要里程碑。
    
    多文字信息描述类,IO读写性能变差
    存在文档数据库MongDB中
    
    商品图片展现类
    分布式文件系统中
    - 淘宝自己的 TFS
    - Google的 GFS
    - Hadoop的 HDFS
    
    1、商品的基本信息
    2、商品描述、详情、评价信息(多文字类)
    3、商品的图片
    4、商品的关键字
    名称、价格、出厂日期、生产厂商等
    关系型数据库:mysql、oracle目前淘宝在去O化(也即,拿掉Oracle)
    注意,淘宝内部用的MySQL是里面的大牛自己改造过的。
    为什么去IOE:
    2008年,王坚博士加入阿里巴巴,成为首席架构师。把云计算植入阿里IT基因。
    2013年5月17日,阿里集团最后一台IBM小机在支付宝下线。这是自2009年“去IOE”战略透露以来,“去
    IOE”非常重要的一个节点。“去 IOE”指的是摆脱掉IT部署中原有的IBM小型机、Oracle数据库以及EMC
    存储的过度依赖。告别最后一台小机,意味着整个阿里集团尽管还有一些Oracle数据库和EMC存储,但是
    IBM小型机已全部被替换。2013年7月10日,淘宝重中之重的广告系统使用的Oracle数据库下线,也是整
    个淘宝最后一个 Oracle数据库。这两件事合在一起是阿里巴巴技术发展过程中的一个重要里程碑。
    
    多文字信息描述类,IO读写性能变差
    存在文档数据库MongDB中
    
    商品图片展现类
    分布式文件系统中
    - 淘宝自己的 TFS
    - Google的 GFS
    - Hadoop的 HDFS
    
    5、商品的波段性的热点高频信息
    6、商品的交易,价格计算,积分累计!
    大型互联网应用(大数据,高并发,多样数据类型)的难点和解决方案
    数据类型多样性
    数据源多样性变化冲欧
    数据源改造数据服务平台不需要面积大重构
    
    
    以一个电商客户,订单,订购,地址模型来对比下关系型数据库和非关系型数据库
    传统的关系型数据库你如何设计?
    ER图(1:1/1:N/N:N,主外键等常见)
    用户对应多个订单多个地址
    每个订单对应每个商品、价格、地址
    每个商品对应产品
    NoSQL你如何设计
    可以尝试使用BSON。
    BSON是一种类json的一种二进制形式的存储格式,简称Binary JSON,它和JSON一样,支持内嵌的文档
    对象和数组对象
    用BSon画出构建的数据模型
    
    {
    "customer":{
    "id":1000,
    "name":"Z3",
    "billingAddress":[{"city":"beijing"}],
    "orders":[
    {
    "id":17,
    
    想想关系模型数据库你如何查?如果按照我们新设计的BSon,是不是查询起来很简单。
    高并发的操作是不太建议有关联查询的,互联网公司用冗余数据来避免关联查询
    分布式事务是支持不了太多的并发的
    
    
    

    NoSQL四大分类

    KV键值

    新浪:BerkeleyDB+redis 美团:redis+tair 阿里、百度:memcache+redis

    文档型数据库(bson格式比较多):
    CouchDB
    MongoDB
    MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可
    扩展的高性能数据存储解决方案。
    MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰
    富,最像关系数据库的。
    
    列存储数据库:
    Cassandra, HBase
    分布式文件系统
    图关系数据库
    它不是放图形的,放的是关系比如:朋友圈社交网络、广告推荐系统
    社交网络,推荐系统等。专注于构建关系图谱
    Neo4J, InfoGrid
    

    image-20220826195158544

    CAP+BASE

    关系型数据库遵循ACID规则

    A (Atomicity) 原子性
    C (Consistency) 一致性
    I (Isolation) 隔离性
    D (Durability) 持久性
    CAP(三进二)
    原子性很容易理解,也就是说事务里的所有操作要么全部做完,要么都不做,事务成功的条件是事务
    里的所有操作都成功,只要有一个操作失败,整个事务就失败,需要回滚。
    比如银行转账,从A账户转100元至B账户,分为两个步骤:
    1)从A账户取100元;
    2)存入100元至B账户。
    这两步要么一起完成,要么一起不完成,如果只完成第一步,第二步失败,钱会莫名其妙少了100
    元。
    
    1 事务前后数据的完整性必须保持一致。
    所谓的独立性是指并发的事务之间不会互相影响,如果一个事务要访问的数据正在被另外一个事务修
    改,只要另外一个事务未提交,它所访问的数据就不受未提交事务的影响。比如现有有个交易是从A
    账户转100元至B账户,在这个交易还未完成的情况下,如果此时B查询自己的账户,是看不到新增加
    的100元的
    
    1 持久性是指一旦事务提交后,它所做的修改将会永久的保存在数据库上,即使出现宕机也不会丢失。
    C : Consistency(强一致性)
    A : Availability(可用性)
    P : Partition tolerance(分区容错性)
    

    CPA理论就是说在分布式存储系统中,最多只能实现上面两点

    而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容错性是我们必须需要实现的。
    所以我们只能在一致性和可用性之间进行权衡,没有NoSQL系统能同时保证这三点。
    注意:分布式架构的时候必须做出取舍。
    一致性和可用性之间取一个平衡。多余大多数web应用,其实并不需要强一致性。
    因此牺牲C换取P,这是目前分布式数据库产品的方向
    一致性与可用性的决择
    对于web2.0网站来说,关系数据库的很多主要特性却往往无用武之地
    数据库事务一致性需求
    很多web实时系统并不要求严格的数据库事务,对读一致性的要求很低, 有些场合对写一致性要求并不
    高。允许实现最终一致性。
    数据库的写实时性和读实时性需求
    对关系数据库来说,插入一条数据之后立刻查询,是肯定可以读出来这条数据的,但是对于很多web应
    用来说,并不要求这么高的实时性,比方说发一条消息之 后,过几秒乃至十几秒之后,我的订阅者才看
    到这条动态是完全可以接受的。
    对复杂的SQL查询,特别是多表关联查询的需求
    任何大数据量的web系统,都非常忌讳多个大表的关联查询,以及复杂的数据分析类型的报表查询,特
    别是SNS类型的网站,从需求以及产品设计角度,就避免了这种情况的产生。往往更多的只是单表的主
    键查询,以及单表的简单条件分页查询,SQL的功能被极大的弱化了。
    CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,
    最多只能同时较好的满足两个。因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP
    原则和满足 AP 原则三 大类:
    CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
    CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
    AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
    
    

    image-20220826195636603

    BASE理论是由eBay架构师提出的。BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互 联网分布式系统实践的总结,是基于CAP定律逐步演化而来。其核心思想是即使无法做到强一致性,但 每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
    

    BASE就是为了解决关系型数据库一致性引起的问题而引起的可用性降低而提出的解决方案

    基本可用

    基本可用是指分布式系统在出现故障的时候,允许损失部分可用 性,即保证核心可用。电商大促时,为了应对访问量激增,部分用户可能会被引导到降级页面,服 务层也可能只提供降级服务。这就是损失部分可用性的体现。
    

    软状态

    软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用
    性。分布式存储中一般一份数据至少会有三个副本,允许不同节点间副本同步的延时就是软状态的
    体现。MySQL Replication 的异步复制也是一种体现。
    

    最终一致性

    最终一致性是指系统中的所有数据副本经过一定时间后,最
    终能够达到一致的状态。弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况。
    
    
    它的思想是通过让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能上改观。为什么
    这么说呢,缘由就在于大型系统往往由于地域分布和极高性能的要求,不可能采用分布式事务来完成这
    些指标,要想获得这些指标,我们必须采用另外一种方式来完成,这里BASE就是解决这个问题的办法!
    

    解释

    分布式:不同的多台服务器部署不同服务模块,他们之间通过Rpc通信和调用,对外提供服务和组内写作

    集群:不同多台服务器上部署相同的服务模块,通过分布式调度软件进行统一的调度,对外提供服务和访问

    Redis入门

    Redis:REmote DIctionary Server(远程字典服务器)
    是完全开源免费的,用C语言编写的,遵守BSD协议,是一个高性能的(Key/Value)分布式内存数据
    库,基于内存运行,并支持持久化的NoSQL数据库,是当前最热门的NoSQL数据库之一,也被人们称为
    数据结构服务器
    Redis与其他key-value缓存产品有以下三个特点
    Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使
    用。
    Redis不仅仅支持简单的 key-value 类型的数据,同时还提供list、set、zset、hash等数据结构的存
    储。
    Redis支持数据的备份,即master-slave模式的数据备份。
    Redis能干嘛
    内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务
    取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面
    发布、订阅消息系统
    地图信息分析
    定时器、计数器
    ......
    特性
    数据类型、基本操作和配置
    持久化和复制,RDB、AOF
    事务的控制
    .....
    常用网站
    https://redis.io/ 官网
    http://www.redis.cn 中文网
    Windows安装
    下载地址:https://github.com/dmajkic/redis/downloads ( 素材提供 )
    解压到自己电脑的环境目录即可
    
    
    # 基本的set设值
    127.0.0.1:6379> set key kuangshen
    OK
    # 取出存储的值
    127.0.0.1:6379> get key
    "kuangshen"
    重要提示
    由于企业里面做Redis开发,99%都是Linux版的运用和安装,几乎不会涉及到Windows版,上一步的讲
    解只是为了知识的完整性,Windows版不作为重点,大家可以自己玩,企业实战就认一个版:Linux版
    http://www.redis.cn/topics/introduction
    

    image-20220827151740836

    安装步骤
    1、下载获得 redis-5.0.7.tar.gz 后将它放到我们Linux的目录下 /opt
    2、/opt 目录下,解压命令 : tar -zxvf redis-5.0.7.tar.gz
    3、解压完成后出现文件夹:redis-5.0.7
    4、进入目录: cd redis-5.0.7
    5、在 redis-5.0.7 目录下执行 make 命令
    
    运行make命令时故意出现的错误解析:
    1. 安装gcc (gcc是linux下的一个编译程序,是c程序的编译工具)
    能上网: yum install gcc-c++
    版本测试: gcc-v
    2. 二次make
    3. Jemalloc/jemalloc.h: 没有那个文件或目录
    运行 make distclean 之后再make
    4. Redis Test(可以不用执行)
    6、如果make完成后继续执行 make install
    7、查看默认安装目录:usr/local/bin
    /usr 这是一个非常重要的目录,类似于windows下的Program Files,存放用户的程序
    cd /usr/local/bin
    ls -l
    # 在redis的解压目录下备份redis.conf
    mkdir myredis
    cp redis.conf myredis # 拷一个备份,养成良好的习惯,我们就修改这个文件
    # 修改配置保证可以后台应用
    vim redis.conf
    

    image-20220827152057072

    daemonize 设置yes或者no区别
    daemonize:yes
    redis采用的是单进程多线程的模式。当redis.conf中选项daemonize设置成yes时,代表开启
    守护进程模式。在该模式下,redis会在后台运行,并将进程pid号写入至redis.conf选项
    pidfile设置的文件中,此时redis将一直运行,除非手动kill该进程。
    daemonize:no
    当daemonize选项设置成no时,当前界面将进入redis的命令行界面,exit强制退出或者关闭
    连接工具(putty,xshell等)都会导致redis进程退出。
    
    # 【shell】启动redis服务
    [root@192 bin]# cd /usr/local/bin
    [root@192 bin]# redis-server /opt/redis-5.0.7/redis.conf
    # redis客户端连接===> 观察地址的变化,如果连接ok,是直接连上的,redis默认端口号 6379
    [root@192 bin]# redis-cli -p 6379
    127.0.0.1:6379> ping
    PONG
    127.0.0.1:6379> set k1 helloworld
    OK
    127.0.0.1:6379> get k1
    "helloworld"
    # 【shell】ps显示系统当前进程信息
    [root@192 myredis]# ps -ef|grep redis
    root 16005 1 0 04:45 ? 00:00:00 redis-server
    127.0.0.1:6379
    root 16031 15692 0 04:47 pts/0 00:00:00 redis-cli -p 6379
    root 16107 16076 0 04:51 pts/2 00:00:00 grep --color=auto redis
    # 【redis】关闭连接
    127.0.0.1:6379> shutdown
    not connected> exit
    # 【shell】ps显示系统当前进程信息
    [root@192 myredis]# ps -ef|grep redis
    root 16140 16076 0 04:53 pts/2 00:00:00 grep --color=auto redi
    

    基础知识

    压力测试工具

    redis-benchmark

    Redis-benchmark是官方自带的Redis性能测试工具,可以有效的测试Redis服务的性能。

    image-20220827152218808

    # 测试一:100个并发连接,100000个请求,检测host为localhost 端口为6379的redis服务器性
    能
    redis-benchmark -h localhost -p 6379 -c 100 -n 100000
    # 测试出来的所有命令只举例一个!
    ====== SET ======
    100000 requests completed in 1.88 seconds # 对集合写入测试
    100 parallel clients # 每次请求有100个并发客户端
    3 bytes payload # 每次写入3个字节的数据,有效载荷
    keep alive: 1 # 保持一个连接,一台服务器来处理这些请求
    17.05% <= 1 milliseconds
    97.35% <= 2 milliseconds
    99.97% <= 3 milliseconds
    100.00% <= 3 milliseconds # 所有请求在 3 毫秒内完成
    53248.14 requests per second # 每秒处理 53248.14 次请求
    
    
    
    查看 redis.conf ,里面有默认的配置
    databases 16
    # Set the number of databases. The default database is DB 0, you can select
    # a different one on a per-connection basis using SELECT <dbid> where
    # dbid is a number between 0 and 'databases'-1
    databases 16
    
    select 命令切换数据库
    不同数据库存不同数据
    127.0.0.1:6379> select 7
    OK
    127.0.0.1:6379[7]> DBSIZE
    (integer) 0
    127.0.0.1:6379[7]> select 0
    OK
    127.0.0.1:6379> DBSIZE
    (integer) 5
    127.0.0.1:6379> keys * # 查看具体的key
    1) "counter:__rand_int__"
    2) "mylist"
    3) "k1"
    4) "myset:__rand_int__"
    5) "key:__rand_int__"
    
    FLASHDB :清空当前库
    FLASHALL:清空全部的库
    
    127.0.0.1:6379> DBSIZE
    (integer) 5
    127.0.0.1:6379> FLUSHDB
    OK
    127.0.0.1:6379> DBSIZE
    (integer) 0
    
    Redis采用的是基于内存的采用的是单进程单线程模型的 KV 数据库,由C语言编写,官方提供的数据是
    可以达到100000+的QPS(每秒内查询次数)。这个数据不比采用单进程多线程的同样基于内存的 KV
    数据库 Memcached 差!
    Redis为什么这么快?
    1)以前一直有个误区,以为:高性能服务器 一定是多线程来实现的
    原因很简单因为误区二导致的:多线程 一定比 单线程 效率高,其实不然!
    在说这个事前希望大家都能对 CPU 、 内存 、 硬盘的速度都有了解了!
    2)redis 核心就是 如果我的数据全都在内存里,我单线程的去操作 就是效率最高的,为什么呢,因为
    多线程的本质就是 CPU 模拟出来多个线程的情况,这种模拟出来的情况就有一个代价,就是上下文的切
    换,对于一个内存的系统来说,它没有上下文的切换就是效率最高的。redis 用 单个CPU 绑定一块内存
    的数据,然后针对这块内存的数据进行多次读写的时候,都是在一个CPU上完成的,所以它是单线程处
    理这个事。在内存的情况下,这个方案就是最佳方案。
    因为一次CPU上下文的切换大概在 1500ns 左右。从内存中读取 1MB 的连续数据,耗时大约为 250us,
    假设1MB的数据由多个线程读取了1000次,那么就有1000次时间上下文的切换,那么就有1500ns *
    1000 = 1500us ,我单线程的读完1MB数据才250us ,你光时间上下文的切换就用了1500us了,我还不
    算你每次读一点数据 的时间。
    

    五大数据类型

    全段翻译:
    Redis是一个开放源代码(BSD许可)的内存中数据结构存储,用作数据库,缓存和消息代理。它支持数
    据结构,例如字符串,哈希,列表,集合,带范围查询的排序集合,位图,超日志,带有半径查询和流
    的地理空间索引。Redis具有内置的复制,Lua脚本,LRU驱逐,事务和不同级别的磁盘持久性,并通过
    Redis Sentinel和Redis Cluster自动分区提供了高可用性。
    

    redis key 键

    # keys * 查看所有的key
    127.0.0.1:6379> keys *
    (empty list or set)
    127.0.0.1:6379> set name qinjiang
    OK
    127.0.0.1:6379> keys *
    1) "name"
    # exists key 的名字,判断某个key是否存在
    127.0.0.1:6379> EXISTS name
    (integer) 1
    127.0.0.1:6379> EXISTS name1
    (integer) 0
    # move key db ---> 当前库就没有了,被移除了
    127.0.0.1:6379> move name 1
    (integer) 1
    127.0.0.1:6379> keys *
    (empty list or set)
    # expire key 秒钟:为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删
    除。
    # ttl key 查看还有多少秒过期,-1 表示永不过期,-2 表示已过期
    127.0.0.1:6379> set name qinjiang
    OK
    127.0.0.1:6379> EXPIRE name 10
    (integer) 1
    127.0.0.1:6379> ttl name
    (integer) 4
    127.0.0.1:6379> ttl name
    (integer) 3
    127.0.0.1:6379> ttl name
    (integer) 2
    127.0.0.1:6379> ttl name
    (integer) 1
    127.0.0.1:6379> ttl name
    (integer) -2
    127.0.0.1:6379> keys *
    (empty list or set)
    # type key 查看你的key是什么类型
    127.0.0.1:6379> set name qinjiang
    OK
    127.0.0.1:6379> get name
    "qinjiang"
    127.0.0.1:6379> type name
    string
    
    

    字符串String 单值单Value 常用命令说明:

    # ===================================================
    # set、get、del、append、strlen
    # ===================================================
    127.0.0.1:6379> set key1 value1 # 设置值
    OK
    127.0.0.1:6379> get key1 # 获得key
    "value1"
    127.0.0.1:6379> del key1 # 删除key
    (integer) 1
    127.0.0.1:6379> keys * # 查看全部的key
    (empty list or set)
    127.0.0.1:6379> exists key1 # 确保 key1 不存在
    (integer) 0
    127.0.0.1:6379> append key1 "hello" # 对不存在的 key 进行 APPEND ,等同于 SET
    key1 "hello"
    (integer) 5 # 字符长度
    127.0.0.1:6379> APPEND key1 "-2333" # 对已存在的字符串进行 APPEND
    (integer) 10 # 长度从 5 个字符增加到 10 个字符
    127.0.0.1:6379> get key1
    "hello-2333"
    127.0.0.1:6379> STRLEN key1 # # 获取字符串的长度
    (integer) 10
    # ===================================================
    # incr、decr 一定要是数字才能进行加减,+1 和 -1。
    # incrby、decrby 命令将 key 中储存的数字加上指定的增量值。
    # ===================================================
    
    127.0.0.1:6379> set views 0 # 设置浏览量为0
    OK
    127.0.0.1:6379> incr views # 浏览 + 1
    (integer) 1
    127.0.0.1:6379> incr views # 浏览 + 1
    (integer) 2
    127.0.0.1:6379> decr views # 浏览 - 1
    (integer) 1
    127.0.0.1:6379> incrby views 10 # +10
    (integer) 11
    127.0.0.1:6379> decrby views 10 # -10
    (integer) 1
    # ===================================================
    # range [范围]
    # getrange 获取指定区间范围内的值,类似between...and的关系,从零到负一表示全部
    # ===================================================
    127.0.0.1:6379> set key2 abcd123456 # 设置key2的值
    OK
    127.0.0.1:6379> getrange key2 0 -1 # 获得全部的值
    "abcd123456"
    127.0.0.1:6379> getrange key2 0 2 # 截取部分字符串
    "abc"
    # ===================================================
    # setrange 设置指定区间范围内的值,格式是setrange key值 具体值
    # ===================================================
    127.0.0.1:6379> get key2
    "abcd123456"
    127.0.0.1:6379> SETRANGE key2 1 xx # 替换值
    (integer) 10
    127.0.0.1:6379> get key2
    "axxd123456"
    # ===================================================
    # setex(set with expire)键秒值
    # setnx(set if not exist)
    # ===================================================
    127.0.0.1:6379> setex key3 60 expire # 设置过期时间
    OK
    127.0.0.1:6379> ttl key3 # 查看剩余的时间
    (integer) 55
    127.0.0.1:6379> setnx mykey "redis" # 如果不存在就设置,成功返回1
    (integer) 1
    127.0.0.1:6379> setnx mykey "mongodb" # 如果存在就设置,失败返回0
    (integer) 0
    127.0.0.1:6379> get mykey
    "redis"
    # ===================================================
    # mset Mset 命令用于同时设置一个或多个 key-value 对。
    # mget Mget 命令返回所有(一个或多个)给定 key 的值。
    # 如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。
    # msetnx 当所有 key 都成功设置,返回 1 。
    # 如果所有给定 key 都设置失败(至少有一个 key 已经存在),那么返回 0 。原子操
    作
    # ===================================================
    127.0.0.1:6379> mset k10 v10 k11 v11 k12 v12
    
    OK
    127.0.0.1:6379> keys *
    1) "k12"
    2) "k11"
    3) "k10"
    127.0.0.1:6379> mget k10 k11 k12 k13
    1) "v10"
    2) "v11"
    3) "v12"
    4) (nil)
    127.0.0.1:6379> msetnx k10 v10 k15 v15 # 原子性操作!
    (integer) 0
    127.0.0.1:6379> get key15
    (nil)
    # 传统对象缓存
    set user:1 value(json数据)
    # 可以用来缓存对象
    mset user:1:name zhangsan user:1:age 2
    mget user:1:name user:1:age
    # ===================================================
    # getset(先get再set)
    # ===================================================
    127.0.0.1:6379> getset db mongodb # 没有旧值,返回 nil
    (nil)
    127.0.0.1:6379> get db
    "mongodb"
    127.0.0.1:6379> getset db redis # 返回旧值 mongodb
    "mongodb"
    127.0.0.1:6379> get db
    "redis"
    
    String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。
    常规key-value缓存应用:
    常规计数:微博数,粉丝数等。
    
    

    列表List 单值多Value

    # ===================================================
    # Lpush:将一个或多个值插入到列表头部。(左)
    # rpush:将一个或多个值插入到列表尾部。(右)
    # lrange:返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。
    # 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。
    # 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此
    类推。
    # ===================================================
    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> RPUSH list "right"
    (integer) 3
    127.0.0.1:6379> Lrange list 0 -1
    1) "two"
    2) "one"
    3) "right"
    127.0.0.1:6379> Lrange list 0 1
    1) "two"
    2) "one"
    # ===================================================
    # lpop 命令用于移除并返回列表的第一个元素。当列表 key 不存在时,返回 nil 。
    # rpop 移除列表的最后一个元素,返回值为移除的元素。
    # ===================================================
    127.0.0.1:6379> Lpop list
    "two"
    127.0.0.1:6379> Rpop list
    "right"
    127.0.0.1:6379> Lrange list 0 -1
    1) "one"
    # ===================================================
    # Lindex,按照索引下标获得元素(-1代表最后一个,0代表是第一个)
    # ===================================================
    127.0.0.1:6379> Lindex list 1
    (nil)
    127.0.0.1:6379> Lindex list 0
    "one"
    127.0.0.1:6379> Lindex list -1
    "one"
    # ===================================================
    # llen 用于返回列表的长度。
    # ===================================================
    127.0.0.1:6379> flushdb
    OK
    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> Llen list # 返回列表的长度
    (integer) 3
    # ===================================================
    # lrem key 根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素。
    # ===================================================
    127.0.0.1:6379> lrem list 1 "two"
    (integer) 1
    127.0.0.1:6379> Lrange list 0 -1
    1) "three"
    2) "one"
    # ===================================================
    # Ltrim key 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区
    间之内的元素都将被删除。
    # ===================================================
    
    
    
    127.0.0.1:6379> RPUSH mylist "hello"
    (integer) 1
    127.0.0.1:6379> RPUSH mylist "hello"
    (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) "hello"
    2) "hello2"
    # ===================================================
    # rpoplpush 移除列表的最后一个元素,并将该元素添加到另一个列表并返回。
    # ===================================================
    127.0.0.1:6379> rpush mylist "hello"
    (integer) 1
    127.0.0.1:6379> rpush mylist "foo"
    (integer) 2
    127.0.0.1:6379> rpush mylist "bar"
    (integer) 3
    127.0.0.1:6379> rpoplpush mylist myotherlist
    "bar"
    127.0.0.1:6379> lrange mylist 0 -1
    1) "hello"
    2) "foo"
    127.0.0.1:6379> lrange myotherlist 0 -1
    1) "bar"
    # ===================================================
    # lset key index value 将列表 key 下标为 index 的元素的值设置为 value 。
    # ===================================================
    127.0.0.1:6379> exists list # 对空列表(key 不存在)进行 LSET
    (integer) 0
    127.0.0.1:6379> lset list 0 item # 报错
    (error) ERR no such key
    127.0.0.1:6379> lpush list "value1" # 对非空列表进行 LSET
    (integer) 1
    127.0.0.1:6379> lrange list 0 0
    1) "value1"
    127.0.0.1:6379> lset list 0 "new" # 更新值
    OK
    127.0.0.1:6379> lrange list 0 0
    1) "new"
    127.0.0.1:6379> lset list 1 "new" # index 超出范围报错
    (error) ERR index out of range
    # ===================================================
    # linsert key before/after pivot value 用于在列表的元素前或者后插入元素。
    # 将值 value 插入到列表 key 当中,位于值 pivot 之前或之后。
    # ===================================================
    redis> RPUSH mylist "Hello"
    (integer) 1
    redis> RPUSH mylist "World"
    (integer) 2
    
    
    redis> LINSERT mylist BEFORE "World" "There"
    (integer) 3
    redis> LRANGE mylist 0 -1
    1) "Hello"
    2) "There"
    3) "World"
    
    性能总结
    它是一个字符串链表,left,right 都可以插入添加
    如果键不存在,创建新的链表
    如果键已存在,新增内容
    如果值全移除,对应的键也就消失了
    链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就很惨淡了。
    list就是链表,略有数据结构知识的人都应该能理解其结构。使用Lists结构,我们可以轻松地实现最新消
    息排行等功能。List的另一个应用就是消息队列,可以利用List的PUSH操作,将任务存在List中,然后工
    作线程再用POP操作将任务取出进行执行。Redis还提供了操作List中某一段的api,你可以直接查询,删
    除List中某一段的元素。
    Redis的list是每个子元素都是String类型的双向链表,可以通过push和pop操作从列表的头部或者尾部
    添加或者删除元素,这样List即可以作为栈,也可以作为队列。
    

    集合Set 单值多value

    # ===================================================
    # sadd 将一个或多个成员元素加入到集合中,不能重复
    # smembers 返回集合中的所有的成员。
    # sismember 命令判断成员元素是否是集合的成员。
    # ===================================================
    127.0.0.1:6379> sadd myset "hello"
    (integer) 1
    127.0.0.1:6379> sadd myset "kuangshen"
    (integer) 1
    127.0.0.1:6379> sadd myset "kuangshen"
    (integer) 0
    127.0.0.1:6379> SMEMBERS myset
    1) "kuangshen"
    2) "hello"
    127.0.0.1:6379> SISMEMBER myset "hello"
    (integer) 1
    127.0.0.1:6379> SISMEMBER myset "world"
    (integer) 0
    # ===================================================
    # scard,获取集合里面的元素个数
    # ===================================================
    127.0.0.1:6379> scard myset
    (integer) 2
    # ===================================================
    # srem key value 用于移除集合中的一个或多个成员元素
    # ===================================================
    127.0.0.1:6379> srem myset "kuangshen"
    (integer) 1
    
    127.0.0.1:6379> SMEMBERS myset
    1) "hello"
    # ===================================================
    # srandmember key 命令用于返回集合中的一个随机元素。
    # ===================================================
    127.0.0.1:6379> SMEMBERS myset
    1) "kuangshen"
    2) "world"
    3) "hello"
    127.0.0.1:6379> SRANDMEMBER myset
    "hello"
    127.0.0.1:6379> SRANDMEMBER myset 2
    1) "world"
    2) "kuangshen"
    127.0.0.1:6379> SRANDMEMBER myset 2
    1) "kuangshen"
    2) "hello"
    # ===================================================
    # spop key 用于移除集合中的指定 key 的一个或多个随机元素
    # ===================================================
    127.0.0.1:6379> SMEMBERS myset
    1) "kuangshen"
    2) "world"
    3) "hello"
    127.0.0.1:6379> spop myset
    "world"
    127.0.0.1:6379> spop myset
    "kuangshen"
    127.0.0.1:6379> spop myset
    "hello"
    # ===================================================
    # smove SOURCE DESTINATION MEMBER
    # 将指定成员 member 元素从 source 集合移动到 destination 集合。
    # ===================================================
    127.0.0.1:6379> sadd myset "hello"
    (integer) 1
    127.0.0.1:6379> sadd myset "world"
    (integer) 1
    127.0.0.1:6379> sadd myset "kuangshen"
    (integer) 1
    127.0.0.1:6379> sadd myset2 "set2"
    (integer) 1
    127.0.0.1:6379> smove myset myset2 "kuangshen"
    (integer) 1
    127.0.0.1:6379> SMEMBERS myset
    1) "world"
    2) "hello"
    127.0.0.1:6379> SMEMBERS myset2
    1) "kuangshen"
    2) "set2"
    # ===================================================
    - 数字集合类
    - 差集: 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"
    (integer) 1
    127.0.0.1:6379> sadd key2 "d"
    (integer) 1
    127.0.0.1:6379> sadd key2 "e"
    (integer) 1
    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) "a"
    2) "b"
    3) "c"
    4) "e"
    5) "d"
    

    在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为 集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功 能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集 合中。

    哈希Hash kv模式不变,但V是一个键值对

    # ===================================================
    # hset、hget 命令用于为哈希表中的字段赋值 。
    # hmset、hmget 同时将多个field-value对设置到哈希表中。会覆盖哈希表中已存在的字段。
    # hgetall 用于返回哈希表中,所有的字段和值。
    # hdel 用于删除哈希表 key 中的一个或多个指定字段
    # ===================================================
    127.0.0.1:6379> hset myhash field1 "kuangshen"
    (integer) 1
    127.0.0.1:6379> hget myhash field1
    "kuangshen"
    127.0.0.1:6379> HMSET myhash field1 "Hello" field2 "World"
    OK
    127.0.0.1:6379> HGET myhash field1
    "Hello"
    127.0.0.1:6379> HGET myhash field2
    "World"
    127.0.0.1:6379> hgetall myhash
    1) "field1"
    2) "Hello"
    3) "field2"
    
    4) "World"
    127.0.0.1:6379> HDEL myhash field1
    (integer) 1
    127.0.0.1:6379> hgetall myhash
    1) "field2"
    2) "World"
    # ===================================================
    # hlen 获取哈希表中字段的数量。
    # ===================================================
    127.0.0.1:6379> hlen myhash
    (integer) 1
    127.0.0.1:6379> HMSET myhash field1 "Hello" field2 "World"
    OK
    127.0.0.1:6379> hlen myhash
    (integer) 2
    # ===================================================
    # hexists 查看哈希表的指定字段是否存在。
    # ===================================================
    127.0.0.1:6379> hexists myhash field1
    (integer) 1
    127.0.0.1:6379> hexists myhash field3
    (integer) 0
    # ===================================================
    # hkeys 获取哈希表中的所有域(field)。
    # hvals 返回哈希表所有域(field)的值。
    # ===================================================
    127.0.0.1:6379> HKEYS myhash
    1) "field2"
    2) "field1"
    127.0.0.1:6379> HVALS myhash
    1) "World"
    2) "Hello"
    # ===================================================
    # hincrby 为哈希表中的字段值加上指定增量值。
    # ===================================================
    127.0.0.1:6379> hset myhash field 5
    (integer) 1
    127.0.0.1:6379> HINCRBY myhash field 1
    (integer) 6
    127.0.0.1:6379> HINCRBY myhash field -1
    (integer) 5
    127.0.0.1:6379> HINCRBY myhash field -10
    (integer) -5
    # ===================================================
    # hsetnx 为哈希表中不存在的的字段赋值 。
    # ===================================================
    127.0.0.1:6379> HSETNX myhash field1 "hello"
    (integer) 1 # 设置成功,返回 1 。
    127.0.0.1:6379> HSETNX myhash field1 "world"
    (integer) 0 # 如果给定字段已经存在,返回 0 。
    127.0.0.1:6379> HGET myhash field1
    "hello"
    
    

    Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。 存储部分变更的数据,如用户信息等。

    有序集合Zset 在set基础上,加一个score值。之前set是k1 v1 v2 v3,现在zset是 k1 score1 v1 score2 v2

    # ===================================================
    # zadd 将一个或多个成员元素及其分数值加入到有序集当中。
    # zrange 返回有序集中,指定区间内的成员
    # ===================================================
    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"
    # ===================================================
    # zrangebyscore 返回有序集合中指定分数区间的成员列表。有序集成员按分数值递增(从小到大)
    次序排列。
    # ===================================================
    127.0.0.1:6379> zadd salary 2500 xiaoming
    (integer) 1
    127.0.0.1:6379> zadd salary 5000 xiaohong
    (integer) 1
    127.0.0.1:6379> zadd salary 500 kuangshen
    (integer) 1
    # Inf无穷大量+∞,同样地,-∞可以表示为-Inf。
    127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 显示整个有序集
    1) "kuangshen"
    2) "xiaoming"
    3) "xiaohong"
    127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # 递增排列
    1) "kuangshen"
    2) "500"
    3) "xiaoming"
    4) "2500"
    5) "xiaohong"
    6) "5000"
    127.0.0.1:6379> ZREVRANGE salary 0 -1 WITHSCORES # 递减排列
    1) "xiaohong"
    2) "5000"
    3) "xiaoming"
    4) "2500"
    5) "kuangshen"
    6) "500"
    127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 WITHSCORES # 显示工资 <=2500
    的所有成员
    1) "kuangshen"
    2) "500"
    3) "xiaoming"
    4) "2500"
    
    # ===================================================
    # zrem 移除有序集中的一个或多个成员
    # ===================================================
    127.0.0.1:6379> ZRANGE salary 0 -1
    1) "kuangshen"
    2) "xiaoming"
    3) "xiaohong"
    127.0.0.1:6379> zrem salary kuangshen
    (integer) 1
    127.0.0.1:6379> ZRANGE salary 0 -1
    1) "xiaoming"
    2) "xiaohong"
    # ===================================================
    # zcard 命令用于计算集合中元素的数量。
    # ===================================================
    127.0.0.1:6379> zcard salary
    (integer) 2
    OK
    # ===================================================
    # zcount 计算有序集合中指定分数区间的成员数量。
    # ===================================================
    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
    # ===================================================
    # zrank 返回有序集中指定成员的排名。其中有序集成员按分数值递增(从小到大)顺序排列。
    # ===================================================
    127.0.0.1:6379> zadd salary 2500 xiaoming
    (integer) 1
    127.0.0.1:6379> zadd salary 5000 xiaohong
    (integer) 1
    127.0.0.1:6379> zadd salary 500 kuangshen
    (integer) 1
    127.0.0.1:6379> ZRANGE salary 0 -1 WITHSCORES # 显示所有成员及其 score 值
    1) "kuangshen"
    2) "500"
    3) "xiaoming"
    4) "2500"
    5) "xiaohong"
    6) "5000"
    127.0.0.1:6379> zrank salary kuangshen # 显示 kuangshen 的薪水排名,最少
    (integer) 0
    127.0.0.1:6379> zrank salary xiaohong # 显示 xiaohong 的薪水排名,第三
    (integer) 2
    # ===================================================
    # zrevrank 返回有序集中成员的排名。其中有序集成员按分数值递减(从大到小)排序。
    # ===================================================
    127.0.0.1:6379> ZREVRANK salary kuangshen # 狂神第三
    (integer) 2
    127.0.0.1:6379> ZREVRANK salary xiaohong # 小红第一
    (integer) 0
    

    和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列,比如 一个存储全班同学成绩的sorted set,其集合value可以是同学的学号,而score就可以是其考试得分, 这样在数据插入集合的时候,就已经进行了天然的排序。可以用sorted set来做带权重的队列,比如普 通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让 重要的任务优先执行。 排行榜应用,取TOP N操作 !

    三种特殊数据类型

    GEO地理位置
    简介
    Redis 的 GEO 特性在 Redis 3.2 版本中推出, 这个功能可以将用户给定的地理位置信息储存起来, 并对
    这些信息进行操作。来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能。geo的数据类型为
    zset。
    GEO 的数据结构总共有六个常用命令:geoadd、geopos、geodist、georadius、
    georadiusbymember、gethash
    官方文档:https://www.redis.net.cn/order/3685.htm
    

    geoadd

    # 语法
    geoadd key longitude latitude member ...
    # 将给定的空间元素(纬度、经度、名字)添加到指定的键里面。
    # 这些数据会以有序集he的形式被储存在键里面,从而使得georadius和georadiusbymember这样的
    命令可以在之后通过位置查询取得这些元素。
    # geoadd命令以标准的x,y格式接受参数,所以用户必须先输入经度,然后再输入纬度。
    # geoadd能够记录的坐标是有限的:非常接近两极的区域无法被索引。
    # 有效的经度介于-180-180度之间,有效的纬度介于-85.05112878 度至 85.05112878 度之间。,
    当用户尝试输入一个超出范围的经度或者纬度时,geoadd命令将返回一个错误。
    
    127.0.0.1:6379> geoadd china:city 116.23 40.22 北京
    (integer) 1
    127.0.0.1:6379> geoadd china:city 121.48 31.40 上海 113.88 22.55 深圳 120.21
    30.20 杭州
    (integer) 3
    127.0.0.1:6379> geoadd china:city 106.54 29.40 重庆 108.93 34.23 西安 114.02
    30.58 武汉
    (integer) 3
    

    geopos

    # 语法
    geopos key member [member...]
    #从key里返回所有给定位置元素的位置(经度和纬度)
    127.0.0.1:6379> geopos china:city 北京
    1) 1) "116.23000055551528931"
    2) "40.2200010338739844"
    127.0.0.1:6379> geopos china:city 上海 重庆
    1) 1) "121.48000091314315796"
    2) "31.40000025319353938"
    2) 1) "106.54000014066696167"
    2) "29.39999880018641676"
    127.0.0.1:6379> geopos china:city 新疆
    1) (nil)
    

    geodist

    # 语法
    geodist key member1 member2 [unit]
    # 返回两个给定位置之间的距离,如果两个位置之间的其中一个不存在,那么命令返回空值。
    # 指定单位的参数unit必须是以下单位的其中一个:
    # m表示单位为米
    # km表示单位为千米
    # mi表示单位为英里
    # ft表示单位为英尺
    # 如果用户没有显式地指定单位参数,那么geodist默认使用米作为单位。
    #geodist命令在计算距离时会假设地球为完美的球形,在极限情况下,这一假设最大会造成0.5%的误
    差。
    
    127.0.0.1:6379> geodist china:city 北京 上海
    "1088785.4302"
    127.0.0.1:6379> geodist china:city 北京 上海 km
    "1088.7854"
    127.0.0.1:6379> geodist china:city 重庆 北京 km
    "1491.6716"
    

    georadius

    # 语法
    georadius key longitude latitude radius m|km|ft|mi [withcoord][withdist]
    [withhash][asc|desc][count count]
    # 以给定的经纬度为中心, 找出某一半径内的元素
    测试:重新连接 redis-cli,增加参数 --raw ,可以强制输出中文,不然会乱码
    
    [root@kuangshen bin]# redis-cli --raw -p 6379
    # 在 china:city 中寻找坐标 100 30 半径为 1000km 的城市
    127.0.0.1:6379> georadius china:city 100 30 1000 km
    重庆
    西安
    # withdist 返回位置名称和中心距离
    127.0.0.1:6379> georadius china:city 100 30 1000 km withdist
    重庆
    635.2850
    西安
    963.3171
    # withcoord 返回位置名称和经纬度
    127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord
    重庆
    106.54000014066696167
    29.39999880018641676
    西安
    108.92999857664108276
    34.23000121926852302
    # withdist withcoord 返回位置名称 距离 和经纬度 count 限定寻找个数
    127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord withdist count
    1
    重庆
    635.2850
    106.54000014066696167
    29.39999880018641676
    127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord withdist count
    2
    重庆
    635.2850
    106.54000014066696167
    29.39999880018641676
    西安
    963.3171
    108.92999857664108276
    34.23000121926852302
    

    georadiusbymember

    # 语法
    georadiusbymember key member radius m|km|ft|mi [withcoord][withdist]
    [withhash][asc|desc][count count]
    # 找出位于指定范围内的元素,中心点是由给定的位置元素决定
    
    127.0.0.1:6379> GEORADIUSBYMEMBER china:city 北京 1000 km
    北京
    西安
    127.0.0.1:6379> GEORADIUSBYMEMBER china:city 上海 400 km
    杭州
    上海
    

    geohash

    # 语法
    geohash key member [member...]
    # Redis使用geohash将二维经纬度转换为一维字符串,字符串越长表示位置更精确,两个字符串越相似
    表示距离越近。
    
    127.0.0.1:6379> geohash china:city 北京 重庆
    wx4sucu47r0
    wm5z22h53v0
    127.0.0.1:6379> geohash china:city 北京 上海
    wx4sucu47r0
    wtw6sk5n300
    

    zrem

    GEO没有提供删除成员的命令,但是因为GEO的底层实现是zset,所以可以借用zrem命令实现对地理位 置信息的删除

    127.0.0.1:6379> geoadd china:city 116.23 40.22 beijin
    1
    127.0.0.1:6379> zrange china:city 0 -1 # 查看全部的元素
    重庆
    西安
    深圳
    武汉
    杭州
    上海
    beijin
    北京
    127.0.0.1:6379> zrem china:city beijin # 移除元素
    1
    127.0.0.1:6379> zrem china:city 北京 # 移除元素
    1
    127.0.0.1:6379> zrange china:city 0 -1
    重庆
    西安
    深圳
    武汉
    杭州
    上海
    

    HyperLogLog

    Redis 在 2.8.9 版本添加了 HyperLogLog 结构。
    Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积
    非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。
    在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基
    数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
    HyperLogLog则是一种算法,它提供了不精确的去重计数方案。
    举个栗子:假如我要统计网页的UV(浏览用户数量,一天内同一个用户多次访问只能算一次),传统的
    解决方案是使用Set来保存用户id,然后统计Set中的元素数量来获取页面UV。但这种方案只能承载少量
    用户,一旦用户数量大起来就需要消耗大量的空间来存储用户id。我的目的是统计用户数量而不是保存
    用户,这简直是个吃力不讨好的方案!而使用Redis的HyperLogLog最多需要12k就可以统计大量的用户
    数,尽管它大概有0.81%的错误率,但对于统计UV这种不需要很精确的数据是可以忽略不计的。
    
    

    什么是基数

    比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。
    基数估计就是在误差可接受的范围内,快速计算基数。
    
    

    基本命令

    image-20220827211636044

    127.0.0.1:6379> PFADD mykey a b c d e f g h i j
    1
    127.0.0.1:6379> PFCOUNT mykey
    10
    127.0.0.1:6379> PFADD mykey2 i j z x c v b n m
    1
    127.0.0.1:6379> PFMERGE mykey3 mykey mykey2
    OK
    127.0.0.1:6379> PFCOUNT mykey3
    15
    

    BitMap

    在开发中,可能会遇到这种情况:需要统计用户的某些信息,如活跃或不活跃,登录或者不登录;又如
    需要记录用户一年的打卡情况,打卡了是1, 没有打卡是0,如果使用普通的 key/value存储,则要记录
    365条记录,如果用户量很大,需要的空间也会很大,所以 Redis 提供了 Bitmap 位图这中数据结构,
    Bitmap 就是通过操作二进制位来进行记录,即为 0 和 1;如果要记录 365 天的打卡情况,使用 Bitmap
    表示的形式大概如下:0101000111000111...........................,这样有什么好处呢?当然就是节约内存
    了,365 天相当于 365 bit,又 1 字节 = 8 bit , 所以相当于使用 46 个字节即可。
    BitMap 就是通过一个 bit 位来表示某个元素对应的值或者状态, 其中的 key 就是对应元素本身,实际上
    底层也是通过对字符串的操作来实现。Redis 从 2.2 版本之后新增了setbit, getbit, bitcount 等几个
    bitmap 相关命令。
    

    setbit 设置操作

    SETBIT key offset value : 设置 key 的第 offset 位为value (1或0)

    用 bitmap 来记录上述事例中一周的打卡记录如下所示:
    # 周一:1,周二:0,周三:0,周四:1,周五:1,周六:0,周天:0 (1 为打卡,0 为不打卡)
    127.0.0.1:6379> setbit sign 0 1
    0
    127.0.0.1:6379> setbit sign 1 0
    0
    127.0.0.1:6379> setbit sign 2 0
    0
    127.0.0.1:6379> setbit sign 3 1
    0
    127.0.0.1:6379> setbit sign 4 1
    0
    127.0.0.1:6379> setbit sign 5 0
    0
    127.0.0.1:6379> setbit sign 6 0
    0
    
    

    getbit 获取操作

    GETBIT key offset 获取offset设置的值,未设置过默认返回0

    127.0.0.1:6379> getbit sign 3 # 查看周四是否打卡
    1
    127.0.0.1:6379> getbit sign 6 # 查看周七是否打卡
    0
    

    bitcount 统计操作

    bitcount key [start, end] 统计 key 上位为1的个数
    # 统计这周打卡的记录,可以看到只有3天是打卡的状态:
    127.0.0.1:6379> bitcount sign
    3
    
    

    Redis.conf

    Redis 的配置文件位于 Redis 安装目录下,文件名为 redis.conf

    config get * # 获取全部的配置
    
    

    配置大小单位,只支持bytes,不支持bit

    对大小写不敏感

    INCLUDES

    和Spring配置文件类似,可以通过includes包含,redis.conf 可以作为总文件,可以包含其他文件

    NETWORK 网络配置

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

    GENERAL

    daemonize yes # 默认情况下,Redis不作为守护进程运行。需要开启的话,改为 yes
    supervised no # 可通过upstart和systemd管理Redis守护进程
    pidfile /var/run/redis_6379.pid # 以后台进程方式运行redis,则需要指定pid 文件
    loglevel notice # 日志级别。可选项有:
    # debug(记录大量日志信息,适用于开发、测试阶段);
    # verbose(较多日志信息);
    # notice(适量日志信息,使用于生产环境);
    # warning(仅有部分重要、关键信息才会被记录)。
    logfile "" # 日志文件的位置,当指定为空字符串时,为标准输出
    databases 16 # 设置数据库的数目。默认的数据库是DB 0
    always-show-logo yes # 是否总是显示logo
    
    
    

    SNAPSHOPTING

    # 900秒(15分钟)内至少1个key值改变(则进行数据库保存--持久化)
    save 900 1
    # 300秒(5分钟)内至少10个key值改变(则进行数据库保存--持久化)
    save 300 10
    # 60秒(1分钟)内至少10000个key值改变(则进行数据库保存--持久化)
    save 60 10000
    stop-writes-on-bgsave-error yes # 持久化出现错误后,是否依然进行继续进行工作
    rdbcompression yes # 使用压缩rdb文件 yes:压缩,但是需要一些cpu的消耗。no:不压
    缩,需要更多的磁盘空间
    rdbchecksum yes # 是否校验rdb文件,更有利于文件的容错性,但是在保存rdb文件的时
    候,会有大概10%的性能损耗
    dbfilename dump.rdb # dbfilenamerdb文件名称
    dir ./ # dir 数据目录,数据库的写入会在这个目录。rdb、aof文件也会写在这个目录
    

    SECURITY

    # 启动redis
    # 连接客户端
    # 获得和设置密码
    config get requirepass
    config set requirepass "123456"
    #测试ping,发现需要验证
    127.0.0.1:6379> ping
    NOAUTH Authentication required.
    # 验证
    127.0.0.1:6379> auth 123456
    OK
    127.0.0.1:6379> ping
    PONG
    
    maxclients 10000 # 设置能连上redis的最大客户端连接数量
    maxmemory <bytes> # redis配置的最大内存容量
    maxmemory-policy noeviction # maxmemory-policy 内存达到上限的处理策略
    #volatile-lru:利用LRU算法移除设置过过期时间的key。
    #volatile-random:随机移除设置过过期时间的key。
    #volatile-ttl:移除即将过期的key,根据最近过期时间来删除(辅以TTL)
    #allkeys-lru:利用LRU算法移除任何key。
    #allkeys-random:随机移除任何key。
    #noeviction:不移除任何key,只是返回一个写错误。
    

    append only 模式

    appendonly no # 是否以append only模式作为持久化方式,默认使用的是rdb方式持久化,这种
    方式在许多应用中已经足够用了
    appendfilename "appendonly.aof" # appendfilename AOF 文件名称
    appendfsync everysec # appendfsync aof持久化策略的配置
    # no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。
    # always表示每次写入都执行fsync,以保证数据同步到磁盘。
    # everysec表示每秒执行一次fsync,可能会导致丢失这1s数据
    

    常见配置介绍

    1、Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
    daemonize no
    2、当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指
    定
    pidfile /var/run/redis.pid
    3、指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认
    端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字
    port 6379
    4、绑定的主机地址
    bind 127.0.0.1
    maxclients 10000 # 设置能连上redis的最大客户端连接数量
    maxmemory <bytes> # redis配置的最大内存容量
    maxmemory-policy noeviction # maxmemory-policy 内存达到上限的处理策略
    #volatile-lru:利用LRU算法移除设置过过期时间的key。
    #volatile-random:随机移除设置过过期时间的key。
    #volatile-ttl:移除即将过期的key,根据最近过期时间来删除(辅以TTL)
    #allkeys-lru:利用LRU算法移除任何key。
    #allkeys-random:随机移除任何key。
    #noeviction:不移除任何key,只是返回一个写错误。
    
    appendonly no # 是否以append only模式作为持久化方式,默认使用的是rdb方式持久化,这种
    方式在许多应用中已经足够用了
    appendfilename "appendonly.aof" # appendfilename AOF 文件名称
    appendfsync everysec # appendfsync aof持久化策略的配置
    # no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。
    # always表示每次写入都执行fsync,以保证数据同步到磁盘。
    # everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。
    1
    2
    3
    4
    5
    6
    7
    8
    5、当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
    timeout 300
    6、指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为
    verbose
    loglevel verbose
    7、日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方
    式为标准输出,则日志将会发送给/dev/null
    logfile stdout
    8、设置数据库的数量,默认数据库为0,可以使用SELECT 命令在连接上指定数据库id
    databases 16
    9、指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
    save 
    Redis默认配置文件中提供了三个条件:
    save 900 1
    save 300 10
    save 60 10000
    分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更
    改。
    10、指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时
    间,可以关闭该选项,但会导致数据库文件变的巨大
    rdbcompression yes
    11、指定本地数据库文件名,默认值为dump.rdb
    dbfilename dump.rdb
    12、指定本地数据库存放目录
    dir ./
    13、设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master
    进行数据同步
    slaveof
    14、当master服务设置了密码保护时,slav服务连接master的密码
    masterauth
    15、设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH 命令提供密码,
    默认关闭
    requirepass foobared
    16、设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可
    以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,
    Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
    maxclients 128
    17、指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝
    试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,
    但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
    maxmemory
    18、指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不
    开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来
    同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
    appendonly no
    19、指定更新日志文件名,默认为appendonly.aof
    appendfilename appendonly.aof
    20、指定更新日志条件,共有3个可选值:
    no:表示等操作系统进行数据缓存同步到磁盘(快)
    always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
    everysec:表示每秒同步一次(折衷,默认值)
    appendfsync everysec
    21、指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将
    访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔
    细分析Redis的VM机制)
    vm-enabled no
    22、虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
    vm-swap-file /tmp/redis.swap
    23、将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据
    都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有
    value都存在于磁盘。默认值为0
    vm-max-memory 0
    24、Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多
    个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page
    大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用
    默认值
    vm-page-size 32
    25、设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中
    的,,在磁盘上每8个pages将消耗1byte的内存。
    vm-pages 134217728
    26、设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都
    是串行的,可能会造成比较长时间的延迟。默认值为4
    vm-max-threads 4
    27、设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
    glueoutputbuf yes
    28、指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
    hash-max-zipmap-entries 64
    hash-max-zipmap-value 512
    29、指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)
    activerehashing yes
    30、指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各
    个实例又拥有自己的特定配置文件
    include /path/to/local.conf
    

    Redis的持久化

    Redis 是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中 的数据库状态也会消失。所以 Redis 提供了持久化功能!

    RDB(Redis DataBase)

    在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快 照文件直接读到内存里

    Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程 都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。 这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那 RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。

    Fork

    Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量,环境变量,程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。

    image-20220828110416419

    配置位置及SNAPSHOTTING解析

    image-20220828110432364

    这里的触发条件机制,我们可以修改测试一下:

    save 120 10 # 120秒内修改10次则触发RDB

    RDB 是整合内存的压缩过的Snapshot,RDB 的数据结构,可以配置复合的快照触发条件。

    默认: 1 save 120 10 # 120秒内修改10次则触发RDB 1分钟内改了1万次 5分钟内改了10次 15分钟内改了1次 如果想禁用RDB持久化的策略,只要不设置任何save指令,或者给save传入一个空字符串参数也可以。 若要修改完毕需要立马生效,可以手动使用 save 命令!立马生效 !

    Stop-writes-on-bgsave-error:如果配置为no,表示你不在乎数据不一致或者有其他的手段发现和控
    制,默认为yes。
    rbdcompression:对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用
    LZF算法进行压缩,如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能。
    rdbchecksum:在存储快照后,还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约
    10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。默认为yes
    
    如何触发RDB快照
    1、配置文件中默认的快照配置,建议多用一台机子作为备份,复制一份 dump.rdb
    2、命令save或者是bgsave
    save 时只管保存,其他不管,全部阻塞
    bgsave,Redis 会在后台异步进行快照操作,快照同时还可以响应客户端请求。可以通过lastsave
    命令获取最后一次成功执行快照的时间。
    3、执行flushall命令,也会产生 dump.rdb 文件,但里面是空的,无意义 !
    4、退出的时候也会产生 dump.rdb 文件!
    
    如何恢复
    1、将备份文件(dump.rdb)移动到redis安装目录并启动服务即可
    2、CONFIG GET dir 获取目录
    优点和缺点
    优点:
    1、适合大规模的数据恢复
    2、对数据完整性和一致性要求不高
    127.0.0.1:6379> config get dir
    dir
    /usr/local/bin
    缺点:
    1、在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改
    2、Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑。
    

    image-20220828110559768

    AOF(Append Only File)

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

    配置

    appendonly no # 是否以append only模式作为持久化方式,默认使用的是rdb方式持久化,这
    种方式在许多应用中已经足够用了
    appendfilename "appendonly.aof" # appendfilename AOF 文件名称
    appendfsync everysec # appendfsync aof持久化策略的配置
    # no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。
    # always表示每次写入都执行fsync,以保证数据同步到磁盘。
    # everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。
    No-appendfsync-on-rewrite #重写时是否可以运用Appendfsync,用默认no即可,保证数据安
    全性
    Auto-aof-rewrite-min-size # 设置重写的基准值
    Auto-aof-rewrite-percentage #设置重写的基准值
    
    AOF 启动/修复/恢复
    正常恢复:
    启动:设置Yes,修改默认的appendonly no,改为yes
    将有数据的aof文件复制一份保存到对应目录(config get dir)
    恢复:重启redis然后重新加载
    异常恢复:
    启动:设置Yes
    故意破坏 appendonly.aof 文件!
    修复: redis-check-aof --fix appendonly.aof 进行修复
    恢复:重启 redis 然后重新加载
    
    AOF 采用文件追加方式,文件会越来越大,为避免出现此种情况,新增了重写机制,当AOF文件的大小
    超过所设定的阈值时,Redis 就会启动AOF 文件的内容压缩,只保留可以恢复数据的最小指令集,可以
    使用命令 bgrewriteaof !
    重写原理:
    AOF 文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再
    rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧
    的aof文件,这点和快照有点类似!
    触发机制:
    Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的已被且文件大
    于64M的触发。
    
    
    优点:
    1、每修改同步:appendfsync always 同步持久化,每次发生数据变更会被立即记录到磁盘,性能较差
    但数据完整性比较好
    2、每秒同步: appendfsync everysec 异步操作,每秒记录 ,如果一秒内宕机,有数据丢失
    3、不同步: appendfsync no 从不同步
    缺点:
    1、相同数据集的数据而言,aof 文件要远大于 rdb文件,恢复速度慢于 rdb。
    2、Aof 运行效率要慢于 rdb,每秒同步策略效率较好,不同步效率和rdb相同。
    

    image-20220828110905531

    1、RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储
    2、AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始
    的数据,AOF命令以Redis 协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重
    写,使得AOF文件的体积不至于过大。
    3、只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化
    4、同时开启两种持久化方式
    在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF
    文件保存的数据集要比RDB文件保存的数据集要完整。
    RDB 的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者
    建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有
    AOF可能潜在的Bug,留着作为一个万一的手段。
    5、性能建议
    因为RDB文件只用作后备用途,建议只在Slave上持久化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事务的概念:
    Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列
    化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事
    务执行命令序列中。
    总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
    Redis事务没有隔离级别的概念:
    批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行!
    Redis不保证原子性:
    Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其
    余的命令仍会被执行。
    Redis事务的三个阶段
    开始事务
    命令入队
    执行事务
    

    相关命令

    watch key1 key2 ... #监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则
    事务被打断 ( 类似乐观锁 )
    multi # 标记一个事务块的开始( queued )
    exec # 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 )
    discard # 取消事务,放弃事务块中的所有命令
    unwatch # 取消watch对所有key的监控
    
    

    redis事务

    Redis事务的概念:
    Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列
    化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事
    务执行命令序列中。
    总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
    Redis事务没有隔离级别的概念:
    批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行!
    Redis不保证原子性:
    Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其
    余的命令仍会被执行。
    Redis事务的三个阶段:
    开始事务
    命令入队
    执行事务
    
    

    Redis事务相关命令:

    watch key1 key2 ... #监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则
    事务被打断 ( 类似乐观锁 )
    multi # 标记一个事务块的开始( queued )
    exec # 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 )
    discard # 取消事务,放弃事务块中的所有命令
    unwatch # 取消watch对所有key的监控
    

    image-20220828140349734

    放弃事务

    image-20220828140408735

    若在事务队列中存在命令性的错误,则执行exec命令时候,所有命令都不会执行

    image-20220828140453138

    若在事务队列中存在语法性错误(类似于java的1/0的运行时异常),则执行EXEC命令时,其他正确 命令会被执行,错误命令抛出异常。

    image-20220828140528029

    watch监控

    悲观锁

    悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在 拿数据的时候都会上锁,这样别人想拿到这个数据就会block直到它拿到锁。传统的关系型数据库里面就 用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在操作之前先上锁。

    乐观锁

    每次拿数据都认为别人不会修改所以不会上锁,但是在更新时候会判断一下在此器件别人有美欧去更新这个数据,,乐 观锁适用于多读的应用类型,这样可以提高吞吐量,乐观锁策略:提交版本必须大于记录当前版本才能 执行更新。

    1、初始化信用卡可用余额和欠额
    127.0.0.1:6379> set balance 100
    OK
    127.0.0.1:6379> set debt 0
    OK
    
    2、使用watch检测balance,事务期间balance数据未变动,事务执行成功
    127.0.0.1:6379> watch balance
    OK
    127.0.0.1:6379> MULTI
    OK
    127.0.0.1:6379> decrby balance 20
    QUEUED
    127.0.0.1:6379> incrby debt 20
    QUEUED
    127.0.0.1:6379> exec
    1) (integer) 80
    2) (integer) 20
    3、使用watch检测balance,事务期间balance数据变动,事务执行失败!
    # 窗口一
    127.0.0.1:6379> watch balance
    OK
    127.0.0.1:6379> MULTI # 执行完毕后,执行窗口二代码测试
    OK
    127.0.0.1:6379> decrby balance 20
    QUEUED
    127.0.0.1:6379> incrby debt 20
    QUEUED
    127.0.0.1:6379> exec # 修改失败!
    (nil)
    # 窗口二
    127.0.0.1:6379> get balance
    "80"
    127.0.0.1:6379> set balance 200
    OK
    # 窗口一:出现问题后放弃监视,然后重来!
    127.0.0.1:6379> UNWATCH # 放弃监视
    OK
    127.0.0.1:6379> watch balance
    OK
    127.0.0.1:6379> MULTI
    OK
    127.0.0.1:6379> decrby balance 20
    QUEUED
    127.0.0.1:6379> incrby debt QUEUED
    127.0.0.1:6379> exec # 成功!
    1) (integer) 180
    2) (integer) 4020
    
    

    说明: 一但执行 EXEC 开启事务的执行后,无论事务使用执行成功, WARCH 对变量的监控都将被取消。 故当事务执行失败后,需重新执行WATCH命令对变量进行监控,并开启新的事务进行操作。

    小结 watch指令类似于乐观锁,在事务提交时,如果watch监控的多个KEY中任何KEY的值已经被其他客户端 更改,则使用EXEC执行事务时,事务队列将不会被执行,同时返回Nullmulti-bulk应答以通知调用者事 务执行失败

    Redis 发布订阅

    发布订阅是一种消息通信模式:发送者发送消息,订阅者接收消息

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

    image-20220828141113810

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

    image-20220828141124514

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

    image-20220828141141587

    命令

    image-20220828141206000

    redis 127.0.0.1:6379> SUBSCRIBE redisChat
    Reading messages... (press Ctrl-C to quit)
    1) "subscribe"
    2) "redisChat"
    3) (integer) 1
    
    redis 127.0.0.1:6379> PUBLISH redisChat "Hello,Redis"
    (integer) 1
    redis 127.0.0.1:6379> PUBLISH redisChat "Hello,Kuangshen"
    (integer) 1
    # 订阅者的客户端会显示如下消息
    1) "message"
    2) "redisChat"
    3) "Hello,Redis"
    1) "message"
    2) "redisChat"
    3) "Hello,Kuangshen"
    
    Redis是使用C实现的,通过分析 Redis 源码里的 pubsub.c 文件,了解发布和订阅机制的底层实现,籍
    此加深对 Redis 的理解。
    Redis 通过 PUBLISH 、SUBSCRIBE 和 PSUBSCRIBE 等命令实现发布和订阅功能。
    通过 SUBSCRIBE 命令订阅某频道后,redis-server 里维护了一个字典,字典的键就是一个个 channel
    ,而字典的值则是一个链表,链表中保存了所有订阅这个 channel 的客户端。SUBSCRIBE 命令的关
    键,就是将客户端添加到给定 channel 的订阅链表中。
    通过 PUBLISH 命令向订阅者发送消息,redis-server 会使用给定的频道作为键,在它所维护的 channel
    字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者。
    Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以设定对某一个
    key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应
    的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。
    使用场景
    Pub/Sub构建实时消息系统
    Redis的Pub/Sub系统可以构建实时的消息系统
    比如很多用Pub/Sub构建的实时聊天系统的例子。
    

    redis主从复制

    概念
    主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点
    (master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。
    Master以写为主,Slave 以读为主。
    默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从
    节点只能有一个主节点。
    主从复制的作用主要包括:
    1、数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
    redis 127.0.0.1:6379> PUBLISH redisChat "Hello,Redis"
    (integer) 1
    redis 127.0.0.1:6379> PUBLISH redisChat "Hello,Kuangshen"
    (integer) 1
    # 订阅者的客户端会显示如下消息
    1) "message"
    2) "redisChat"
    3) "Hello,Redis"
    1) "message"
    2) "redisChat"
    3) "Hello,Kuangshen"
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    2、故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务
    的冗余。
    3、负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务
    (即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写
    少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
    4、高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是
    Redis高可用的基础。
    一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的,原因如下:
    1、从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较
    大;
    2、从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G,也不能将所有
    内存用作Redis存储内存,一般来说,单台Redis最大使用内存不应该超过20G。
    电商网站上的商品,一般都是一次上传,无数次浏览的,说专业点也就是"多读少写"。
    对于这种场景,我们可以使如下这种架构:
    

    image-20220828153757900

    环境配置
    基本配置
    配从库不配主库,从库配置:
    每次与 master 断开之后,都需要重新连接,除非你配置进 redis.conf 文件!
    slaveof 主库ip 主库端口 # 配置主从
    Info replication # 查看信息
    

    修改配置文件

    一主二从,配置三个客户端

    image-20220828153855255

    1、拷贝多个redis.conf 文件
    2、指定端口 6379,依次类推
    3、开启daemonize yes
    4、Pid文件名字 pidfile /var/run/redis_6379.pid , 依次类推
    5、Log文件名字 logfile "6379.log" , 依次类推
    6、Dump.rdb 名字 dbfilename dump6379.rdb , 依次类推
    

    image-20220828153918875

    环境初始化

    查看节点

    info replication

    配置6379 是主服务器

    配置6380 6381是从服务器

    image-20220828154022138

    3、在主机设置值,在从机都可以取到!从机不能写值!

    测试一:主机挂了,查看从机信息,主机恢复,再次查看信息
    测试二:从机挂了,查看主机信息,从机恢复,查看从机信息
    主机写值给从机,从机不能给主机写
    

    image-20220828154052591

    上一个Slave 可以是下一个slave 和 Master,Slave 同样可以接收其他 slaves 的连接和同步请求,那么 该 slave 作为了链条中下一个的master,可以有效减轻 master 的写压力!

    设置6380是6379从机,设置6381是6380的从机

    image-20220828154141113

    谋权篡位

    一主二从的情况下,如果主机断了,从机可以使用命令 SLAVEOF NO ONE 将自己改为主机!这个时 候其余的从机链接到这个节点。对一个从属服务器执行命令 SLAVEOF NO ONE 将使得这个从属服务器 关闭复制功能,并从从属服务器转变回主服务器,原来同步所得的数据集不会被丢弃。

    6379宕机,后6380用slaveof no one 使自己成为主机,

    即使6379回来,6380仍然是主机

    image-20220828154211002

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

    哨兵模式

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

    image-20220828154348757

    哨兵模式的两个作用

    通过发送命令,让redis 服务器回到监控其运行状态,包括主服务器和从服务器

    当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服 务器,修改配置文件,让它们切换主机。

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

    image-20220828154450064

    假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认
    为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一
    定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。
    切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为
    客观下线
    
    1、调整结构,6379带着80、81
    2、自定义的 /myredis 目录下新建 sentinel.conf 文件,名字千万不要错
    3、配置哨兵,填写内容
    sentinel monitor 被监控主机名字 127.0.0.1 6379 1
    上面最后一个数字1,表示主机挂掉后slave投票看让谁接替成为主机,得票数多少后成为主机
    4、启动哨兵
    Redis-sentinel /myredis/sentinel.conf
    上述目录依照各自的实际情况配置,可能目录不同
    5、正常主从演示
    6、原有的Master 挂了
    7、投票新选
    8、重新主从继续开工,info replication 查查看
    9、问题:如果之前的master 重启回来,会不会双master 冲突? 之前的回来只能做小弟了
    

    哨兵模式优缺点

    优点

    哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都有
    主从可以切换,故障可以转移,系统可用性好
    哨兵模式是主从模式升级,系统更加健壮,可用性更高
    

    缺点

    redis较难支持在线扩容,在集群数量到达上限时,在线扩容会变得很复杂
    实现哨兵模式的配置也不简单,甚至可以说有些繁琐
    

    配置

    # Example sentinel.conf
    # 哨兵sentinel实例运行的端口 默认26379
    port 26379
    # 哨兵sentinel的工作目录
    dir /tmp
    # 哨兵sentinel监控的redis主节点的 ip port
    # master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
    # quorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
    # sentinel monitor <master-name> <ip> <redis-port> <quorum>
    sentinel monitor mymaster 127.0.0.1 6379 2
    
    # 当在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
    

    缓存穿透和雪崩

    Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一
    些问题。其中,最要害的问题,就是数据的一致性问题,从严格意义上讲,这个问题无解。如果对数据
    的一致性要求很高,那么就不能使用缓存。
    另外的一些典型问题就是,缓存穿透、缓存雪崩和缓存击穿。目前,业界也都有比较流行的解决方案。
    

    缓存穿透

    redis没有命中,数据库也没有,

    缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于
    是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是
    都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
    

    解决方案,布隆过滤器

    布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则
    丢弃,从而避免了对底层存储系统的查询压力;
    

    image-20220828160615353

    布隆过滤器本质上是一个二进制数组,元素的值不是1就是0. 当我们存一个商品id为10的商品,假设我们经过三次哈希,存的数组下标为1,3,7,就将这三个下标的元素改为1.这样每次访问redis之前,先访问布隆过滤器。查询id为10的商品的时候,经过布隆过滤器的哈希算法,获取到该商品对应的下标是1,3,7。那么,如果这三个数组的下标对应的元素都为1 则表示存在该商品,放行这次请求。如果有一个为0,则不存在该商品。
    
    布隆过滤器判断存在不一定真的存在,但是,判断不存在则一定不存在。
    
    针对布隆过滤器的一些误判,1我们可以增加二进制数组位数或者增加Hash次数来解决。
    
    

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

    image-20220828160830907

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

    缓存击穿

    这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中
    对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一
    个屏障上凿开了一个洞。
    当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访
    问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。
    

    解决

    设置热点数据永不过期
    从缓存层面来看,没有设置过期时间,所以不会出现热点 key 过期后产生的问题。
    加互斥锁
    分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布
    式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考
    验很大
    

    缓存雪崩

    在一段时间,缓存集中过期失效

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

    image-20220828160946109

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

    缓存倾斜

    缓存倾斜又称为:热点key倾斜。
    缓存中存在这个key,但是由于这个key突然成为高热点key,比如明星离婚,这样导致大量的用户突然高并发的访问这
    个高热点key所在的那台缓存服务器,最终导致那台缓存服务器崩掉,继而请求又到达下一个缓存服务器,下一个缓存
    服务器又承受不住而崩掉,最终导致整个缓存模块崩掉。(是缓存崩掉了,跟数据库关系不大)
    

    数据倾斜的原因

    存在bigkey

    业务层避免bigkey

    将集合类型bigkey拆分为多个小集合

    slot手工分配不均匀

    将一些特别热点的key直接放在客户端进行存储,设置过期时间,过期后在从后台中查询
    方案二:
    我们可以将这个热点key复制出多个子key,每个子key的value值一样,查询的时候使用hash取模算法,将压
    力分摊到不同的节点。或者存储在二级缓存 比如jvm缓存中
    方案三:
    增加实例配置/集群配置
    
    

    缓存脑裂

    脑裂问题主要产生在Redis进行高可用,高并发设置时候,进行晒并模式的配置

    所谓脑裂,是在主从集群中发生数据丢失,最常见的原因就是主库的数据还没有同步到从库,结果主库发生了故障,的那个从库升级为主库,未同步的数据就丢失了

    为什么发生脑裂
    由于网络原因,master节点和slave、sentinel节点之间不能通信,sentinel节点无法感知到master节点
    的存在,于是会从slave节点中选出一个成为master节点,此时就存在多个master节点,这就是脑裂。
    如果在网络恢复之前,还有请求发送到之前的master节点,那么新的master节点就会丢失这部分数据。
    因为等到网络恢复了之后,sentinel节点会把之前的master节点降为slave节点,然后新的master节点同步时,就
    会丢失掉这部分数据。
    

    为什么脑裂会导致数据丢失

    主从切换后,从库一旦升级为新主库,哨兵就会让原主库执行 slave of 命令,和新主库重新进行全量同步。
    而在全量同步执行的最后阶段,原主库需要清空本地的数据,加载新主库发送的 RDB 文件,这样一来,原主库在主从
    切换期间保存的新写数据就丢失了
    

    解决方案

    Redis 已经提供了两个配置项来限制主库的请求处理,分别是 min-slaves-to-write 和 min-slavesmax-lag。
    min-slaves-to-write:这个配置项设置了主库能进行数据同步的最少从库数量; min-slaves-max-lag:这个
    配置项设置了主从库间进行数据复制时,从库给主库发送 ACK 消息的最大延迟(以秒为单位)。 我们可以把 minslaves-to-write 和 min-slaves-max-lag 这两个配置项搭配起来使用,分别给它们设置一定的阈值,假设为
    N 和 T。这两个配置项组合后的要求是,主库连接的从库中至少有 N 个从库,和主库进行数据复制时的 ACK 消息
    延迟不能超过 T 秒,否则,主库就不会再接收客户端的请求了。
    即使原主库是假故障,它在假故障期间也无法响应哨兵心跳,也不能和从库进行同步,自然也就无法和从库进行
    ACK 确认了。这样一来,min-slaves-to-write 和 min-slaves-max-lag 的组合要求就无法得到满足,原主
    库就会被限制接收客户端请求,客户端也就不能在原主库中写入新数据了。
    

    redis高可用
    这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续
    工作,其实就是搭建的集群。
    限流降级
    这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对
    某个key只允许一个线程查询数据和写缓存,其他线程等待。
    数据预热
    数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀

    Redis Cluster集群

    简介
    集群模式是实际使用最多的模式。
    Redis Cluster是社区版推出的Redis分布式集群解决方案,主要解决Redis分布式方面的需求,比如,当遇
    到单机内存,并发和流量等瓶颈的时候,Redis Cluster能起到很好的负载均衡的目的。
    为什么使用redis-cluster?
    为了在大流量访问下提供稳定的业务,集群化是存储的必然形态
    未来的发展趋势肯定是云计算和大数据的紧密结合
    只有分布式架构能满足要求
    

    集群描述

    集群搭建方案

    Redis集群搭建的方式有多种,但从redis 3.0之后版本支持redis-cluster集群,至少需要3(Master)+3(Slave)
    才能建立集群。Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所
    有 节点连接。其redis-cluster架构图如如下侧:
    

    image-20220829171137772

    Redis Cluster集群节点最小配置6个节点以上(3主3从),其中主节点提供读写操作,从节点作为备用节 点,不提供请求,只作为故障转移使用。

    image-20220829171212069

    1、所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。 
    2、节点的fail是通过集群中超过半数的节点检测失效时才生效。 
    3、客户端与redis节点直连,不需要中间proxy层.客户
    端不需要连接集群所有节点,连接集群中任何一个可用节点即可。 4、redis-cluster把所有的物理节点映射到[0-16383]slot上(不一定是平均分配),cluster 负责维护
    5、Redis集群预分好16384个哈希槽,当需要在 Redis 集群中放置一个 key-value 时, redis 先对key 使用crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点
    
    

    Redis Cluster容错

    容错性,是指软件检测应用程序所运行的软件或硬件中发生的错误并从错误中恢复的能力,通常可以从系统的可靠性、可用性、可测性等几个方面来衡量。
    
    1什么时候判断master不可用?
    投票机制。投票过程是集群中所有master参与,如果半数以上master节点与master节点通信超时(clusternode-timeout),认为当前master节点挂掉.
    2什么时候整个集群不可用(cluster_state:fail)? 
    如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成集群的slot映射[0-16383]不完整时进入fail状态. 如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态
    

    Redis Cluster节点分配

    Redis Cluster采用虚拟槽分区,所有的键根据哈希函数映射到0~16383个整数槽内,每个节点负责维护一
    部分槽以及槽所印映射的键值数据。
    三个主节点分别是:A, B, C 三个节点,它们可以是一台机器上的三个端口,也可以是三台不同的服务器。那么,采用哈希槽 (hash slot)的方式来分配16384个slot 的话,它们三个节点分别承担的slot 区间是
    节点A覆盖0-5460;
    节点B覆盖5461-10922;
    节点C覆盖10923-16383
    

    集群搭建实操

    redis集群需要至少要三个master节点,我们这里搭建三个master节点,并且给每个master再搭建一个
    slave节点,总共6个redis节点,这里用一台机器(可以多台机器部署,修改一下ip地址就可以了)部署6
    个redis实例,三主三从,搭建集群的步骤如下:
    
    

    创建Redis节点安装目录

    mkdir -p /usr/local/redis_cluster :指定目录下 创建 redis_cluster
    

    2、在redis_cluster目录下,创建7000-7005个文件夹下

    mkdir 7000 7001 7002 7003 7004 7005
    

    3、并将redis-conf分别拷贝到7000-7005文件夹下

    cp /opt/redis-5.0.4/redis.conf ./7000
    

    4.修改Redis配置文件

    /usr/local/redis_cluster/7000/redis.conf
    
    # 关闭保护模式 用于公网访问
    protected-mode no
    port 7000
    # 开启集群模式
    cluster-enabled yes
    cluster-config-file nodes-7000.conf
    cluster-node-timeout 5000
    # 后台启动
    daemonize yes
    pidfile /var/run/redis_7000.pid
    logfile "7000.log"
    #dir /redis/data
    # 此处绑定ip 可以是阿里内网ip 和 本地ip 也可以直接注释掉该项
    #bind 127.0.0.1
    #用于连接主节点密码
    masterauth guoweixin
    #设置redis密码 各个节点请保持密码一致
    requirepass guoweixin
    
    5、依次复制并修改 6个redis.conf
    cp ./7000/redis.conf ./7001/ :依次进行复制
    vim ./7001/redis.conf  :执行 %s/old/new/g 全部替换 :wq 保存并退出 即可
    

    6、依次启动6个节点

    将安装的redis 目录下的src 复制到 cluster下,方便启动服务端

    cd /opt/redis-5.0.0 :进入redis安装目录
    cp -r ./src/ /usr/local/redis_cluster/ :将src文件复制到redis_cluster目录中
    ./src/redis-server ./7000/redis.conf
    ./src/redis-server ./7001/redis.conf
    ./src/redis-server ./7002/redis.conf
    ./src/redis-server ./7003/redis.conf
    ./src/redis-server ./7004/redis.conf
    ./src/redis-server ./7005/redis.conf
    

    7、创建集群

    Redis 5版本后 通过redis-cli 客户端命令来创建集群。

    ./src/redis-cli --cluster create -a guoweixin 127.0.0.1:7000 127.0.0.1:7001
    127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 --cluster-replicas 1
    

    Redis Cluster集群验证

    在某台机器上(或)连接集群的7001端口的节点:

    redis-cli -h 127.0.0.1 -c -p 7000 -a guoweixin :加参数 -c 可连接到集群
    
    redis cluster在设计的时候,就考虑到了去中心化,去中间件,也就是说,集群中的每个节点都是平等的
    关系,都是对等的,每个节点都保存各自的数据和整个集群的状态。每个节点都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任意一个节点,就可以获取到其他节点的数据
    
    

    基本命令

    info replication通过Cluster Nodes命令和Cluster Info命令来看看集群效果

    image-20220829190238644

    Redis Cluster总结

    简介:
    Redis cluster 为了保证数据的高可用性,加入了主从模式,一个主节点对应一个或多个从节点,主节点提
    供数据存取,从节点则是从主节点拉取数据备份,当这个主节点挂掉后,就会有这个从节点选取一个来
    充当主节点,从而保证集群不会挂掉。
    集群有ABC三个主节点, 如果这3个节点都没有加入从节点,如果B挂掉了,我们就无法访问整个集群了。
    A和C的slot也无法访问。
    所以我们在集群建立的时候,一定要为每个主节点都添加了从节点, 比如像这样, 集群包含主节点A、B、
    C, 以及从节点A1、B1、C1, 那么即使B挂掉系统也可以继续正确工作。
    B1节点替代了B节点,所以Redis集群将会选择B1节点作为新的主节点,集群将会继续正确地提供服务。
    当B重新开启后,它就会变成B1的从节点。
    不过需要注意,如果节点B和B1同时挂了,Redis集群就无法继续正确地提供服务了
    

    五 Redis Cluster关闭集群

    开启全部redis节点 redisall.sh

    /usr/local/redis_cluster/src/redis-server ./7000/redis.conf
    /usr/local/redis_cluster/src/redis-server ./7001/redis.conf
    /usr/local/redis_cluster/src/redis-server ./7002/redis.conf
    /usr/local/redis_cluster/src/redis-server ./7003/redis.conf
    /usr/local/redis_cluster/src/redis-server ./7004/redis.conf
    /usr/local/redis_cluster/src/redis-server ./7005/redis.conf
    

    chmod u+x redisall.sh :执行将redisall.sh变成可执行文件 ./redisall.sh :在当前目录下启动:

  • 相关阅读:
    IOS-SQLite3的封装
    IOS-SQLite3
    IOS-真机相关
    IOS-将任意对象存进数据库
    IOS-支付宝
    IOS-推送通知
    iOS-证书真机调试
    iOS-免证书真机调试
    iOS-沙盒路径
    Android之发送短信的两种方式
  • 原文地址:https://www.cnblogs.com/abldh12/p/16632377.html
Copyright © 2020-2023  润新知