首页 > 技术文章 > Redis学习笔记

henry829 2021-01-29 17:36 原文

Nosql概述

为什么要用Nosql

单机MySql时代

一个基本的网站访问量不会太大,单个数据库完全足够

整个网站的问题:

  1. 数据量如果太多,会放不下

  2. 数据的索引问题

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

     

缓存+MySql+垂直拆分(读写分离)

网站大多数情况下都是在读,每次都要去查询数据库的话就比较麻烦,可以使用缓存

 

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

现在

Mysql等关系型数据库不够用了,数据量很多,变化很快

Mysql有时候存储一些比较大的文件,博客,图片,数据库表很大,效率低

 

 

 

 

用户的个人信息,社交,地理位置自己产生的数据,日志爆发式增长 ,这个时候需要使用Nosql数据库来处理

 

Nosql

Nosql--Not Only Sql,泛指非关系型数据

关系型数据库:表,行,列

很多数据,如用户的信息,社交,地理位置,这些数据类型的存储都不需要一个固定的格式,使用Map<String,object>键值对来控制

Nosql特点

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

  2. 大数据量高性能,Redis一秒写8万次,读取11万

  3. 数据类型是多样的

关系型数据库和Nosql(非关系型数据库)

关系型数据库:
结构化组织
sql
数据和关系都存在单独的表中
严格的一致性
基础的事务
Nosql:
没有固定的存储语言
键值对存储(redis),列存储(mongodb),文档存储,图形数据库
最终一致性
高性能,高可用,可扩展

 

# 商品的基本信息
名称、价格、商家信息
关系型数据库就可以 Mysql

# 商品的描述、评论(文字比较多)
文档型数据库 MongoDB

# 图片
分布式文件系统 FastDFS
淘宝的     TFS
Hadoop   HDFS

# 商品的关键字(搜索)
搜索引擎 solr elasticsearch

 

Nosql的四大分类

kv键值对

  • Redis Tair memacahe

文档型数据库

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

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

列存储

  • HBase

  • 分布式文件系统

图关系数据库

  • 不是存的图形,存放的是关系,比如社交网络,广告推荐等

  • Neo4j

 

Redis

概述

Redis(Remote Dictionary Server ),即远程字典服务,c语言编写,支持网络,可基于内存亦可以持久化的日志型、key-value数据库

redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步

Redis作用

  1. 内存存储、持久化,内存是断电即失

  2. 效率高,可以用于告诉缓存

  3. 发布订阅系统

  4. 地图信息分析

  5. 计时器,计数器,浏览量

Redis特性

多样的数据类型,持久化,集群,事务

 

Windows下安装

  1. 下载https://github.com/microsoftarchive/redis/releases/tag/win-3.2.100

  2. 解压,文件夹重命名为redis

  3. 启动redis-server,运行服务

  4. 启动客户端,rdedis-cli.exe

    测试连接ping

Redis推荐在Linux下使用redis

 

Linux下安装

最新的redis6.0 需要gcc 5以上,先升级gcc ,centos自带的gcc是4.8的

sudo yum install centos-release-scl
sudo yum install devtoolset-7-gcc*
scl enable devtoolset-7 bash
gcc --version

 

  1. 下载安装包redis-6.0.10.tar.gz

  2. 上传到Linux服务器的opt目录下

  3. 解压文件

    [root@localhost opt]# tar -zxvf redis-6.0.10.tar.gz
  4.  

    make
    make install
  5. redis的默认安装路径 /usr/local/bin

  6. 将redis配置文件拷贝到/usr/local/bin的某个目录下

    mkdir hconfig
    cp /opt/redis-6.0.10/redis.conf hconfig

    之后我们使用这个配置文件启动

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

    vim redis.conf

  8. 启动redis服务

    [root@localhost bin]# redis-server hconfig/redis.conf
  9. 使用redis-cli测试

  10. 查看redis进程是否开启

  11. 关闭redis服务

  12. 再次查看进程

     

     

 

测试性能

 

 

redis-benchmark -h localhost -p 6379 -c 100 -n 10000

 

基础知识

redis默认有16个数据库

默认使用的是第0个数据库

可用使用select进行切换数据库

127.0.0.1:6379> select 2  # 切换数据库
OK
127.0.0.1:6379[2]> dbsize # 查看数据库大小
(integer) 0
127.0.0.1:6379[2]> set name hen
OK
127.0.0.1:6379[2]> get name
"hen"
127.0.0.1:6379[2]> keys *   # 查看数据库所有的key
1) "name"

 

清空当前数据库flushdb

127.0.0.1:6379[2]> flushdb
OK
127.0.0.1:6379[2]> keys *
(empty array)

 

清空全部的数据库

flushall

 

redis是单线程的

redis是基于内存操作,cpu不是redis的性能瓶颈,redis受限于机器的内存和网络带宽

redis是将所有的数据全部放在内存中的,所以使用单线程操作效率是最高的,多线程(cpu上下文切换,耗时的操作),对于内存来说,没有上下文切换效率就是最高的

 

五大数据类型

redis是一个开源的,内存中的数据结构存储系统,可用作数据库、缓存和消息中间件。支持多种类型的数据结构,如字符串(String),哈希(hash),列表(list),集合(sets),有序集合(sorted sets)与范围查询, bitmapshyperloglogs地理空间(geospatial) 索引半径查询

Redis-Key

127.0.0.1:6379> set name ha
OK
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> set age 12
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> exists name # 判断name这个key是否存在
(integer) 1
127.0.0.1:6379> exists name1
(integer) 0
127.0.0.1:6379> move name 1  # 移动name到数据库1
(integer) 1
​
# 设置key自动过期时间 s
127.0.0.1:6379> expire name 10
(integer) 1
# ttl查看key剩余时间,负数表示已过期
127.0.0.1:6379> ttl name  
(integer) 2
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)

查看key的类型

127.0.0.1:6379> type name
string
127.0.0.1:6379> type age
string

 

String

127.0.0.1:6379> set k1 v1 # 设置值
OK
127.0.0.1:6379> get k1  # 获取值
"v1"
127.0.0.1:6379> keys *  # 获取所有的Key
1) "k1"
127.0.0.1:6379> exists k1 # 判断某个key是否存在
(integer) 1
127.0.0.1:6379> append k1 222 # 追加k1,如果key不存在则会创建key
(integer) 5
127.0.0.1:6379> get k1
"v1222"
127.0.0.1:6379> strlen k1 # k1的长度
(integer) 5
127.0.0.1:6379> 

 

增长incr,减少decr操作

127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views  # 自增1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views 
"2"
127.0.0.1:6379> decr views # 自减1
(integer) 1
127.0.0.1:6379> incrby views 10 # 设置指定增长量
(integer) 11
127.0.0.1:6379> decrby views 5 # 设置指定减少量
(integer) 6

 

字符串截取

127.0.0.1:6379> get name
"zjdkcdf"
127.0.0.1:6379> getrange name 1 3 # 截取[1,3]
"jdk"
127.0.0.1:6379> getrange name 0 -1 #获取name
"zjdkcdf"127.0.0.1:6379> setrange name 1 1 # 将位置1的地方替换为1
(integer) 7
127.0.0.1:6379> get name
"z1dkcdf"
​
# setex 设置key过期时间
# setnx 不存在key则设置(在分布式锁中会常常使用)
127.0.0.1:6379> setex k1 10 hello # 创建k1值为hello,过期时间为10s
OK
127.0.0.1:6379> ttl k1 # 查看key的剩余过期时间
(integer) 5
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> set m ras
OK
127.0.0.1:6379> get m
"ras"
127.0.0.1:6379> setnx m aa # 如果key存在则创建失败
(integer) 0

 

批量设置值

127.0.0.1:6379> mset k1 a1 k2 a2  # 同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
127.0.0.1:6379> mget k1 k2  # 同时获取多个值
1) "a1"
2) "a2"
127.0.0.1:6379> msetnx k1 aa k3 a3 # msetnx原子性,k1存在 设置失败
(integer) 0
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
# 对象
set user:1 {name:da,age:22}
​
# user:id:key:value
127.0.0.1:6379> mset user:1:name zha user:1:age 12
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zha"
2) "12"

 

getset

127.0.0.1:6379> getset db redis # 如果不存在,则返回nil并设置值
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb # 如果存在值,获取原来的值并更新
"redis"
127.0.0.1:6379> get db
"mongodb"

 

 

List(列表)

在redis中,list可以使用为栈,队列

所有的list命令都用l开头

放入元素

127.0.0.1:6379> lpush list l1  # 将值插入到头部(往左放)
(integer) 1
127.0.0.1:6379> lpush list l2
(integer) 2
127.0.0.1:6379> lpush list l3
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "l3"
2) "l2"
3) "l1"
127.0.0.1:6379> lrange list 0 1
1) "l3"
2) "l2"127.0.0.1:6379> rpush list aa # 放到列表的尾部(放到右部)
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "l3"
2) "l2"
3) "l1"
4) "aa"
 

移除元素

127.0.0.1:6379> lrange list 0 -1
1) "l3"
2) "l2"
3) "l1"
4) "aa"
127.0.0.1:6379> lpop list # 移除第一个元素
"l3"
127.0.0.1:6379> rpop list # 移除最后一个元素
"aa"
127.0.0.1:6379> lrange list 0 -1
1) "l2"
2) "l1"
​
# 移除指定的值
127.0.0.1:6379> lrange list 0 -1
1) "l2"
2) "l2"
3) "l1"
127.0.0.1:6379> lrem list 1 l1  # 移除1个l1
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "l2"
2) "l2"
127.0.0.1:6379> lrem list 2 l2  # 移除2个l2
(integer) 2
127.0.0.1:6379> lrange list 0 -1
(empty array)
 

根据下标获取值--lindex

127.0.0.1:6379> lindex list 0
"l2"
127.0.0.1:6379> lindex list 1
"l1"
127.0.0.1:6379> lindex list 3
(nil)

 

获取list的长度--llen

llen list

 

截取list---ltrim

127.0.0.1:6379> lrange list 0 -1
1) "hello3"
2) "hello2"
3) "hello1"
127.0.0.1:6379> ltrim list 1 2
OK
127.0.0.1:6379> lrange list 0 -1
1) "hello2"
2) "hello1"

roplpush 移除列表的最后一个元素,将他放入新的列表

127.0.0.1:6379> rpoplpush list lsit2
"hello1"
127.0.0.1:6379> lrange list 0 -1
1) "hello3"
2) "hello2"
127.0.0.1:6379> lrange lsit2 0 -1
1) "hello1"

 

更新list中的元素值--lset

# 判断这个list是否存在
127.0.0.1:6379> exists list
(integer) 0
# 不存在更新会报错
127.0.0.1:6379> lset list 0 h1
(error) ERR no such key
127.0.0.1:6379> lpush list val
(integer) 1127.0.0.1:6379> lrange list 0 0
1) "val"
127.0.0.1:6379> lset list 0 ha
OK
127.0.0.1:6379> lrange list 0 0
1) "ha"

 

 

插入某个值插入到list某个元素的前面或者后面--linsert

127.0.0.1:6379> linsert list before ha aa
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "vk"
2) "aa"
3) "ha"
127.0.0.1:6379> linsert list after ha bb
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "vk"
2) "aa"
3) "ha"
4) "bb"

 

 

Set(集合)

set中的值是不能重复的,是无序不重复集合

set的命令都是以s开头的

 

添加,查看元素,判断值是否在集合中存在

127.0.0.1:6379> sadd myset ha  # 向set中添加值
(integer) 1
127.0.0.1:6379> sadd myset hb
(integer) 1
127.0.0.1:6379> sadd myset hc
(integer) 1
127.0.0.1:6379> smembers myset  # 查看set中所有值
1) "hc"
2) "hb"
3) "ha"
127.0.0.1:6379> sismember myset ha # 判断某个值在set中是否存在
(integer) 1
127.0.0.1:6379> sismember myset kk
(integer) 0

 

获取set集合的元素个数--scard

127.0.0.1:6379> scard myset
(integer) 3

 

移除set集合中的指定元素--srem

127.0.0.1:6379> smembers myset
1) "hc"
2) "hb"
3) "ha"
127.0.0.1:6379> srem myset ha
(integer) 1
127.0.0.1:6379> smembers myset
1) "hc"
2) "hb"

 

随机移除set中的元素 -- spop

 

从集合中随机抽选--srandmember

SRANDMEMBER myset   # 挑选一个
SRANDMEMBER myset 2 # 挑选两个

 

将集合中指定的一个值,移动到另外一个中--smove

127.0.0.1:6379> smove m1 m2 ha # 将m1中的ha移动到m2
(integer) 1

 

集合类--关于集合的运算

差集--sdiff

交集--sinter

并集--sunion

127.0.0.1:6379> sadd k1 a
(integer) 1
127.0.0.1:6379> sadd k1 b
(integer) 1
127.0.0.1:6379> sadd k1 c
(integer) 1
127.0.0.1:6379> sadd k2 b
(integer) 1
127.0.0.1:6379> sadd k2 d
(integer) 1
127.0.0.1:6379> sdiff k1 k2
1) "c"
2) "a"
127.0.0.1:6379> sinter k1 k2
1) "b"
127.0.0.1:6379> sunion k1 k2
1) "d"
2) "c"
3) "a"
4) "b"

这个可以做共同好友的功能

 

Hash(map集合)

key-map

添加,查看,获取所有的--hset hget hmset hmget hgetall

127.0.0.1:6379> hset myhash f1 a\  #设置一个hash(key-value)
(integer) 1
127.0.0.1:6379> hget myhash f1  # 获取
"a\\"
127.0.0.1:6379> hmset myhash f1 a f2 b  # 设置多个
OK
127.0.0.1:6379> hmget myhash f1 f2 # 获取多个
1) "a"
2) "b"
127.0.0.1:6379> hgetall myhash # 获取全部的数据
1) "f1"
2) "a"
3) "f2"
4) "b"

删除-- hdel

127.0.0.1:6379> hdel myhash f1  # 删除key ,value也会没有
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "f2"
2) "b"

hash的长度--hlen

127.0.0.1:6379> HLEN myhash
(integer) 1

判断hash中key是否存在--hexists

127.0.0.1:6379> HEXISTS myhash f2
(integer) 1
127.0.0.1:6379> hexists myhash f1
(integer) 0

获取hash中的key,value--hkeys hvals

127.0.0.1:6379> hkeys myhash
1) "f2"
2) "f3"
127.0.0.1:6379> HVALS myhash
1) "b"
2) "c"

增加,减少--hincrby

5) "f4"
6) "3"
127.0.0.1:6379> hincrby myhash f4 1
(integer) 4
127.0.0.1:6379> hincrby myhash f4 -1
(integer) 3

 

设置hash中不存在的key--hsetnx

127.0.0.1:6379> hsetnx myhash f4 9 # hash中存在f4则不能设置
(integer) 0
127.0.0.1:6379> hsetnx myhash f1 a
(integer) 1

应用:存储经常变动的数据,适合对象的存储 如购物车

 

Zset(有序集合)

z开头的命令

添加,查看元素--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 # 查看所有的元素

移除元素--zrem

127.0.0.1:6379> zrem salary vva

 

查看有序集合中元素个数--zcard

127.0.0.1:6379> zcard salary
(integer) 2

 

 

有序集合排序--zrange key min max(从小到大)zrevrange

zrangebyscore zrevrangebyscore可以指定范围

# 排序指定范围-inf表示负无穷
127.0.0.1:6379> zrangebyscore salary -inf +inf
1) "zha"
2) "vva"
3) "jca"127.0.0.1:6379> zrangebyscore salary -inf +inf withscores
1) "zha"
2) "100"
3) "vva"
4) "399"
5) "jca"
6) "588"
127.0.0.1:6379> zrangebyscore salary -inf 500 withscores
1) "zha"
2) "100"
3) "vva"
4) "399"
​
​
# 排序所有的
127.0.0.1:6379> zrange salary 0 -1 withscores
1) "zha"
2) "100"
3) "vva"
4) "399"
5) "jca"
6) "588"
127.0.0.1:6379> zrevrange salary 0 -1 withscores
1) "jca"
2) "588"
3) "vva"
4) "399"
5) "zha"
6) "100"
 

获取指定区间的成员数量--zcount

127.0.0.1:6379> zcount salary 0 500
(integer) 1

作用:班级成绩,工资表,排行榜

 

三种特殊数据类型

Geospatial(地理位置)

命令以geo开头

添加数据--geoadd

# geoadd key 经度 纬度 城市
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 120.61 31.29 suzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 118.76 32.04 nanjing
(integer) 1
127.0.0.1:6379> geoadd china:city 114.08 22.54 shenzhen
(integer) 1
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1

 

用于从给定的 key 里获取指定的--geopos获取当前定位

127.0.0.1:6379> geopos china:city beijing # 获取指定城市的经度和纬度
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"

 

geodist 用于返回两个给定位置之间的距离

GEODIST key member1 member2 [m|km|ft|mi]
127.0.0.1:6379> geodist china:city beijing shanghai km
"1067.3788"

 

georadius找出附近的

# 以110 30为中心,寻找1000km的china:city中的城市
127.0.0.1:6379> georadius china:city 110 30 1000 km 
1) "shenzhen"
2) "nanjing"
127.0.0.1:6379> georadius china:city 110 30 1000 km withdist withcoord count 1  # withdist显示距离 withcoord显示经维度
1) 1) "nanjing"
   2) "864.9816"
   3) 1) "118.75999957323074341"
      2) "32.03999960287850968"

georadiusbymember

# 找出位于指定元素指定位置的元素
127.0.0.1:6379> GEORADIUSBYMEMBER china:city nanjing 1000 km
1) "nanjing"
2) "beijing"
3) "suzhou"
4) "shanghai"

 

geohash,获取一个或多个位置元素的 geohash 值返回11个字符的geohash字符串

# 将二维的经纬度变成字符串
127.0.0.1:6379> geohash china:city beijing
1) "wx4fbxxfke0"

 

geo的底层实现原理是zset,可以使用zset命令来操作geo

查看所有zrange 移除zrem

127.0.0.1:6379> zrange china:city 0 -1
1) "shenzhen"
2) "suzhou"
3) "shanghai"
4) "nanjing"
5) "beijing"
127.0.0.1:6379> zrem china:city beijing
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "shenzhen"
2) "suzhou"
3) "shanghai"
4) "nanjing"

 

 

Hyperloglog

基数:一个集合中不重复元素的个数

pfadd pfcount pfmerge

127.0.0.1:6379> pfadd mykey a b c c d # 创建第一组元素
(integer) 1
127.0.0.1:6379> pfcount mykey  # 统计基数的数量
(integer) 4
127.0.0.1:6379> pfadd mykey2 d f
(integer) 1
127.0.0.1:6379> pfmerge mykey3 mykey mykey2 # 合并两组,并集
OK
127.0.0.1:6379> pfcount mykey3
(integer) 5

有一定的容错率,但是占用的内存固定只要12KB内存

 

Bitmap

Bitmap,位图,都是操作二进制来进行记录只有0和1两个状态

用户登录未登录,打卡未打卡,两个状态的都可以使用Bitmap

记录了前三天打卡的记录--setbit

127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 0
(integer) 0
127.0.0.1:6379> setbit sign 2 1
(integer) 0

 

查看记录--getbit

127.0.0.1:6379> getbit sign 1
(integer) 0

 

统计操作,统计打卡的天数-- bitcount

127.0.0.1:6379> bitcount sign
(integer) 2

 

事务

redis单条命令是保证原子性的,但是事务不保证原子性

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

一次性、顺序性、排他性

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

redis的事务:

  • 开启事务(multi)

  • 命令入队

  • 执行事务(exec)

127.0.0.1:6379> MULTI   # 开启事务
OK
127.0.0.1:6379> set k1 v1 # 操作
QUEUED
127.0.0.1:6379> set k2 v2 # 操作
QUEUED
127.0.0.1:6379> get k2   # 操作
QUEUED
127.0.0.1:6379> set k3 v3  # 操作
QUEUED
127.0.0.1:6379> exec # 执行事务
1) OK
2) OK
3) "v2"
4) OK

 

放弃事务 DISCARD

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a aa
QUEUED
127.0.0.1:6379> DISCARD
OK
127.0.0.1:6379> get a
127.0.0.1:6379> get a
(nil)

 

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

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
# 错误的命令
127.0.0.1:6379> setget j a
(error) ERR unknown command `setget`, with args beginning with: `j`, `a`, 
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec # 执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1
(nil)

 

运行时异常,如果事务队列中存在某个语句存在异常,执行命令时候其他命令是可以正常执行的

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> incr k1 # 对字符串增1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> get k2
"v2"

 所有的对象都需要序列化

 

 RedisTemplate默认使用的是jdk序列化方式

 

 

 

RedisConfig

@Configuration
public class RedisConfig {

    // 编写自己的redisTemplate
    @Bean
    public org.springframework.data.redis.core.RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        org.springframework.data.redis.core.RedisTemplate<String, Object> template = new org.springframework.data.redis.core.RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        // Json序列化配置
        Jackson2JsonRedisSerializer objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        objectJackson2JsonRedisSerializer.setObjectMapper(om);
        // String的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        template.setKeySerializer(objectJackson2JsonRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        template.setValueSerializer(objectJackson2JsonRedisSerializer);
        template.setHashValueSerializer(stringRedisSerializer);


        return template;
    }
}

 

Redis.conf

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

单位

 

 

配置文件unit单位对大小写不敏感

包含

 

 

网络

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

 

通用配置General

daemonize yes # 以守护进程的方式运行,默认是no,需要改为yes
​
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 # 数据库的数量
always-show-logo yes # 是否显示logo

 

快照SNAPSHOTTING rdb配置

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

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

# 如果900s内,如果至少有一个Key进行了修改,就进行持久化操作
save 900 1
# 如果300s内,如果至少有10个Key进行了修改,就进行持久化操作
save 300 10
save 60 10000
​
stop-writes-on-bgsave-error yes  # 持久化出错,是否继续工作
​
rdbcompression yes  # 是否压缩rdb文件,需要消耗一些cpu资源
​
rdbchecksum yes # 报错rdb文件时候,进行检查校验
​
dir ./ # rdb文件保存的目录

 

REPLICATION主从复制

 

SECURITY安全

可以在配置文件设置redis的密码,默认是没有密码(# requirepass foobared)

也可以通过命令行设置redis的密码

config get requirepass # 获取redis的密码
config set requirepass 12345 # 设置redis的密码
auth 12345 # 验证登录后,才可以操作
​
# 去掉密码
config set requirepass ""

 

限制CLIENTS

一般不需要修改

maxclients 10000 # 最多能连接redis的客户端的数量
maxmemory <bytes> # redis配置最大的内存容量
maxmemory-policy noeviction # 内存到达上限之后的处理策略

 

APPEND ONLY MODE aof配置

appendonly no # 默认是不开启aof模式的,默认使用rdb持久化

 

Redis持久化

redis是内存数据库,如果不将内存中的数据库保存,断电即失

RDB

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

rdb保存的文件是dump.rdb

 

 

 

触发机制

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

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

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

如何恢复rdb文件

只需要将dump.rdb文件放到rdis的启动目录下(/usr/local/bin)就可以,redis启动的时候会自动检查dump.rdb文件并恢复

优缺点

优点:

适合大规模的数据恢复

缺点:

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

 

AOF(Append Only File)

以日志的方式来记录每个写操作,将redis执行过的所有写指令记录下来,只许追加文件但不能改写文件,redis启动时候会读取文件重构数据

aof保存的是appendonly.aof文件

默认是不开启的,需要手动进行配置,只需要appendonly改为yes

重启redis后会生成appendonly.aof

如果aof文件有问题,需要修复这个aof文件

redis-check-aof --fix appendonly.aof

 

优缺点

优点:

  • 每一次修改都同步,文件的完整性会好(appendfsync always)

  • 每秒同步一次,可能会丢失一秒的数据(appendfsync everysec)

缺点:

  • 数据文件来说,aof大于rdb,修复的速度比rdb慢

  • aof是往文件写操作,运行效率比rdb慢,redis默认使用的是rdb持久化

 

Redis发布订阅

redis发布订阅是一种消息通信模式,发送者(pub)发送消息,订阅者(sub)接受消息

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

 

 

常用的命令

订阅者

127.0.0.1:6379> SUBSCRIBE ha  # 订阅一个频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "ha"
3) (integer) 1
# 等待读取推送的消息
1) "message" 
2) "ha"
3) "hellosub"

发送者

127.0.0.1:6379> publish ha hellosub
(integer) 1

 

Redis主从复制

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

主从复制,读写分离,很多情况下都是进行读操作,可以减缓服务器压力

默认情况下,每台redis服务器都是主节点,一个主节点可以有多个从节点,但一个从节点只能有一个主节点

主从复制的作用:

  1. 数据备份:实现了数据的热备份,是持久化之外的一种数据备份方式

  2. 故障恢复:当主节点出现问题时,可以从节点提供服务,实现快速故障恢复

  3. 负载均衡:在主从复制的基础上配合读写分离,主节点提供写服务,从节点提供读服务,分担服务器的负载

  4. 高可用基石

环境配置

只需要配置从库,不用配置主库

info replication # 查看当前库的信息
role:master
connected_slaves:0

 

复制三个配置文件,修改对应的信息

[root@localhost hconfig]# cp redis.conf redis79.conf
[root@localhost hconfig]# cp redis.conf redis80.conf
[root@localhost hconfig]# cp redis.conf redis81.conf
  1. 端口号

  2. pid名字

  3. 日志文件名

  4. dump.rdb文件名

 

查看是否有三个redis进程

ps -ef|grep redis

 

一主二从

配置从机

SLAVEOF 127.0.0.1 6379

主机中查看,显示有两个从机

 

 

 

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

replicaof <masterip> <masterport>
# 如果主机有密码的话,设置密码
masterauth <master-password>

主机可以写,从机不能写只能读,主机中的所有信息和数据都会被从机自动保存

主机断开连接,从机依旧连接到主机,如果主机连接上,从机依旧可以获取到主机写的信息

如果是使用命令行配置的主从,从机如果重启了从机就会变为主机。

复制原理

层层链路

80依旧是从节点,无法写入,也可以完成主从复制

使某个从节点变成主节点

如果主机断开连接,可以选择新的主机

slaveof no one

 

哨兵模式

自动选择主机

  1. 配置哨兵配置文件sentinel.conf

    vim sentinel.conf
    ​
    # sentinel monitor 被监控的名称 host port 1
    sentinel monitor myredis 127.0.0.1 6379 1
  2. 启动哨兵进程

    redis-sentinel hconfig/sentinel.conf
  3. 如果主机断开了,就会在从机中投票选择一个主服务器

  4. 如果主机连接,只能当作从机

 

缓存穿透和雪崩

缓存穿透

 

 

概念

key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源

解决方案

布隆过滤器

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

缓存空对象

当数据库不命中时候,仍把这个空对象缓存,同时设置一个过期时间

问题:

 

 

 

缓存击穿

key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,会访问数据库来查询数据并回写缓存,导致数据库瞬间压力过大

 

缓存雪崩

当缓存服务器重启或者大量缓存集中在某一个时间段失效

解决方案

 

 

 

推荐阅读