Redis 学习笔记

Redis 学习笔记

    • NoSQL 概述
    • 什么是NoSQL
    • 阿里巴巴演进分析
    • NoSQL 四大分类
    • Redis 入门
      • Docker 安装
      • Redis-benchmark 性能测试工具
      • 基础知识
    • 五大基本数据类型
      • redis-key
      • String(字符串)
      • List(列表)
      • Set(集合)
      • Hash(哈希)
      • Zset(有序集合)
    • 三种特殊数据类型
      • geospatial 地理位置
      • hyperloglogs
      • Bitmaps
    • 事务
      • 编译型异常
      • 运行时异常
      • 监控
    • Jedis
      • 常用Api
      • 事务
    • redis.conf 详情
    • Redis 持久化
      • RDB
      • AOF
      • 总结
    • Redis 订阅发布
      • 命令
    • 主从复制

NoSQL 概述

  1. 单机 MySQL 年代

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

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

解决 读 的操作压力

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

解决 写 的操作压力

  1. 现在时代

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

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

什么是NoSQL

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

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

特点:

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

(大数据时代)了解 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. 商品的交易,外部的支付接口
        - 三方应用

大型互联网应用问题

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

解决方案: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
  1. 下载 redis.conf 配置文件到 conf 目录下,后给文件授权
wget http://download.redis.io/redis-stable/redis.conf
chmod 777 redis.conf
  1. 修改配置信息
bind 127.0.0.1 通过#注释掉,解除本地连接限制
protected-mode yes 默认no,保护模式,限制为本地访问,修改后解除保护模式
daemonize yes 注释掉
appendonly yes 持久化
requirepass 123456 密码
  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  数据卷映射

进入容器

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

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千请求

基础知识

默认有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 // 清空所有数据库
  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 查看数据长度
自增1 incr view
自减1 decr view
setex // 设置过期时间 setex name 30 ‘xiong’ (30 秒后过期)
setnx // 不存在再设置 setnx name ‘xiong’ (如果存在,不设置,不存在就设置)
mset // 设置多个值 mset k1 v1 k2 v2
mget // 获取多个值 mget k1 k2
getset getset 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"

小结

  • 实际上是一个链表,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 # 求取并集

用途:

  • 微博,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 表的所有值

应用

  • 更适合对象的存储

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 # 获取指定区间内的元素个数

应用

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

三种特殊数据类型

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"
# 将二维经纬度转换为一维字符串,如果两个字符串越接近,则距离越近

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

如果允许容错,则使用 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

事务

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>

编译型异常

代码有问题,命令有错误

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)

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

运行时异常

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

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

监控

悲观锁

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

乐观锁

  • 很乐观,什么时候都不会出问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据
  • 获取 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)

失败后的步骤

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

Jedis

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

构建项目:

创建一个空项目:

构建 maven 项目 -> 修改:

Redis 学习笔记_第1张图片Redis 学习笔记_第2张图片

导入对应依赖


  redis.clients
  jedis
  3.3.0



  com.alibaba
  fastjson
  1.2.68

测试连接

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(); // 关闭连接
    }
}

常用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"}

redis.conf 详情

配置文件对大小写不敏感

网络配置:

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

通用配置:

# 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  
#
#   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 文件保存路径

主从复制:

安全:

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

客户端限制:

maxclients 10000        # 设置能连接的redis 最大客户端数量
maxmemory        # 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  # 内存满后的策略

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                # 系统自动同步数据,不追加同步,速度最快

Redis 持久化

RDB

持久化操作流程图:

Redis 学习笔记_第3张图片

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

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

触发规则:

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

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

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

127.0.0.1:6379> save
OK
复制代码

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

127.0.0.1:6379> bgsave
Background saving started
  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 一个新的进程来将我们的文件进行重写

Redis 学习笔记_第4张图片

触发保存规则时会将所写入的命令记录进 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可以保证数据的不丢失

Redis 订阅发布

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

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

消息发布图

Redis 学习笔记_第5张图片

测试:

127.0.0.1:6379> subscribe xiongxinq     // 订阅频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "xiongxinq"
3) (integer) 1
        # 第二个客户端
    127.0.0.1:6379> publish xiongxinq "hello wirld"     // 向指定频道发送消息
    (integer) 1
    127.0.0.1:6379>
1) "message"
2) "xiongxinq"
3) "hello wirld"

命令

1 [PSUBSCRIBE pattern pattern …] 订阅一个或多个符合给定模式的频道。
2 [PUBSUB subcommand argument [argument …]] 查看订阅与发布系统状态。
3 PUBLISH channel message 将信息发送到指定的频道。
4 [PUNSUBSCRIBE pattern [pattern …]] 退订所有给定模式的频道。
5 [SUBSCRIBE channel channel …] 订阅给定的一个或多个频道的信息。
6 [UNSUBSCRIBE channel [channel …]] 指退订给定的频道。

原理 Redis 使用 c 实现的们可以通过 pubsub.c 文件了解

使用场景:

  • 实时消息系统
  • 实时聊天(聊天室)
  • 订阅、关注系统

稍微复杂的场景会使用消息中间件

主从复制

你可能感兴趣的:(学习笔记,java,redis,数据库)