Redis初探

Redis

nosql

为什么要用nosql

为什么要用nosql?

大数据时代,springboot、springcloud。

现代的计算系统上每天网络上都会产生庞大的数据量。

这些数据有很大一部分是由关系数据库管理系统(RDBMS)来处理。 1970年 E.F.Codd’s提出的关系模型的论文 “A relational model of data for large shared data banks”,这使得数据建模和应用程序编程更加简单。

通过应用实践证明,关系模型是非常适合于客户服务器编程,远远超出预期的利益,今天它是结构化数据存储在网络和商务应用的主导技术。

NoSQL 是一项全新的数据库革命性运动,早期就有人提出,发展至2009年趋势越发高涨。NoSQL的拥护者们提倡运用非关系型的数据存储,相对于铺天盖地的关系型数据库运用,这一概念无疑是一种全新的思维的注入。

NoSQL,指的是非关系型的数据库。NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称。

NoSQL用于超大规模数据的存储。(例如谷歌或Facebook每天为他们的用户收集万亿比特的数据)。这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。

1、单机Mysql的年代

image-20210118151356838

当初一个网站的访问量不大,单个数据库 完全 够。服务器没有太大的压力。

出现的问题:

1、数据量太大,一个机器放不下。

2、数据的 索引,一个机器内存也放不下。

3、访问量一个服务器承受不了

2、缓存 Memcahed+mysql+垂直拆分(读写分离)

网站80%都是在读,每次都要去查询数据库十分麻烦!可以用缓存来解决。

image-20210118152151119

3、分库分表+水平拆分

image-20210118152842076

技术和业务发展的同时,对人的要求也越来越高了。

4、如今时代

翻天覆地的变化。

mysql等关系型数据库已经不够用了。

mysql有的使用它来储存一些较大的文件。

目前互联网项目架构

image-20210118154330289

为什么要用Nosql

用户的个人信息,社交网络,用户自己产生的数据,用户日志爆发式增长。

什么是Nosql

NoSQL,指的是非关系型的数据库。NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称。

非关系型的数据库:随着web2.0时代产生,传统的数据库很难应付,尤其是超大规模的高并发社区。

Nosql在目前环境下发展的非常迅速,redis发展是最快的。

Nosql特点

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

2、大数据量高性能(Nosql的缓存是细密度的)

3、数据类型是多样型的!(不需要事先设计数据库!随取随用。)

4、传统的关系型数据库和Nosql

传统的关系型数据库
-结构化组织
-sql
-数据 和关系存在单独表中
-严格的一致性

Nosql
-不仅仅是数据
-没有固定的查询语句
-键值对,列存储。文档存储,图像数据库
-CAP定理和BASE
-

阿里架构分析

image-20210118161318353 image-20210118161525173 image-20210118161543849

Nosql的四大分类

KV键值对

  • 新浪:Redis
  • 美团:Redis+Tail
  • 阿里百度:Redis +memecache

文档型数据库

  • MngoDB

    基于分布式存储数据库。

    是一个介于关系型和非关系型数据中间的。

列存储数据库

  • HBash
  • 分布式数据库

图关系数据库

  • 不是存图的,而是存关系的

Redis

概述

Redis是什么

REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统,远程服务字典。

当前Nosql技术之一,也被称为结构化数据库。

Redis能做什么

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

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

3、发布订阅

4、地图信息分析

5、计时器,计数器

Redis特性

1、多样数据类型

2、持久化

3、集群

4、事务

注意什么

Redis都是推荐在Linux上搭建。

安装

windows安装

下载安装包:github下载

解压,完成!双击运行服务即可 默认端口:6379

使用redis客户端连接服务器

Linux安装

**下载地址:**http://redis.io/download,下载最新稳定版本。

1、centos安装

1、下载 https://redis.io/

2、上传服务器

3、解压安装包 程序放在opt目录

2、docker安装

安装的时候可以简化,先不挂载,进入步骤:1、2、5.2、6、7、8

1、这里我们拉取官方的最新版本的镜像:

$ docker pull redis:latest

2、使用以下命令来查看是否已安装了 redis:

$ docker images

3、 创建本机redis挂载目录

mkdir -p /root/redis/data /root/redis/conf

4、在/root/redis/conf目录中创建文件 redis.conf

touch redis.conf

5、安装完成后,我们可以使用以下命令来运行 redis 容器:

1、$ docker run -d -p 6379:6379 -v /root/redis/conf/redis.conf:/redis.conf -v /root/redis/data:/data redis redis-server --appendonly yes

2、docker run -d -p 6379:6379 redis redis-server --appendonly yes

6、查看redis是否运行

docker ps

7、接着我们通过 redis-cli 连接测试使用 redis 服务。

$ docker exec -it 容器ID /bin/bash

8、进入终端

docker exec -it 容器ID redis-cli

image-20210118221313830

性能 测试
redis-benchmark 

redis 性能测试工具可选参数如下所示:

序号选项描述默认值
1-h指定服务器主机名127.0.0.1
2-p指定服务器端口6379
3-s指定服务器 socket
4-c指定并发连接数50
5-n指定请求数10000
6-d以字节的形式指定 SET/GET 值的数据大小2
7-k1=keep alive 0=reconnect1
8-rSET/GET/INCR 使用随机 key, SADD 使用随机值
9-P通过管道传输 请求1
10-q强制退出 redis。仅显示 query/sec 值
11–csv以 CSV 格式输出
12-l生成循环,永久执行测试
13-t仅运行以逗号分隔的测试命令列表。
14-IIdle 模式。仅打开 N 个 idle 连接并等待。
# 测试100个并发连接,每个并发10万个请求

基础知识

redis 默认有16个数据库

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

数据库切换

#设置key
set name cbbgs
#查看key的值
get name 

#切换到数据库3
select 3
#查看数据库大小
DBsize

127.0.0.1:6379> select 3
OK
127.0.0.1:6379[3]> DBsize
(integer) 0
#查看数据库所有的key
keys *

清除数据库

#清除当前数据库
flushdb
#清空全部的数据库
flushall

redis是单线程的

redis是很快的,是基于内存操作的。,CUP不是redis的性能瓶颈 ,Redis的瓶颈是根据机器的内存和网络的带宽。

Redis为什么单线程还这么快?

1、误区1:高性能的服务器一定是多线程的?

2、误区2:多线程一定比单线程效率高。

核心:redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的。

五大基本数据类型

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。

image-20210119090857194
Redis-key
#添加
set name hello
#获取
get name
#查看一个key是否存在
exists keyname
#移除key
move keyname#1表示当前数据库
#设置过期时间 10秒
expire keyname
# 查看过期时间
ttl keyname

#查看key的数据类型
type keyname

官方命令查询 http://www.redis.cn/commands.html

string

字符串类型

追加字符串
#在某个key的值后面追加字符串
append keyname 'hello'

127.0.0.1:6379> set name cbbgs
OK
127.0.0.1:6379> get name
"cbbgs"
127.0.0.1:6379> append name 'hello'
(integer) 10
127.0.0.1:6379> get  name
"cbbgshello"
查看value的长度
#查看key的值的长度
strlen keyname

127.0.0.1:6379> strlen name
(integer) 10
自增和自减
#自动加一
incr keyname
#自动减一
decr keyname 

#设置步长
incrby keyname 10 #自动加10
decrby keyname 10 #自动减10
字符串范围
#字符串范围 getrange 
 getrange keyname 0 3  # 截取字符串0-3的字符
 getrange keyname 0 -1 #查看全部字符串
 
 127.0.0.1:6379> set name hello_cbbgs
OK
127.0.0.1:6379> getrange name 0 3
"hell"
127.0.0.1:6379> getrange name 0 -1
"hello_cbbgs"
替换字符串
# 替换 setrange
setrange name 7 xxx  #从第七个字符开始替换

127.0.0.1:6379> setrange name 7 xxx
(integer) 11
127.0.0.1:6379> get  name
"hello_cxxxs"
setex
#设置过期时间,过期之前会变成你设置的
setex keyname 30 'hello'  #设置keyname的值为hello,10,秒过期

127.0.0.1:6379> setex name 10 "hello"
OK
127.0.0.1:6379> get name
"hello"


setnx
#不存在的设置
setnx keyname 'hello'  #如果keyname不存在会创建keyname ,如果存在会创建失败

批量操作
# 批量设置
mset k1 v1 k2 v3 k3 v3
#批量获取
mget k1 k2 k3

#如果不存在就创建,存在就创建失败
msetnx k1 v1 k4 v4  #要么一起成功,要么一起失败

127.0.0.1:6379> mset k1 v1 k2 v3 k3 v3
OK
127.0.0.1:6379> mget k1 k3 k2
1) "v1"
2) "v3"
3) "v3"
对象
#设置一个user:1对象,值为json字符串来保存一个对象
set user:1 {name:zhangsan,age 3}

#这个的key是一个巧妙的设计,  user:id:filed
27.0.0.1:6379> mset user:1:name zzhangsan user:1:age 2
OK
127.0.0.1:6379> get user
(nil)
127.0.0.1:6379> mget user:1:name user:1:age
1) "zzhangsan"
2) "2"
getset
#先get再set
getset key value 


127.0.0.1:6379> getset db redis #如果不存在,返回null,则去创建
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mm #如果存在则会返回值,再去设置值
"redis"
127.0.0.1:6379> get db
"mm"

string应用场景,value除了可以是字符串还可以是数字。统计数量,计数器,粉丝数。

List

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

在list命令都是l开头的。

添加到列表
#添加到列表的头部(左)
lpush list one
#添加到列表的尾部(右)
rpush list one

#得到部分值
lrange list 0 -1

127.0.0.1:6379> flushall
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> lrange list 0 -1  #查看元素
1) "two"
2) "one"

移除一个元素
#移除左边的
lpop list 
#移除右边
rpop list

127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
3) "one"
127.0.0.1:6379> lpop list 
"two"
127.0.0.1:6379> Rpop list 
"one"
127.0.0.1:6379> lrange list 0 -1
1) "one"

#移除指定的值
lrem list 1 one  #移除一个one

127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
3) "1"
4) "one"
127.0.0.1:6379> lrem list 1 one
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
3) "1"
通过下标得到值
#通过下标的到值
127.0.0.1:6379> lindex list 0
"one"

list长度
llen list 

127.0.0.1:6379> llen list
(integer) 4
截取很多元素
#截取很多元素 
ltrim  list 1 2

127.0.0.1:6379> ltrim  list 1 2
OK
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "1"
rpoplpush
#移除最后一个元素,并添加到另一个list中
rpoplpush list list1

127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "1"
127.0.0.1:6379> rpoplpush list list1
"1"
127.0.0.1:6379> lrange list1 0 -1
1) "1"
127.0.0.1:6379> lrange list 0 -1
1) "2"

设置值
#设置值 将list 下标为1 的值改成hello
lset list 1 hello
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "2"
127.0.0.1:6379> lset list 1 hello
OK
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "hello"

插入一个值在某个元素后面或前面
#在list中hello后面插入word
linsert  list before hello word
#在list中hello前面插入word
linsert  list after hello word

127.0.0.1:6379> linsert list before hello wperld
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "wperld"
3) "hello"

127.0.0.1:6379> linsert  list after hello word
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "wperld"
3) "hello"
4) "word"
set

Redis 的 Set 是 string 类型的无序集合。

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

添加元素
#添加
sadd set1 hello

127.0.0.1:6379> sadd set1 hello
(integer) 1
127.0.0.1:6379> sadd set1 world
(integer) 1
查看set的所有值
smembers  setname

127.0.0.1:6379> smembers set1
1) "world"
2) "hello"
sismember
#判断一个元素是不是在集合中
sismember setname hello

127.0.0.1:6379> sismember set1 hello
(integer) 1
获取元素的个数
#得到个数
scard setname
移除元素
#移除指定元素
srme setname hello

127.0.0.1:6379> srem set1 hello
(integer) 1
127.0.0.1:6379> smembers set1
1) "world"

#随机 删除key
spop setname

127.0.0.1:6379> smembers set1
1) "world"
2) "python"
3) "php"
4) "java"
127.0.0.1:6379> spop set1
"php"
127.0.0.1:6379> smembers set1
1) "world"
2) "python"
3) "java"

#将一个指定的元素删除,加到另一个集合
smove set1 set2 java

127.0.0.1:6379> smove set1 set2 java
(integer) 1
127.0.0.1:6379> smembers set2
1) "java"

随机元素
#随机抽取一个元素
srandember setname 1

127.0.0.1:6379> srandmember set1
"world"
127.0.0.1:6379> srandmember set1
"hello"
并集
#两个集合的并集
sunion set1 set2
差集
#两个集合的差集
sdiff set1 set2
交集
#两个集合的交集
sinter set1 set2
Hash(哈希)

map集合,key-map 值是map集合

Redis hash 是一个键值(key=>value)对集合。

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

添加和获取
#添加
hset hashname key value

#获取
hget  hashname key

#同时添加多个值
hmset hashname k1 v1 k2 v2
#同时获得多个值
hmget hashname k1 k2

hgetall hashname

127.0.0.1:6379> hmset hash1 k1 v1 k2 v2
OK
127.0.0.1:6379> hmget hash1 k1 k2
1) "v1"
2) "v2"
127.0.0.1:6379> hgetall hash1
1) "name"
2) "cbbgs"
3) "k1"
4) "v1"
5) "k2"
6) "v2"
删除
#删除指定的值
hdel hashname key
获取长度
#长度
hlen hashname

127.0.0.1:6379> hlen hash1
(integer) 3

判断是否存在
#判断hash的key是否存在
hexists hashname key

只获得key
hkeys hashname
股只得value
hvalues hashname
自增自减
#自增
hincrby hashname key 1
#自减
hdecrby hashname key -1

应用:user :name age

Zset

Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。

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

zset的成员是唯一的,但分数(score)却可以重复。

添加
#添加
zadd zsetname 1 hello

127.0.0.1:6379> zadd zsetname 1 hello
(integer) 1
127.0.0.1:6379> zadd zsetname 2 woerld 3 java
(integer) 2
127.0.0.1:6379> zrange zsetname 0 -1
1) "hello"
2) "woerld"
3) "java"
排序
#排序,负无穷到正无穷,返回带值
zrangebyscore zsetname -inf +inf withscores

#添加三个用户
127.0.0.1:6379> zadd zset2 money 2000
(error) ERR value is not a valid float
127.0.0.1:6379> zadd zset2 2000 money
(integer) 1
127.0.0.1:6379> zadd zset2 20999 a
(integer) 1
127.0.0.1:6379> zadd zset2 20000 b
(integer) 1
127.0.0.1:6379> zrangebyscore zset2 -inf +inf withscores
1) "money"
2) "2000"
3) "b"
4) "20000"
5) "a"
6) "20999"
#从大到小排序
zrevrange zsetname 0 -1
移除元素
#移除元素
zrem zsetname money

127.0.0.1:6379> zrem zset2 money
(integer) 1


查看数量
#获取有序集合的个数
zcard zzsetname

#查看区间的值有几个  
zcount zsetname 1 3  #1 -3之间有几个成员数量

三种特殊数据类型

geospatial

查询经纬度

https://www.bejson.com/convert/map/

image-20210119145347272

添加位置
# 添加位置
#两极无法直接导入,
# 参数 key 值(纬度 经度 名称)
geoadd china:city 116.40 39.90 北京
geoadd china:city 121.47 31.23 上海
geoadd china:city 106.50 29.53  重庆 114.05 22.52 深圳
查询位置的值
# 获取指定位置的经度和纬度
geopos china:city 北京 重庆


127.0.0.1:6379> geopos china:city 北京 重庆
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
2) 1) "106.49999767541885376"
   2) "29.52999957900659211"
两个位置的距离
# m 米
# km 千米
geodist China:city 北京 重庆

127.0.0.1:6379> geodist china:city 北京 重庆 km
"1464.0708"
范围
# 在 110 30 为点,1000为半径的位置 withcoord 带经纬度  withdist 距离    count 2 显示几个

georadius china:city 110 30 1000 km withcoord
以城市名查询
#指定位置查询其他位置
georadiusbymember china:city 北京 1000 km
查看全部城市
#查看全部城市
zrange china :city 0 -1
#删除
zrem china :city beijingchina :city
hyperloglogs

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

网页的UV(一个人访问一个网站多次,只算一次。

什么是基数?

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

下表列出了 redis HyperLogLog 的基本命令:

序号命令及描述
1[pfadd ement element …] 添加指定元素到 HyperLogLog 中。
2[pfcount key …] 返回给定 HyperLogLog 的基数估算值。
3[ pfmerge destkey sourcekey sourcekey …]
Bitmaps

统计用户信息,登录、打卡等。两个状态都可以用这个。

Bitmap位图,数据结构,都是二进制进行记录,只有0和1,

setbit
#添加
setbit sign 0 1
setbit sign 1 0

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

get bit
#查看值
getbit sign 2

127.0.0.1:6379> getbit sign 2
(integer) 0

查看打卡的天数
bitcount sign 

127.0.0.1:6379> bitcount sign 
(integer) 1

事务

Redis 要么同时成功,同时失败!

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

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

一次性、顺序性,排他性。

Redis没有隔离级别的概念。并没有被直接执行,只有执行命令的时候才会执行 !

Redis的事务:

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

正常执行事务

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


#取消事务

127.0.0.1:6379> multi  #开启事务
OK
127.0.0.1:6379> set k1 v2
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> discard #放弃事务
OK
# 事务中的队列都不会被执行
# 如果在事务队列中部分有错误,会跳过错误的执行其他的

编译型异常

所有的执行都不会被执行。直接错误!

运行时异常

执行命令时,其他正确的队列会继续执行,错误指令抛出异常。

监控

悲观锁

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

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

Redis监测

正常情况,单线程执行

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

测试多线程修改值,使用watch可以当Redis的乐观锁!

怎么从新操作呢,要先解锁,unwatch

Jedis

用java来操作redis

开始在 Java 中使用 Redis 前, 我们需要确保已经安装了 redis 服务及 Java redis 驱动,且你的机器上能正常使用 Java。 Java的安装配置可以参考我们的 Java 开发环境配置 接下来让我们安装 Java redis 驱动:

  • 首先你需要下载驱动包 下载 jedis.jar,确保下载最新驱动包。
  • 在你的 classpath 中包含该驱动包。

使用java操作Redis中间件。

测试

1、导入依赖

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

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
    </dependencies>

2、测试编码

  • 链接数据库
  • 操作命令
  • 断开命令
public class RedisTest {
    public static void main(String[] args) {
        /*new 一个jedis对象即可*/
      Jedis jedis =   new Jedis("47.93.49.117",6379);
      /*所有的指令都是我们之前学的*/
        System.out.println(jedis.ping());

    }
}

image-20210119232618523
常用的api

String

List

Set

Hash

zset

image-20210119232827951

Jedis的事务
public class RedisTest {
    public static void main(String[] args) {
        /*new 一个jedis对象即可*/
      Jedis jedis =   new Jedis("47.93.49.117",6379);
      /*所有的指令都是我们之前学的*/
       /* System.out.println(jedis.ping());*/
        JSONObject  jsonObject = new JSONObject();
        jsonObject.put("hello","world");
        jsonObject.put("name","zhangsan");
        /*
        * 开启事务*/
        Transaction multi =  jedis.multi();
        String result = jsonObject.toJSONString();
        multi.set("user1",result);
        multi.set("user2",result);
        /*
        * 执行事务
        * */
        multi.exec();
    jedis.close();
    }
}

Springboot整合Redis

springboot操作数据.springboot2.x之后,原来使用的jedis被替换成了lettuce

jedis:采用直连,多个线程操作的话,是不安全的,使用jedis pool连接池。

lettuce:采用net同意,实例可以在多个线程中共享,不存在线程不安全的情况,可以减少线程数据。

整合项目

1、导入依赖

 <dependencies>
        <!--Redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

2、Redis配置

#Redis配置
spring.redis.host=47.93.49.117
spring.redis.pore=6379

3、测试 在springboot的test包中测试

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class SpringbootRedisApplicationTests {
    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    void contextLoads() {
    }
    @Test
    public void test1(){
        //opsForValue 相对于操作字符串
        //opsForList 相当于操作List ...

        redisTemplate.opsForValue().set("cbbgs","陈斌");
        System.out.println(redisTemplate.opsForValue().get("cbbgs"));
    }
}

关于对象的保存需要序列化之后用json字符串;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;

import java.io.Serializable;

@Component
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Users implements Serializable {
    private String name;
    private Integer age;
}

测试里面

@SpringBootTest
class SpringbootRedisApplicationTests {
    @Autowired
    private RedisTemplate redisTemplate;
  
    @Test
    public void test2() throws JsonProcessingException {

        //用json字符串来传递对象
        Users user = new Users("陈斌",23);
        String jsonUser = new ObjectMapper().writeValueAsString(user);
        redisTemplate.opsForValue().set("user",jsonUser);
        System.out.println(redisTemplate.opsForValue().get(user));
    }
}

配置自己的RedisTemplate

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.net.UnknownHostException;

@Configuration
public class RedisConfig {
    /*
    * 编写自己的redistemplate
    * */
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory)throws UnknownHostException {
        
            //设置具体的序列化方式
            RedisTemplate<String, Object> template = new RedisTemplate<String,
                    Object> ();
            template.setConnectionFactory (factory);
            Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
                    Jackson2JsonRedisSerializer (Object.class);
            ObjectMapper om = new ObjectMapper ();
            om.setVisibility (PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            om.enableDefaultTyping (ObjectMapper.DefaultTyping.NON_FINAL);
            jackson2JsonRedisSerializer.setObjectMapper (om);
            StringRedisSerializer stringRedisSerializer = new
                    StringRedisSerializer ();
            // key采用String的序列化方式
            template.setKeySerializer (stringRedisSerializer);
            // hash的key也采用String的序列化方式
            template.setHashKeySerializer (stringRedisSerializer);
            // value序列化方式采用jackson
            template.setValueSerializer (jackson2JsonRedisSerializer);
            // hash的value序列化方式采用jackson
            template.setHashValueSerializer (jackson2JsonRedisSerializer);
            template.afterPropertiesSet ();
            return template;
    }
}

  	@Autowired
    @Qualifier("redisTemplate")
    private RedisTemplate redisTemplate;

我们可以继续编写一个工具类来开发,不使用原生的。

package cbbgs.cn;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Component
public final  class RedisUtil {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    // =============================common============================
    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     */
    public boolean expire (String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire (key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace ();
            return false;
        }
    }
    /**
     * 根据key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire (String key) {
        return redisTemplate.getExpire (key, TimeUnit.SECONDS);
    }
    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey (String key) {
        try {
            return redisTemplate.hasKey (key);
        } catch (Exception e) {
            e.printStackTrace ();
            return false;
        }
    }
    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings ("unchecked")
    public void del (String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete (key[0]);
            } else {
                redisTemplate.delete (Arrays.asList(key));
            }
        }
    }
// ============================String=============================
    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get (String key) {
        return key == null ? null : redisTemplate.opsForValue ().get (key);
    }
    /**
     * 普通缓存放入
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set (String key, Object value) {
        try {
            redisTemplate.opsForValue ().set (key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace ();
            return false;
        }
    }
    /**
     * 普通缓存放入并设置时间
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set (String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue ().set (key, value, time,
                        TimeUnit.SECONDS);
            } else {
                set (key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace ();
            return false;
        }
    }
    /**
     * 递增
     * @param key   键
     * @param delta 要增加几(大于0)
     */
    public long incr (String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException ("递增因子必须大于0");
        }
        return redisTemplate.opsForValue ().increment (key, delta);
    }
    /**
     * 递减
     * @param key   键
     * @param delta 要减少几(小于0)
     */
    public long decr (String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException ("递减因子必须大于0");
        }
        return redisTemplate.opsForValue ().increment (key, - delta);
    }
// ================================Map=================================
    /**
     * HashGet
     * @param key  键 不能为null
     * @param item 项 不能为null
     */
    public Object hget (String key, String item) {
        return redisTemplate.opsForHash ().get (key, item);
    }
    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget (String key) {
        return redisTemplate.opsForHash ().entries (key);
    }
    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应多个键值
     */
    public boolean hmset (String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash ().putAll (key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace ();
            return false;
        }
    }
    /**
     * HashSet 并设置时间
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset (String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash ().putAll (key, map);
            if (time > 0) {
                expire (key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace ();
            return false;
        }
    }
    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset (String key, String item, Object value) {
        try {
            redisTemplate.opsForHash ().put (key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace ();
            return false;
        }
    }
    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset (String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash ().put (key, item, value);
            if (time > 0) {
                expire (key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace ();
            return false;
        }
    }
    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel (String key, Object... item) {
        redisTemplate.opsForHash ().delete (key, item);
    }
    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey (String key, String item) {
        return redisTemplate.opsForHash ().hasKey (key, item);
    }
    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     */
    public double hincr (String key, String item, double by) {
        return redisTemplate.opsForHash ().increment (key, item, by);
    }
    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     */
    public double hdecr (String key, String item, double by) {
        return redisTemplate.opsForHash ().increment (key, item, - by);
    }
// ============================set=============================
    /**
     * 根据key获取Set中的所有值
     *
     * @param key 键
     */
    public Set<Object> sGet (String key) {
        try {
            return redisTemplate.opsForSet ().members (key);
        } catch (Exception e) {
            e.printStackTrace ();
            return null;
        }
    }
    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey (String key, Object value) {
        try {
            return redisTemplate.opsForSet ().isMember (key, value);
        } catch (Exception e) {
            e.printStackTrace ();
            return false;
        }
    }
    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet (String key, Object... values) {
        try {
            return redisTemplate.opsForSet ().add (key, values);
        } catch (Exception e) {
            e.printStackTrace ();
            return 0;
        }
    }
    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime (String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet ().add (key, values);
            if (time > 0)
                expire (key, time);
            return count;
        } catch (Exception e) {
            e.printStackTrace ();
            return 0;
        }
    }
    /**
     * 获取set缓存的长度
     *
     * @param key 键
     */
    public long sGetSetSize (String key) {
        try {
            return redisTemplate.opsForSet ().size (key);
        } catch (Exception e) {
            e.printStackTrace ();
            return 0;
        }
    }
    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove (String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet ().remove (key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace ();
            return 0;
        }
    }
// ===============================list=================================
    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     */
    public List<Object> lGet (String key, long start, long end) {
        try {
            return redisTemplate.opsForList ().range (key, start, end);
        } catch (Exception e) {
            e.printStackTrace ();
            return null;
        }
    }
    /**
     * 获取list缓存的长度
     *
     * @param key 键
     */
    public long lGetListSize (String key) {
        try {
            return redisTemplate.opsForList ().size (key);
        } catch (Exception e) {
            e.printStackTrace ();
            return 0;
        }
    }
    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0
     *              时,-1,表尾,-2倒数第二个元素,依次类推
     */
    public Object lGetIndex (String key, long index) {
        try {
            return redisTemplate.opsForList ().index (key, index);
        } catch (Exception e) {
            e.printStackTrace ();
            return null;
        }
    }
    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     */
    public boolean lSet (String key, Object value) {
        try {
            redisTemplate.opsForList ().rightPush (key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace ();
            return false;
        }
    }
    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     */
    public boolean lSet (String key, Object value, long time) {
        try {
            redisTemplate.opsForList ().rightPush (key, value);
            if (time > 0)
                expire (key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace ();
            return false;
        }
    }
    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet (String key, List<Object> value) {
        try {
            redisTemplate.opsForList ().rightPushAll (key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace ();
            return false;
        }
    }
    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet (String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList ().rightPushAll (key, value);
            if (time > 0)
                expire (key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace ();
            return false;
        }
    }
    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex (String key, long index, Object value) {
        try {
            redisTemplate.opsForList ().set (key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace ();
            return false;
        }
    }
    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove (String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList ().remove (key, count,
                    value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace ();
            return 0;
        }
    }
}


使用方法


@SpringBootTest
class SpringbootRedisApplicationTests {
    @Autowired
    private RedisUtil redisUtil;
    @Test
    public void test3(){
       redisUtil.set("name","cbbgs");
        System.out.println(redisUtil.get("name"));
    }
}

Redis持久化

RDB

RDB即redis database,它是redis默认采用支持持久化的方式。RDB通过快照实现持久化的支持,当满足一定条件时,RDB将对内存中的所有数据生成快照,并存放到硬盘中,默认存放在当前执行redis服务的根目录的dump.rdb中。

触发机制

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

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

3、退出redis也会触发

恢复rdb

1、只需要将rdb文件放在我们redis启动的目录就可以,redis启动会自动恢复文件中的数据。

2、查看存在的位置

127.0.0.1:6379> config get dir
1) "dir"
2) "/data" #把 文件放在在这里就会自动恢复

优缺点

1、适合大规模数据恢复

2、如果对数据完成性要求不高!

1、需要一定的时间间隔!

2、fork进程的时候会占用一定的内存空间

AOF

AOF持久化(原理是将Reids的操作日志以追加的方式写入文件)

全量备份总是耗时的,有时候我们提供一种更加高效的方式AOF,工作机制很简单,redis会将每一个收到的写命令都通过write函数追加到文件中。通俗的理解就是日志记录。

默认是不开启 的,需要用要开启!我们只需要appendonly改成yes即可!

优点

(1)AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据。(2)AOF日志文件没有任何磁盘寻址的开销,写入性能非常高,文件不容易破损。

(3)AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。

(4)AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据

缺点

(1)对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大

(2)AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的

(3)以前AOF发生过bug,就是通过AOF记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。

Redis发布订阅

image-20210120132900634

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

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

下表列出了 redis 发布订阅常用命令:

序号命令及描述
1[PSUBSCRIBE pattern pattern …] 订阅一个或多个符合给定模式的频道。
2[PUBSUB subcommand argument [argument …]] 查看订阅与发布系统状态。
3PUBLISH channel message 将信息发送到指定的频道。
4[PUNSUBSCRIBE pattern [pattern …]] 退订所有给定模式的频道。
5[SUBSCRIBE channel channel …] 订阅给定的一个或多个频道的信息。
6[UNSUBSCRIBE channel [channel …]] 指退订给定的频道。
#订阅端
127.0.0.1:6379> SUBSCRIBE cbbgs   #订阅一个频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "cbbgs"
3) (integer) 1
1) "message"  # 等待消息
2) "cbbgs"  #那个频道的信息
3) "hello"  #消息的具体内容
1) "message"
2) "cbbgs"
3) "redis"

#发送端
127.0.0.1:6379> publish cbbgs hello  #发布者发布信息
(integer) 1
127.0.0.1:6379> publish cbbgs redis
(integer) 1


redis的主从复制

主从复制

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

13.1、概念

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈斌-cb

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值