redis学习笔记(三)

NoSQL概述

背景:

  1. 单机MySQL时期,一般的网站访问量不大。网站瓶颈:
    • 数据量太大,一个机器放不下
    • 数据索引(B+Tree),一个机器内存放不下
    • 访问量(读写混合),一个机器承受不了
  2. Memcached(缓存)+ MySQL + 垂直拆分(读写分离)
    • 发展过程:优化数据结构呵索引 -> 文件缓存IO -> Memcached
  3. 分库分表 + 水平拆分 + MySQL集群

在这里插入图片描述

  1. NoSQL数据库

什么是NoSQL

Not Only SQL 泛指非关系型数据库。传统关系型数据库已经无法应对web2.0时代。尤其是超大规模的高并发社区。

特点:解耦

  1. 方便扩展(数据之间没有关系)
  2. 大数据量高性能(1秒写8万次,读11万次。缓存是记录级的,是一种细粒度的缓存,性能更高)
  3. 数据类型多样性(无需事件设计数据库)
  4. 传统的RDBMS 和 NoSQL

    传统的RDBMS关系型数据库

    • 结构化组织
    • SQL
    • 数据和关系都存放在单独的表中
    • 数据操作,数据定义语音
    • 严格的一致性
    • 基本的实务操作

    NoSQL

    • 不仅仅是数据
    • 没有固定的查询语言
    • 键值对存储,列存储,文档存储,图形数据库
    • 最终一致性
    • CAP定理 和 BASE理论(异地多活)
    • 高性能,高可用,高可扩

了解:大数据时代的3V(问题) + 3高(要求):

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

  1. 海量Volume
  2. 多样Variety
  3. 实时Velocity

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

  1. 高并发
  2. 高可扩(随时水平拆分)
  3. 高性能(保证用户体验)

NoSQL四大分类

K:V键值对: Redis + Tair / Memecache

文档型数据库(bson格式(类似JSON)): MongoDB(MongoDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量文档。是一个介于关系型数据库和非关系型数据库中间的产品)。ConthDB。

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

图形关系数据库: 不是存储图形,而是存储关系。eg:朋友圈社交网络。Neo4j。 InfoGrid。

在这里插入图片描述


Redis入门

概述

Redis是什么?

Redis(Remote Dictionary Server ),即远程字典服务。C语言,支持网络,基于内存,可持久化的日志型的key-value数据库。能够周期性的把数据写入磁盘,并实现了主从同步。

Redis应用场景:

  1. 内存存储、持久化
  2. 效率高,可用于告诉缓存
  3. 发布订阅系统
  4. 地图信息分析
  5. 计时器、计数器(浏览量)

特性:

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

Linux安装Redis

# 安装gcc依赖
 `[root@localhost local]# yum install -y gcc `
# 下载并解压安装包
`[root@localhost local]# wget http://download.redis.io/releases/redis-5.0.3.tar.gz`
`[root@localhost local]# tar -zxvf redis-5.0.3.tar.gz`
# cd切换到redis解压目录下,执行编译
`[root@localhost local]# cd redis-5.0.3`
`[root@localhost redis-5.0.3]# make`
## 安装并指定安装目录
[root@localhost redis-5.0.3]# make install PREFIX=/usr/local/redis
# 启动服务
## 前台启动
`[root@localhost redis-5.0.3]# cd /usr/local/redis/bin/`
`[root@localhost bin]# ./redis-server`
## 后台启动
从 redis 的源码目录中复制 redis.conf 到 redis 的安装目录
[root@localhost bin]# cp /usr/local/redis-5.0.3/redis.conf /usr/local/redis/bin/
后台启动
[root@localhost bin]# ./redis-server redis.conf

引用: Centos7安装Redis

测试性能

redis-benchmark 是一个官方的压力测试工具。

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

在这里插入图片描述

基础知识


# 切换数据库
127.0.0.1:6379> select 3 
OK
# 查看当前数据库大小
127.0.0.1:6379[3]> dbsize
(integer) 0
# 设值
127.0.0.1:6379[3]> set name huathy
OK
# 取值
127.0.0.1:6379[3]> get name
"huathy"
127.0.0.1:6379[3]> dbsize
(integer) 1
# 查看数据库所有的key
127.0.0.1:6379[3]> keys *
1) "name"
# 清空当前数据库
127.0.0.1:6379[3]> flushdb
OK
# 清空所有数据库
127.0.0.1:6379[3]> flushall
OK

Redis是单线程的!

Redis基于内存操作,是很快的。CPU不是redis的性能瓶颈。Redis的性能瓶颈受机器内存以及带宽的影响。

Redis为什么单线程还是那么快?

Redis的所有数据放在内存中,由于多线程上下文切换耗时,故单线程操作效率最高。


Redis五大基本数据类型

Redis-Key

127.0.0.1:6379> set name huathy
OK
127.0.0.1:6379> set age 22
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"
# 判断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> move name 1
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> set name huathy
OK
# 设置过期时间
127.0.0.1:6379> expire name 15
(integer) 1
# 查询当前key的剩余时间
127.0.0.1:6379> ttl name
(integer) 11
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

String(字符串)

# 设置值
127.0.0.1:6379> set k1 v1
OK
# 获取值
127.0.0.1:6379> get k1
"v1"
# 获得所有的key
127.0.0.1:6379> keys *
1) "k1"
# 判断key是否存在
127.0.0.1:6379> exists k1
(integer) 1
# 追加字符串,如果key不存在,则相当于set key
127.0.0.1:6379> append k1 hello
(integer) 7
127.0.0.1:6379> get k1
"v1hello"
# 获取字符串长度
127.0.0.1:6379> strlen k1
(integer) 7
127.0.0.1:6379> append k2 xixi
(integer) 4
127.0.0.1:6379> keys *
1) "k2"
2) "k1"

自增自减

127.0.0.1:6379> set views 0
OK
# 自增1
127.0.0.1:6379> incr views
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
# 自减1
127.0.0.1:6379> decr views
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
# 自增步长
127.0.0.1:6379> incrby views 10
(integer) 10
# 自减步长
127.0.0.1:6379> decrby views 5
(integer) 5
127.0.0.1:6379> get views
"5"

字符串范围range,替换

127.0.0.1:6379> set k1 "hello,huathy"
OK
127.0.0.1:6379> get k1
"hello,huathy"
# 截取字符串[0,3]
127.0.0.1:6379> getrange k1 0 4
"hello"
# 获取全部字符串,和get key相同
127.0.0.1:6379> getrange k1 0 -1
"hello,huathy"
127.0.0.1:6379> set k2 abcdefg
OK
# 替换指定位置开始的字符串
127.0.0.1:6379> setrange k2 1 xx
(integer) 7
127.0.0.1:6379> get k2
"axxdefg"
# setex(set with expire) #设置过期时间
# setnx(set if not exist) # 不存在再设置

#设置k3的值并指定30秒过期
127.0.0.1:6379> setex k3 30 hello
OK
127.0.0.1:6379> ttl k3
(integer) 26
127.0.0.1:6379> get k3
"hello"
# 如果k4不存在,则创建k4
127.0.0.1:6379> setnx k4 redis
(integer) 1
127.0.0.1:6379> keys *
1) "k2"
2) "k1"
3) "k4"
# 如果k4存在,则创建失败
127.0.0.1:6379> setnx k4 "mongoDB"
(integer) 0
127.0.0.1:6379> get k4
"redis"

批量设值,取值

# 批量设值
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"
# 如果不存在,则批量设置。mset是一个原子性的操作。
127.0.0.1:6379> msetnx k1 v1 k4 v4
(integer) 0
127.0.0.1:6379> get k4
(nil)

对象

set user:1 {name:xixi,age:3}  #设hi一个user:1对象,值为json格式的字符串来保存一个对象。

127.0.0.1:6379> mset user:1:name xixi user:1:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "xixi"
2) "2"

getset

# 如果不存在,则返回null。并设置值
127.0.0.1:6379> getset db redis
(nil)
# 如果存在,则先取值,再设值
127.0.0.1:6379> getset db mongodb
"redis"
127.0.0.1:6379> get db
"mongodb"

String类型的使用场景:

value除了是字符串还可以是数字。

  1. 计数器
  2. 统计多单位的数量。

List

在redis中,List可以实现栈,队列,阻塞队列。

所有list命令都是以L开头的。

# 将一个或多个值,插入到列表头部(左)
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"
# 通过区间获取list中的值
127.0.0.1:6379> lrange list 0 1
1) "three"
2) "two"
# 将一个或多个值,插入到列表尾部(右)
127.0.0.1:6379> Rpush list Rpush1
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "Rpush1"
############################################################
# 移除列表第一个元素
127.0.0.1:6379> lpop list
"three"
# 移除列表最后一个元素
127.0.0.1:6379> rpop list
"Rpush1"
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> llen list
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lpush list three
(integer) 4
# 移除list集合中指定个数的value
127.0.0.1:6379> lrem list 1 one
(integer) 1
127.0.0.1:6379> lrem list 2 three
(integer) 2
############################################################
# trim截断截取
127.0.0.1:6379> rpush mylist 0
(integer) 1
127.0.0.1:6379> rpush mylist 2
(integer) 2
127.0.0.1:6379> rpush mylist 3
(integer) 3
127.0.0.1:6379> rpush mylist 4
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "0"
2) "2"
3) "3"
4) "4"
# 通过下标截取指定长度,不在范围内的元素全部删除。
127.0.0.1:6379> ltrim mylist 2 3
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "3"
2) "4"
############################################################
#移除列表最后一个元素,并将他移动到一个新的list
127.0.0.1:6379> rpoplpush mylist otherlist
"4"
127.0.0.1:6379> lrange mylist 0 -1  # 查看原来的list
1) "2"
2) "1"
3) "3"
127.0.0.1:6379> lrange otherlist 0 -1 #查看新的list
1) "4"
###########################################################
# lset:将列表中指定下标的值,替换为另一个值。更新操作
127.0.0.1:6379> exists list   # 判断这个list是否存在
(integer) 0
127.0.0.1:6379> lset list 0 item    # 不存在则报错
(error) ERR no such key
127.0.0.1:6379> lpush list value1
(integer) 1
127.0.0.1:6379> lset list 0 item  # 如果存在,则更新当前下标的值
OK
127.0.0.1:6379> lrange list 0 -1  # 如果不存在,则报错
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 world
(integer) 2
127.0.0.1:6379> linsert mylist before word xixi
(integer) -1
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "world"
127.0.0.1:6379> linsert mylist before world xixi
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "xixi"
3) "world"
127.0.0.1:6379> linsert mylist after xixi haha
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "xixi"
3) "haha"
4) "world"

小结:

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

如果key不存在,则创建新的链表。如果key存在,新增内容。如果移除了所有值,空链表也表示不存在。在两边插入或者改动值,效率最高。中间的元素改动相对较慢。

消息队列:Lpush,Rpop;栈:Lpush,Lpop


Set(集合)

set中的值是不能重复读。

# set集合中添加一个或多个元素
127.0.0.1:6379> sadd myset xixi huathy haha
(integer) 3
# 查看指定set的所有值
127.0.0.1:6379> smembers myset
1) "haha"
2) "huathy"
3) "xixi"
# 判断元素是否存在
127.0.0.1:6379> sismember myset xixi
(integer) 1
127.0.0.1:6379> sismember myset hello
(integer) 0
127.0.0.1:6379> scard myset
(integer) 3
# 移除set中的指定元素
127.0.0.1:6379> srem myset haha
(integer) 1
127.0.0.1:6379> scard myset
(integer) 2
###########################################################
# 随机抽选一个元素
127.0.0.1:6379> srandmember myset
1) "hello"
# 随机抽选指定个数的元素
127.0.0.1:6379> srandmember myset 2
1) "haha"
2) "xixi"
# 随机删除一些set集合中的元素
127.0.0.1:6379> spop myset
"hello"
127.0.0.1:6379> smembers myset
1) "huathy"
2) "world"
3) "xixi"
4) "haha"
###########################################################
# 将一个指定的值,移动到另外一个set集合中
127.0.0.1:6379> sadd myset world huathy xixi haha
(integer) 4
127.0.0.1:6379> smove myset myset2 world    # 将一个指定的值,移动到另外一个set集合中
(integer) 1
127.0.0.1:6379> smembers myset
1) "haha"
2) "xixi"
3) "huathy"
127.0.0.1:6379> smembers myset2
1) "world"
###########################################################
微博,B站:共同关注。(并集)
数字集合类:
- 差集:SDIFF
- 交集:SINTER
- 并集:SUNION
127.0.0.1:6379> sadd k1 a b  f e
(integer) 4
127.0.0.1:6379> sadd k2 b c d e
(integer) 4
127.0.0.1:6379> sdiff k1 k2   #差集
1) "a"
2) "f"
127.0.0.1:6379> sinter k1 k2   #交集
1) "e"
2) "b"
127.0.0.1:6379> sunion k1 k2   #并集
1) "f"
2) "a"
3) "b"
4) "c"
5) "e"
6) "d"

Hash(哈希)

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

127.0.0.1:6379> hset myhash f1 v1   #set一个具体的key-value
(integer) 1
127.0.0.1:6379> hget myhash f1    #获取一个字段的值
"v1"
127.0.0.1:6379> hmset myhash f1 hello f2 world  #set多个具体的key-value
OK
127.0.0.1:6379> hmget myhash f1 f2      #获取多个字段的值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash      #获取全部的数据
1) "f1"     #key
2) "hello"  #value
3) "f2"
4) "world"
127.0.0.1:6379> hdel myhash f1    #删除hash指定的key字段。
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "f2"
2) "world"
###########################################################
# 获取hash表的字段数量
127.0.0.1:6379> hmset myhash f1 hello f2 world
OK
127.0.0.1:6379> hlen myhash
(integer) 2
###########################################################
# 判断hash中指定的key是否存在
127.0.0.1:6379>  hexists myhash f1
(integer) 1
127.0.0.1:6379> hexists myhash f3
(integer) 0
###########################################################
# 只获取所有的field
127.0.0.1:6379> hkeys myhash
1) "f2"
2) "f1"
# 只获取所有的value
127.0.0.1:6379> hvals myhash
1) "world"
2) "hello"
###########################################################
127.0.0.1:6379> hset myhash f3 5  #指定增量
(integer) 1
127.0.0.1:6379> hincrby myhash f3 1   #自增1
(integer) 6
127.0.0.1:6379> hincrby myhash f3 -1  #自增-1
(integer) 5
127.0.0.1:6379> hsetnx myhash f4 hello  #如果不存在则可以新增
(integer) 1
127.0.0.1:6379> hsetnx myhash f4 world  # 如果不存在则不能新增
(integer) 0

Hash变更的数据,user name age。做用户信息的保存,经常变更的数据。更适合存储对象。

127.0.0.1:6379> hmset user:1 name xixi age 22
OK
127.0.0.1:6379> hgetall user:1
1) "name"
2) "xixi"
3) "age"
4) "22"

Zset(有序集合)

在set的基础上,增加了一个值。

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 xixi 5000 haha 500 huathy  #添加三个记录
(integer) 3
127.0.0.1:6379> zrangebyscore salary  -inf +inf   #按照从小到大排序
1) "huathy"
2) "xixi"
3) "haha"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores   #按照从小到大排序,并且附带值
1) "huathy"
2) "500"
3) "xixi"
4) "2500"
5) "haha"
6) "5000"
127.0.0.1:6379> zrevrange salary 0 -1   #按照从大到小排序
1) "xixi"
2) "huathy"
###########################################################
# 移除元素
127.0.0.1:6379> zrange salary 0 -1
1) "huathy"
2) "xixi"
3) "haha"
127.0.0.1:6379> zrem salary haha
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "huathy"
2) "xixi"
###########################################################
# 获取集合中的个数
127.0.0.1:6379> zcard salary
(integer) 2
###########################################################
# 获取指定区间的成员数量
127.0.0.1:6379> zadd myset 1 hello 2 world 3 huathy
(integer) 3
127.0.0.1:6379> zcount myset 1 3    # 获取指定区间的成员数量
(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2

应用场景:set排序,可存储班级成绩表,工资表排序。带权重的普通消息,重要消息。排行榜应用。


三种特殊的数据类型

geospatital 地理位置

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

Redis的Geo可以推算地理位置信息,两地间的距离,方圆几里的人。

GEOADD:

GEODIST:

GEOHASH:

GEOPOS:

GEORADIUS:

GEORADIUSBYMEMBER:


# geoAdd添加地理位置
# 规则:两极是无法直接添加的,一般的下载城市数据,通过java导入
# 参数:key value(经度  维度  名称)
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 113.27 23.16 guangzhou
(integer) 2
127.0.0.1:6379> geoadd china:city 114.05 22.50 shenzhen 120.16 30.24 hangzhou
(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 shenzhen guangzhou
1) 1) "114.04999762773514"
   2) "22.500001138003192"
2) 1) "113.27000051736832"
   2) "23.159999437635349"
###################################################################
# geoDist 获取两地之间的直线距离
单位:m 米;km 千米;mi 英里;ft 英尺
127.0.0.1:6379> geodist china:city beijing shanghai
"1067378.7564"
127.0.0.1:6379> geodist china:city beijing shanghai km    #以km为单位
"1067.3788" 
127.0.0.1:6379> geodist china:city hangzhou shenzhen km
"1053.9526"
###################################################################
# 附近的人(获取所有附近的人的定位,通过半径查询)
# geoRadios 以给定的经纬度为中心,找出某一半径内的元素
127.0.0.1:6379> georadius china:city 110 30 1000 km
1) "shenzhen"
2) "guangzhou"
3) "hangzhou"
127.0.0.1:6379> georadius china:city 110 30 1000 km withdist  #显示到中心位置的距离
1) 1) "shenzhen"
   2) "926.6568"
2) 1) "guangzhou"
   2) "827.2683"
3) 1) "hangzhou"
   2) "977.5143"
127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord   #显示到中心位置的位置信息
1) 1) "shenzhen"
   2) 1) "114.04999762773514"
      2) "22.500001138003192"
2) 1) "guangzhou"
   2) 1) "113.27000051736832"
      2) "23.159999437635349"
3) 1) "hangzhou"
   2) 1) "120.16000002622604"
      2) "30.240000322949022"
127.0.0.1:6379> georadius china:city 110 30 1000 km withdist count 1  #筛选出一条记录
1) 1) "guangzhou"
   2) "827.2683"
###################################################################
# geoRadios:找出位于指定元素周围的其他元素
127.0.0.1:6379> geoRadiusByMember china:city shanghai 1000 km
1) "hangzhou"
2) "shanghai"
###################################################################
# geoHash:返回11个字符的geoHash字符串。将二维的经纬度,转为一维的字符串。若两个字符串越像,则距离越近。
127.0.0.1:6379> geoHash china:city shanghai hangzhou
1) "wtw3sj5zbj0"
2) "wtmkn31bfb0"

GEO底层的实现原理是zSet。可以使用zSet命令来操作GEO。

127.0.0.1:6379> zrange china:city 0 -1
1) "shenzhen"
2) "guangzhou"
3) "hangzhou"
4) "shanghai"
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) "guangzhou"
3) "hangzhou"
4) "shanghai"

HyperLogLog

基数(不重复的元素)A{1,3,5} B{1,2,4} 基数=

网页的UV(用户浏览量)(一个人访问多次,还是算作一次)。传统的方式,set保存用户id,然后统计set元素数。如果保存大量用户id,浪费空间。目的:计数。

HyperLogLog:占用内存固定。有一定的错误率。

127.0.0.1:6379> pfadd mykey a b c d e f   # 添加元素
(integer) 1
127.0.0.1:6379> pfcount mykey   # 统计元素数量
(integer) 6
127.0.0.1:6379> pfadd mykey2 c d e f j h i
(integer) 1
127.0.0.1:6379> pfcount mykey2
(integer) 7
127.0.0.1:6379> pfmerge mykey3 mykey mykey2   #合并两组元素
OK
127.0.0.1:6379> pfcount mykey3  
(integer) 9

BitMaps

位存储:统计用户信息(活跃-不活跃;登录-未登录)两种状态的情况。

# 周一到周五的打卡状态信息
127.0.0.1:6379> setbit sign 0 0
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(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> getbit sign 3   #查看周四是否打卡
(integer) 1
#######################################################
127.0.0.1:6379> bitcount sign   # 统计打卡记录天数
(integer) 3

Redis事务

MySQL:ACID。原子性。

区别:

Redis的单条命令保证原子性的,但事务不保证原子性。一个事务中的所有的命令都会被序列化,在事务执行的过程中,会按照顺序执行。

Redis事务没有隔离级别的概念,所有的命令在事务中,并没有直接执行,只有发起执行命令的时候才会执行。

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

Redis的事务:

  • 开启事务(multi)
  • 命令入队
  • 执行事务(exec)
  • 放弃事务(discard)
# 开启事务
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
#################################################
# 放弃事务
127.0.0.1:6379> multi
OK
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> getset k2   #错误命令
(error) ERR wrong number of arguments for 'getset' command
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)

运行时异常(1/0),错误命令抛出异常,其他命令正常执行。

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 "v1"
QUEUED
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) OK
2) (error) ERR value is not an integer or out of range
3) OK
4) "v2"

监控Watch,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
#########################################################
# 模拟多线程情况操作失败。类比乐观锁 
127.0.0.1:6379> watch 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
#############################
# 线程2修改数据
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> set money 1000
OK
############################
127.0.0.1:6379> exec
(nil)
######################################################
# 解决方法
127.0.0.1:6379> unwatch     #解除监视器。如果事务执行失败,先解锁
OK
127.0.0.1:6379> watch 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
1) (integer) 990
2) (integer) 30

Jedis

  1. 什么是Jedis:Jedis是官方推荐的Java连接开发工具。
  2. 编码测试:连接数据库 - 操作命令 - 断开连接

说明:在SpringBoot 2.X之后,原来使用的jedis被替换为了lettuce?

jedis:采用的直连,多线程操作不安全。为避免不安全可采用jedis pool连接池。

Lettuce:采用netty,实例可以在多个线程中国共享,不存在线程不安全的情况。可以减少线程数量。


SpringBoot整合Redis

源码分析:

@Bean
@ConditionalOnMissingBean(
    name = {"redisTemplate"}
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class) //可以自定义一个RedisTemplate来替换
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    //默认的RedisTemplate没有太多的配置。redis对象都是需要序列化的。
    RedisTemplate<Object, Object> template = new RedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
    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. 配置连接
    spring:
    ###### Redis配置 #####
    redis:
       port: 6379
       url: 127.0.0.1
    
  3. 测试
    package com.hx.redis;
    
    

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.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class RedisApplicationTests {

@Autowired
private RedisTemplate redisTemplate;

@Test
void contextLoads() {
/**
* api和指令是相同的
* opsForValue() 操作字符串,类似String。
* opsForList() 操作List
* opsForSet() 操作set
*/
// redisTemplate.opsForValue().set("","");
//
// //除了基本操作,常用的可以使用redisTemplate方法直接操作。如事务和基本的CRUD
// redisTemplate.multi();
// redisTemplate.exec();
//
// //获取redis的连接对象
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();

  redisTemplate.opsForValue().set("name","xixi");
  redisTemplate.opsForValue().set("姓名","嘻嘻");
  System.out.println(redisTemplate.opsForValue().get("name"));
  System.out.println(redisTemplate.opsForValue().get("姓名"));

}
}

序列化配置。
默认的序列化方式,采用JDK序列化。可能会有转义。采用JSON序列化。
```java
// org.springframework.data.redis.core.RedisTemplate
@Nullable
private RedisSerializer keySerializer = null;
@Nullable
private RedisSerializer valueSerializer = null;
@Nullable
private RedisSerializer hashKeySerializer = null;
@Nullable
private RedisSerializer hashValueSerializer = null;

if (this.defaultSerializer == null) {
   this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}

redis.conf 配置文件

  1. units are case insensitive so 1GB 1Gb 1gB are all the same.
    配置文件对大小写不敏感。

  2. 可以引入配置文件。
    在这里插入图片描述

  3. NETWORK 网络

    bind 127.0.0.1       #绑定的IP
    protected-mode yes   #开启保护模式
    port 6379            #端口号
    
  4. GENERAL 通用配置

    #以守护进程方式运行。默认no不开启,需要手动开启为yes
    deamonize yes        
    
    

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

日志配置

# 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 ""        
# 数据库数量配置,默认16
databases 16
  1. SNAPSHOTTING 快照配置

持久化,在规定时间内,执行了多少次操作,则会持久化到一个文件(.rdb .aof)。
redis是内存数据库,如果没有持久化,断电则会丢失。

# 如果在X秒内有Y个key发生修改,则进行持久化操作。
save 900 1
save 300 10
save 60 10000
# 持久化出错,是否继续工作
stop-writes-on-bgsave-error yes
# 是否雅俗RDB文件,需要消耗CPU的资源
rdbcompression yes
# 保存rdb文件时,进行错误校验
rdbchecksum yes
# rdb文件的保存目录
dir ./
  1. REPLICATION 复制,主从复制
  2. SECURITY 安全配置
8. 0.0.1:6379> config set requirepass 123456   #设置redis密码
OK
9. 0.0.1:6379> config get requirepass    #尝试获取redis密码
(error) NOAUTH Authentication required.   #没有权限
10. 0.0.1:6379> auth 123456               #验证密码
OK
11. 0.0.1:6379> config get requirepass    #获取redis密码
12. "requirepass"
13. "123456"
    ```
    ```Properties
    requirepass 123456
    ```
14. CLIENTS 客户端限制
    ```properties
    # 最大客户端连接数量限制
    # maxclients 10000
    ```
15. MEMORY MANAGEMENT 内存管理配置
    ```properties
    # redis最大内存容量
    # maxmemory <bytes>
    # 内存达到上限的处理策略
    # maxmemory-policy noeviction
    
    
    
    ```

# noeviction: 不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信息。(默认值)

# allkeys-lru: 所有key通用; 优先删除最近最少使用(less recently used ,LRU) 的 key。

# volatile-lru: 只限于设置了 expire 的部分; 优先删除最近最少使用(less recently used ,LRU) 的 key。

# allkeys-random: 所有key通用; 随机删除一部分 key。

# volatile-random: 只限于设置了 expire 的部分; 随机删除一部分 key。

# volatile-ttl: 只限于设置了 expire 的部分; 优先删除剩余时间(time to live,TTL) 短的key。

# https://www.cnblogs.com/yueerya/p/11507898.html

  1. APPEND ONLY MODE 模式 AOF配置
# 默认不开启AOF模式,默认采用RDB持久化。在大多数情况下,RDB已经够用。
appendonly no
# 持久化的文件名
appendfilename "appendonly.aof"
# 同步策略
# appendfsync always    #每次修改都会同步
appendfsync everysec    #每秒同步一次,但可能丢失这一秒的数据
# appendfsync no        #不同步,这时操作系统自己同步数据,速度最快。

Redis持久化

Redis是内存数据库,如果不将内存中的数据保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。故Redis提供了持久化功能。

RDB(Redis DataBase)

在这里插入图片描述

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork(创建)一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。整个过程中,主进程不进行任何IO操作。确保极高的性能。若需要进行大规模的恢复,且数据完整性不敏感,则RDB较AOF更高效。RDB缺点:最后一次的持久化数据可能丢失。

RDB保存的文件是dbfilename dump.rdb

触发机制

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

  2. 执行了flushAll,会触发rdb规则。

  3. 退出redis,也会产生rdb文件。
    备份会自动生成一个dump.rdb文件。

    如何恢复rdb文件
    是需要将rdb文件放在redis的指定目录下即可。redis启动时会自动检查dump.rdb文件,并恢复其中的数据。

    
    # 查看备份文件指定的存放路径
    
  4. 0.0.1:6379> config get dir

  5. “dir”

  6. “/usr/local/bin” #如果该目录下存在dump.rdb文件,启动就会自动恢复其中的数据

    优点:
    
  7. 适合大规模的数据恢复

  8. 对数据的完整性要求不高
    缺点:

  9. 需要一定的时间间隔进行操作。如果redis意外宕机,那么最后一次修改的数据就没有记录。

  10. fork进程的时候,会占用一定的内存空间。

AOF(Append Only File)

将所有的命令记录下来,history,恢复时再将所有的命令全部执行一遍.
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Huathy-雨落江南,浮生若梦

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

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

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

打赏作者

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

抵扣说明:

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

余额充值