Redis 回顾(上)

为什么要用Nosql

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

网站80%的情况都是在读,每次都要去查询数据库十分麻烦!所以为了减轻数据库压力,用缓存来保证效率.解决读的压力

在这里插入图片描述

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

早年MyISAM: 表锁,查询数据时锁定一张表,十分影响效率!

现在Innodb: 行锁.并支持ACID

慢慢的就开始使用分库分表来解决写的压力!

MySQL 的 集群,很好满足哪个年代的所有需求!
在这里插入图片描述

如今最近的年代

2010–2020 十年之间,世界已经发生了翻天覆地的变化;(定位,也是一种数据,音乐,热榜!)

MySQL 等关系型数据库就不够用了!数据量很多,变化很快~!

MySQL 用来使用它存储一些比较大的文件,博客,图片!数据库表很大,效率就低了!如果有一种数据库来专门处理这种数据(MongoDB) MySQL压力就变得十分小(研究如何处理这些问题!)大数据的IO压力下,表几乎没法更大!

一个基本的网络项目应该如下图

在这里插入图片描述

为什么要用NoSQL

用户的大型个人信息,社交网络,地理信息,用户自己的数据,爆发增长,关系型数据库已经达到瓶颈,这时候产生了NoSQL数据库,可以很好处理以上情况

什么是NoSQL

NoSQL = Not Only SQL (不仅仅是sql) 泛指非关系型数据库

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

泛指非关系型数据库的,随着web2.0互联网的诞生!传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区! 暴露出来很多难以克服的问题,NoSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的,而且是我们当下必须要掌握的一个技术!

很多的数据类型用户的个人信息,社交网络,地理位置。这些数据类型的存储不需要一个固定的格式!不需要多余的操作就可以横向扩展的 ! Map<String,Object> 使用键值对来控制!

NoSQL特点

解耦!

1、方便扩展(数据之间没有关系,很好扩展!)
2、大数据量高性能(Redis 一秒写8万次,读取11万,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)
3、数据类型是多样型的!(不需要事先设计数据库!随取随用!如果是数据量十分大的表,很多人就无法设计了!)
4、传统 RDBMS(关系数据库管理系统) 和 NoSQL

传统的 RDBMS

  • 结构化组织
  • SQL
  • 数据和关系都存在单独的表中 row col
  • 操作操作,数据定义语言
  • 严格的一致性
  • 基础的事务

Nosql

  • 不仅仅是数据

  • 没有固定的查询语言

  • 键值对存储,列存储,文档存储,图形数据库(社交关系)

  • 最终一致性,

  • CAP定理和BASE (异地多活)

  • 高性能,高可用,高可扩

真正在公司中的实践:NoSQL + RDBMS 一起使用才是最强的!
技术没有高低之分,就看你如何去使用!(提升内功,思维的提高! 技术急不得,越是慢慢学,才能越扎实!没有什么是加一层解决不了的! )

1、商品的基本信息

名称、价格、商家信息;

关系型数据库就可以解决了! MySQL / Oracle

2、商品的描述、评论(文字比较多)

文档型数据库中,MongoDB

3、图片

分布式文件系统 FastDFS(分布式文件系统)

  • 淘宝自己的 TFS
  • Gooale的 GFS
  • Hadoop HDFS
  • 阿里云的 oss

4、商品的关键字 (搜索)

搜索引擎 solr elasticsearch

ISerach:多隆(多去了解一下这些技术大佬!)
所有牛逼的人都有一段苦逼的岁月!但是你只要像SB一样的去坚持,终将牛逼!

5、商品热门的波段信息

  • 内存数据库
  • Redis Tair、Memache…

6、商品的交易,外部的支付接口

  • 三方应用

NoSQL的四大分类

KV键值对:

新浪:Redis
美团:Redis + Tair
阿里、百度:Redis + memecache

文档型数据库(bson格式 和json一样):

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

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

列存储数据库
HBase
分布式文件系统

图关系数据库

Neo4j

Neo4j是一个高性能的,NOSQL图形数据库,它将结构化数据存储在网络上而不是表中 他不是存图形,放的是关系 Neo4j也可以被看作是一个高性能的图引擎
在这里插入图片描述
敬畏之心可以使人进步!宇宙!科幻!
活着的意义? 追求幸福(帮助他人,感恩之心),探索未知(努力的学习,不要这个社会抛弃)


Redis入门

概述

Redis 是什么?

Redis(Remote Dictionary Server ),即远程字典服务 !
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
在这里插入图片描述
redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了
master-slave(主从)同步。
免费和开源!是当下最热门的 NoSQL 技术之一!也被人们称之为结构化数据库!

Redis 能干嘛?

1、内存存储、持久化,内存中是断电即失、所以说持久化很重要(rdb、aof)
2、效率高,可以用于高速缓存
3、发布订阅系统
4、地图信息分析
5、计时器、计数器(浏览量!)
6、…

特性

1、多样的数据类型
2、持久化
3、集群
4、事务

Linux安装

1.解压Redis的安装包! 程序/opt
在这里插入图片描述
2.进入解压后的文件,可以看到我们redis的配置文件
在这里插入图片描述
3.基本的环境安装

yum install gcc-c++
make
make install

在这里插入图片描述
4.redis的默认安装路径/usr/local/bin用户安装的都生成在此目录
在这里插入图片描述
5.将redis配置文件。复制到我们当前目录下
在这里插入图片描述
6.redis默认不是后台启动的,修改配置文件!
在这里插入图片描述
7.启动redis服务!
在这里插入图片描述
8.使用redis-cli 进行连接测试!
在这里插入图片描述
以下实例演示了如何连接到主机为 127.0.0.1,端口为 6379 ,密码为 mypass 的 redis 服务上

$redis-cli -h 127.0.0.1 -p 6379 -a "mypass"

9.查看redis的进程是否开启!
在这里插入图片描述
10.如何关闭Redis服务呢?
在这里插入图片描述
11.再次查看进程是否存在
在这里插入图片描述


测试性能

redis-benchmark 是一个压力测试工具!

官方自带的性能测试工具!

redis-benchmark 命令参数!

图片来自菜鸟教程:
在这里插入图片描述
我们来简单测试下:

# 测试:100个并发连接 100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

在这里插入图片描述
如何查看这些分析呢?
在这里插入图片描述

基础的知识

redis默认有16个数据库 在redis.conf中
在这里插入图片描述
默认使用的是第0个

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

127.0.0.1:6379> select 3 #切换到数据库 3

127.0.0.1:6379[3]> dbsize # 查看db大小

127.0.0.1:6379[3]> keys *  # 查看数据库所有的key

127.0.0.1:6379[3]> flushdb # 清除当前数据库

127.0.0.1:6379[3]> flushAll # 清除全部数据库内容!!

思考:为什么redis是 6379!

答 : 创始人喜欢的意大利女歌手 9宫格名字是 6379……


Redis 是单线程的!

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

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

QPS: 每秒查询率(QPS,Queries-per-second)是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准

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

1、误区1:高性能的服务器一定是多线程的?
2、误区2:多线程(CPU上下文会切换!)一定比单线程效率高!

CPU>内存>硬盘 (QPS)

核心:redis 是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文会切换:耗时的操作!!!),对于内存系统来说,如果没有上下文切换效率就是最高的!多次读写都是在一个CPU上的,在内存情况下,这个就是最佳的方案!


五大数据类型

官方 : Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件MQ。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合 (sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication)LUA脚本(Luascripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

Redis-Key

# set key
127.0.0.1:6379> set name zjt  
OK

# get key
127.0.0.1:6379> get name 
"zjt"

# 查看所有的key 
127.0.0.1:6379> keys * 
1) "name"

# 查看当前key的一个类型!
127.0.0.1:6379> type name  
string

# 判断当前的key是否存在
127.0.0.1:6379> EXISTS name  
(integer) 1
127.0.0.1:6379> EXISTS name1
(integer) 0

# 设置key的过期时间,单位是秒 可以用于热点信息 设置过期时间
127.0.0.1:6379> EXPIRE name 10  
(integer) 1

# 查看当前key的剩余时间
127.0.0.1:6379> ttl name  
(integer) 0
127.0.0.1:6379> ttl name
(integer) -2

# 移动当前key到 1 数据库
127.0.0.1:6379> move name 1  
(integer) 1

在这里插入图片描述

String(字符串)

# 追加字符串,如果当前key不存在,就相当于setkey
127.0.0.1:6379> APPEND test "hello"  
(integer) 7
127.0.0.1:6379> get test
"aahello"

# 获取字符串的长度!
127.0.0.1:6379> STRLEN test  
(integer) 7
127.0.0.1:6379> APPEND test ",zjtzjt"
(integer) 13
127.0.0.1:6379> STRLEN test
(integer) 13
127.0.0.1:6379> get key1
"aahello,zjtzjt"
################################################
# i++   步长 i+= 可以用做浏览量持久化 访问+1

# 初始浏览量为0
127.0.0.1:6379> set look 0 
# 自增1 浏览量变为1
127.0.0.1:6379> incr look 
(integer) 1
# 自增1 浏览量变为2
127.0.0.1:6379> incr look 
(integer) 2
127.0.0.1:6379> get look
"2"

# 自减1  浏览量-1
127.0.0.1:6379> decr look 
(integer) 1
# 自减1  浏览量-2
127.0.0.1:6379> decr look 
(integer) -2
127.0.0.1:6379> get look
"-3"

# 可以设置步长,指定增量!
127.0.0.1:6379> INCRBY look 10
(integer) 10
127.0.0.1:6379> INCRBY look 10
(integer) 20

# 指定减量!
127.0.0.1:6379> DECRBY look 20
(integer) 0
################################################
# 字符串取值范围 range

# 设置 key1 的值
127.0.0.1:6379> set key1 "hello,zjt"

# 截取字符串 [0,3]
127.0.0.1:6379> getrange key1 0 3
"hell"
# 截取字符串 [0,-1] 获取全部的字符串 和 get key是一样的
127.0.0.1:6379> getrange key1 0 -1
"hello,zjt"

#替换!
127.0.0.1:6379> set key2 12345
OK
# 替换指定位置开始的字符串! 从第0位开始替换
127.0.0.1:6379> setrange key2 0 xx 
(integer) 5
127.0.0.1:6379> get key2
"xx345"
################################################
# setex (set with expire)  # 设置过期时间
# setnx (set if not exist)  # 不存在在设置 (在分布式锁中会常常使用!)

# 设置key3 的值为 hello,30秒后过期
127.0.0.1:6379> setex key3 30 "hello" 
OK
127.0.0.1:6379> ttl key3
(integer) 26
127.0.0.1:6379> get key3
"hello"

# 如果mykey 不存在,创建mykey
127.0.0.1:6379> setnx mykey "redis"  
(integer) 1
127.0.0.1:6379> keys *
1) "mykey"
127.0.0.1:6379> ttl key3
(integer) -2

# 如果mykey存在,创建失败!
127.0.0.1:6379> setnx mykey "MongoDB"  
(integer) 0
127.0.0.1:6379> get mykey
"redis"
################################################
# mset mget

# 同时设置多个值
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"

# 同时获取多个值
127.0.0.1:6379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"

# msetnx 不存在设置,存在失败! 它是一个原子性的操作,要么一起成功,要么一起失败!
127.0.0.1:6379> msetnx k1 v2 k4 v4
(integer) 0

# 对象
# 设置一个user:1 对象 值为 json 字符来保存一个对象!
127.0.0.1:6379> set user:1 {name:zjt,age:22}
# 这里的key是一个巧妙的设计:user:{id}:{filed} , 如此设计在Redis中是完全OK了! 对象类型更应该用 hash存储
# 设置user:id为1的name属性为lisi
127.0.0.1:6379> mset user:1:name lisi user:1:age 23
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "lisi"
2) "23"
################################################
# getset  先get然后在set

# 如果不存在值,则返回 nil
127.0.0.1:6379> getset db redis  
(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"

String类似的使用场景:value除了是我们的字符串还可以是我们的数字!

  • 计数器
  • 统计多单位的数量
  • 粉丝数
  • 对象缓存存储!

List(列表)

基本的数据类型,列表
在这里插入图片描述
在redis里面,我们可以把list玩成 ,栈、队列、阻塞队列!

所有的list命令都是用 l 开头的,Redis不区分大小命令

# 将一个值或者多个值,插入到列表头部 (左)
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

# 获取list中值!
127.0.0.1:6379> LRANGE list 0 -1  
1) "three"
2) "two"
3) "one"

# 通过区间获取具体的值!
127.0.0.1:6379> LRANGE list 0 1  
1) "three"
2) "two"

# 将一个值或者多个值,插入到列表位部 (右)
127.0.0.1:6379> Rpush list righr  
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "righr"
################################################
LPOP RPOP

# 获取list中值!
127.0.0.1:6379> LRANGE list 0 -1 
1) "three"
2) "two"
3) "one"
4) "righr"

# 移除左边 list的第一个元素
127.0.0.1:6379> Lpop list  
"three"

# 移除右边 list的最后一个元素
127.0.0.1:6379> Rpop list  
"righr"

127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
################################################
Lindex 可以做阻塞等待 和生产者和消费者模式

127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"

# 通过下标获得 list 中的某一个值!
127.0.0.1:6379> lindex list 0
"two"
127.0.0.1:6379> lindex list 1  
"one"
################################################
Llen

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> Llen list  
(integer) 2
################################################
移除指定的值!
Lrem

127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "three"
3) "two"
4) "one"

 # 移除list集合中指定个数的value,精确匹配
127.0.0.1:6379> lrem list 1 one 
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "three"
3) "two"

 # 移除list集合中指定2个的value,精确匹配
127.0.0.1:6379> lrem list 2 three
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
################################################
trim 修剪。; list 截断!

127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> Rpush mylist "hello"
(integer) 1
127.0.0.1:6379> Rpush mylist "hello1"
(integer) 2
127.0.0.1:6379> Rpush mylist "hello2"
(integer) 3
127.0.0.1:6379> Rpush mylist "hello3"
(integer) 4

# 通过下标截取指定的长度,这个list已经被改变了,截断了 只剩下截取的元素!
127.0.0.1:6379> ltrim mylist 1 2  
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello1"
2) "hello2"
################################################
rpoplpush 组合命令 移除列表的最后一个元素,将他移动到新的列表中

127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "hello1"
(integer) 2
127.0.0.1:6379> rpush mylist "hello2"
(integer) 3
# 移除列表的最后一个元素,将他移动到新的列表中!
127.0.0.1:6379> rpoplpush mylist myotherlist  
"hello2"

# 查看原来的列表
127.0.0.1:6379> lrange mylist 0 -1 
1) "hello"
2) "hello1"

# 查看目标列表中,确实存在改值!
127.0.0.1:6379> lrange myotherlist 0 -1  
1) "hello2"
################################################
lset 将列表中指定下标的值替换为另外一个值,更新操作

# 判断这个列表是否存在
127.0.0.1:6379> EXISTS list  
(integer) 0
# 如果不存在列表我们去更新就会报错
127.0.0.1:6379> lset list 0 item  
(error) ERR no such key

# 存一下value1
127.0.0.1:6379> lpush list value1
(integer) 1
127.0.0.1:6379> LRANGE list 0 0
1) "value1"

# 如果存在,更新当前下标的值
127.0.0.1:6379> lset list 0 item  
OK
127.0.0.1:6379> LRANGE list 0 0
1) "item"

# 如果不存在,则会报错!
127.0.0.1:6379> lset list 1 other   
(error) ERR index out of range
################################################
linsert 将某个具体的value插入到列把你中某个元素的前面或者后面!

127.0.0.1:6379> Rpush mylist "hello"
127.0.0.1:6379> Rpush mylist "world"

# 往mylist列表 中的 world 之前插入 other
127.0.0.1:6379> LINSERT mylist before "world" "other"
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "other"
3) "world"

# 往mylist列表 中的 world 之后插入 new
127.0.0.1:6379> LINSERT mylist after world new
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "other"
3) "world"
4) "new"

小结

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

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


Set(集合)

set中的值是不能重读的!

# set集合中添加元素
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 "lovekuangshen"
(integer) 1

# 查看指定set的所有值
127.0.0.1:6379> smembers myset   
1) "hello"
2) "lovekuangshen"
3) "kuangshen"

# 判断某一个值是不是在set集合中!
127.0.0.1:6379> sismember myset hello   
(integer) 1
127.0.0.1:6379> sismember myset world
(integer) 0
################################################
scard 获取set集合中的内容元素个数!

127.0.0.1:6379> scard myset  
(integer) 4
################################################
rem 移除set集合中的指定元素

127.0.0.1:6379> srem myset hello
(integer) 1
################################################
set 无序不重复集合,抽奖随机

127.0.0.1:6379> SMEMBERS set1
1) "hello"
2) "test"
3) "world"

# 随机抽选出一个元素
127.0.0.1:6379> srandmember set1
"world"
127.0.0.1:6379> srandmember set1
"test"

# 随机抽选出指定个数的元素
127.0.0.1:6379> srandmember set1 2
1) "hello"
2) "world"
127.0.0.1:6379> srandmember set1 2
1) "world"
2) "test"
################################################
随机删除key

# 随机删除一个元素
127.0.0.1:6379> spop set1
1) "hello"

# 随机删除指定个数的元素
127.0.0.1:6379> spop set1 2
1) "test"
2) "world"
################################################
smove 将一个指定的值,移动到另外一个set集合!

127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "zjt"
3) "world"
127.0.0.1:6379> sadd myset2 v1

# 将myset 移动到 myset2 成员为 zjt
127.0.0.1:6379> smove myset myset2 zjt
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "world"
127.0.0.1:6379> SMEMBERS myset2
1) "zjt"
2) "v1"
################################################
微博,b站,共同关注(并集)
数字集合类:
- 差集 sdiff
- 交集 sinter
- 并集 sunion

# 增加测试数据
127.0.0.1:6379> sadd set a
(integer) 1
127.0.0.1:6379> sadd set b
(integer) 1
127.0.0.1:6379> sadd set c
(integer) 1

127.0.0.1:6379> sadd set2 c
(integer) 1
127.0.0.1:6379> sadd set2 d
(integer) 1
127.0.0.1:6379> sadd set2 e
(integer) 1

# sdiff 差集 : 排除两个集合的差值
127.0.0.1:6379> sdiff set set2
1) "b"
2) "a"
127.0.0.1:6379> sdiff set2 set
1) "e"
2) "d"

# sinter 交集 : 获取两个集合一样的元素
127.0.0.1:6379> sinter set set2
1) "c"

# sunion 并集 : 合并两个集合中的所有元素只保留一份
127.0.0.1:6379> sunion set set2
1) "b"
2) "c"
3) "e"
4) "a"
5) "d"

微博,A用户将所有关注的人放在一个set集合中!将它的粉丝也放在一个集合中!
共同关注(交集),共同爱好,二度好友,推荐好友!


Hash(哈希)

Map集合,key-map! 时候这个值是一个map集合! 本质和String类型没有太大区别,还是一个简单的 key-vlaue! 这里的更像是 key <key,value> 像一个对象 中的具体属性的值

set user:name zjt

# set一个具体 key-vlaue
127.0.0.1:6379> hset user name bmw

# 获取一个字段值
127.0.0.1:6379> hget user name
"bmw"

# set多个key-vlaue 类似mset user:name zjt user:age 23
127.0.0.1:6379> hmset user name zjt age 23
OK

# 获取多个字段值 类似 mget user:name user:age
127.0.0.1:6379> hmget user name age
1) "zjt"
2) "23"

# 获取全部的数据 以key value 格式展示
127.0.0.1:6379> hgetall user
1) "name"
2) "zjt"
3) "age"
4) "23"

# 删除hash指定key字段!对应的value值也就消失了!
127.0.0.1:6379> hdel user name
(integer) 1
127.0.0.1:6379> hgetall user
1) "age"
2) "23"
################################################
hlen

# 获取hash表的字段数量
127.0.0.1:6379> hlen user
(integer) 3
################################################
hexists

# 判断hash中指定字段是否存在!
127.0.0.1:6379> hexists user name
(integer) 1
127.0.0.1:6379> hexists user aa
(integer) 0
################################################
只获得所有 field & 只获得所有 value

127.0.0.1:6379> hkeys user
1) "age"
2) "name"
3) "tizhong"
127.0.0.1:6379> hvals user
1) "23"
2) "zjt"
3) "150"
################################################
incr & hsetnx

127.0.0.1:6379> hset user age 20
# 指定增量
127.0.0.1:6379> hincrby user age 2
(integer) 22
127.0.0.1:6379> hincrby user age 1
(integer) 23

# 负数表示减
127.0.0.1:6379> hincrby user age -1
(integer) 22

# 如果不存在 field 则可以设置
127.0.0.1:6379> hsetnx user name zjt
(integer) 1
# 如果存在则不能设置
127.0.0.1:6379> hsetnx user name yl
(integer) 0

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


Zset(有序集合)

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

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"
################################################
排序如何实现

# 添加三个用户
127.0.0.1:6379> zadd salary 2500 xiaohong  
127.0.0.1:6379> zadd salary 5000 zhangsan
127.0.0.1:6379> zadd salary 200 ks

# zrangebyscore key min max
# 显示全部的用户 从小到大!
127.0.0.1:6379> zrangebyscore salary -inf +inf 
1) "ks"
2) "xiaohong"
3) "zhangsan"

# 从大到进行排序!
127.0.0.1:6379> zrevrange salary 0 -1
1) "zhangsan"
2) "xiaohong"
3) "ks"

# 显示全部的用户并且附带成绩
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores
1) "ks"
2) "200"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"

# 显示工资小于等于2500员工的升序排序!
127.0.0.1:6379> zrangebyscore salary -inf 2500 withscores
1) "ks"
2) "200"
3) "xiaohong"
4) "2500"
################################################
rem 移除指定元素

127.0.0.1:6379> zrange salary 0 -1
1) "ks"
2) "xiaohong"
3) "zhangsan"

# 移除有序集合中的指定元素
127.0.0.1:6379> zrem salary xiaohong
(integer) 1

127.0.0.1:6379> zrange salary 0 -1
1) "ks"
2) "zhangsan"

# 获取有序集合中的个数
127.0.0.1:6379> zcard salary
(integer) 2

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

三种特殊数据类型

Geospatial 地理位置

朋友的定位,附近的人,打车距离计算?

Redis 的 Geo 在Redis3.2 版本就推出了! 这个功能可以推算地理位置的信息,两地之间的距离,附近的人!

可以查询一些测试数据:经纬

只有 六个命令:
在这里插入图片描述
官方文档:链接

Hyperloglog(超日志)

什么是基数?

A {1,3,5,7,8,7}
B{1,3,5,7,8}
基数(不重复的元素) = 5,可以接受误差!

简介

Redis 2.8.9 版本就更新了 Hyperloglog 数据结构!

Redis Hyperloglog 基数统计的算法!

优点:统计不重复数量 ,大数据有误差,小数据几乎没有, 占用的内存是固定,2^64 不同的元素的技术,只需要废 12KB内存!如果要从内存角度来比较的话 Hyperloglog 首选!

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

传统的方式, set 保存用户的id,然后就可以统计 set 中的元素数量作为标准判断 !

这个方式如果保存大量的用户id,就会比较麻烦!我们的目的是为了计数,而不是保存用户id;

0.81% 错误率! 统计UV任务,可以忽略不计的!

测试使用

# 创建第一组元素 mykey
127.0.0.1:6379> PFadd mykey a b c d e f g h i j  
# 统计 mykey 元素的基数数量
127.0.0.1:6379> PFCOUNT mykey  
(integer) 10
# 创建第二组元素 mykey2
127.0.0.1:6379> PFadd mykey2 i j z x c v b n m  
# 统计 mykey2 元素的基数数量
127.0.0.1:6379> PFCOUNT mykey2
(integer) 9
# 合并两组 mykey mykey2 => mykey3 并集
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2  
OK
# 看并集的数量! 去除了重复元素
127.0.0.1:6379> PFCOUNT mykey3 
(integer) 15

如果允许容错,那么一定可以使用 Hyperloglog !

如果不允许容错,就使用 set 或者自己的数据类型即可!


Bitmap

这些在生活中或者开发中,都有十分多的应用场景,学习了,就是多一个思路~

位存储

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

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

365 天 = 365 bit 1字节 = 8bit 46 个字节左右!

测试

在这里插入图片描述

使用bitmap 来记录 周一到周日的打卡!

周一:1 周二 : 0 周三:0 周四:1 …
在这里插入图片描述
查看某一天是否有打卡!

# getbit	0没有 1有
127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 6
(integer) 0

统计操作,统计 打卡的天数!

# bitcount 统计这周的打卡记录,就可以看到是否有全勤!
127.0.0.1:6379> bitcount sign
(integer) 3

事务

Redis 事务本质 :

  1. 单独的隔离操作:一组命令的集合! 一个事务中的所有命令都会被序列化 , 在事务执行过程中, 会按顺序执行! 在执行的过程中不会被其他客户端发送来的命令打断
  2. Redis事务没有隔离级别的概念!:队列中的命令在事务没有被提交之前不会被实际执行 只有发起执行命令的时候才会执行 ! exec(执行)
  3. 事务不保证原子性Redis所有单个命令的执行都是原子性的, redis中的一个事务中如果存在命令执行失败,那么其他命令依然会被执行,没有回滚机制

三特性 : 一次性、顺序性、排他性 !

redis的事务:

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

正常执行事务!

# 开启事务
127.0.0.1:6379> multi
OK

# 命令入队 queued(排队)
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k1
QUEUED

# 执行事务 
127.0.0.1:6379> exec
1) OK
2) OK
3) "v1"
127.0.0.1:6379> 

放弃事务 !

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> set k4 v4
QUEUED

# 取消事务 丢弃
127.0.0.1:6379> discard 
OK

# 事务队列中命令都不会被执行!
127.0.0.1:6379> get k4
(nil)

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

127.0.0.1:6379> multi
OK

127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> getset k3   # 错误的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k5 v5
QUEUED

127.0.0.1:6379> exec  # 执行事务报错!
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5  # 所有的命令都不会被执行!
(nil)

运行时异常比如(1/0), 如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常!不存在回滚

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1 # 会执行的时候失败
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
# 虽然第一条命令报错了,但是其他的依旧正常执行成功了!
1) (error) ERR value is not an integer or out of range
2) OK
3) "v2"
127.0.0.1:6379> get k2
"v2"

监控! Watch (面试常问!)适用于秒杀场景

悲观锁:

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

乐观锁:

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

Redis测监视测试

正常执行成功!

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

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

监控命令ok的时候 获取money的值 在执行事务之前 再一次比对与之前获取的值 如果一致 执行操作 不一致就 ( nil )

127.0.0.1:6379> watch money  # 监视 money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 10 # 减10
QUEUED
127.0.0.1:6379> INCRBY out 10 # 加 10
QUEUED
# 执行之前,另外一个线程,修改了我们的值,这个时候,就会导致事务执行失败!
127.0.0.1:6379> exec  
(nil)

如果修改失败,获取最新的值就好

在这里插入图片描述

Jedis

Jedis 是 Redis 官方推荐的 java连接开发工具! 使用Java 操作Redis 中间件!如果你要使用 java操作redis,那么一定要对Jedis 十分的熟悉!

测试

1、导入对应的依赖

<!-- jedis包 -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.2.0</version>
</dependency>
<!-- fastjson -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.67</version>
</dependency>

2、编码测试

  • 连接redis数据库

    自己踩的坑!! 如果连接不上

    • 1. 先查看防火墙状态 firewall-cmd --state 如果为(not running)再继续排查 注意centos7版本 和之前版本的防火墙不一样

    • 2. 修改redis.conf

      (1) 注释掉 #127.0.0.1 使所有连接都能访问

      (2) 关闭默认保护模式 更改至 protected-mode no

      (3) jedis 连接 host:虚拟机ip

  • 操作命令

  • 断开连接!

import redis.clients.jedis.Jedis;

public class TestPing {
    public static void main(String[] args) {
        // 1、 new Jedis 对象即可
        Jedis jedis = new Jedis("192.168.31.132", 6379);
		// jedis 所有的命令就是redis 的命令
        System.out.println(jedis.ping());
    }
}

输出:
在这里插入图片描述
常用的API : String List(链表) Set Hash Zset(有序集合)

所有的api命令,都跟redis的一样没有变化

事务 java code demo

import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class TestPing {
  public static void main(String[] args) {

    // 连接 redis 虚拟机IP地址 默认redis端口 6379
    Jedis jedis = new Jedis("192.168.31.132", 6379);

    // 测试事务异常,清空缓存
    jedis.flushDB();
    // 创建一个json对象 import com.alibaba.fastjson.JSONObject;
    JSONObject jsonObject = new JSONObject();
    jsonObject.put("hello", "world");
    jsonObject.put("name", "zjt");
    // 开启事务
    Transaction multi = jedis.multi();
    // 装换为字符串
    String result = jsonObject.toJSONString();
    // jedis.watch(result);
    try {
        // 命令入队
        multi.set("user1", result);
        multi.set("user2", result);
        int i = 1 / 0; // 代码抛出异常,事务执行失败!
        multi.exec(); // 执行事务
    } catch (Exception e) {
        multi.discard(); // 出现异常 放弃事务
        e.printStackTrace();
    } finally {
        System.out.println(jedis.get("user1"));
        System.out.println(jedis.get("user2"));
        jedis.close(); // 关闭连接
    }}}

result:
    {"name":"zjt","hello":"world"}
    {"name":"zjt","hello":"world"}
    
Exception result:
    java.lang.ArithmeticException: / by zero
        at com.zjt.TestPing.main(TestPing.java:27)
    null
    null

SpringBoot整合

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

SpringData 也是和 SpringBoot 齐名的项目!

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

jedis : 采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用 jedis pool 连接池! 更像 BIO 模式

  • BIO: 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善

lettuce : 采用netty(以后再学!),实例可以再多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据了,更像 NIO 模式

  • NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理

源码分析:

@Bean// 我们可以自己定义一个 redisTemplate来替换这个默认的
@ConditionalOnMissingBean(name = "redisTemplate") 
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory
redisConnectionFactory)
  throws UnknownHostException {
  // 默认的 RedisTemplate 没有过多的设置,redis 对象都是需要序列化!
  // 两个泛型都是 Object, Object 的类型,我们后使用需要强制转换 <String, Object>
  RedisTemplate<Object, Object> template = new RedisTemplate<>();
  template.setConnectionFactory(redisConnectionFactory);
  return template;
}
@Bean
@ConditionalOnMissingBean  
// 由于 String 是redis中最常使用的类型,所以说单独提出来了一个bean!
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory
redisConnectionFactory)
  throws UnknownHostException {
  StringRedisTemplate template = new StringRedisTemplate();
  template.setConnectionFactory(redisConnectionFactory);
  return template;

整合测试一下

1、导入依赖

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

2、配置连接

spring.redis.host=192.168.31.132 # 默认为localhost
spring.redis.port=6379 # 默认为6379 6379端口写不写都行
spring.redis.database=0 # 默认为 0号数据库 0写不写都行

3、测试!

@Autowired //引入 RedisTemplate
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
   在企业开发中,我们80%的情况下,都不会使用这个原生的方式去编写代码 可以写一个工具类 RedisUtils  
   redisTemplate 操作不同的数据类型,api和我们的指令是一样的
   opsForValue 操作字符串 类似String
   opsForList  操作List 类似List
   opsForSet
   opsForHash
   opsForZSet
   opsForGeo  操作距离定位
   opsForHyperLogLog 操作统计UV
   除了基本的操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务,和基本的CRUD
   获取redis的连接对象
  //   RedisConnection connection =redisTemplate.getConnectionFactory().getConnection();
   //    connection.flushDb();
   //    connection.flushAll();
     redisTemplate.opsForValue().set("mykey","哈哈哈");
     System.out.println(redisTemplate.opsForValue().get("mykey"));
}

在这里插入图片描述
在这里插入图片描述
关于对象的保存:

  • 真实开发一般都使用 json 传输对象, 所有的对象都需要 序列化
    在这里插入图片描述
序列化类
public class User implements Serializable {}
序列化一个对象
new ObjectMapper().writeValueAsString(user); 

我们来编写一个自己的 RedisTemplete

package com.kuang.config;
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;
@Configuration
public class RedisConfig {
  // 这是固定模板,拿去就可以直接使用!
  // 自己定义了一个 RedisTemplate
  @Bean
  @SuppressWarnings("all")
  public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory
factory) {
    // 我们为了自己开发方便,一般直接使用 <String, Object>
    RedisTemplate<String, Object> template = new RedisTemplate<String,
Object>();
    template.setConnectionFactory(factory);
   
    // Json序列化配置
    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);
    // String 的序列化
    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;
 }
}

所有的redis操作,其实对于java开发人员来说,十分的简单,更重要是要去理解redis的思想和每一种数据结构的用处和作用场景!

猜你喜欢

转载自blog.csdn.net/weixin_44905070/article/details/105402673