Redis

NoSQL的简介

什么是NoSQL

NoSQL 是 Not Only SQL 的缩写,意即"不仅仅是SQL"的意思,泛指非关系型的数据库。强调Key-Value Stores和文档数据库的优点。
NoSQL产品是传统关系型数据库的功能阉割版本,通过减少用不到或很少用的功能,来大幅度提高产品性能;

NoSQL起源

过去,关系型数据库(SQL Server、Oracle、MySQL)是数据持久化的唯一选择,但随着发展,关系型数据库存在以下问题。

问题1:不能满足高性能查询需求

我们使用:Java、.Net等语言编写程序,是面向对象的。但用数据库都是关系型数据库。存储结构是面向对象的,但是数据库却是关系的,所以在每次存储或者查询数据时,我们都需要做转换。类似Hibernate、Mybatis这样的ORM框架确实可以简化这个过程,但是在对高性能查询需求时,这些ORM框架就捉襟见肘了。

问题2:应用程序规模的变大

网络应用程序的规模变大,需要储存更多的数据、服务更多的用户以及需求更多的计算能力。为了应对这种情形,我们需要不停的扩展。
扩展分为两类:一种是纵向扩展,即购买更好的机器,更多的磁盘、更多的内存等等。另一种是横向扩展,即购买更多的机器组成集群。在巨大的规模下,纵向扩展发挥的作用并不是很大。首先单机器性能提升需要巨额的开销并且有着性能的上限,在Google和Facebook这种规模下,永远不可能使用一台机器支撑所有的负载。鉴于这种情况,我们需要新的数据库,因为关系数据库并不能很好的运行在集群上;

NoSQL数据库类型

① 键值(Key-Value)数据库[Redis/Memcached]

适用场景

  • 储存用户信息,比如会话、配置文件、参数、购物车等等。这些信息一般都和ID(键)挂钩,这种情景下键值数据库是个很好的选择。

不适用场景

  1. 取代通过键查询,而是通过值来查询。Key-Value数据库中根本没有通过值查询的途径。
  2. 需要储存数据之间的关系。在Key-Value数据库中不能通过两个或以上的键来关联数据。
  3. 事务的支持。在Key-Value数据库中故障产生时不可以进行回滚。

② 面向文档(Document-Oriented)数据库[MongoDB]

数据可以使用XML、JSON或者JSONB等多种形式存储。

适用场景

  1. 日志
  2. 分析

不适用场景

  • 不支持事务

③ 列存储(Wide Column Store/Column-Family)数据库[HBASE]

列存储数据库将数据储存在列族(column family)中,一个列族存储经常被一起查询的相关数据。举个例子,如果我们有一个Person类,我们通常会一起查询他们的姓名和年龄而不是薪资。这种情况下,姓名和年龄就会被放入一个列族中,而薪资则在另一个列族中。

适用场景

  1. 日志
  2. 博客平台,我们储存每个信息到不同的列族中。
    举个例子,标签可以储存在一个,类别可以在一个,而文章则在另一个。

不适用场景

  1. ACID事务
  2. 原型设计
    在模型设计之初,我们根本不可能去预测它的查询方式,而一旦查询方式改变,我们就必须重新设计列族。

④ 图(Graph-Oriented)数据库[Neo4J]

适用范围很小,主要用用网络拓扑分析 如脉脉的人员关系图等

传统RDBMS VS NOSQL

RDBMS

  • 高度组织化结构化数据
  • 结构化查询语言(SQL)
  • 数据和关系都存储在单独的表中。
  • 数据操纵语言,数据定义语言
  • 严格的一致性
  • 基础事务

NoSQL

  • 代表着不仅仅是SQL
  • 没有声明性查询语言
  • 没有预定义的模式
  • 键 - 值对存储,列存储,文档存储,图形数据库
  • 最终一致性,而非ACID【原子,一致,隔离,持久】属性
  • 非结构化和不可预知的数据
  • CAP定理【一致性,可用性,容错性】
  • 高性能,高可用性和可伸缩性

常见的NoSQL及区别

常见的NoSQL数据库

区别

Memcached

  • 挥发性(临时性)的键值存储
  • 一般作为关系型数据库的缓存来使用
  • 具有非常快的处理速度
  • 由于存在数据丢失的可能,所以一般用来处理不需要持久保存的数据
  • 用于需要使用expires时(需要定期清除数据)
  • 使用一致性散列(Consistent Hashing)算法来分散数据

Tokyo Tyrant

  • 持久性的键值存储
  • 用来处理需要持久保存,高速处理的数据
  • 具有非常快的处理速度
  • 用于不需要定期清除的数据
  • 使用一致性散列(Consistent Hashing)算法来分散数据

Redis ★

  • 兼具Memcached和Tokyo Tyrant优势的键值存储
  • 擅长处理数组类型的数据
  • 具有非常快的处理速度
  • 可以高速处理时间序列的数据,易于处理集合运算
  • 拥有很多可以进行原子操作的方法
  • 使用一致性散列(Consistent Hashing)算法来分散数据

MongoDB

  • 面向无需定义表结构的文档数据
  • 具有非常快的处理速度
  • 通过BSON的形式可以保存和查询任何类型的数据
  • 无法进行JOIN处理,但是可以通过嵌入(embed)来实现同样的功能
  • 使用sharding(范围分割)算法来分散数据

Redis简介

Redis简介

Redis:Remote Dictionary Server(远程字典服务器)

  • Redis是当前比较热门的NOSQL系统之一,它是一个开源的使用ANSI c语言编写的key-value存储系统(区别于MySQL的二维表格的形式存储。)。和Memcache类似,但很大程度补偿了Memcache的不足。和Memcache一样,Redis数据都是缓存在计算机内存中,不同的是,Memcache只能将数据缓存到内存中,无法自动定期写入硬盘,这就表示,一断电或重启,内存清空,数据丢失。所以Memcache的应用场景适用于缓存无需持久化的数据。而Redis不同的是它会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,实现数据的持久化

Redis的特点

  1. Redis读取的速度是 110000 次/s,写的速度是 81000 次/s
  2. 原子 。Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
  3. 支持多种数据结构:string(字符串);list(列表);hash(哈希),set(集合);zset(有序集合)
  4. 持久化,主从复制(集群)
  5. 支持过期时间,支持事务,消息订阅。
  6. 官方不支持window,但是有第三方版本。

Redis的应用场景

① 数据缓存(提高访问性能)

将一些数据在短时间之内不会发生变化,而且它们还要被频繁访问,为了提高用户的请求速度和降低网站的负载,降低数据库的读写次数,就把这些数据放到缓存中。

② 会话缓存

(session cache,保存web会话信息)

③ 排行榜/计数器

(NGINX+lua+Redis计数器进行IP自动封禁)

④ 消息队列

(构建实时消息系统,聊天,群聊)

Redis的安装及启动停止

下载Redis4

Redis目前的最新版本是5.X此文章是以4.X为基础来整理的,大家尽量保持一致;

下载地址

把下载的Redis利用XFtp上传到centerOS中的 /usr/local/Temp中

开始安装

① 安装gcc 目地是编译软件

yum install gcc-c++

② 解压

tar -zxvf redis-4.0.14.tar.gz

③ 把解压的文件copy到/usr/local/java/redis里面

cp -r /usr/local/Temp/redis-4.0.12 /usr/local/java/redis

③ 打开/usr/local/java/redis/deps进行编译依赖项

cd /usr/local/java/redis/deps
make hiredis lua jemalloc linenoise

④ 打开/usr/local/java/redis进行编译

cd /usr/local/java/redis
make

⑤ 在上面的Redis目录安装把它安装到/usr/local/java/redis里面

mkdir /usr/local/java/redis
make install PREFIX=/usr/local/java/redis

  • 看到上面的说明安装成功了哦

⑥ 验证安装是否成功

cd /usr/local/java/redis/bin
ls
  • 看到如下启动文件就可以了

  • 使用 which 命令查看系统里面是否有 Redis 的服务
which redis-server

  • 显示为没有

⑦ 把配置文件移动到/usr/local/java/myredis目录[目录可以自定义] 可以为/root/myredis

mkdir /usr/local/java/myredis
cp /usr/local/java/redis/redis.conf /usr/local/java/myredis

⑧ 启动Redis

cd /usr/local/java/redis/bin
./redis-server /usr/local/java/myredis/redis.conf

⑨ 默认情况,Redis不是在后台运行,我们需要把Redis放在后台运行

vim /usr/local/java/myredis/redis.conf

⑩ 再次启动查看进程

./redis-server /usr/local/java/myredis/redis.conf 
  • 查看进程
ps -ef|grep redis

  • 可以看到在 6379 端口号已启动了 Redis

客户端链接和退出

  • 连接
cd /usr/local/java/redis/bin
  • 默认是-h 127.0.0.1 -p 6379
./redis-cli
  • 退出
quit
  • 测试
ping

停止redis

cd /usr/local/java/redis/bin
./redis-cli shutdown
  • 或者
pkill redis-server
  • 再次查看进程
ps -ef|grep redis

开机自启Redis的配置

vim /etc/rc.local
  • 加入
/usr/local/java/redis/bin/redis-server /usr/local/java/myredis/redis-conf

bin目录的文件说明

  • redis-benchmark:redis性能测试工具
  • redis-check-aof:检查aof日志的工具
  • redis-check-dump:检查rdb日志的工具
  • redis-cli:连接用的客户端
  • redis-server:redis服务进程

Redis配置[后面细说]

  • daemonize:如需要在后台运行,把该项的值改为yes
  • pdifile:把pid文件放在/var/run/redis.pid,可以配置到其他地址
  • bind:指定redis只接收来自该IP的请求,如果不设置,那么将处理所有请求,在生产环节中最好设置该项
  • port:监听端口,默认为6379
  • timeout:设置客户端连接时的超时时间,单位为秒
  • loglevel:等级分为4级,debug,revbose,notice和warning。生产环境下一般开启notice
  • logfile:配置log文件地址,默认使用标准输出,即打印在命令行终端的端口上
  • database:设置数据库的个数,默认使用的数据库是0
  • save:设置redis进行数据库镜像的频率
  • rdbcompression:在进行镜像备份时,是否进行压缩
  • dbfilename:镜像备份文件的文件名
  • dir:数据库镜像备份的文件放置的路径
  • slaveof:设置该数据库为其他数据库的从数据库
  • masterauth:当主数据库连接需要密码验证时,在这里设定
  • requirepass:设置客户端连接后进行任何其他指定前需要使用的密码
  • maxclients:限制同时连接的客户端数量
  • maxmemory:设置redis能够使用的最大内存
  • appendonly:开启appendonly模式后,redis会把每一次所接收到的写操作都追加到appendonly.aof文件中,当redis重新启动时,会从该文件恢复出之前的状态
  • appendfsync:设置appendonly.aof文件进行同步的频率
  • vm_enabled:是否开启虚拟内存支持
  • vm_swap_file:设置虚拟内存的交换文件的路径
  • vm_max_momery:设置开启虚拟内存后,redis将使用的最大物理内存的大小,默认为0
  • vm_page_size:设置虚拟内存页的大小
  • vm_pages:设置交换文件的总的page数量
  • vm_max_thrrads:设置vm IO同时使用的线程数量

启动后相关知识串讲

单进程单线程

采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗)

  • 多线程处理可能涉及到锁。
  • 多线程处理会涉及到线程切换而消耗CPU。
  • 单进程不存在线程安全问题。
  • 缺点
    • 无法发挥多核CPU性能,不过可以通过在单机开多个Redis实例来完善。

默认16个兄弟一起站台

默认16个数据库,类似数组下表从零开始,初始默认使用零号库。

切换数据库命令

select 命令切换数据库

常用基本命令

  • dbsize 查看当前数据库的key的数量。
  • flushdb:清空当前库。
  • Flushall;通杀全部库。

其它说明

  • 统一密码管理,16个库都是同样密码,要么都OK要么一个也连接不上。
  • Redis索引都是从零开始。

为什么默认端口是6379

  • 先介绍下redis的作者Salvatore Sanfilippo(Antirez),意大利人,就是下图这位。

  • Antirez现在已经40多岁了,依然奋斗在代码一线,为开源社区做贡献。Antirez出生在非英语系国家,所以英语一直是他的短板。他曾经写过一篇博文,《英语伤痛 15 年》,以自己的实际经历鼓励非英语系国家的程序员突破英语障碍。或说回来,在他的另一篇博文《Redis as an LRU cache 》中,写到了为什么选用6379端口。
  • 用一张图片来翻译一下,6379 就是这个意思。

  • 而Merz全名Alessia Merz,是意大利的一位广告女郎,就是下面这位。

  • 在Antirez看来,这个名字是愚蠢的代名词,所以就选了这个6379。
  • 这个还真不知道有没有其它原因(自行脑补)

Redis的数据类型

概述

使用Redis进行应用设计和开发的一个核心概念是数据类型。与关系数据库不同,在Redis中不存在需要我们担心的表,在使用Redis进行应用设计和开发时,我们首先应该考虑的是Redis原生支持的哪种数据类型阳适合我们的应该场景,此外,我们无法像在关系数据库中那样,使用sql来操作Redis中的数据,相反,我们需要直接使用API发送数据反对应的命令,来操作想要操作的数据。

字符串类型

  • 字符串类型是编程语言和应用程序中最常见和最有用的数据类型,也是Redis的基本数据类型之一,事实上,Redis中所有键都必须是字符串。

list数据类型

  • 列表是应用我只是应该程序开发中非常有用的数据类型之一,列表能存在一组对象,因此它也可以被用于栈或者队列,在Redis中,与键相关的联的值可以是字符串组成的列表,Redis中的列表更像是数据结构中的双向链表。

hash数据类型

  • 哈希表示字段和值之间的映射关系,与JAVA中的Map类似,Redis数据集本身就可以看做一个哈希,其中字符串类型的键关联到如字符串和列表之类的数据对象,而Reidis的数据对象也可以再次使用哈希,其字段和值必须 是字符串。

set数据类型

  • 集合类型是由唯一,无序对象组成的集合(collection).它经常用于测试某个成员是集合中,重复项删除和集合运算(求并,交,差集),Redis的值对象可以是字符串集合。

zset(sortset)数据类型

  • 有序集合是一个类似于set但是更复杂的数据类型,单词sorted意为着这种集合中的每个元素都有一个可用于排序的权重,并且我们可以按顺序从集合中得到元素在某些需要一个保持数据有序的场景中,使用这种原生的序的特性是很方便的。

Redis的相关命令详解

关于命令的学习查询看这个网站: https://www.redis.net.cn/order/
或者:http://redisdoc.com/

常用命令

  • keys * 获取所有的key。
  • select 0 选择第一个库。
  • move myString 1 将当前的数据库key移动到某个数据库,目标库有,则不能移动。
  • flushdb 清除指定库。
  • randomkey 从当前数据库中随机返回。
  • type key 类型。
  • del key1 删除key。
  • exists key 判断是否存在key。
  • expire key 10 10过期。
  • pexpire key 1000 毫秒。
  • persist key 删除过期时间。
  • ttl key 查看还有多少秒过期,-1表示永不过期,-2表示已过期。

String类型相关命令

  • set key value
  • get key
  • getrange name 0 -1 字符串分段 0 -1是全部 0 -2 == n-1。
  • getset key newValue 设置值,返回旧值。
  • mset key1 key2 批量设置。
  • mget key1 key2 批量获取。
  • setnx key value 不存在就插入(not exists)
  • setrange key index value 从index开始替换value。
  • incr age 递增。
  • incrby age 10 递增。
  • decr age 递减。
  • decrby age 10 递减。
  • incrbyfloat 增减浮点数(将 key 所储存的值加上给定的浮点增量值(increment) 。)
  • append 追加。
  • strlen 长度。
  • object encoding key 得到key 的类型 string里面有三种编码。
    • int 用于能够副作用64位有符号整数表示的字符串。
    • embstr 用于长度小于或等于44字节 Redis3.x中是39字节,这种类型的编码在内存使用时性能更好。
    • raw 用于长度大于44字节的。

list

  • lpush mylist a b c 左插入。
  • rpush mylist x y z 右插入。
  • lrange mylist 0 -1 取出数据集合 0 -1 是取出所有 0 1取第第一个和第二个。
  • lpop mylist 弹出集合最后一个元素 弹出之后就没有了哦。
  • rpop mylist 弹出第一个元素 弹出之后就没有了哦。
  • llen mylist 长度。
  • lrem mylist count value 删除。
    • | COUNT 的值可以是以下几种:
      • | count > 0 : 从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为 COUNT 。
      • | count < 0 : 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值。
      • | count = 0 : 移除表中所有与 VALUE 相等的值。
  • lindex mylist 2 指定索引的值。
  • lset mylist 2 n 索引设值。
  • ltrim mylist 0 4
    • | 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
      • 下标 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
      • 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
  • linsert mylist before a 插入。
  • linsert mylist after a 插入。
    • | 命令用于在列表的元素前或者后插入元素。 当指定元素不存在于列表中时,不执行任何操作。 当列 表不存在时,被视为空列表,不执行任何操作。 如果 key 不是列表类型,返回一个错误。
  • rpoplpush list list2 转移列表的数据
    • | 命令用于移除列表的最后一个元素,并将该元素添加到另一个列表并返回。

hash

  • hset myhash name value
    • | 命令用于为哈希表中的字段赋值 。
      • | 如果哈希表不存在,一个新的哈希表被创建并进行 HSET 操作。
      • | 如果字段已经存在于哈希表中,旧值将被覆盖。
  • hget myhash name
  • hmset myhash name cxx age 25 note "i am notes"
  • hmget myhash name age note
  • hgetall myhash 获取所有的
  • hexists myhash name 是否存在
  • hsetnx myhash score 100 设置不存在的
  • hincrby myhash id 1 递增
  • hdel myhash name 删除
  • hkeys myhash 只取key
  • hvals myhash 只取value
  • hlen myhash 长度

set

  • sadd myset redis
  • smembers myset 数据集合
  • srem myset set1 删除
  • sismember myset set1 判断元素是否在集合中
  • scard key_name 个数
  • sdiff | sinter | sunion 操作:集合间运算:差集 | 交集 | 并集
  • srandmember 随机获取集合中的元素
  • spop 从集合中弹出一个元素

zset

  • zadd zset 1 one
  • zadd zset 2 two
  • zadd zset 3 three
  • zincrby zset 1 one 增长分数
  • zscore zset two 获取分数
  • zrange zset 0 -1 withscores 范围值
  • zrangebyscore zset 10 25 withscores 指定范围的值
  • zrangebyscore zset 10 25 withscores limit 1 2 分页
  • Zrevrangebyscore zset 10 25 withscores 指定范围的值
  • zcard zset 元素数量
  • Zcount zset 获得指定分数范围内的元素个数
  • Zrem zset one two 删除一个或多个元素
  • Zremrangebyrank zset 0 1 按照排名范围删除元素
  • Zremrangebyscore zset 0 1 按照分数范围删除元素
  • Zrank zset 0 -1 分数最小的元素排名为0
  • Zrevrank zset 0 -1 分数最大的元素排名为0
  • Zinterstore
  • zunionstore rank:last_week 7 rank:20150323 rank:20150324 rank:20150325 weights 1 1 1 1 1 1 1

解析配置文件redis.conf

配置文件在哪

Units单位

  • 配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit。
  • 对大小写不敏感。

INCLUDES包含

  • 和我们的spring配置文件类似,可以通过includes包含,redis.conf可以作为总闸,包含其他。

NETWORK通用

  • 默认情况下,如果没有指定“bind”配置指令,则Redis侦听用于连接服务器上所有可用的网络接口。
  • 可以只监听一个或多个选择的接口 "bind"配置指令,后面跟着一个或多个IP地址。
  • bind
    • 默认情况下,redis 在 server 上所有有效的网络接口上监听客户端连接。如果只想让它在一个或多个网络接口上监听,那你就绑定一个IP或者多个IP。多个ip空格分隔即可。

  • port 运行端口

  • Tcp-backlog
    • 设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列 + 已经完成三次握手队列。
    • 在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。注意Linux内核会将这个值减小到/proc/sys/net/core/somaxconn的值,所以需要确认增大somaxconn和tcp_max_syn_backlog两个值,来达到想要的效果。
  • timeout

  • 当客户端闲置多少秒后关闭连接,如果设置为0表示关闭该功能。

  • tcp-keepalive 300

    • 单位是秒,表示将周期性的使用SO_KEEPALIVE检测客户端是否还处于健康状态,避免服务器一直阻塞,官方给出的建议值是300S。
    • 建议设置成60。

GRNERAL通用

  • daemonize no
    • 是否以守护模式启动,默认为no,配置为yes时以守护模式启动,这时redis instance会将进程号pid写入默认文件/var/run/redis.pid。
  • supervised no
    • 可以通过upstart和systemd管理Redis守护进程,这个参数是和具体的操作系统相关的。
  • pidfile /var/run/redis_6379.pid
    • 配置pid文件路径。当redis以守护模式启动时,如果没有配置pidfile,pidfile默认值是/var/run/redis.pid 。
  • loglevel notice
    • 日志级别。可选项有:debug(记录大量日志信息,适用于开发、测试阶段); verbose(较多日志信息); notice(适量日志信息,使用于生产环境);warning(仅有部分重要、关键信息才会被记录)。
  • logfile ""
    • 日志文件的位置,当指定为空字符串时,为标准输出,如果redis已守护进程模式运行,那么日志将会输出到 /dev/null 。
  • syslog-enabled no
    • 是否把日志记录到系统日志。
  • syslog-ident
    • 设置系统日志的id 如 syslog-ident redis。
  • databases 16
    • 设置数据库的数目。默认的数据库是DB 0 ,可以在每个连接上使用select 命令选择一个不同的数据库,dbid是一个介于0到databases - 1 之间的数值。
  • always-show-logo yes
    • 是否一直显示日志。

SNAPSHOTTING快照

  • save
    • 保存数据到磁盘。格式是:save ,含义是在 seconds 秒之后至少有 changes个keys 发生改变则保存一次。
      • save 900 1 900秒有一条数据改变就保存
      • save 300 10 300秒有10条数据改变就保存
      • save 60 10000 600秒有10000条数据改变就保存
  • stop-writes-on-bgsave-error yes
    • 默认情况下,如果 redis 最后一次的后台保存失败,redis 将停止接受写操作,这样以一种强硬的方式让用户知道数据不能正确的持久化到磁盘, 否则就会没人注意到灾难的发生。 如果后台保存进程重新启动工作了,redis 也将自动的允许写操作。然而你要是安装了靠谱的监控,你可能不希望 redis 这样做,那你就改成 no 好了。
  • rdbcompression yes
    • 是否在dump .rdb数据库的时候压缩字符串,默认设置为yes。如果你想节约一些cpu资源的话,可以把它设置为no,这样的话数据集就可能会比较大。
  • rdbchecksum yes
    • 是否CRC64校验rdb文件,会有一定的性能损失(大概10%)
  • dbfilename dump.rdb
    • rdb文件的名字。

REPLICATION复制

主从复制在列举

SECURITY安全

  • 主要用于访问密码和查看,设置和取消

LIMITS限制

  • maxclients 10000
    • 设置redis同时可以与多少个客户端进行连接。默认情况下为10000个客户端。当你无法设置进程文件句柄限制时,redis会设置为当前的文件句柄限制值减去32,因为redis会为自身内部处理逻辑留一些句柄出来。如果达到了此限制,redis则会拒绝新的连接求,并且向这些连接请求方发出“max number of clients reached”以作回应。
  • maxmemory
    • 设置redis可以使用的内存量。一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定。如果redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”,那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。
    • 但是对于无内存申请的指令,仍然会正常响应,比如GET等。如果你的redis是主redis(说明你的redis有从redis),那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是“不移除”的情况下,才不用考虑这个因素。
  • maxmemory-policy
    • ① volatile-lru: 使用LRU算法移除key,只对设置了过期时间的键。
    • ② allkeys-lru: 使用LRU算法移除key。
    • ③ volatile-random: 在过期集合中移除随机的key,只对设置了过期时间的键。
    • ④ allkeys-random: 移除随机的key。
    • ⑤ volatile-ttl: 移除那些TTL值最小的key,即那些最近要过期的key。
    • ⑥ noeviction: 不进行移除。针对写操作,只是返回错误信息。
  • maxmemory-samples
    • 设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,redis默认会检查这么多个key并选择其中LRU的那个。

APPEND ONLY MODE 追加[持久化]

常见配置redis.conf介绍

参数说明
redis.conf 配置项说明如下:

  1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
    daemonize no

  2. 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
    pidfile /var/run/redis.pid

  3. 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字
    port 6379

  4. 绑定的主机地址
    bind 127.0.0.1
    5.当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
    timeout 300

  5. 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
    loglevel verbose

  6. 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null
    logfile stdout

  7. 设置数据库的数量,默认数据库为0,可以使用SELECT 命令在连接上指定数据库id
    databases 16

  8. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
    save <seconds> <changes>
    Redis默认配置文件中提供了三个条件:
    save 900 1
    save 300 10
    save 60 10000
    分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。

  9. 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
    rdbcompression yes

  10. 指定本地数据库文件名,默认值为dump.rdb
    dbfilename dump.rdb

  11. 指定本地数据库存放目录
    dir ./

  12. 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步
    slaveof <masterip> <masterport>

  13. 当master服务设置了密码保护时,slav服务连接master的密码
    masterauth <master-password>

  14. 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH 命令提供密码,默认关闭
    requirepass foobared

  15. 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
    maxclients 128

  16. 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
    maxmemory <bytes>

  17. 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
    appendonly no

  18. 指定更新日志文件名,默认为appendonly.aof
    appendfilename appendonly.aof

  19. 指定更新日志条件,共有3个可选值:
    no:表示等操作系统进行数据缓存同步到磁盘(快)
    always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
    everysec:表示每秒同步一次(折衷,默认值)
    appendfsync everysec

  20. 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)
    vm-enabled no

  21. 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
    vm-swap-file /tmp/redis.swap

  22. 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0
    vm-max-memory 0

  23. Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值
    vm-page-size 32

  24. 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。
    vm-pages 134217728

  25. 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4
    vm-max-threads 4

  26. 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
    glueoutputbuf yes

  27. 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
    hash-max-zipmap-entries 64
    hash-max-zipmap-value 512

  28. 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)
    activerehashing yes

  29. 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
    include /path/to/local.conf

Redis的持久化

概述

https://redis.io/topics/persistence

此页面提供了有关Redis持久性的技术说明,建议所有Redis用户阅读。有关Redis持久性及其提供的持久性保证的更广泛概述,您可能还需要阅读神秘化的Redis持久性。

Redis持久性

Redis提供了不同的持久性选项范围:

  • RDB持久性按指定的时间间隔执行数据集的时间点快照。
  • AOF持久性会记录服务器接收的每个写入操作,这些操作将在服务器启动时再次播放,以重建原始数据集。使用与Redis协议本身相同的格式记录命令,并且仅采用追加方式。当日志太大时,Redis可以在后台重写日志。
  • 如果希望,只要您的数据在服务器运行时就一直存在,则可以完全禁用持久性。
  • 可以在同一实例中同时合并AOF和RDB。请注意,在这种情况下,当Redis重新启动时,AOF文件将用于重建原始数据集,因为它可以保证是最完整的。

RDB【Redis DataBase】

什么是RDB

RDB的优势

  • RDB是Redis数据的非常紧凑的单文件时间点表示。RDB文件非常适合备份。例如,您可能希望在最近的24小时内每小时存档一次RDB文件,并在30天之内每天保存一次RDB快照。这使您可以在发生灾难时轻松还原数据集的不同版本。
  • RDB对于灾难恢复非常有用,它是一个紧凑的文件,可以传输到远程数据中心或Amazon S3(可能已加密)上。
  • RDB最大限度地提高了Redis的性能,因为Redis父进程为了持久化所需要做的唯一工作就是分叉一个孩子,其余的都将做。父实例将永远不会执行磁盘I / O或类似操作。
  • 与AOF相比,RDB允许大型数据集更快地重启。

RDB的缺点

  • 如果您需要在Redis停止工作(例如断电后)的情况下最大程度地减少数据丢失的机会,则RDB不好。您可以在生成RDB的位置配置不同的保存点(例如,在至少五分钟之后,对数据集进行100次写入,但是您可以有多个保存点)。但是,通常会每隔五分钟或更长时间创建- 一次RDB快照,因此,如果Redis出于任何原因在没有正确关闭的情况下停止工作,则应该准备丢失最新的数据分钟。
  • RDB需要经常使用fork()才能使用子进程将其持久化在磁盘上。如果数据集很大,Fork()可能很耗时,并且如果数据集很大且CPU性能不佳,则可能导致Redis停止为客户端服务几毫秒甚至一秒钟。AOF还需要fork(),但您可以调整要重写日志的频率,而无需在 - 持久性上进行权衡。
  • save 900 1
  • save 300 10
  • save 60 10000
  • 在指定的时间间隔内将内存中的数据集快照写入磁盘, 也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能 如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方 式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。

什么是FORK

  • Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。

保存位置及配置位置

  • Rdb 保存的是dump.rdb文件

如何触发RDB快照

  • 执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义。

如何恢复数据

  • 将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可。
CONFIG GET dir获取目录

优点

  • 适合大规模的数据恢复。
  • 对数据完整性和一致性要求不高。

缺点

  • 在一定间隔时间做一次备份,所以如果redis意外down掉的话,就 会丢失最后一次快照后的所有修改,Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑。

AOF

概述

AOF的优势

  • 使用AOF Redis更加持久:您可以有不同的fsync策略:完全没有fsync,每秒fsync,每个查询fsync。使用默认策略fsync时,每秒的写入性能仍然很好(fsync是使用后台线程执行的,并且在没有进行fsync的情况下,主线程将尽力执行写入操作。)但是您只能损失一秒钟的写入时间。

  • AOF日志仅是一个追加日志,因此,如果断电,也不会出现寻道或损坏问题。即使由于某种原因(磁盘已满或其他原因)以半写命令结束日志,redis-check-aof工具也可以轻松修复它。

  • Redis太大时,Redis可以在后台自动重写AOF。重写是完全安全的,因为Redis继续追加到旧文件时,会生成一个全新的文件,其中包含创建当前数据集所需的最少操作集,一旦准备好第二个文件,Redis会切换这两个文件并开始追加到新的那一个。

  • AOF以易于理解和解析的格式包含所有操作的日志。您甚至可以轻松导出AOF文件。例如,即使您使用FLUSHALL命令刷新了所有错误文件,如果在此期间未执行日志重写,您仍然可以保存数据集,只是停止服务器,删除最新命令,然后重新启动Redis。
    AOF的缺点

  • 对于相同的数据集,AOF文件通常大于等效的RDB文件。

  • 根据确切的fsync策略,AOF可能比RDB慢。通常,在将fsync设置为每秒的情况下,性能仍然很高,并且在禁用fsync的情况下,即使在高负载下,它也应与RDB一样快。即使在巨大的写负载情况下,RDB仍然能够提供有关最大延迟的更多保证。
    过去,我们在特定命令中遇到过罕见的错误(例如,其中一个涉及阻止命令,例如BRPOPLPUSH),导致生成的AOF在重载时无法重现完全相同的数据集。这些错误很少见,我们在测试套件中进行了测试,自动创建了随机的复杂数据集,然后重新加载它们以检查一切是否正常。但是,RDB持久性几乎是不可能的。更明确地说:Redis AOF通过增量更新现有状态来工作,就像MySQL或MongoDB一样,而RDB快照一次又一次地创建所有内容,从概念上讲更健壮。但是-1)请注意,每次Redis重写AOF时,都会从数据集中包含的实际数据开始重新创建AOF,与始终附加AOF文件(或重写为读取旧AOF而不是读取内存中的数据)相比,提高了对错误的抵抗力。2)我们从未收到过有关真实环境中检测到的AOF损坏的用户报告。

原理

  • 以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,Redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

保存位置及位置配置

  • Aof保存的是appendonly.aof文件

AOF启动/修复/恢复

  • 正常恢复
    • 启动:设置Yes 修改默认的appendonly no,改为yes。
    • 将有数据的aof文件复制一份保存到对应目录(config get dir)
    • 恢复:重启redis然后重新加载。
  • 异常恢复
    • 启动:设置Yes 修改默认的appendonly no,改为yes。
    • 备份被写坏的AOF文件。
    • 修复: Redis-check-aof --fix进行修复。
    • 恢复:重启redis然后重新加载。

优势

  • 每修改同步:appendfsync always 同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好。
  • 每秒同步:appendfsync everysec 异步操作,每秒记录 如果一秒内宕机,有数据丢失,不同步:appendfsync no 从不同步。

劣势

  • 相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb。
  • Aof运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同。

说了那么多,我们选择哪一个呢

官方建议

  • 好的,那我该怎么用?
  • 通常的指示是,如果您想要某种与PostgreSQL可以提供的功能相当的数据安全性,则应同时使用两种持久性方法。
  • 如果您非常关心数据,但是在灾难情况下仍然可以承受几分钟的数据丢失,则只需使用RDB。
  • 有很多用户单独使用AOF,但我们不建议这样做,因为不时拥有RDB快照对于进行数据库备份,加快重启速度以及AOF引擎中存在错误是一个好主意。
  • 注意:由于所有这些原因,我们将来可能会最终将AOF和RDB统一为一个持久性模型(长期计划)。
  • 以下各节将说明有关这两个持久性模型的更多详细信息。

整理我们的理解及处理方式

  • RDB 持久化方式能够在指定的时间间隔能对你的数据进行快照存储
  • AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些 命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾. Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
    • 只做缓存
      • 如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式。

同时开启两种持久化方式

  • 在这种情况下,当Redis重启的时候会优先载入AOF文件来恢复原始的数据, 因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
  • RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。那要不要只使用AOF呢? 作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份), 快速重启,而且不会有AOF可能潜在的bug,留着作为一个万一的手段。

性能建议

  • 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。
  • 如果Enalbe AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。代价一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。默认超过原大小100%大小时重写可以改到适当的数值。
  • 如果不Enable AOF ,仅靠Master-Slave Replication 实现高可用性也可以。能省掉一大笔IO也减少了rewrite时带来的系统波动。代价是如果Master/Slave同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个。新浪微博就选用了这种架构。

Redis的事务

什么是Redis事务

可以一次执行多个命令,本质是一组命令的集合。一个事务中的 所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞。

  • 官网说明

https://redis.io/topics/transactions

能为我们做什么

一个队列中,一次性、顺序性、排它性的执行一系列命令。

怎么使用呢?

① 常用命令

② 情况1:正常执行

③ 情况2:放弃事务

④ 情况3:全体连坐

⑤ 情况4:冤头债主

⑥ 情况5:watch监控

  • 悲观锁/乐观锁

  • 悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写- 锁等,都是在做操作之前先上锁。

  • 乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,
    乐观锁策略:提交版本必须大于记录当前版本才能执行更新。

  • 初始化信用卡可用余额和欠额

  • 无加塞篡改

  • 先监控再开启multi, 保证两笔金额变动在同一个事务内

  • 有加塞篡改

  • 监控了key,如果key被修改了,后面一个事务的执行失效

  • unwatch

  • 一旦执行了exec之前加的监控锁都会被取消掉了

小结

  • Watch指令,类似乐观锁,事务提交时,如果Key的值已被别的客户端改变, 比如某个list已被别的客户端push/pop过了,整个事务队列都不会被执行。
  • 通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化, EXEC命令执行的事务都将被放弃,同时返回Nullmulti-bulk应答以通知调用者事务执行失败。

阶段

  • 开启:以MULTI开始一个事务。
  • 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面。
  • 执行:由EXEC命令触发事务。

特性

  • 单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  • 没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行, 也就不存在”事务内的查要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题。
  • 不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。

Redis的复制(Master/Slave)

什么是复制

  • Redis 的读并发量太大怎么办?
  • 单机版的Redis 挂掉怎么办?
  • 需要写并发又要安全 在redis 3.0 后,官方发布了集群方案。

① 官网说明

② 行话

  • 行话:也就是我们所说的主从复制,主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主。

有什么作用

  • 读写分离。
  • 容灾恢复。

怎么使用

① 配从不配主

② 配置命令 【SLAVEOF 主库IP 主库端口】

  • 每次与master断开之后,都需要重新连接,除非你配置进redis.conf文件Info replication可以查看。

③ 详细操作

  1. 拷贝多个 redis.conf 文件
  2. 开启 daemonize yes
  3. pid 文件名字
  4. 指定端口
  5. log 文件名字
  6. dump.rdb 名字

④ 一主二仆

演示问题

  • 切入点问题?slave1、slave2是从头开始复制还是从切入点开始复制?比如从k4进来,那之前的123是否也可以复制。
  • 从机是否可以写?set可否?
  • 主机shutdown后情况如何?从机是上位还是原地待命。
  • 主机又回来了后,主机新增记录,从机还能否顺利复制?
  • 其中一台从机down后情况如何?依照原有它能跟上大部队吗?

⑤ 薪火相传

  • 上一个Slave可以是下一个slave的Master,Slave同样可以接收其他 slaves的连接和同步请求,那么该slave作为了链条中下一个的master, 可以有效减轻master的写压力中途变更转向:会清除之前的数据,重新建立拷贝最新的Slaveof 新主库IP 新主库端口。

⑥ 反客为主

SLAVEOF no one
  • 使当前数据库停止与其他数据库的同步,转成主数据库。

复制的原理

Slave启动成功连接到master后会发送一个sync命令Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令, 在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步。

  • 全量复制: 而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
  • 增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步但是只要是重新连接master,一次完全同步(全量复制)将被自动执行。

哨兵模式

① 什么是哨兵模式

反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。

② 怎么玩(使用步骤)

  • 调整结构,6379带着80、81。
  • 自定义的/myredis目录下新建 sentinel.conf 文件,名字绝不能错。
  • 配置哨兵,填写内容
sentinel monitor 被监控主机名字(自己起名字) 127.0.0.1 6379 1
  • 如: sentinel monitor hos6379 127.0.0.1 6379 1
  • 上面最后一个数字1,表示主机挂掉后salve投票看让谁接替成为主机,得票数多少后成为主机。
  • 启动哨兵
./redis-sentinel /root/master-slaver/sentinel.conf
  • 正常主从演示。
  • 原有的master挂了。
  • 投票新选。
  • 重新主从继续开工,info replication查查看。
  • 问题
    如果之前的master重启回来,会不会双master冲突?

③ 一组sentinel能同时监控多个Master

复制的缺点

由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。

高可用高并发集群配置

中心化和去中心化

① 中心化

  • 意思是所有的节点都要有一个主节点。

缺点

  • 中心挂了,服务就挂了
    • 中心处理数据的能力有限,不能把节点性能发挥到最大。

特点

  • 就是一个路由作用。

② 去中心化

特点

  • 去掉路由,我自己来路由。

以上通俗的就是

  • 中心化: 几个经过认证的嘉宾在‘讲话’,所有其他人在听。
  • 去中心化:每个人都可以‘讲话’,每个人都可以选择听或者讲。

Redis集群的执行流程分析

① 哈希槽说明

  • Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。

  • 当你往Redis Cluster中加入一个Key时,会根据crc16(key) mod 16384计算这个key应该分布到哪个hash slot中,一个hash slot中会有很多key和value。你可以理解成表的分区,使用单节点时的redis时只有一个表,所有的key都放在这个表里;改用Redis Cluster以后会自动为你生成16384个分区表,你insert数据时会根据上面的简单算法来决定你的key应该存在哪个分区,每个分区里有很多key。

② 执行流程分析

  • 假如redis集群里面能存放90个key,那么redis集群把90key平分到3个主机redis对每个主机里面30个存储位置都编号,当应用连接到主机1上面时,应该发送一个写的命令。
  • 主机使用crc16算出槽号
  • 如果槽号在1-30 可以直接操作主机1。
  • 如果槽号在31-60那么redis会转发到主机2。
  • 如果应该再发一个命令set age 22。
  • 那么主机2使用crc16再算槽号再转发。

Redis集群的搭建

① 文档

http://redis.cn/topics/cluster-tutorial.html

② 原理:去中心化

③ 集群规则

机器编号 ip port
1 192.168.120.129 7000
2 192.168.120.129 7001
3 192.168.120.129 7002
4 192.168.120.129 7003
5 192.168.120.129 7004
6 192.168.120.129 7005

④ 搭建过程

  1. 新建文件夹。
  2. 准备一个服务端程序。
  3. 准备6个redis的配置文件。

Redis-1

bind 0.0.0.0                    69行
port 7000                       92行
daemonize yes                   136行
# 打开aof 持久化
appendonly yes                  672行 
# 开启集群
cluster-enabled yes             814行
# 集群的配置文件,该文件自动生成   
cluster-config-file nodes-7000.conf  822行
# 集群的超时时间
cluster-node-timeout 5000         828行

Redis-2

daemonize yes
bind 0.0.0.0
port 7001
# 打开aof 持久化
appendonly yes
# 开启集群
cluster-enabled yes
# 集群的配置文件,该文件自动生成
cluster-config-file nodes-7001.conf
# 集群的超时时间
cluster-node-timeout 5000

Redis-3

daemonize yes
bind 0.0.0.0
port 7002
# 打开aof 持久化
appendonly yes
# 开启集群
cluster-enabled yes
# 集群的配置文件,该文件自动生成
cluster-config-file nodes-7002.conf
# 集群的超时时间
cluster-node-timeout 5000

Redis-4

daemonize yes
bind 0.0.0.0
port 7003
# 打开aof 持久化
appendonly yes
# 开启集群
cluster-enabled yes
# 集群的配置文件,该文件自动生成
cluster-config-file nodes-7004.conf
# 集群的超时时间
cluster-node-timeout 5000

Redis-5

daemonize yes
bind 0.0.0.0
port 7004
# 打开aof 持久化
appendonly yes
# 开启集群
cluster-enabled yes
# 集群的配置文件,该文件自动生成
cluster-config-file nodes-7005.conf
# 集群的超时时间
cluster-node-timeout 5000

Redis-6

daemonize yes
bind 0.0.0.0
port 7005
# 打开aof 持久化
appendonly yes
# 开启集群
cluster-enabled yes
# 集群的配置文件,该文件自动生成
cluster-config-file nodes-7006.conf
# 集群的超时时间
cluster-node-timeout 5000
  • 同时启动所有的redis

  • 使用脚本创建集群(分配槽)

  • 找到集群脚本,在src/src/redis-trib.rb 要安装Ruby的环境【不推荐】

  • 使用docker 下载redis-trib的镜像运行【推荐】

  • 安装Docker

yum install docker
  • 启动docker
systemctl start docker
  • 下载镜像
docker pull inem0o/redis-trib
docker run -it --net host inem0o/redis-trib create --replicas 1 
    192.168.120.129:7000 192.168.120.129:7001 
    192.168.120.129:7002 192.168.120.129:7003 
    192.168.120.129:7004 192.168.120.129:7005
  • -it 是为了可以输入。
  • --net host 是为了上docker容器能连接上本地的宿主机。

  • 测试集群环境

  • -c 表示连接集群。

  • 到此集群搭建完成。

Redis客户端使用

工具说明

  • 使用 可以看到层级关系。

  • 使用

  • 这个没有层级关系

下载客户端redis plus

安装

连接

关闭linux防火墙

  • Centos6
查看防火墙状态: 
[root@centos6 ~]# service iptables status
iptables:未运行防火墙。
开启防火墙:
[root@centos6 ~]# service iptables start
关闭防火墙:
[root@centos6 ~]# service iptables stop
  • centos7
查看防火墙状态: 
firewall-cmd --state
关闭防火墙
systemctl stop firewalld.service
开启防火墙
systemctl start firewalld.service
禁用防火墙
systemctl disable firewalld.service
  • 注释redis.conf里面的bind 127.0.0.1

  • 修改redis.conf里面的密码

  • 验证密码是否成功

  • 连接

  • 连接成功

Java连接 Redis

Jedis所需要的jar包依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.1.0</version>
</dependency>

Jedis常用操作

① 测试连通性

public class TestRedisPing {
    public static void main(String[] args) {
        //创建连接
        Jedis jedis=new Jedis("192.168.120.129", 6379);
        //设置密码 如果没有密码可以不设置
        jedis.auth("123456");
        //调用 ping方法
        String ping = jedis.ping();
        //输出PONG
        System.out.println(ping);
        jedis.close();
    }
}

② 常规操作

public class TestReidsCommon {

    public static void main(String[] args) {
        // 创建连接
        Jedis jedis = new Jedis("192.168.120.129", 6379);
        // 设置密码 如果没有密码可以不设置
        jedis.auth("123456");
        // key
        Set<String> keys = jedis.keys("*");
        for (Iterator<String> iterator = keys.iterator(); iterator.hasNext();) {
            String key = (String) iterator.next();
            System.out.println(key);
        }
        System.out.println("jedis.exists====>" + jedis.exists("k2"));
        System.out.println(jedis.ttl("k1"));
        // String
        // jedis.append("k1","myreids");
        System.out.println(jedis.get("k1"));
        jedis.set("k4", "k4_redis");
        System.out.println("----------------------------------------");
        jedis.mset("str1", "v1", "str2", "v2", "str3", "v3");
        System.out.println(jedis.mget("str1", "str2", "str3"));
        // list
        System.out.println("----------------------------------------");
        // jedis.lpush("mylist","v1","v2","v3","v4","v5");
        List<String> list = jedis.lrange("mylist", 0, -1);
        for (String element : list) {
            System.out.println(element);
        }
        // set
        jedis.sadd("orders", "jd001");
        jedis.sadd("orders", "jd002");
        jedis.sadd("orders", "jd003");
        Set<String> set1 = jedis.smembers("orders");
        for (Iterator<String> iterator = set1.iterator(); iterator.hasNext();) {
            String string = (String) iterator.next();
            System.out.println(string);
        }
        jedis.srem("orders", "jd002");
        System.out.println(jedis.smembers("orders").size());
        // hash
        jedis.hset("hash1", "userName", "lisi");
        System.out.println(jedis.hget("hash1", "userName"));
        Map<String, String> map = new HashMap<String, String>();
        map.put("telphone", "13874578451");
        map.put("address", "xhh");
        map.put("email", "303158131@qq.com");
        jedis.hmset("hash2", map);
        List<String> result = jedis.hmget("hash2", "telphone", "email");
        for (String element : result) {
            System.out.println(element);
        }
        // zset
        jedis.zadd("zset01", 60d, "v1");
        jedis.zadd("zset01", 70d, "v2");
        jedis.zadd("zset01", 80d, "v3");
        jedis.zadd("zset01", 90d, "v4");

        Set<String> s1 = jedis.zrange("zset01", 0, -1);
        for (Iterator<String> iterator = s1.iterator(); iterator.hasNext();) {
            String string = (String) iterator.next();
            System.out.println(string);
        }
        jedis.close();
    }
}

③ 事务提交

  • 常规操作
public class TestReidsTranaction {

    public static void main(String[] args) {
        // 创建连接
        Jedis jedis = new Jedis("192.168.120.129", 6379);
        // 设置密码 如果没有密码可以不设置
        jedis.auth("123456");
         //监控key,如果该动了事务就被放弃
         /*3
         jedis.watch("serialNum");
         jedis.set("serialNum","s#####################");
         jedis.unwatch();*/
         
         Transaction transaction = jedis.multi();//被当作一个命令进行执行
         Response<String> response = transaction.get("serialNum");
         transaction.set("serialNum","s002");
         response = transaction.get("serialNum");
         transaction.lpush("list3","a");
         transaction.lpush("list3","b");
         transaction.lpush("list3","c");
         
         transaction.exec();
         //2 transaction.discard();
         System.out.println("serialNum***********"+response.get());
        jedis.close();

    }

}
  • 加锁操作
public class TestReidsTranactionLock {

    public boolean transMethod() {
        // 创建连接
        Jedis jedis = new Jedis("192.168.120.129", 6379);
        // 设置密码 如果没有密码可以不设置
        jedis.auth("123456");
        int balance;// 可用余额
        int debt;// 欠额
        int amtToSubtract = 10;// 实刷额度

        jedis.watch("balance");
        // jedis.set("balance","5");//此句不该出现,讲课方便。模拟其他程序已经修改了该条目
        balance = Integer.parseInt(jedis.get("balance"));
        if (balance < amtToSubtract) {
            jedis.unwatch();
            System.out.println("modify");
            jedis.close();
            return false;
        } else {
            System.out.println("***********transaction");
            Transaction transaction = jedis.multi();
            transaction.decrBy("balance", amtToSubtract);
            transaction.incrBy("debt", amtToSubtract);
            transaction.exec();
            balance = Integer.parseInt(jedis.get("balance"));
            debt = Integer.parseInt(jedis.get("debt"));
            System.out.println("*******" + balance);
            System.out.println("*******" + debt);
            jedis.close();
            return true;
        }
    }

    /**
     * 通俗点讲,watch命令就是标记一个键,如果标记了一个键, 在提交事务前如果该键被别人修改过,那事务就会失败,这种情况通常可以在程序中 重新再尝试一次。
     * 首先标记了键balance,然后检查余额是否足够,不足就取消标记,并不做扣减; 足够的话,就启动事务进行更新操作,
     * 如果在此期间键balance被其它人修改, 那在提交事务(执行exec)时就会报错, 程序中通常可以捕获这类错误再重新执行一次,直到成功。
     */
    public static void main(String[] args) {
        TestReidsTranactionLock test = new TestReidsTranactionLock();
        boolean retValue = test.transMethod();
        System.out.println("main retValue-------: " + retValue);
    }

}

④ 主从复制

public class TestReidsMS {

    public static void main(String[] args) throws InterruptedException {
        // 创建连接
        Jedis jedis_M = new Jedis("192.168.120.129", 6379);
        Jedis jedis_S = new Jedis("192.168.120.129", 6380);

        jedis_S.slaveof("127.0.0.1", 6379);

        jedis_M.set("k6", "v6");

        Thread.sleep(500);
        System.out.println(jedis_S.get("k6"));
        jedis_M.close();
        jedis_S.close();
    }

}

JedisPool

① 为什么要使用JedisPool

  • 获取Jedis实例需要从JedisPool中获取。
  • 用完Jedis实例需要返还给JedisPool。
  • 如果Jedis在使用过程中出错,则也需要还给JedisPool。

② 案例见代码

JedisPoolUtil

package com.qc.redis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * @author 30315
 * @title: JedisPoolUtil
 * @projectName JedisPro-01
 * @description: TODO
 * @date 2020-02-2810:17
 */
public class JedisPoolUtil {

    private static volatile JedisPool jedisPool = null;
    private static final String HOST = "192.168.41.129";
    private static final Integer PORT = 6379;

    private JedisPoolUtil() {
    }

    public static JedisPool getJedisPoolInstance() {
        if (null == jedisPool) {
            synchronized (JedisPoolUtil.class) {
                if (null == jedisPool) {
                    JedisPoolConfig poolConfig = new JedisPoolConfig();
                    poolConfig.setMaxTotal(1000);
                    poolConfig.setMaxIdle(32);
                    poolConfig.setMaxWaitMillis(100 * 1000);
                    poolConfig.setTestOnBorrow(true);

                    jedisPool = new JedisPool(poolConfig, HOST, PORT);
                }
            }
        }
        return jedisPool;
    }

    public static void release(JedisPool jedisPool, Jedis jedis) {
        if (null != jedis) {
            try {
                jedis.close();
            } finally {
                jedis.close();
            }
        }
    }


}
  • 使用
package com.qc.redis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

/**
 * @author 30315
 * @title: TestJedisPool
 * @projectName JedisPro-01
 * @description: TODO
 * @date 2020-02-2810:20
 */
public class TestJedisPool {

    public static void main(String[] args) {
        JedisPool jedisPool = JedisPoolUtil.getJedisPoolInstance();
        Jedis jedis = null;

        try {
            jedis = jedisPool.getResource();
            String str = jedis.set("key1", "value1");
            System.out.println(str);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JedisPoolUtil.release(jedisPool, jedis);
        }
    }

}
  • 配置总结all
属性名 描述
JedisPool 的配置参数大部分是由JedisPoolConfig的对应项来赋值的。
maxTotal/maxActive 控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted。
maxIdle 控制一个pool最多有多少个状态为idle(空闲)的jedis实例;
whenExhaustedAction 表示当pool中的jedis实例都被allocated完时,pool要采取的操作;默认有三种。
WHEN_EXHAUSTED_FAIL 表示无jedis实例时,直接抛出NoSuchElementException;
WHEN_EXHAUSTED_BLOCK 则表示阻塞住,或者达到maxWait时抛出JedisConnectionException;
WHEN_EXHAUSTED_GROW 则表示新建一个jedis实例,也就说设置的maxActive无用;
setMaxWaitMillis/maxWait 表示当borrow一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛JedisConnectionException;
testOnBorrow 获得一个jedis实例的时候是否检查连接可用性(ping());如果为true,则得到的jedis实例均是可用的;
testOnReturn return 一个jedis实例给pool时,是否检查连接可用性(ping());
testWhileIdle 如果为true,表示有一个idle object evitor线程对idle object进行扫描,如果validate失败,此object会被从pool中drop掉;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义;
timeBetweenEvictionRunsMillis 表示idle object evitor两次扫描之间要sleep的毫秒数;
numTestsPerEvictionRun 表示idle object evitor每次扫描的最多的对象数;
minEvictableIdleTimeMillis 表示一个对象至少停留在idle状态的最短时间,然后才能被idle object evitor扫描并驱逐;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义;
softMinEvictableIdleTimeMillis 在minEvictableIdleTimeMillis基础上,加入了至少minIdle个对象已经在pool里面了。如果为-1,evicted不会根据idle time驱逐任何对象。如果minEvictableIdleTimeMillis>0,则此项设置无意义,且只有在timeBetweenEvictionRunsMillis大于0时才有意义;
lifo borrowObject返回对象时,是采用DEFAULT_LIFO(last in first out,即类似cache的最频繁使用队列),如果为False,则表示FIFO队列;
  • 其中JedisPoolConfig对一些参数的默认设置如下
属性名
testWhileIdle true
minEvictableIdleTimeMills 60000
timeBetweenEvictionRunsMillis 30000
numTestsPerEvictionRun -1

Spring里面使用Redis并实现缓存

Spring集成Redis

① 创建项目

② 修改pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.qc</groupId>
    <artifactId>Spring-Redis-Pro-01</artifactId>
    <version>1.0</version>

    <!-- jar包版本声明 -->
    <properties>
        <spring.version>4.3.24.RELEASE</spring.version>
        <jedis.version>3.1.0</jedis.version>
    </properties>

    <!-- 声明需要的依赖的具体的资源 -->
    <dependencies>
        <!-- 导入spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>${jedis.version}</version>
        </dependency>
    </dependencies>
</project>

③ 搭建spring的环境

application-redis.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">

        <property name="maxIdle" value="20"></property>
        <property name="maxTotal" value="25"></property>
        <property name="minIdle" value="10"></property>
    </bean>
    <bean id="jedisPool" class="redis.clients.jedis.JedisPool">
        <constructor-arg name="poolConfig" ref="poolConfig"></constructor-arg>
        <constructor-arg name="host" value="192.168.41.129"></constructor-arg>
        <constructor-arg name="port" value="6379"></constructor-arg>
        <!--
        <constructor-arg name="password" value="123456"></constructor-arg>
        <constructor-arg name="timeout" value="5000"></constructor-arg>
        -->
    </bean>
</beans>

④ 测试

package com.qc.redis.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

/**
 * @author 30315
 * @title: SpringRedisTest
 * @projectName Spring-Redis-Pro-01
 * @description: TODO
 * @date 2020-02-2810:50
 */
public class SpringRedisTest {

    public static void main(String[] args) {
        ApplicationContext applicationContext =
        new ClassPathXmlApplicationContext("classpath:application-redis.xml");

        JedisPool jedisPool = applicationContext.getBean(JedisPool.class);
        System.out.println(jedisPool);

        Jedis jedis = jedisPool.getResource();
        //接下来的操作就和java里面一样的了

    }

}

实现菜单数据的缓存

① 简单原理图

② 修改pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.qc</groupId>
    <artifactId>Spring-Redis-Pro-01</artifactId>
    <version>1.0</version>

    <!-- jar包版本声明 -->
    <properties>
        <mybatis.version>3.5.2</mybatis.version>
        <mybatis-spring.version>2.0.2</mybatis-spring.version>
        <spring.version>4.3.24.RELEASE</spring.version>
        <druid.version>1.0.18</druid.version>
        <mysql.version>8.0.17</mysql.version>
        <fastjson.version>1.2.59</fastjson.version>
        <!-- 注意只能使用2.0以下的版本 -->
        <log4j.version>1.2.17</log4j.version>
        <jedis.version>3.1.0</jedis.version>
    </properties>
    <!-- 声明需要的依赖的具体的资源 -->
    <dependencies>
        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>${mybatis.version}</version>
        </dependency>
        <!-- mybatis-spring -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>${mybatis-spring.version}</version>
        </dependency>
        <!-- 导入spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- mysql数据库驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <!-- json -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
        <!-- log4j -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>${jedis.version}</version>
        </dependency>
    </dependencies>
</project>

③ 创建Menu

public class Menu {
    private Integer id;
    private Integer pid;
    private String title;
    private String href;
    private Integer spread;
    private String target;
    private String icon;
    private Integer available;
    //get set方法
}

④ 创建MenuMapper

public interface MenuMapper {
    /**
     * 查询所有菜单
     */
    List<Menu> queryAllMenu();
   
}

⑤ 创建MenuMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qc.mapper.MenuMapper">
    <resultMap id="BaseResultMap" type="com.qc.domain.Menu">
        <id column="id" jdbcType="INTEGER" property="id" />
        <result column="pid" jdbcType="INTEGER" property="pid" />
        <result column="title" jdbcType="VARCHAR" property="title" />
        <result column="href" jdbcType="VARCHAR" property="href" />
        <result column="spread" jdbcType="INTEGER" property="spread" />
        <result column="target" jdbcType="VARCHAR" property="target" />
        <result column="icon" jdbcType="VARCHAR" property="icon" />
        <result column="available" jdbcType="INTEGER"
                property="available" />
    </resultMap>
    <sql id="Base_Column_List">
        id, pid, title, href, spread, target, icon, available
    </sql>
    <!-- 查询所有菜单 -->
    <select id="queryAllMenu" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from sys_menu
    </select>
</mapper>

⑥ 创建MenuService

/**
 * 菜单管理的服务接口
 * @author LJH
 *
 */ 
public interface MenuService {

    /**
     * 查询所有菜单返回
     * List<Menu>
     */
    public List<Menu> queryAllMenuForList();
    
}

⑦ 创建MenuServiceImpl

@Service
public class MenuServiceImpl implements MenuService {
    @Autowired
    private MenuMapper menuMapper;

    @Override
    public List<Menu> queryAllMenuForList() {
        return menuMapper.queryAllMenu();
    }
}

⑧ application-dao.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 引入db.properties -->
    <context:property-placeholder
            location="classpath:db.properties" system-properties-mode="FALLBACK"/>

    <!-- 声明dataSource -->
    <bean id="dataSource"
          class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!-- 注入连接属性 -->
        <property name="driverClassName" value="${driverClassName}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
    </bean>
    <!-- 声明sessionFactory 并注入mybatis.cfg.xml -->
    <bean id="sqlSessionFactory"
          class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 注入数据源 -->
        <property name="dataSource" ref="dataSource"></property>
        <!-- 注入mapper.xml -->
        <property name="mapperLocations">
            <array>
                <value>classpath:mapper/*Mapper.xml</value>
            </array>
        </property>
    </bean>

    <!-- 扫描mapper接口 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 注入mapper接口所在的包 注意多个包的情况的配置 -->
        <property name="basePackage">
            <value>
                com.qc.mapper
            </value>
        </property>
        <!-- 注入sqlSessionFactory -->
        <property name="sqlSessionFactoryBeanName"
                  value="sqlSessionFactory"></property>
    </bean>
</beans>

⑨ application-service.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <context:component-scan base-package="com.qc.service.impl"/>

    <!-- 1,声明事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 启动注解事务 -->
    <!-- <tx:annotation-driven/> -->
    <!-- 2,声明事务的传播特性 也就是通知 -->
    <tx:advice id="advise" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 以add开头的方法名需要事务 -->
            <tx:method name="add*" propagation="REQUIRED"/>
            <tx:method name="save*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="change*" propagation="REQUIRED"/>
            <tx:method name="reset*" propagation="REQUIRED"/>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="load*" read-only="true"/>
            <tx:method name="*" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    <!-- 3进行AOP织入 -->
    <aop:config>
        <!-- 声明切面 -->
        <aop:pointcut expression="execution(* com.qc.service.impl.*.*(..))" id="pc"/>
        <!-- 织入 -->
        <aop:advisor advice-ref="advise" pointcut-ref="pc"/>
    </aop:config>
</beans>

⑩ application-redis.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxIdle" value="20"></property>
        <property name="maxTotal" value="25"></property>
        <property name="minIdle" value="10"></property>
    </bean>

    <bean id="jedisPool" class="redis.clients.jedis.JedisPool">
        <constructor-arg name="poolConfig" ref="poolConfig"></constructor-arg>
        <constructor-arg name="host" value="192.168.41.129"></constructor-arg>
        <constructor-arg name="port" value="6379"></constructor-arg>
        <!--
        <constructor-arg name="password" value="123456"></constructor-arg>
        <constructor-arg name="timeout" value="5000"></constructor-arg>
        -->
    </bean>
</beans>

⑪ applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <import resource="classpath:application-dao.xml"/>
    <import resource="classpath:application-service.xml"/>
    <import resource="classpath:application-redis.xml"/>
</beans>

⑫ log4j.properties

# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# MyBatis logging configuration...
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

⑬ db.properties

jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
jdbc.username=root
jdbc.password=1234

⑭ CacheAspect

import com.alibaba.fastjson.JSON;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

@Component
@Aspect
@EnableAspectJAutoProxy
public class CacheAspect {
    @Autowired
    private JedisPool jedisPool;

    private static final String ALL_MENU_LABEL = "alll-menu-data";

    @Pointcut("execution(* com.qc.service.impl.MenuServiceImpl.queryAllMenuForList())")
    public void menuQueryPc() {

    }
    @Around(value="menuQueryPc()")
    public Object cache(ProceedingJoinPoint point) {
        Jedis jedis = jedisPool.getResource();
        if(jedis.exists(ALL_MENU_LABEL)) {
            String menuJson = jedis.get(ALL_MENU_LABEL);
            List<Menu> menu = JSON.parseArray(menuJson, Menu.class);
            return menu ;
        }
        Object result = null ;
        try {
            System.out.println("执行真实方法的调用");
            result = point.proceed(point.getArgs()); // 在此实现了真实方法的调用
            jedis.set(ALL_MENU_LABEL,JSON.toJSONString(result));
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return result;
    }

}

⑮ application-aspect.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

    <context:component-scan base-package="com.qc.aspect"/>

</beans>

⑯ applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <import resource="classpath:application-dao.xml"/>
    <import resource="classpath:application-service.xml"/>
    <import resource="classpath:application-redis.xml"/>
    <import resource="classpath:application-aspect.xml"/>
</beans>

⑰ 打断点测试

public class SpringRedisTest {
    
    public static void main(String[] args) {
        
        ApplicationContext context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        MenuService menuService=context.getBean(MenuService.class);
        //接下来的操作就和java里面一样的了
        List<Menu> list = menuService.queryAllMenuForList();
        for (Menu menu : list) {
            System.out.println(menu);
        }       
    }
}

Spring+Redis实现秒杀

  • 页面 倒计时
  • 倒计时结束,用户可以点击按钮抢购
  • 只要用户点击,相当于一个线程到后台服务器 (一次启动1000线程去抢购)
  • 把商品初始化到redis list
    • Lpop
    • rpop
public class TestRedisMKill {

	private static final String PRODUCTID = "productid";

	public static void main(String[] args) {
		
		ApplicationContext context=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
		//向redis初始化商品
		JedisPool jedisPool = context.getBean(JedisPool.class);
		Jedis jedis = jedisPool.getResource();
		jedis.flushAll();
		for (int i = 1; i <=100; i++) {
			jedis.lpush(PRODUCTID, "商品ID: "+i);
		}
		jedis.close();
		
		//模拟线程抢购
		for (int i = 1; i <=1000; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					Jedis jedis2 = jedisPool.getResource();
					String lpop = jedis2.lpop(PRODUCTID);
					if(null==lpop) {
						System.out.println(Thread.currentThread().getName()+":抢购失败");
					}else {
						System.out.println(Thread.currentThread().getName()+":抢购成功 商品ID:"+lpop);
						//向MQ发送一个消息  把商品ID和用户ID传到后台  启动一个线程去处理抢购成功这后的业务
					}
					jedis2.close();
				}
			},"张三"+i).start();
		}
	}	
}

SpringBoot中使用Redis

posted @ 2020-02-19 18:44  Leader_TBlog  阅读(404)  评论(0)    收藏  举报