Redis 学习笔记

Redis 学习笔记

NoSQL 概述

  1. 单机 MySQL 年代1. 数据量太大 2. 数据索引太大 3. 访问量(读写混合)过高
    • 1
    • 2
    • 3
  2. Memcached(缓存)+ MySQL + 垂直拆分(读写分离)

发展过程:优化数据结构和索引 -> 文件缓存 -> Memcached

解决 读 的操作压力

  1. 分库分表 + 水平拆分(集群)

解决 写 的操作压力

  1. 现在时代

数据量太大,关系型数据库不够用

用户自己产生的信息,地理位置,博客,视频等

什么是NoSQL

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

非关系型数据库:一些数据不需要固定格式,不需要多余的操作就可以横向扩展

特点:

  1. 方便扩展,数据之间没有关系
  2. 大数据量,高性能(Redis 一秒写8万次,读11万次,是一种细粒度缓存,性能比较高)
  3. 数据类型多样(不需要事先设计数据库)
  4. 传统 RDBMS 和 NoSQL
传统的 RDBMS
    - 结构化组织
  - SQL
  - 数据和关系都存在单独的表
  - 操作,数据定义语言
  - 严格的一致性
  - 基础的事务
  - ......
NoSQL
    - 不仅仅是数据
  - 没有固定的查询语言
  - 键值对存储,列存储,文档存储,图形数据库
  - 最终一致性
  - CAP 定理和 BASE(异地多活)初级架构师!
  - 高性能,高可用,高可扩
  - ......
12345678910111213141516

(大数据时代)了解 3V + 3高:

3V 主要描述问题:

  • 海量
  • 多样
  • 实时

3高 主要描述对程序问题

  • 高并发
  • 高可扩
  • 高性能

阿里巴巴演进分析

推荐书籍:(王坚:阿里云的这群疯子)

如果未来当一个架构师:没有什么是加一层解决不了的

# 1. 商品基本信息
        名称、价格、商家信息
    关系型数据库:MySQL / Oracle (淘宝早年就去IOE(在阿里巴巴的IT架构中,去掉IBM的小型机、Oracle数据库、EMC存储设备,代之以自己在开源软件基础上开发的系统)!)
    淘宝内部的 MySQL 不是大家用的 MySQL,他根据自己的需求做了相应修改
    
# 2. 商品描述、评论(文字比较多)
        文档数据库 MangoDB
    
# 3. 图片
        分布式文件系统 FastDFS
    - 淘宝自己的 TFS
    - Google GFS
    - Hadoop HDFS
    - 阿里云 OSS
    
# 4. 商品关键词(搜索)
        - 搜索引擎 solr elasticsearch
    - ISerach 淘宝 多隆
    
# 5. 商品热门的波段信息
        - 内存数据库
        - redis tair memache
    
# 6. 商品的交易,外部的支付接口
        - 三方应用
12345678910111213141516171819202122232425

大型互联网应用问题

  • 数据类型太多
  • 数据源多,经常重构
  • 数据要改造,大面积改造

解决方案:UDSL(统一数据服务平台)

NoSQL 四大分类

键值对

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

文档数据库

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

图关系数据库

  • 放的是关系,如:朋友圈社交网络,广告推荐等

列存储数据库

  • Hbase
  • 分布式文件系统

Redis 入门

什么是 Redis

远程字典服务(Remote Dictionary Server)

是一个由 Salvatore Sanfilippo 写的 key-value 存储系统,是跨平台的非关系型数据库。

Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言的 API。

Redis 通常被称为数据结构服务器,因为值(value)可以是字符串(String)、哈希(Hash)、列表(list)、集合(sets)和有序集合(sorted sets)等类型

能干什么

内存存储、持久化、内存中断电即失,所以持久化很重要(RDB、AOF)

发布订阅系统

地图信息分析

计数器,计时器

特性

多样化数据类型

持久化

集群

官网:http://www.redis.cn/

Docker 安装

  1. 创建外部挂载目录:
mkdir -p /root/docker/redis/data
mkdir -p /root/docker/redis/conf
12
  1. 下载 redis.conf 配置文件到 conf 目录下,后给文件授权
wget http://download.redis.io/redis-stable/redis.conf
chmod 777 redis.conf
12
  1. 修改配置信息
bind 127.0.0.1 通过#注释掉,解除本地连接限制
protected-mode yes 默认no,保护模式,限制为本地访问,修改后解除保护模式
daemonize yes 注释掉
appendonly yes 持久化
requirepass 123456 密码
12345
  1. 启动命令
docker run -p 6379:6379 -v /root/docker/redis/data:/data -v /root/docker/redis/conf/redis.conf:/etc/redis/redis.conf --name myredis -d redis redis-server /etc/redis/redis.conf --appendonly yes --requirepass 123456

# 命令解析
    -p 6379:6379    端口映射
  --name myredis 指定容器名称
  -d redis 后台启动
  --appendonly yes 开启持久化
  --requirepass 123456 设置密码
  -v /root/docker/redis/data:/data 
  -v /root/docker/redis/conf/redis.conf:/etc/redis/redis.conf --name myredis  数据卷映射
12345678910

进入容器

# docker exec -it myredis bash
# redis-cli
127.0.0.1>auth 123456   // 认证用户,不然无法使用
123

Redis-benchmark 性能测试工具

# 命令
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
# 运行结果
====== SET ======
  100000 requests completed in 3.12 seconds  // 10万个请求在3.12秒内完成
  100 parallel clients  // 100 个并发客户端
  3 bytes payload  // 每次写入3个字节
  keep alive: 1  // 只有一台服务器来处理这些请求,单机性能
  multi-thread: no

0.09% <= 1 milliseconds
99.54% <= 2 milliseconds
99.86% <= 3 milliseconds
99.96% <= 4 milliseconds
100.00% <= 4 milliseconds
32061.56 requests per second  // 每秒处理3万2千请求
12345678910111213141516

基础知识

默认有16个数据库 在配置文件中 database 关键字中配置

  1. 切换数据库
127.0.0.1:6379> select 3   // 切换数据库
OK
127.0.0.1:6379[3]>DBSIZE   // 返回当前数据库内 keys 数量
(integer) 0
127.0.0.1:6379[3]> set name 'xiong' // 添加键值
OK
127.0.0.1:6379[3]> get name  // 根据 key 查询 value
"xiong"
127.0.0.1:6379[3]> DBSIZE
(integer) 1
127.0.0.1:6379[3]> keys *  // 查看所有键
1) "name"
127.0.0.1:6379[3]>flushdb  // 清空本数据库
127.0.0.1:6379[3]>flushall // 清空所有数据库
1234567891011121314
  1. redis 是单线程的

redis 基于内存操作,CPU 不是 redis 性能瓶颈,reids 的瓶颈为机器内存和网络带宽

redis 为单线程还这么快?

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

误区2:多线程(CPU上下文切换)一定比单线程效率高

CPU > 内存 > 硬盘

核心:redis 是将所有的数据放在内存中,所以说使用单线程操作是效率最高的,多线程(CPU上下文切换)耗时间,对于内存来说没有上下文切换效率就是最高的!多次读写都是在一个CPU上

五大基本数据类型

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

redis-key

基础命令:http://www.redis.cn/commands.html

判断是否存在exists name
移除一个键move name 1 (1 代表当前数据库)
设置过期时间expire name 10 (设置为10s 过期)
查看当前数据类型type name

String(字符串)

追加字符append key ‘hello’ (如果key不存在,则新建)
获取数据长度strlen key 查看数据长度
自增1incr view
自减1decr view
setex // 设置过期时间setex name 30 ‘xiong’ (30 秒后过期)
setnx // 不存在再设置setnx name ‘xiong’ (如果存在,不设置,不存在就设置)
mset // 设置多个值mset k1 v1 k2 v2
mget // 获取多个值mget k1 k2
getsetgetset name redis # 如果不存在值,则返回 nil;如果存在,则返回原来的值再修改
设置对象set user:1:{name:zhang,age:3} // 设置一个user:1 对象值为 json 字符串mset user:1:name zhang user:1:age:3 // 批量设置的方法

使用场景:

  • 计数器 (博客浏览量等)
  • 统计多单位的数量
  • 粉丝数
  • 对象缓存存储!

List(列表)

基本数据类型:列表

在 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> lrange list 0 -1  // 获取指定范围内的值
1) "two"
2) "one"
127.0.0.1:6379> rpush list three  // 向右插入(尾部插入)
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
3) "three"
==================================================
pop # 移除操作 
127.0.0.1:6379> lpop list   // 移除头部的值
"two"
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "three"
127.0.0.1:6379> rpop list   // 移除尾部的值
"three"
127.0.0.1:6379> lrange list 0 -1
1) "one"
===================================================
lindex # 获取下标某个值
127.0.0.1:6379> lindex list 1
"two"
==================================================
# 获取长度 llen
127.0.0.1:6379> llen list
(integer) 3
==================================================
lrem # 移除指定个数的 value 
127.0.0.1:6379> lrem list 1 one  # lrem key count value
(integer) 1
==================================================
ltrim # 截取范围内的值 
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> lrange mylist 0 -1
1) "hello"
2) "hello1"
3) "hello2"
4) "hello3"
127.0.0.1:6379> ltrim mylist 1 2    # ltrim mylist start end
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello1"
2) "hello2"
======================================================
rpoplpush # 移除最后一个元素并保存到其他列表  
127.0.0.1:6379> rpush list 'hello' 'hello1' 'hello2' 'hello3'
(integer) 4
127.0.0.1:6379> rpoplpush list otherlist
"hello3"
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "hello1"
3) "hello2"
127.0.0.1:6379> lrange otherlist 0 -1
1) "hello3"
========================================================
lset # 修改列表内的值 (不存在的值会报错)
127.0.0.1:6379> rpush list 'hello'
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "hello"
127.0.0.1:6379> lset list 0 'item'  # lset key index element
OK
127.0.0.1:6379> lrange list 0 -1
1) "item"
=========================================================
linsert # 插入一个值,定位为列表中的 value
127.0.0.1:6379> linsert list before item 'new'  # 向前插入
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "new"
2) "item"
127.0.0.1:6379> linsert list after item 'new' # 向后插入
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "new"
2) "item"
3) "new"
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091

小结

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

Set(集合)

set 中的值不能重复

127.0.0.1:6379> sadd myset 'hello' 'xiong' 'love'   # 向集合中添加值
(integer) 3
127.0.0.1:6379> SMEMBERS set
(empty array)
127.0.0.1:6379> SMEMBERS myset      # 查看集合中所有值
1) "xiong"
2) "hello"
3) "love"
127.0.0.1:6379> SISMEMBER myset xiong  # 判断一个值是否在集合中
(integer) 1
127.0.0.1:6379> SISMEMBER myset love
(integer) 1
127.0.0.1:6379> SISMEMBER myset world
(integer) 0
========================================================
scard # 获取集合中元素个数
127.0.0.1:6379> scard myset
(integer) 3
========================================================
srem # 移除某个值
127.0.0.1:6379> srem myset xiong
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "love"
========================================================
SRANDMEMBER # 随机抽选一个值
127.0.0.1:6379> SRANDMEMBER myset    # SRANDMEMBER set count
"love"
127.0.0.1:6379> SRANDMEMBER myset
"hello"
========================================================
spop # 随机删除一个元素
127.0.0.1:6379> spop myset # spop set count
"love"
========================================================
# sdiff # 求取差集
========================================================
sinter # 求取交集
========================================================
sunion # 求取并集
1234567891011121314151617181920212223242526272829303132333435363738394041

用途:

  • 微博,B站的 共同关注 (交集)

Hash(哈希)

Map 集合,key-Map ,本质和 string 没有区别

127.0.0.1:6379> hset myhash filed1 xionog       # 存值
(integer) 1
127.0.0.1:6379> hget myhash filed1      # 取值
"xionog"
========================================================
取值
hmget   # 获取多个
hgetall # 获取全部
========================================================
删除
hdel
========================================================
hlen # 获取 hash 表的字段数量
========================================================
hexists # 判断 hash 表的字段是否存在
========================================================
hkeys # 获取 hash 表的所有值
1234567891011121314151617

应用

  • 更适合对象的存储

Zset(有序集合)

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

127.0.0.1:6379> zadd myset 1 one    # 添加一个值 zadd key score value
(integer) 1
127.0.0.1:6379> zadd myset 2 two
(integer) 1
127.0.0.1:6379> zadd myset 3 three
(integer) 1
127.0.0.1:6379> ZRANGE myset 0 -1  # 查看值
1) "one"
2) "two"
3) "three"
========================================================
ZRANGEBYSCORE # 排序
127.0.0.1:6379> zadd saraly 300 xiong 200 xin 400 qiang
(integer) 3
127.0.0.1:6379> zrange saraly 0 -1
1) "xin"
2) "xiong"
3) "qiang"
127.0.0.1:6379> ZRANGEBYSCORE saraly -inf +inf withscores  # 排序,只能从小到大,在设置值的时候就已经排好序了
1) "xin"
2) "200"
3) "xiong"
4) "300"
5) "qiang"
6) "400"
127.0.0.1:6379> zrevrange saraly 0 -1  # 排序从大到小
1) "qiang"
2) "xiong"
3) "xin"
========================================================
zrem # 移除元素
========================================================
zcard # 获取集合中元素个数
========================================================
zcount # 获取指定区间内的元素个数
1234567891011121314151617181920212223242526272829303132333435

应用

  • 存储班级程序表
  • 普通消息和重要消息区分,按权重进行判断
  • 排行榜实现

三种特殊数据类型

geospatial 地理位置

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

redis 的 Geo 在 Redis 3.2 版本就推出了,可以推算地理位置信息

测试数据连接:http://www.jsons.cn/lngcode/

相关命令:http://www.redis.cn/commands/geoadd.html

  • GEOADD
  • GEODIST
  • GEOHASH
  • GEOPOS
  • GEORADIUS
  • GEORADIUSBYMEMBER
geoadd # 添加地理位置 (两级-南极北极,无法直接添加)
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijin
(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 shengzheng
(integer) 2
========================================================
由EPSG:900913 / EPSG:3785 / OSGEO:41001 规定如下:
有效的经度从-180度到180度
有效的纬度从-85.05112878度到85.05112878度
========================================================
GEODIST key member1 member2
返回两个给定位置之间的距离。
如果两个位置之间的其中一个不存在, 那么命令返回空值。
指定单位的参数 unit 必须是以下单位的其中一个:
    - m 表示单位为米。
    - km 表示单位为千米。
    - mi 表示单位为英里。
    - ft 表示单位为英尺。
如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位。
GEODIST 命令在计算距离时会假设地球为完美的球形, 在极限情况下, 这一假设最大会造成 0.5% 的误差。
127.0.0.1:6379> geodist china:city shanghai beijin
"1067378.7564"
========================================================
GEOPOS key member [member ...]
从key里返回所有给定位置元素的位置(经度和纬度)。
给定一个sorted set表示的空间索引,密集使用 geoadd 命令,它以获得指定成员的坐标往往是有益的。当空间索引填充通过 geoadd 的坐标转换成一个52位Geohash,
所以返回的坐标可能不完全以添加元素的,但小的错误可能会出台。
因为 GEOPOS 命令接受可变数量的位置元素作为输入, 所以即使用户只给定了一个位置元素, 命令也会返回数组回复。
========================================================
GEORADIUS key longitude latitude radius m|km|ft|mi
以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。

案例,获取附近的人:
127.0.0.1:6379> georadius china:city 110 30 500 km
1) "chongqing"
127.0.0.1:6379> georadius china:city 110 30 500 km withcoord  # 查询他人的经纬度
1) 1) "chongqing"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"
127.0.0.1:6379> georadius china:city 110 30 500 km withdist # 查询显示到中心距离的位置
1) 1) "chongqing"
   2) "341.9374"
========================================================
GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]
GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点指定成员的位置被用作查询的中心

127.0.0.1:6379> GEORADIUSBYMEMBER china:city shanghai 1000 km
1) "shanghai"
========================================================
GEOHASH key member [member ...]
该命令返回11个字符的字符串
127.0.0.1:6379> geohash china:city shanghai chongqing
1) "wtw3sj5zbj0"
2) "wm5xzrybty0"
# 将二维经纬度转换为一维字符串,如果两个字符串越接近,则距离越近
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657

GEO 底层为 zset 集合所以可以用 zset 命令

hyperloglogs

什么是基数:

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

优点:占用的内存是固定的,2^64 不同的元素基数,只需要废12kb内存!如果要从内存角度来比骄傲的话 hyperloglog 首选

应用:网页的 UV (一个人访问一个网站多次,访问量为1)

传统方式:使用 set 集合储存用户id,然后统计 set 集合中的元素个数作为标准判断

127.0.0.1:6379> pfadd mykey a b c d e f g h i j  # 添加元素
(integer) 1
127.0.0.1:6379> pfcount mykey       # 统计基数个数
(integer) 10
127.0.0.1:6379> pfadd mykey2 i j a l n m e c x
(integer) 1
127.0.0.1:6379> pfmerge mykey3 mykey mykey2  # 合并两个集合,并集
OK
127.0.0.1:6379> pfcount mykey3
(integer) 14
12345678910

如果允许容错,则使用 hyperloglog,出错率为 0.81%

Bitmaps

位存储(只有 0 和 1)都是操作二进制为来进行记录,就只有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 0
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> getbit sign 3       # 取值
(integer) 0
127.0.0.1:6379> bitcount sign   # 统计
(integer) 2
1234567891011121314

事务

Redis 单条命令保证原子性,但是事务不保证原子性!

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

所有命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行!

Redis 事务

  • 开启事务
  • 命令入队
  • 执行事务
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> get k2
QUEUED
127.0.0.1:6379> exec                # 执行事务
1) OK
2) OK
3) OK
4) "v2"
=============================================================
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>
12345678910111213141516171819202122232425

编译型异常

代码有问题,命令有错误

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> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k4
(nil)
127.0.0.1:6379> get k2
(nil)
123456789101112131415161718

命令都不会运行,整个事务都取消

运行时异常

语法型问题,其他命令正常执行,错误出抛出异常

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> incr k1
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) (error) ERR value is not an integer or out of range
4) OK
123456789101112131415

监控

悲观锁

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

乐观锁

  • 很乐观,什么时候都不会出问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据
  • 获取 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 可以当作 reids 的乐观锁操作
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> get money
        "80"
        127.0.0.1:6379> set money 1000
        OK
127.0.0.1:6379> exec        # 监视失败,事务取消
(nil)
1234567891011121314151617181920212223242526272829303132

失败后的步骤

  1. 放弃监视 unwatch
  2. 重新监视 watch key
  3. 事务执行是比对监视的值是否变化,如果没有变化则执行,如果有变化则放弃监视,重新监视执行

Jedis

是官方推荐的 java 连接开发工具!使用 java 操作的Redis 中间件!

构建项目:

创建一个空项目:

构建 maven 项目 -> 修改:

image.png
image.png

导入对应依赖

<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>3.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.68</version>
</dependency>
1234567891011

测试连接

public class TestPing {
    public static void main(String[] args) {
        // 1. new jedis 对象
        Jedis jedis = new Jedis("ip",6379);
        // 认证
        jedis.auth("123456");
        // 测试连接
        System.out.println(jedis.ping());
        jedis.close(); // 关闭连接
    }
}
1234567891011

常用Api

与常用基础类型的命令基本同,特殊类型也相同,只不过换成了方法

事务

public class TestPing {
    public static void main(String[] args) {
        // 1. new jedis 对象
        Jedis jedis = new Jedis("159.75.113.81",6379);
        // 认证
        jedis.auth("123456");

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello","world");
        jsonObject.put("name","xiong");
        // 开启事务
        Transaction multi = jedis.multi();
        String result = jsonObject.toJSONString();

        try {
            multi.set("user1", result);
            multi.set("user2", result);

            multi.exec();  // 执行事务
        } catch (Exception e) {
            multi.discard();  // 出异常放弃事务
        } finally {
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();  // 关闭连接
        }
    }
}

输出:
{"name":"xiong","hello":"world"}
{"name":"xiong","hello":"world"}
1234567891011121314151617181920212223242526272829303132

redis.conf 详情

配置文件对大小写不敏感

网络配置:

bind 127.0.0.1      # 绑定 ip
protected-mode no       # 保护模式
port 6379                       # 端口号
123

通用配置:

# By default Redis does not run as a daemon. Use 'yes' if you need it.
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
daemonize yes  # 以守护进程方式运行,默认是 no 

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

日志级别:
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel notice

logfile ""  # 日志保存文件名,如果为空,则为输出

databases 16 # 数据库数量
======================================================================================
快照  (持久化使用)
    在规定时间内,执行了多少次操作,则会持久化到文件 rdb aof
  如果没有持久化,则 redis 断电会丢失数据
#   save <seconds> <changes>
#
#   Will save the DB if both the given number of seconds and the given
#   number of write operations against the DB occurred.
#
#   In the example below the behavior will be to save:
#   after 900 sec (15 min) if at least 1 key changed
#   after 300 sec (5 min) if at least 10 keys changed
#   after 60 sec if at least 10000 keys changed
#
#   Note: you can disable saving completely by commenting out all "save" lines.
#
#   It is also possible to remove all the previously configured save
#   points by adding a save directive with a single empty string argument
#   like in the following example:
#
#   save ""
save 900 1 # 如果900 秒内有一个 key 值改变就进行持久化操作
save 300 10 # 如果300 秒内有 10 个 key 值改变就进行持久化操作
save 60 10000 # 如果 60 秒内,有 10000 个 key 值改变就进行持久化操作

stop-writes-on-bgsave-error yes # 持久化出错,是否继续工作

rdbchecksum yes     # 保存压缩rdb 文件的时候,进行错误校验

rdbcompression yes # 是否压缩 rdb 文件,需要消耗 cpu 资源

dir ./ # rdb 文件保存路径
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950

主从复制:

安全:

requirepass 123456      # 设置密码,默认没有
=========================================
通过命令设置密码:
127.0.0.1:6379> config set requirepass "123456"
获取密码
127.0.0.1:6379> config get requirepass
123456

客户端限制:

maxclients 10000        # 设置能连接的redis 最大客户端数量
maxmemory <bytes>       # redis 配置最大的内存

# volatile-lru -> Evict using approximated LRU, only keys with an expire set.
# allkeys-lru -> Evict any key using approximated LRU.
    # volatile-lfu -> Evict using approximated LFU, only keys with an expire set.       只对设置了过时间的key 进行lru(默认值)
    # allkeys-lfu -> Evict any key using approximated LFU.                                                  删除lru 算法的key
    # volatile-random -> Remove a random key having an expire set.                                  随机删除即将过期的key
    # allkeys-random -> Remove a random key, any key.                                                               随机删除key
    # volatile-ttl -> Remove the key with the nearest expire time (minor TTL)               删除即将过期的key
    # noeviction -> Don't evict anything, just return an error on write operations. 永不过期,报错
#
# LRU means Least Recently Used
# LFU means Least Frequently Used
#
# Both LRU, LFU and volatile-ttl are implemented using approximated
# randomized algorithms.
#
# Note: with any of the above policies, Redis will return an error on write
#       operations, when there are no suitable keys for eviction.
#
#       At the date of writing these commands are: set setnx setex append
#       incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
#       sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
#       zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
#       getset mset msetnx exec sort
#
# The default is:
#
# maxmemory-policy noeviction  # 内存满后的策略
123456789101112131415161718192021222324252627282930

AOF 配置:

appendonly yes  # 默认为 no,默认使用 rdb 方式持久化,在大部分情况下, rdb 完全够用

# The name of the append only file (default: "appendonly.aof")

appendfilename "appendonly.aof" # 文件名
自动持久化方式:
# appendfsync always        # 每次修改都追加同步,消耗性能
appendfsync everysec        # 每秒一次
# appendfsync no                # 系统自动同步数据,不追加同步,速度最快
123456789

Redis 持久化

RDB

持久化操作流程图:

img

rdb 保存文件为 dump.rdb(在bin 文件夹中)该文件是一个压缩过的二进制文件,可以通过该文件还原快照时的数据库状态,即生成该RDB文件时的服务器数据

有时候在生产环境中会进行备份

触发规则:

  1. save 规则(配置文件中写的)情况下自动触发保存
  2. 执行savebgsave命令

执行savebgsave命令,可以手动触发快照,生成RDB文件,两者的区别如下

使用save命令会阻塞Redis服务器进程,服务器进程在RDB文件创建完成之前是不能处理任何的命令请求

127.0.0.1:6379> save
OK
复制代码
123

而使用bgsave命令不同的是,bgsave命令会fork一个子进程,然后该子进程会负责创建RDB文件,而服务器进程会继续处理命令请求

127.0.0.1:6379> bgsave
Background saving started
12
  1. 执行 flushall 命令,也会触发保存
  2. 退出 redis,也会触发保存

恢复:

  1. 只需要将 rdb 文件放在 redis 启动目录就可以,redis 启动时会自动检查 dump.rdb 并恢复其中数据!
  2. 查看需要存在的位置 config get dir

优点

  • RDB快照是一个压缩过的非常紧凑的文件,保存着某个时间点的数据集,适合做数据的备份,灾难恢复
  • 可以最大化Redis的性能,在保存RDB文件,服务器进程只需fork一个子进程来完成RDB文件的创建,父进程不需要做IO操作
  • 与AOF相比,恢复大数据集的时候会更快

缺点

  • RDB的数据安全性是不如AOF的,保存整个数据集的过程是比繁重的,根据配置可能要几分钟才快照一次,如果服务器宕机,那么就可能丢失几分钟的数据
  • Redis数据集较大时,fork的子进程要完成快照会比较耗CPU、耗时

AOF

将我们的所有命令都记录下来(在大量数据时效率很慢)

开启:将 appendonly 改为 yes

如果 AOF 文件超过 64mb ,会 fork 一个新的进程来将我们的文件进行重写

image.png

触发保存规则时会将所写入的命令记录进 appendonly.aof 文件中

如果 aof 文件有错误,redis 就无法启动

可以通过 redis-check-aof –fix appendonly.aof 进行修复(通过删除错误命令进行修复)

优点:

  • 数据更完整,安全性更高,秒级数据丢失(取决fsync策略,如果是everysec,最多丢失1秒的数据)
  • AOF文件是一个只进行追加的日志文件,且写入操作是以Redis协议的格式保存的,内容是可读的,适合误删紧急恢复

缺点:

  • 对于相同的数据集,AOF文件的体积要大于RDB文件,数据恢复也会比较慢
  • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB。 不过在一般情况下, 每秒 fsync 的性能依然非常高

总结

  • 如果是数据不那么敏感,且可以从其他地方重新生成补回的,那么可以关闭持久化
  • 如果是数据比较重要,不想再从其他地方获取,且可以承受数分钟的数据丢失,比如缓存等,那么可以只使用RDB
  • 如果是用做内存数据库,要使用Redis的持久化,建议是RDB和AOF都开启,或者定期执行bgsave做快照备份,RDB方式更适合做数据的备份,AOF可以保证数据的不丢失

原创文章,作者:Zhu, Yuanyuan,如若转载,请注明出处:https://www.yidc.net/archives/11308