Redis学习笔记

Redis

学习方式:

  • 基本的理论学习, 然后将知识融会贯通

    学习路径

  • nosql 讲解

  • 阿里巴巴架构眼演进

  • nosql数据模型

  • nosql 四大分类

  • CAP

  • BASE

  • Redis入门

  • Redis安装(windows & linux 服务器)

  • 五大基本数据类型

  • 三种特殊数据类型

  • redis配置详解

  • redis持久化

  • redis事务操作

  • redis 实现订阅发布

  • redis主从复制

  • redis 哨兵模式

  • 缓存穿透及解决方案

  • 缓存击穿及解决方案

  • 缓存雪崩及解决方案

  • 基础API以及jedis 详解

  • springboot 集成redis操作

  • redis的实践分析

Nosql 概述


为什么要用Nosql


大数据时代; 2020

一般的数据库无法进行分析处理.

  1. 单机MYSQL的年代!

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

思考: 网站的瓶颈是什么?

  1. 数量如果太大, 一个机器放不下
  2. 数据的索引(B+ Tree), 一个机器内存放不下
  3. 访问量(读写混合) ,一台服务器承受不了

只要出现以上三种情况之一, 必须晋级

2.Memcached(缓存) + MYSQL + 垂直拆分(读写分离)

网站 80% 的情况都是在读, 每次都要去查询数据库的话就十分的麻烦! 希望减轻数据的压力, 可以使用缓存来保证效率!

发展过程: 优化数据结构和索引 ---> 文件缓存 (IO)---> Memcached(当时最热门的技术)

  1. 分库分表 + 水平拆分 + MySQL 集群

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

本质: 数据库(读, 写)

早些年 MyISAM: 表锁(100万条数据), 十分影响效率! 高并发下就会出现严重的锁问题

转战 Innodb: 行锁,

慢慢的就开始使用分库分表来解决写的压力!! MySQL推出了表分区, 并没有多少公司使用

MySQL的集群, 很好的满足了那个年代的所有需求

4.如今最近的年代

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

图形 JSON

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

目前一个基本的互联网项目!

为什么要用NoSQL!

用户的个人信息, 社交网络, 地理位置, 用户自己产生的数据, 用户日志等等爆发式增长!

这时候我们就需要使用NoSQL数据库, NoSQL可以很好的处理很好的情况!

什么是NoSQL

NoSQL

NoSQL = Not Only SQL 泛指非关系型数据库,

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

NoSQL特点

解耦

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

2.大数据量性能( Redis 一秒可以写8万次, 读取11万, NoSQL的缓存记录级, 是一种细粒度的缓存, 性能会比较高)

3.数据类型多样性! (不需要事先设计数据库! 随取随用, 如果是数据量十分大的表, 很多人就无法设计)

4.传统的 RDBMS和 NoSQL

传统的RDBMS

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

NoSQL

  • 不仅仅是数据
  • 没有固定的查询语言
  • 键值对存储, 列存储, 文档存储, 图形数据库(社交关系)
  • 最终一致性
  • CAP定理 和BASE (异地多活!) 初级架构师
  • 高性能, 高可用, 高可扩展
  • ......

了解 3V + 3高

大数据时代的3V: 主要是描述问题的

1.海量Volume

2.多样Variety

3.实时Velocity

大数据时代的3高: 主要是对程序的要求

  1. 高并发
  2. 高可拓
  3. 高性能(保证用户体验和性能!)

真正在公司中的实践, NoSQL + RDBMS 一起使用才是最强的

NoSQL的四大分类

KV键值对:

  • 新浪: Redis

  • 美团: Redis + Tair

  • 阿里 、百度: Redis + Memache

    文档型数据库(bson格式 和json 意义昂):

  • MongoDB(一般必须要掌握)

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

列存储数据库

  • HBase
  • 分布式文件系统

图型数据库

  • 他不是存图形, 放的是关系, 比如: 朋友圈社交网络

  • Neo4J , InfoGrid

四者对比:

Redis


概述


Redis 是什么?

Redis(Remote Dictionary Server ),即远程字典服务!

是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

免费和开源! 是当下最热门的NoSQL 技术之一! 也被人们称之为结构数据库!

Redis能够干嘛?

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

特性

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

学习中需要用到的东西

五大数据类型


Redis-Key

String


# 对象
set user:1{name:zhangsan,age:3} # 设置一个user:1 对象 值为json 字符来保存一个对象

# 这里的key是一个巧妙的设计: user:{id}:{field}


##########################
getset #先 get然后再set

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"
127.0.0.1:6379>

数据结构是相同的!

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

  • 计数器
  • 统计多单位数量 uid: 386760778:follow incr
  • 粉丝数
  • 对象缓存存储 !

List(列表)


基本的数据类型, 列表

在redis里面, 我们可以把list完成, 栈, 队列, 阻塞队列!

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

#######################################################################
127.0.0.1:6379> lpush list one #lpush 将一个值或者多个值, 插入到列表的头部(左)
(integer) 3
127.0.0.1:6379> lpush list two
(integer) 4
127.0.0.1:6379> lpush list three
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "two"
5) "one"
127.0.0.1:6379> lrange list 0 1
1) "three"
2) "two"
127.0.0.1:6379> rpush list five #rpush 将一个值或者多个值, 插入到列表的头部(右)
(integer) 6
127.0.0.1:6379> lrange 0 -1
(error) ERR wrong number of arguments for 'lrange' command
127.0.0.1:6379> lrange list 00 -1
1) "three"
2) "two"
3) "one"
4) "two"
5) "one"
6) "five"
#######################################################################
LPOP
RPOP
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "two"
5) "one"
6) "five"
127.0.0.1:6379> lpop list #移除list的第一个元素
"three"
127.0.0.1:6379> rpop list # 移除list的最后一个元素
"five"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
3) "two"
4) "one"

#######################################################################
Lindex

127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
3) "two"
4) "one"
127.0.0.1:6379> lindex list 1 # 通过下标获得list中的某一个值
"one"
127.0.0.1:6379> lindex list 0
"two"

#######################################################################
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> lpush list three
(integer) 3
127.0.0.1:6379> llen list # 返回列表的长度
(integer) 3

#######################################################################
移除指定的值!

lrem 

127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> lrem list 1 one # 移除list集合中指定个数的value,精确匹配
(integer) 1
127.0.0.1:6379>  lrange list 0 -1
1) "three"
2) "three"
3) "two"
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> 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
127.0.0.1:6379> ltrim mylist 1 2  # 通过下标截取指定的长度, 这个list已经被改变, 截断了只剩下截取的元素!
OK
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 mylist2  # 移除列表的最后一个元素, 将他移动到新的列表中!
"hello2"
127.0.0.1:6379> lrange mylist 0 -1  # 查看原来的列表
1) "hello"
2) "hello1"
127.0.0.1:6379>
127.0.0.1:6379> lrange mylist2 0 -1 # 查看目标列表中,确实存在值
1) "hello2"

#######################################################################
 lset 将列表中指定下标的值替换为另外一个值, 更新操作
127.0.0.1:6379> exists list	# 判断这个列表是否存在
127.0.0.1:6379> lpush list value 
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "value"
127.0.0.1:6379> lset list 0 key # 如果存在, 更新当前下标的值
OK
127.0.0.1:6379> lrange list 0 -1
1) "key"
127.0.0.1:6379> lset list 1 value # 如果不存在列表(或者列表下标),更新就会保存
(error) ERR index out of range

#######################################################################
linsert # 将某个具体的value插入到列表中某个元素的前面或者后面

127.0.0.1:6379> rpush mylist hello
(integer) 1
127.0.0.1:6379> rpush mylist world
(integer) 2
127.0.0.1:6379> linsert mylist before world other
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "other"
3) "world"
127.0.0.1:6379> linsert mylist after world new
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "other"
3) "world"
4) "new"

小结

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

Set(集合)


set中的值是无序不能重复的!

#######################################################################
127.0.0.1:6379> sadd myset hello	# set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd myset wangjc
(integer) 1
127.0.0.1:6379> sadd myset wangjc666
(integer) 1
127.0.0.1:6379> smembers myset  # 查看指定set的所有值
1) "hello"
2) "wangjc"
3) "wangjc666"
127.0.0.1:6379> sismember myset hello # 判断一个值是不是在set集合中
(integer) 1
127.0.0.1:6379> sismember myset world
(integer) 0

#######################################################################
127.0.0.1:6379> scard myset # 获取set集合中的内容元素个数!
(integer) 4

#######################################################################
rem
127.0.0.1:6379> srem myset hello # 移除set集合中的指定元素
(integer) 1
127.0.0.1:6379> scard myset
(integer) 3
127.0.0.1:6379> smembers myset
1) "wangjc"
2) "wangjc888"
3) "wangjc666"

#######################################################################
127.0.0.1:6379> smembers myset
1) "wangjc"
2) "wangjc888"
3) "wangjc666"
127.0.0.1:6379> SRANDMEMBER myset # 随机抽选出一个元素
"wangjc888"
127.0.0.1:6379> SRANDMEMBER myset
"wangjc"
127.0.0.1:6379> SRANDMEMBER myset 2 # 随机抽选出指定个数的元素
1) "wangjc666"
2) "wangjc888"

#######################################################################
删除指定的key , 随机删除key

127.0.0.1:6379> SMEMBERS myset
1) "wangjc"
2) "wangjc888"
3) "wangjc666"
127.0.0.1:6379> SPOP myset # 随机删除一些set集合中的元素!
"wangjc"
127.0.0.1:6379> SPOP myset
"wangjc666"
127.0.0.1:6379> SMEMBERS myset
1) "wangjc888"

#######################################################################
将一个指定的值, 移动到另外一个set集合中
127.0.0.1:6379> SADD myset hello
(integer) 1
127.0.0.1:6379> SADD myset world
(integer) 1
127.0.0.1:6379> SADD myset wangjc
(integer) 1
127.0.0.1:6379> SADD myset2 set2
(integer) 1
127.0.0.1:6379> SMOVE  myset myset2 wangjc  # 将一个指定的值, 移动到另外一个set集合中
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "world"
127.0.0.1:6379> SMEMBERS myset2
1) "set2"
2) "wangjc"

#######################################################################
数字集合类:
- 差集 sdiff
- 交集 sinter
- 并集 sunion

127.0.0.1:6379> SADD key1 a
(integer) 1
127.0.0.1:6379> SADD key1 b
(integer) 1
127.0.0.1:6379> SADD key1 c
(integer) 1
127.0.0.1:6379> SADD key2 c
(integer) 1
127.0.0.1:6379> SADD key2 d
(integer) 1
127.0.0.1:6379> SADD key2 e
(integer) 1
127.0.0.1:6379> SDIFF key1 key2 # 差集
1) "a"
2) "b"
127.0.0.1:6379> SINTER key1 key2 # 交集 共同好友可以这样实现
1) "c"
127.0.0.1:6379> SDIFF key2 key1
1) "d"
2) "e"
127.0.0.1:6379> SUNION key1 key2 # 并集
1) "a"
2) "c"
3) "b"
4) "d"
5) "e"

Hash(哈希)


Map 集合, key-Map 这个值是一个map集合! 本质和String类型没有太大区别, 还是一个简单的key-value!

set myhash field wangjc

127.0.0.1:6379> hset myhash field wangjc   # set 一个具体的 key-value
(integer) 1
127.0.0.1:6379> hget myhash field		 # 获取一个字段值
"wangjc"
127.0.0.1:6379> HMSET myhash field hello field2 world # set 多个 key-value
OK
127.0.0.1:6379> HMGET myhash field field2  # 获取多个字段值
1) "hello"
2) "world"
127.0.0.1:6379> HGETALL myhash  # 获取全部的数据
1) "field"
2) "hello"
3) "field2"
4) "world"
127.0.0.1:6379> HDEL myhash field  # 删除hash 指定的key字段! 对应的value值也就消失了!
(integer) 1
127.0.0.1:6379> HGETALL myhash
1) "field2"
2) "world"

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

127.0.0.1:6379> HMSET myhash field1 hello
OK
127.0.0.1:6379> HGETALL myhash
1) "field2"
2) "world"
3) "field1"
4) "hello"
127.0.0.1:6379> HLEN myhash  # 获取hash的 字段数量
(integer) 2

#######################################################################
127.0.0.1:6379> HEXISTS myhash field1 # 判断hash中指定字段是否存在
(integer) 1
127.0.0.1:6379> HEXISTS myhash field3 # 判断hash中指定字段是否存在
(integer) 0

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

127.0.0.1:6379> HKEYS myhash  # 只获得所有field
1) "field2"
2) "field1"
127.0.0.1:6379> HVALS myhash  # 只获得所有value
1) "world"
2) "hello"

#######################################################################
incr decr
127.0.0.1:6379> hset myhash field3 5 
(integer) 1
127.0.0.1:6379>  HINCRBY myhash field3 1   # 指定增量
(integer) 6
127.0.0.1:6379> HSETNX myhash field4 hello  # 如果不存在则可以设置	
(integer) 1
127.0.0.1:6379> HSETNX myhash field4 hello   # 如果存在则可以设置
(integer) 0

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

Zset(有序集合)


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

127.0.0.1:6379> ZADD myset 1  one  # 添加一个值
(integer) 1
127.0.0.1:6379> ZADD myset 2  two
(integer) 1
127.0.0.1:6379> ZADD myset 3 three 4 four  #添加多个值
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1
1) "one"
2) "two"
3) "three"
4) "four"

#######################################################################
排序如何实现
127.0.0.1:6379> zadd salary 2500 xiaohong  # 添加三个用户
(integer) 1
127.0.0.1:6379>  zadd salary 5000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 500 wangjc
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf	# 显示全部用户 从小到大
1) "wangjc"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZREVRANGE salary 0 -1	# 从大到小进行排序!
1) "zhangsan"
2) "wangjc"
127.0.0.1:6379> ZRANGEBYSCORE salary  -inf +inf  withscores  # 显示全部用户并且附带成绩 ,
1) "wangjc"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:6379>  ZRANGEBYSCORE salary -inf 2500 withscores # 显示工资小于2500用户的 升序
1) "wangjc"
2) "500"
3) "xiaohong"
4) "2500"


#######################################################################
移除 zset中的元素

127.0.0.1:6379> zrange salary 0 -1
1) "wangjc"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrem salary xiaohong	# 移除有序集合中的元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "wangjc"
2) "zhangsan"
127.0.0.1:6379> ZCARD salary  # 获取有序集合中的个数
(integer) 2

#######################################################################
127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 wangjc
(integer) 2
127.0.0.1:6379> zcount myset 1 3	# 获取指定区间的成员数量!	
(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2

其余的一些API, 查官方文档!

案例思路: Zset( sorted set) 排序 , 存储班级成绩表, 工资表排序,

普通消息1 , 重要消息2 , 带权重进行判断,

排行榜应用实现, 取 Top N 测试

三种特殊数据类型


geospatial 地址位置


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

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

可以查询一些测试数据

只有六个命令

getadd

#   geoadd  添加地理位置
# 规则:两级无法直接添加, 我们一般慧下载城市数据, 直接通过java程序一次性导入
# 参数 key 值 (经度、维度、)

127.0.0.1:6379> GEOADD china:city 116.40  39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> GEOADD china:city 106.50 29.53 chongqing 114.05 22.52 shenzhen
(integer) 2
127.0.0.1:6379> GEOADD china:city 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 2

geopos

获得当前定位: 一定是一个坐标值!

127.0.0.1:6379> GEOPOS china:city beijing  # 获取指定城市的经纬度
1) 1) "116.39999896287918"
   2) "39.900000091670925"
127.0.0.1:6379> GEOPOS china:city shanghai shenzhen hangzhou
1) 1) "121.47000163793564"
   2) "31.229999039757836"
2) 1) "114.04999762773514"
   2) "22.520000087950386"
3) 1) "120.16000002622604"
   2) "30.240000322949022"

geodist

两人之间的距离!

单位:

  • m 表示单位为米。
  • km 表示单位为千米。
  • mi 表示单位为英里。
  • ft 表示单位为英尺
127.0.0.1:6379> GEODIST china:city shanghai  beijing   # 查看上海 到北京的直线距离
"1067378.7564"
127.0.0.1:6379> GEODIST china:city shanghai beijing km
"1067.3788"
127.0.0.1:6379> GEODIST china:city beijing chongqing  # 查看上海 到北京的直线距离
"1464070.8051"
127.0.0.1:6379> GEODIST china:city beijing chongqing km
"1464.0708"

georadius - 以给定的经纬度为中心, 找出某一半径内的元素

我附近的人, (获得所有附近的人的地址, 定位! )通过半径来查询!

获取指定数量的人

所有数据应该都输入:china:city , 才会让结果请求!

127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km # 获取110 30经纬度为中心, 寻找方圆1000km内的城市
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km
1) "chongqing"
2) "xian"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist  # 显示到中心距离的位置, 
1) 1) "chongqing"
   2) "341.9374"
2) 1) "xian"
   2) "483.8340"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord   # 显示他人的定位信息, , 
1) 1) "chongqing"
   2) 1) "106.49999767541885"
      2) "29.529999579006592"
2) 1) "xian"
   2) 1) "108.96000176668167"
      2) "34.2599996441893"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 1 # 筛选出指定的结果!
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885"
      2) "29.529999579006592"
127.0.0.1:6379>  GEORADIUS china:city 110 30 500 km withdist withcoord count 2
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885"
      2) "29.529999579006592"
2) 1) "xian"
   2) "483.8340"
   3) 1) "108.96000176668167"
      2) "34.2599996441893"

GEORADIUSBYMEMBER 命令 - 找出位于指定范围内的元素,中心点是由给定的位置元素决定

# 找到位于指定元素周围的其他元素!
127.0.0.1:6379>  GEORADIUSBYMEMBER china:city beijing 1000 km
1) "beijing"
2) "xian"
127.0.0.1:6379>  GEORADIUSBYMEMBER china:city shanghai  400 km
1) "hangzhou"
2) "shanghai"

GEOHASH 命令 - 返回一个或多个位置元素的 Geohash 表示

该命令将返回11个字符的GeoHash字符串!

# 将二维的经纬度转为 一维的字符串, 如果两个字符串越近, 那么则距离越近!
127.0.0.1:6379> GEOHASH china:city beijing shanghai
1) "wx4fbxxfke0"
2) "wtw3sj5zbj0"

GEO底层的实现原理其实就是Zset, 可以用Zset命令来操作GEO

127.0.0.1:6379> ZRANGE china:city 0 -1 # 查看地图中全部元素
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379>  ZREM china:city beijing   # 移除指定元素
(integer) 1
127.0.0.1:6379>  ZRANGE china:city 0 -1
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"

Hyperloglog


什么是基数?

A {1 , 3, 5, 7,8,7}

B{ 1, 3, 5, 7, 8 }

基数( 不重复的元素) = 5, 可以接受误差

简介

Redis2.8.9 版本就更新了Hyperloglog 数据结构!

Redis Hyperloglog基数统计的算法!

优点: 占用内存是固定的, 2∧64不同的元素的基数, 只需要消耗12kb内存! 如果从内存角度来比较的话, Hyperloglog是首选!

网页的UV(一个人访问一个网站多次, 算做一个人)

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

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

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

测试使用

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

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

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

BitMap


位存储

疫情感染人数: 0 1 0 1; 统计用户信息, 活跃, 不活跃 ; 登录、未登录!; 打卡, 365打卡!

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

测试

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

周一: 1, 周二: 0, 周三: 0 , 周四: 1 ....

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 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> setbit sgign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 0
(integer) 0

查看某一天是否有打卡!

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

统计操作: 统计打卡天数

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

事务


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

一次性、顺序性、排他性!执行一些列的命令!

----  队列  set set set 执行 -----

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> set k3 v3
QUEUED
127.0.0.1:6379> exec	# 执行事务
1) OK
2) OK
3) OK

放弃事务

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>  set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> getset k3 # 错误的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
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 k1  # 所有命令都不会被执行
(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> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range # 虽然第一条命令报错了, 但是其他命令依旧正常执行成功了!
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"

监控! watch ( 面试常问! ! ! )

悲观锁:

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

乐观锁:

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

  • 获取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 out 20
QUEUED
127.0.0.1:6379> exec	# 事务正常结束,  数据期间没有发生变动, 这个时候就正常执行成功!
1) (integer) 80
2) (integer) 20

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

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

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

Jedis


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

测试

1.导入对应 的依赖

 <!--导入 jedis 的依赖-->
    <dependencies>
        <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
    </dependencies>
  1. 编码测试:
    • 连接数据库
    • 操作命令
    • 断开连接
package com.wangjc;

import redis.clients.jedis.Jedis;

public class TestPing {

    public static void main(String[] args) {
        /*1. new Jedis 对象即可*/
        Jedis jedis = new Jedis("127.0.0.1",6379);
        /* jedis 所有的命令就是之前学习的所有指令*/
        System.out.println(jedis.ping());
    }
}

输出

常用API


String

List

Set

Hash

Zset

所有的API命令,

事务

SpringBoot整合


整合测试下

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

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

lettuce : 采用 netty, 实例可以在多个线程中进行共享, 不存在线程不安全的情况! 可以减少线程连接池数量, 更像NIO模式

源码分析:


    @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )  // 可以自己定义一个 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.导入依赖

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

2.配置连接

# 配置redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=0

3.测试

package com.wangjc;

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 Redis02SpringbootApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        // redisTemplate 操作不同的数据类型, api和我们的智力高
        // opsForValue() 操作字符串 类似string
        // opsForList() 操作字符串 类似list
        // opsForSet()
        // opsForHash()
        // opsForZSet()
        // opsForGeo()
        // opsForHyperLogLog()
        // 除了基本的操作 , 我们常用的方法都可以通过 redisTemplate 操作, 比如 事务 , 和基本的crud
        // 获取redis连接对象
//        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//        connection.flushAll();;
//        connection.flushDb();
        redisTemplate.opsForValue().set("mykey","kuangshen");
        System.out.println(redisTemplate.opsForValue().get("mykey"));

    }

}

posted @ 2021-05-12 15:00  startfork  阅读(366)  评论(0编辑  收藏  举报