学习笔记-Redis

Nosql概述

发展

1、单机mysql

瓶颈:

1、数据量如果太大、一个机器放不下了!

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

3、访问量(读写混合),一个服务器承受不了~

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

image

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

MyISAM:表锁,影响效率

Innodb:行锁

4、当今

image

为什么使用Nosql

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

什么是NOsql

not only sql 泛指非关系型数据库

**NoSQL特点 : **

  • 方便扩展:数据之间没有关系

  • 大数据量高性能:一秒写8万次,读取11万

  • 数据类型多样(不需要事先设计数据库)

  • 传统RDBMS和NoSQL:

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

大数据3V+3高

  1. 海量Volume

  2. 多样Variety

  3. 实时Velocity

  4. 高并发

  5. 高可扩

  6. 高性能

NoSQL的四大分类

KV键值对

  • Redis
  • Tair
  • memecache

文档型数据库(bson格式)

  • MongoDB
    • MongoDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档!
    • MongoDB是一个介于关系型数据库和非关系型数据中中间的产品!MongoDB是非关系型数据库中功能最丰富,最像关系型数据库的!

列存储数据库

  • HBase
  • 分布式文件系统

图形关系数据库

Redis入门

概念

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

功能

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

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

3、发布订阅系统

4、地图信息分析

5、计时器、计数器(浏览量!)

资源

redis中文官方网站

Redis

window下载安装

官网下载压缩包解压

开启服务:

image

开启客户端:

image

Liunx !!!

image

基本环境安装:

yum install gcc-c++

make 

安装目录:usr/local/bin/

image

复制配置文件至 yangconfig 文件夹,并修改配置为后台启动:

​ 将安装包解压后的目录下的 redis.conf 文件复制到usr/local/bin/yangconfig , 将 daemonize 属性改为yes,让其后台运行.

image

启动:redis-server 以什么样的配置启动:redis.conf

image

打开客户端测试:

image

查看进程:

image

关闭服务:

image

再次查看进程:

image

测试性能

redis-benchmark 一个压力测试工具

image

可添加参数:

Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>]

 -h <hostname>      Server hostname (default 127.0.0.1)
 -p <port>          Server port (default 6379)
 -s <socket>        Server socket (overrides host and port)
 -a <password>      Password for Redis Auth
 -c <clients>       Number of parallel connections (default 50)
 -n <requests>      Total number of requests (default 100000)
 -d <size>          Data size of SET/GET value in bytes (default 2)
 -dbnum <db>        SELECT the specified db number (default 0)
 -k <boolean>       1=keep alive 0=reconnect (default 1)
 -r <keyspacelen>   Use random keys for SET/GET/INCR, random values for SADD
  Using this option the benchmark will expand the string __rand_int__
  inside an argument with a 12 digits number in the specified range
  from 0 to keyspacelen-1. The substitution changes every time a command
  is executed. Default tests use this to hit random keys in the
  specified range.
 -P <numreq>        Pipeline <numreq> requests. Default 1 (no pipeline).
 -q                 Quiet. Just show query/sec values
 --csv              Output in CSV format
 -l                 Loop. Run the tests forever
 -t <tests>         Only run the comma separated list of tests. The test
                    names are the same as the ones produced as output.
 -I                 Idle mode. Just open N idle connections and wait.

测试:100条进程 100000条请求

redis-benchmark -p 6379 -c 100 -n 100000

image

基础知识

redis默认有16个数据库,默认使用第0个

[root@hostname0yang bin]# redis-cli -p 6379   #开启客户端
127.0.0.1:6379> select 2 					  #选择第2个数据库
OK
127.0.0.1:6379[2]> DBSIZE					  #大小
(integer) 0
127.0.0.1:6379[2]> set name yang			  #存入键值对
OK
127.0.0.1:6379[2]> DBSIZE					  #大小+1
(integer) 1
127.0.0.1:6379[2]> get name					  #获取
"yang"
127.0.0.1:6379[2]> select 3					  #切换数据库
OK
127.0.0.1:6379[3]> get name					  #测试获取键值对  获取不到
(nil)
127.0.0.1:6379[3]> DBSIZE
(integer) 0
127.0.0.1:6379[3]> 

查看所有key值:**keys ***

127.0.0.1:6379[2]> keys *
1) "name"
127.0.0.1:6379[2]> 

清空当前数据库:flushdb

127.0.0.1:6379[2]> DBSIZE
(integer) 1
127.0.0.1:6379[2]> keys *
1) "name"
127.0.0.1:6379[2]> flushdb
OK
127.0.0.1:6379[2]> DBSIZE
(integer) 0
127.0.0.1:6379[2]> 

清空全部数据库:FLUSHALL

redis 是单线程的 新版支持多线程?

Redis是很快的,官方表示,Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了!所有就使用了单线程了!

Redis是C语言写的,官方提供的数据为100000+的QPS,完全不比同样是使用key-vale的Memecache差!

五大数据类型

Redis-Key

判断某个key是否存在

exists key

移除某个key

move key 数据库(0~15)

**设置过期时间: expire 查看剩余时间 : ttl **

127.0.0.1:6379[2]> set name hello  # set key
OK
127.0.0.1:6379[2]> keys *
1) "name"
127.0.0.1:6379[2]> expire name 10  # 单位为秒s
(integer) 1
127.0.0.1:6379[2]> ttl name
(integer) 5
127.0.0.1:6379[2]> ttl name
(integer) 3
127.0.0.1:6379[2]> ttl name
(integer) 1
127.0.0.1:6379[2]> ttl name
(integer) -2
127.0.0.1:6379[2]> get name
(nil)
127.0.0.1:6379[2]> 

查看类型 : type

127.0.0.1:6379[2]> set age 2
OK
127.0.0.1:6379[2]> type age
string

String

应用:字符串、数字、计数器

  • 追加、获取字符串长度
127.0.0.1:6379[2]> get age
"2"
127.0.0.1:6379[2]> append age 00  # 追加字符串
(integer) 3
127.0.0.1:6379[2]> get age
"200"
127.0.0.1:6379[2]> strlen age    # 获取字符串长度
(integer) 3
127.0.0.1:6379[2]> 
  • 加一、减一(自增自减) 按步长加减
127.0.0.1:6379[2]> set views 0
OK
127.0.0.1:6379[2]> incr views  # 自增1
(integer) 1
127.0.0.1:6379[2]> incr views
(integer) 2
127.0.0.1:6379[2]> incr views
(integer) 3
127.0.0.1:6379[2]> get views
"3"
127.0.0.1:6379[2]> decr views  # 自减1
(integer) 2
127.0.0.1:6379[2]> decr views
(integer) 1
127.0.0.1:6379[2]> get views
"1"
127.0.0.1:6379[2]> 
127.0.0.1:6379[2]> get views
"1"
127.0.0.1:6379[2]> incrby views 10
(integer) 11
127.0.0.1:6379[2]> get views
"11"
127.0.0.1:6379[2]> decrby views 5
(integer) 6
127.0.0.1:6379[2]> get views
"6"
127.0.0.1:6379[2]> 
  • 截取字符串 getrange
127.0.0.1:6379[2]> flushdb
OK
127.0.0.1:6379[2]> keys *
(empty array)
127.0.0.1:6379[2]> set key1 hello,world
OK
127.0.0.1:6379[2]> getrange key1 0 4      #闭区间
"hello"
127.0.0.1:6379[2]> getrange key1 0 -1	  # 全部
"hello,world"
127.0.0.1:6379[2]> 
  • 按范围替换 setrange
127.0.0.1:6379[2]> getrange key1 0 -1
"hello,world"
127.0.0.1:6379[2]> setrange key1 6 redis #下标6开始替换
(integer) 11
127.0.0.1:6379[2]> get key1
"hello,redis"
127.0.0.1:6379[2]> 
  • 同时设置key和过期时间 setex
127.0.0.1:6379[2]> setex mykey 20 hello # 设置20s过期
OK
127.0.0.1:6379[2]> ttl mykey
(integer) 12
127.0.0.1:6379[2]> ttl mykey
(integer) 7
127.0.0.1:6379[2]> ttl mykey
(integer) -2
127.0.0.1:6379[2]> get mykey
(nil)
127.0.0.1:6379[2]> 
  • **不存在时设置 setnx ** 分布式锁中常用
127.0.0.1:6379[2]> setnx key3 hh
(integer) 1
127.0.0.1:6379[2]> get key3
"hh"
127.0.0.1:6379[2]> setnx key3 gg
(integer) 0
127.0.0.1:6379[2]> get key3
"hh"
127.0.0.1:6379[2]> 
  • 批量设置、查看 mset mget msetnx
127.0.0.1:6379[2]> keys *
(empty array)
127.0.0.1:6379[2]> mset k1 v1 k2 v2 k3 v3  # 批量设置
OK
127.0.0.1:6379[2]> keys *
1) "k2"
2) "k3"
3) "k1"
127.0.0.1:6379[2]> mget k1 k2 k3    # 批量查看
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379[2]> msetnx k1 vv k4 v4  # 原子性操作,要么都成功,要么都失败
(integer) 0
127.0.0.1:6379[2]> keys *
1) "k2"
2) "k3"
3) "k1"
127.0.0.1:6379[2]> 
  • 对象
127.0.0.1:6379[2]> keys *
(empty array)
127.0.0.1:6379[2]> set user:1 {name:zhangsan,age:3}   # 以json字符串的形式存储
OK

###############################################

127.0.0.1:6379[2]> mset user:2:name lisi user:2:age 5
OK
127.0.0.1:6379[2]> mget user:2:name user:2:age
1) "lisi"
2) "5"
127.0.0.1:6379[2]> 

  • 先get再set
127.0.0.1:6379[2]> keys *
(empty array)
127.0.0.1:6379[2]> getset age 5
(nil)
127.0.0.1:6379[2]> get age
"5"
127.0.0.1:6379[2]> getset age 10
"5"
127.0.0.1:6379[2]> get age
"10"
127.0.0.1:6379[2]> 

List

所有命令加 L 前缀~~

他实际上是一个链表,before Node after , left , right都可以插入值

如果key不存在,创建新的链表,如果key存在,新增内容

如果移除了所有值,空链表,也代表不存在!

在两边插入或者改动值,效率最高!中间元素,相对来说效率会低一点~

使用场景:消息排队,消息队列,栈

  • 堆栈效果 存:lpush 取 :lrange(只是查看,不改变结构)
127.0.0.1:6379[2]> keys *
(empty array)
127.0.0.1:6379[2]> lpush list one two  # 依次从左边存多个
(integer) 2
127.0.0.1:6379[2]> lpush list three	   # 从左边存一个
(integer) 3
127.0.0.1:6379[2]> lrange list 0 -1	   # 取出所有
1) "three"                             # 可以见到遵循:先进后出原则
2) "two"
3) "one"
127.0.0.1:6379[2]> lrange list 0 1
1) "three"
2) "two"
127.0.0.1:6379[2]>
  • 队列效果 存: rpush ,取:lrange 右边存,左边取
127.0.0.1:6379[2]> rpush list 4
(integer) 4
127.0.0.1:6379[2]> lrange list 0 -1
1) "three"                       
2) "two"
3) "one"
4) "4"      # 可见 先进先出  后进后出
127.0.0.1:6379[2]> 

  • **左右移除(弹出之后不再保存,结构改变) rpop lpop **
127.0.0.1:6379[2]> rpop list
"4"
127.0.0.1:6379[2]> lpop list
"three"
127.0.0.1:6379[2]> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379[2]> 

  • index查询 lindex (没有rindex)
127.0.0.1:6379[2]> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379[2]> 
127.0.0.1:6379[2]> lindex list 0
"two"
127.0.0.1:6379[2]> lindex list 1
"one"
127.0.0.1:6379[2]> 
  • 获取长度 llen
127.0.0.1:6379[2]> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379[2]> llen list
(integer) 2
127.0.0.1:6379[2]> 
  • 按内容移除 lrem
127.0.0.1:6379[2]> lrange list 0 -1
1) "one"
2) "two"
3) "one"
127.0.0.1:6379[2]> lrem list 1 one   # 移除一个 one
(integer) 1
127.0.0.1:6379[2]> lrange list 0 -1  
1) "two"							 # 可见移除最左边(最后进来的one)
2) "one"
127.0.0.1:6379[2]> lpush list one 
(integer) 3
127.0.0.1:6379[2]> lrange list 0 -1
1) "one"
2) "two"
3) "one"
127.0.0.1:6379[2]> lrem list 2 one   # 移除两个 one
(integer) 2
127.0.0.1:6379[2]> lrange list 0 -1
1) "two"
127.0.0.1:6379[2]> 
  • 按下标裁剪 ltrim 只保留闭区间里的元素
127.0.0.1:6379[2]> keys *
(empty array)
127.0.0.1:6379[2]> rpush alist hello0 hello1 hello2 hello3
(integer) 4
127.0.0.1:6379[2]> lrange alist 0 -1
1) "hello0"
2) "hello1"
3) "hello2"
4) "hello3"
127.0.0.1:6379[2]> ltrim alist 1 2
OK
127.0.0.1:6379[2]> lrange alist 0 -1
1) "hello1"
2) "hello2"
127.0.0.1:6379[2]> 
  • 移除一个元素到新的列表中 rpoplpush
127.0.0.1:6379[2]> keys *
(empty array)
127.0.0.1:6379[2]> rpush list 0 1 2 3
(integer) 4
127.0.0.1:6379[2]> lrange list 0 -1
1) "0"
2) "1"
3) "2"
4) "3"
127.0.0.1:6379[2]> rpoplpush list newlist
"3"
127.0.0.1:6379[2]> lrange list 0 -1
1) "0"
2) "1"
3) "2"
127.0.0.1:6379[2]> lrange newlist 0 -1
1) "3"
127.0.0.1:6379[2]> 
  • 按下标更新 lset 如果list不存在,或下标越界则报错
127.0.0.1:6379[2]> keys *
(empty array)
127.0.0.1:6379[2]> lpush list 1
(integer) 1
127.0.0.1:6379[2]> lset list 0 hello
OK
127.0.0.1:6379[2]> lrange list 0 0
1) "hello"
127.0.0.1:6379[2]> 
  • 按元素前后插入 linsert 列表 before / after 元素 新元素
127.0.0.1:6379[2]> rpush mylist hello world
(integer) 2
127.0.0.1:6379[2]> linsert mylist before world 55
(integer) 3
127.0.0.1:6379[2]> lrange mylist 0 -1
1) "hello"
2) "55"
3) "world"
127.0.0.1:6379[2]> linsert mylist after 55 66
(integer) 4
127.0.0.1:6379[2]> lrange mylist 0 -1
1) "hello"
2) "55"
3) "66"
4) "world"
127.0.0.1:6379[2]> 

Set

集合,其中的值不可重复

  • 添加 sadd 查看全部 smembers 查询是否存在 sismember
127.0.0.1:6379[2]> keys *
(empty array)
127.0.0.1:6379[2]> sadd myset hello
(integer) 1
127.0.0.1:6379[2]> sadd myset world
(integer) 1
127.0.0.1:6379[2]> sadd myset 123
(integer) 1
127.0.0.1:6379[2]> smembers myset
1) "world"
2) "123"
3) "hello"
127.0.0.1:6379[2]> sismember myset world
(integer) 1
127.0.0.1:6379[2]> sismember myset 456
(integer) 0
127.0.0.1:6379[2]> 
  • 获取set集合中元素个数 scard
127.0.0.1:6379[2]> smembers myset
1) "world"
2) "123"
3) "hello"
127.0.0.1:6379[2]> scard myset
(integer) 3
127.0.0.1:6379[2]> 
  • 移除指定元素 srem
127.0.0.1:6379[2]> smembers myset
1) "world"
2) "123"
3) "hello"
127.0.0.1:6379[2]> srem myset 123
(integer) 1
127.0.0.1:6379[2]> smembers myset
1) "world"
2) "hello"
127.0.0.1:6379[2]> 
  • 随机抽出指定数量元素 srandmember
127.0.0.1:6379[2]> smembers myset
1) "world"
2) "123"
3) "456"
4) "hello"
5) "789"
127.0.0.1:6379[2]> srandmember myset
"789"
127.0.0.1:6379[2]> srandmember myset
"456"
127.0.0.1:6379[2]> srandmember myset
"456"
127.0.0.1:6379[2]> srandmember myset 2
1) "123"
2) "hello"

  • 随机删除 spop
127.0.0.1:6379[2]> smembers myset
1) "123"
2) "789"
3) "world"
4) "hello"
5) "456"
127.0.0.1:6379[2]> spop myset
"hello"
127.0.0.1:6379[2]> spop myset
"456"
127.0.0.1:6379[2]> spop myset
"world"
127.0.0.1:6379[2]> smembers myset
1) "123"
2) "789"
127.0.0.1:6379[2]> 
  • 移动一个元素到新的集合 smove
127.0.0.1:6379[2]> smembers myset
1) "123"
2) "789"
127.0.0.1:6379[2]> smembers set2
1) "hello"
127.0.0.1:6379[2]> smove myset set2 123
(integer) 1
127.0.0.1:6379[2]> smembers set2
1) "123"
2) "hello"
127.0.0.1:6379[2]> smembers myset
1) "789"
127.0.0.1:6379[2]> 
  • 交集 sinter 并集 sunion 差集 sdiff
127.0.0.1:6379[2]> keys *
(empty array)
127.0.0.1:6379[2]> sadd set1 a b c
(integer) 3
127.0.0.1:6379[2]> sadd set2 c d e
(integer) 3
127.0.0.1:6379[2]> sdiff set1 set2
1) "a"
2) "b"
127.0.0.1:6379[2]> sinter set1 set2
1) "c"
127.0.0.1:6379[2]> sunion set1 set2
1) "e"
2) "b"
3) "c"
4) "a"
5) "d"
127.0.0.1:6379[2]> 

Hash

key-map

应用:hash变更的数据user name age,尤其是是用户信息之类的,经常变动的信息! hash更适合于对象的存储,String更加适合字符串存储!

  • hset hget (hmset 设置多个)(hmget 获取多个)( hgetall 获取全部)
127.0.0.1:6379[2]> keys *
(empty array)
127.0.0.1:6379[2]> hset myhash field1 hello   # 设置
(integer) 1
127.0.0.1:6379[2]> hget myhash field1		  # 获取
"hello"
127.0.0.1:6379[2]> hmset myhash field1 hh field2 gg   # 设置多个(key值重复会覆盖)
OK
127.0.0.1:6379[2]> hmget myhash field1 field2  # 获取多个
1) "hh"
2) "gg"
127.0.0.1:6379[2]> hgetall myhash     #获取全部
1) "field1"
2) "hh"
3) "field2"
4) "gg"
127.0.0.1:6379[2]> 

  • 删除指定的key hdel
127.0.0.1:6379[2]> hgetall myhash
1) "field1"
2) "hh"
3) "field2"
4) "gg"
127.0.0.1:6379[2]> hdel myhash field1
(integer) 1
127.0.0.1:6379[2]> hgetall myhash
1) "field2"
2) "gg"
127.0.0.1:6379[2]> 
  • 获取键值对数量 hlen
127.0.0.1:6379[2]> hgetall myhash
1) "field2"
2) "gg"
127.0.0.1:6379[2]> hlen myhash
(integer) 1

  • 判断是否存在此键值对 hexists
127.0.0.1:6379[2]> hgetall myhash
1) "field2"
2) "gg"
127.0.0.1:6379[2]> hexists myhash field1
(integer) 0
127.0.0.1:6379[2]> hexists myhash field2
(integer) 1
127.0.0.1:6379[2]> 
  • 只获取所有的key hkeys 、只获取所有的value hvals
127.0.0.1:6379[2]> hgetall myhash
1) "field2"
2) "gg"
3) "field1"
4) "123"
127.0.0.1:6379[2]> hkeys myhash
1) "field2"
2) "field1"
127.0.0.1:6379[2]> hvals myhash
1) "gg"
2) "123"
127.0.0.1:6379[2]> 

  • **加减 hincrby **
127.0.0.1:6379[2]> hgetall myhash
1) "field2"
2) "gg"
3) "field1"
4) "123"
127.0.0.1:6379[2]> hincrby myhash field1 2
(integer) 125
127.0.0.1:6379[2]> hincrby myhash field1 -5
(integer) 120
127.0.0.1:6379[2]> 

  • hsetnx 不存在设置
127.0.0.1:6379[2]> hsetnx myhash field3 hello
(integer) 1
127.0.0.1:6379[2]> hsetnx myhash field1 hello
(integer) 0
127.0.0.1:6379[2]> 

Zset(有序集合)

在set的基础上,增加了一个值,set k1 v1 zset k1 score1 v1

底层数据结构是跳跃链表

应用: set排序存储班级成绩表,工资表排序!普通消息,1,重要消息2,带权重进行判断!

  • zadd zrange
127.0.0.1:6379[2]> keys *
(empty array)
127.0.0.1:6379[2]> zadd myset 1 one
(integer) 1
127.0.0.1:6379[2]> zadd myset 2 two 3 three
(integer) 2
127.0.0.1:6379[2]> zrange myset 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379[2]> 
  • 排序 zrangebyscore zrevrangebyscore
127.0.0.1:6379[2]> keys *
(empty array)
127.0.0.1:6379[2]> zadd salary 3000 ming
(integer) 1
127.0.0.1:6379[2]> zadd salary 5000 hong
(integer) 1
127.0.0.1:6379[2]> zadd salary 1200 zhao
(integer) 1
127.0.0.1:6379[2]> zrangebyscore salary -inf +inf
1) "zhao"
2) "ming"
3) "hong"
127.0.0.1:6379[2]> zrangebyscore salary -inf +inf withscores   #升序
1) "zhao"
2) "1200"
3) "ming"
4) "3000"
5) "hong"
6) "5000"
127.0.0.1:6379[2]> zrevrangebyscore salary +inf 0 withscores  # 降序
1) "hong"
2) "5000"
3) "ming"
4) "3000"
5) "zhao"
6) "1200"
127.0.0.1:6379[2]> 

  • 移除 zrem 获取数量 zcard
127.0.0.1:6379[2]> zrange salary 0 -1
1) "zhao"
2) "ming"
3) "hong"
127.0.0.1:6379[2]> zrem salary zhao
(integer) 1
127.0.0.1:6379[2]> zrange salary 0 -1
1) "ming"
2) "hong"
127.0.0.1:6379[2]> zcard salary
(integer) 2

三大特殊数据类型

geospatial 地理位置

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

底层是zset

  • 存储 geoadd key 经度 维度 名称
127.0.0.1:6379[2]> geoadd china:city 114.298572 30.584355 wuhan
(integer) 1
127.0.0.1:6379[2]> geoadd china:city 121.472644 31.231706 shanghai
(integer) 1
127.0.0.1:6379[2]> geoadd china:city 116.41 39.91 beijing
(integer) 1
127.0.0.1:6379[2]> geoadd china:city 114.09 22.55 shenzhen
(integer) 1
127.0.0.1:6379[2]> geoadd china:city 120.15 30.29 hangzhou
(integer) 1
127.0.0.1:6379[2]> geoadd china:city 106.50 29.53 chongqing
(integer) 1
127.0.0.1:6379[2]> geoadd china:city 125.15 42.93 xi_an
(integer) 1
127.0.0.1:6379[2]> 

  • 查询 geopos
127.0.0.1:6379[2]> geopos china:city beijing
1) 1) "116.40999823808670044"
   2) "39.90999956664450821"
127.0.0.1:6379[2]> 
127.0.0.1:6379[2]> geopos china:city xi_an wuhan
1) 1) "125.1499977707862854"
   2) "42.93000069610238256"
2) 1) "114.29857403039932251"
   2) "30.58435486605102227"
127.0.0.1:6379[2]> 
  • 查询直线距离 geodist

m表示单位为米。km表示单位为千米。mi表示单位为英里。ft表示单位为英尺。|

127.0.0.1:6379[2]> geodist china:city beijing shanghai km
"1067.9128"
127.0.0.1:6379[2]> 
  • georadius 以给定的经纬度为中心,找出某一半径内的元素
127.0.0.1:6379[2]> georadius china:city 110 30 1200 km  #110 30 经纬度半径1200km
1) "chongqing"
2) "shenzhen"
3) "wuhan"
4) "hangzhou"
5) "shanghai"
127.0.0.1:6379[2]> georadius china:city 110 30 1200 km withdist withcoord count 3
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "wuhan"
   2) "417.8920"
   3) 1) "114.29857403039932251"
      2) "30.58435486605102227"
3) 1) "shenzhen"
   2) "923.3692"
   3) 1) "114.09000009298324585"
      2) "22.5500010475923105"
127.0.0.1:6379[2]> 
  • georadiusbymember 以元素为中心,寻找一定半径范围的元素
127.0.0.1:6379[2]> georadiusbymember china:city hangzhou 1000 km
1) "wuhan"
2) "hangzhou"
3) "shanghai"
127.0.0.1:6379[2]> 
  • geohash 返回一个或多个位置元素的Geohash表示
127.0.0.1:6379[2]> geohash china:city beijing
1) "wx4g0crhte0"
127.0.0.1:6379[2]> 
  • 删除 利用zset指令
127.0.0.1:6379[2]> zrange china:city 0 -1
1) "chongqing"
2) "shenzhen"
3) "wuhan"
4) "hangzhou"
5) "shanghai"
6) "beijing"
7) "xi_an"
127.0.0.1:6379[2]> zrem china:city xi_an
(integer) 1
127.0.0.1:6379[2]> zrange china:city 0 -1
1) "chongqing"
2) "shenzhen"
3) "wuhan"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379[2]> 

Hyperloglog

定义基数 集合中不重复的元素个数

网页uv 是unique visitor的简写,是指通过互联网访问、浏览这个网页的自然人

​ (一个人访问一个网站多次,但是还是算作一个人!)

Hyperloglog 基数统计算法

优点:占用内存固定,2^64不同的元素的基数,只需要12kB的内存

存在0.81%的误差

  • 创建 pfadd 统计 pfcount 合并 pfmerge
127.0.0.1:6379[2]> pfadd mykey a b c d e f
(integer) 1
127.0.0.1:6379[2]> pfcount mykey
(integer) 6
127.0.0.1:6379[2]> pfadd mykey2 a s d f g h
(integer) 1
127.0.0.1:6379[2]> pfcount mykey2
(integer) 6
127.0.0.1:6379[2]> pfmerge mykey3 mykey mykey2
OK
127.0.0.1:6379[2]> pfcount mykey3
(integer) 9
127.0.0.1:6379[2]> 

Bitmap

位存储

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

Bitmaps位图,数据结构!

  • setbit key 下标 value
127.0.0.1:6379[2]> setbit sign 0 0
(integer) 0
127.0.0.1:6379[2]> setbit sign 1 0
(integer) 0
127.0.0.1:6379[2]> setbit sign 2 1
(integer) 0
127.0.0.1:6379[2]> setbit sign 3 0
(integer) 0
127.0.0.1:6379[2]> setbit sign 4 1
(integer) 0
127.0.0.1:6379[2]> setbit sign 5 1
(integer) 0
127.0.0.1:6379[2]> setbit sign 6 0
(integer) 0
127.0.0.1:6379[2]> 

  • 查询
127.0.0.1:6379[2]> getbit sign 1
(integer) 0
127.0.0.1:6379[2]> getbit sign 4
(integer) 1
127.0.0.1:6379[2]> 
  • 统计1的个数
127.0.0.1:6379[2]> bitcount sign
(integer) 3
127.0.0.1:6379[2]> 

事务

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

一次性、顺序性、排他性!

redis事务没有隔离级别的概念

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

redis 事务不保证原子性!!!单条命令保证原子性

  • 开启事务:multi

  • 编写命令

  • 执行:exec

127.0.0.1:6379[2]> multi
OK
127.0.0.1:6379[2](TX)> set k1 v1
QUEUED
127.0.0.1:6379[2](TX)> set k2 v2
QUEUED
127.0.0.1:6379[2](TX)> get k2
QUEUED
127.0.0.1:6379[2](TX)> set k3 v3
QUEUED
127.0.0.1:6379[2](TX)> keys *
QUEUED
127.0.0.1:6379[2](TX)> exec     # 执行事务
1) OK
2) OK
3) "v2"
4) OK
5) 1) "k3"
   2) "k2"
   3) "k1"
127.0.0.1:6379[2]> get k3
"v3"

放弃事务 discard

127.0.0.1:6379[2]> multi
OK
127.0.0.1:6379[2](TX)> get k3
QUEUED
127.0.0.1:6379[2](TX)> set k4 v4
QUEUED
127.0.0.1:6379[2](TX)> discard
OK
127.0.0.1:6379[2]> get k4
(nil)
127.0.0.1:6379[2]> 

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

127.0.0.1:6379[2]> multi
OK
127.0.0.1:6379[2](TX)> set k1 v1
QUEUED
127.0.0.1:6379[2](TX)> set k2 v2
QUEUED
127.0.0.1:6379[2](TX)> getset k1 
(error) ERR wrong number of arguments for 'getset' command   # 编译报错
127.0.0.1:6379[2](TX)> set k3 v3
QUEUED
127.0.0.1:6379[2](TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.  #执行失败
127.0.0.1:6379[2]> get k1    # 所有的命令都没执行
(nil)
127.0.0.1:6379[2]> get k3
(nil)
127.0.0.1:6379[2]> 

运行时异常,如果事务队列中存在语法性错误,在执行时,其他命令可以正常执行,错误命令抛出异常

127.0.0.1:6379[2]> multi
OK
127.0.0.1:6379[2](TX)> set k1 vv
QUEUED
127.0.0.1:6379[2](TX)> incr k1     
QUEUED
127.0.0.1:6379[2](TX)> set k2 2
QUEUED
127.0.0.1:6379[2](TX)> exec
1) OK
2) (error) ERR value is not an integer or out of range   # 对字符串自增抛出异常
3) OK
127.0.0.1:6379[2]> mget k1 k2   # 其他语句正常执行
1) "vv"
2) "2"
127.0.0.1:6379[2]> 

监控

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

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

  • 线程1 监控 watch money 之后在事务执行之前完成线程2 在乐观锁机制下事务失败
127.0.0.1:6379[2]> set money 100
OK
127.0.0.1:6379[2]> set out 0
OK
127.0.0.1:6379[2]> watch money
OK
127.0.0.1:6379[2]> multi
OK
127.0.0.1:6379[2](TX)> decrby money 20
QUEUED
127.0.0.1:6379[2](TX)> incrby out 20
QUEUED
127.0.0.1:6379[2](TX)> exec
(nil)
127.0.0.1:6379[2]> 
  • 线程2
127.0.0.1:6379[2]> get money
"100"
127.0.0.1:6379[2]> set money 1000
OK
127.0.0.1:6379[2]> get money
"1000"
127.0.0.1:6379[2]> 
  • 取消监控 (事务执行后会自动解锁?)
127.0.0.1:6379[2]> unwatch
OK
127.0.0.1:6379[2]> 

Jedis

java操作redis

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.0.1</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.70</version>
</dependency>

(14条消息) Redis远程连接Linux云服务器(Jedis方式或Springboot方式)_大吉的博客-CSDN博客_redis连接linux

1、redis.conf修改:

  • daemonize yes
  • protected-mode no
  • 注释掉 bind 127.0.0.1
  • requirepass 你的密码 (设置访问redis的密码)【注意以后xshell中连接也要输入密码】

2、安全组&防火墙

​ 如果你是阿里云服务器,那你就进入云控制台,开启6379(Redis的默认端口号)。其他服务器类似

​ 然后回到你的Linux,

​ 开启6379端口:firewall-cmd --zone=public --add-port=6379/tcp --permanent
​ 重启防火墙:systemctl restart firewalld.service

public class TestPing {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("47.xxx.xxx.xxx",6379);
        jedis.auth("密码");
        jedis.set("name","yangzx");
        System.out.println(jedis.get("name"));
        System.out.println(jedis.ping());
        
        jedis.close();
    }
}

事务

public static void main(String[] args) {

    Jedis jedis = new Jedis("47.113.229.158",6379);
    jedis.auth("123456");
	jedis.flushDB();    //清空
    JSONObject object = new JSONObject();
    object.put("hello","world");
    object.put("age","20");
    String s = object.toString();

    //开启事务
    Transaction multi = jedis.multi();

    try {
        multi.set("user1",s);
        multi.set("user2",s);
        multi.exec();
    } catch (Exception e) {
        multi.discard();//异常时放弃事务
        e.printStackTrace();
    } finally {
        System.out.println(jedis.get("user1"));
        System.out.println(jedis.get("user2"));
        jedis.close();
    }
}

image

SpringBoot整合

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

SpringData也是和SpringBoot齐名的项目!

说明︰在SpringBoot2.x之后,原来使用的jedis 被替换为了lettuce?

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

新建springboot项目

image-20220202185513708

核心依赖:

image-20220202185659990

配置文件:

spring.redis.host=47.xxx.xxx.xxx
spring.rabbitmq.port=6379
spring.redis.password=密码
# 连接池使用spring.redis.lettuce.pool

使用:

image

  • redis连接对象操作
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.flushDb();
connection.close();
  • 一般使用:
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
    redisTemplate.opsForValue().set("keyy","123456");
    System.out.println(redisTemplate.opsForValue().get("keyy"));
}

image

image

序列化

未序列化时:

image

序列化后:

@AllArgsConstructor
@NoArgsConstructor
@Data
//所有实体类都要序列化!!!!
public class User implements Serializable {
    private String name;
    private int age;
}

image

自定义RedisTemplate

//自定义一个RedisTemplate
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        // JSon序列化配置
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        serializer.setObjectMapper(om);
        //String的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        //Key采用String 的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        //hash的key也采用String 的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        //value 序列化方式采用jackson
        template.setValueSerializer(serializer);
        //hash的value序列化方式采用jackson
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }
}

Redis.conf

单位&大小写不敏感

image

引入其他配置文件

image

网络

# bind 127.0.0.1 -::1
bind 0.0.0.0            # 远程连接需要配置

protected-mode no       # 是否受保护   远程连接时no  

port 6379

通用GENERAL

daemonize yes   #以守护进程的方式运行,默认为no

pidfile /var/run/redis_6379.pid  # 如果以后台方式运行,需要指定一个pid

########## 日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel notice
logfile ""          #日志的文件位置

databases 16         # 数据库数量默认16
always-show-logo no    #  是否显示logo

SNAPSHOTTING 快照

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

# save 3600 1     	# 3600s内如果至少有一个key进行了修改,我们就进行持久化操作
# save 300 100		# 300s内如果至少有100key进行了修改,我们就进行持久化操作
# save 60 10000		# 60s内如果至少有10000key进行了修改,我们就进行持久化操作

stop-writes-on-bgsave-error yes   # 持久化如果出错是否还继续工作

rdbcompression yes			 	  # 是否压缩rdb文件   需要消耗cpu资源
rdbchecksum yes					  # 保存rdb文件的时候进行错误的检查校验
dir ./							  # rdb 生成在当前目录

REPLICATION 主从复制

SECURITY 安全

# requirepass foobared    #  默认没有哦密码
requirepass xxxxx     # 设置密码

# 在命令行中设置:
config set requirepass "xxxxx"
# 登录
auth  xxxxx

CLIENTS 限制

# maxclients 10000    #设置能连接上redis最大客户端数量
# maxmemory <bytes>   # 设置最大内存容量
# maxmemory-policy noeviction  # 内存达到上限后的处理策略
    1、volatile-lru:只对设置了过期时间的key进行LRU(默认值) 
    2、allkeys-lru : 删除lru算法的key   
    3、volatile-random:随机删除即将过期key   
    4、allkeys-random:随机删除   
    5、volatile-ttl : 删除即将过期的   
    6、noeviction : 永不过期,返回错误

APPEND ONLY MODE aof 模式配置

appendonly no  # 默认不开启aof 使用rdb
appendfilename "appendonly.aof"   # 持久化文件名称

# appendfsync always  	# 每次修改都会 sync(同步)  消耗性能
appendfsync everysec    # 每秒执行一次 sync 可能会丢失这一秒的数据
# appendfsync no		# 不执行 sync  操作系统自己同步数据,速度最快

Redis 持久化

RDB (Redis DataBase)

默认文件名:dump.rdb

image

配置保存机制:

image

触发机制:

1、save规则满足时会自动触发rdb规则

2、执行flushall 命令,也会触发rdb规则

3、退出redis 也会产生rdb文件

image

AOF (Append Only File)

Aof保存的是appendonly.aof 文件

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

默认不开启需要手动配置

开启后会生成文件:

image

如果aof文件有错误,redis是不能启动的,可以使用 redis-check-aof 进行修复

Redis 发布和订阅

Redis 发布订阅(pub/sub)是一种消息通信模式∶发送者(pu)发送消息,订阅者(sub)接收消息。关注系统!

  • 订阅:
127.0.0.1:6379> subscribe yang  # 订阅频道 yang
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "yang"
3) (integer) 1
# 等待读取信息
1) "message"
2) "yang"
3) "hello,world"

  • 发布:
127.0.0.1:6379> publish yang hello,world    # 向频道 yang 中发送消息 hello,world
(integer) 1
127.0.0.1:6379> 

应用:实时消息系统、实时聊天、订阅关注系统

Redis 主从复制

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

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

一台redis 使用内存不要超过20G

主从复制、读写分离:主机写、从机读

image

主从复制的作用主要包括:
1、数据冗余I:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
2、故障恢复∶当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
3、负载均衡∶在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
4、高可用基石︰除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

环境配置

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

127.0.0.1:6379> info replication   # 查看信息
# Replication
role:master    # 主!!!!!!!!!!!!!!!!
connected_slaves:0
master_failover_state:no-failover
master_replid:a93884b9151f60dd4bb5c47c01e93a8f068b631f
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379> 

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

1、端口

2、pid 名字

3、log文件名字

4、dump.rdb名字

分别用三个配置文件启动3个redis-server

  • 命令方式

在从机中配置

这样配置如果从机断开连接,再启动后就不再是从机,需要重新配置,配置之后可以拿到主机中写入的所有数据(即使是断开连接时写入的数据);而主机断开连接后从机依旧只能运行读操作,主机重启后恢复正常

slaveof 127.0.0.1 6379

slaveof no one # 会恢复主机身份
  • 配置文件方式

    replicaof <masterip> <masterport>
    masterauth <master-password>
    

image

复制原理

Slave启动成功连接到master后会发送一个sync同步命令
Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。

  • 全量复制︰而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。

  • 增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步

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

链路模型

之前:

image-20220203165146242

现在:

image-20220203165226499

哨兵模式

image

image

基本原理:

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

注意:哨兵模式下,若主机宕机,哨兵完成选举后,主机重启会自动成为新主机的从机!!!

优点:

  • 哨兵集群,基于主从复制模式,所有的主从配置优点,它全有

  • 主从可以切换,故障可以转移,系统的可用性就会更好

  • 哨兵模式就是主从模式的升级,手动到自动,更加健壮!

缺点∶

  • Redis不好啊在线扩容的,集群容量一旦到达上限,在线扩容就十分麻烦!

  • 实现哨兵模式的配置其实是很麻烦的,里面有很多选择!

配置哨兵配置文件 sentinel.conf

sentinel monitor myreids[这是自定义名称] 127.0.0.1 6379 1
# 这里的1代表只要1个哨兵认为主机挂了就开始选举
# 地址和端口写主机

启动

reids-sentinel yangconfig/sentinel.conf

全部配置

# 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无法正常启动成功。

#通知脚本
# shell编程
# 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内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。

解决:

  • 布隆过滤器

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

  • 缓存空对象

    新问题:

    1、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;

    2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。

缓存击穿

缓存击穿,是指一个key非常热点,在不停的打扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。

解决:

  • 设置热点数据永不过期

  • 加互斥锁

    分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。

缓存雪崩

缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis宕机!

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

解决方案:

  • redis高可用

  • 限流(服务)降级

  • 数据预热

posted @   酷酷丶吖  阅读(115)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示