【狂神说Java】Redis最新超详细版教程通俗易懂_哔哩哔哩_bilibili

总体大纲outline

nosql讲解,非关系型数据库

阿里巴巴架构演进

nosql数据模型

Nosql四大分类

CAP

BASE

Redis入门

五大基本类型

  • String
  • List
  • Set
  • Hash
  • Zset

三种特殊数据类型

  • geo
  • hyprologlog
  • bitmap

Redis配置文件如何读取
Redis持久化

  • RDB
  • AOF

Redis的一些事务操作
Redis实现订阅发布

  • 消息队列

Redis主从复制

  • 哨兵模式,现在公司中所有的集群都采用哨兵模式

缓存穿透,以及解决方式
缓存雪崩,以及雪崩方式,微服务一环套一环,可能会导致雪崩
基础API之jedis详情
SpringBoot集成Redis操作
Redis的实践分析

Nosql

大数据时代,一般的数据库无法进行分析处理了Hadoop

单机SQL时代,更多使用静态网页

  • 如果数据量过大,一个机器放不下
  • 数据量超过300万时一定要创建索引,数据库现在使用B+Tree,一个机器的内存也放不下
  • 数据库访问量比较大,一开始是读写混合的,一个服务器也承受不了

出现这三种情况,就需要升级,但是如果没有这么大的数据量,应该从单机开始

Memcached缓存+MySQL+垂直拆分(读写分离)
网站百80%的操作都是在读,减轻数据库的压力可以使用缓存提高效率,缓存使用什么技术都没所谓

  • 发展过程,先是优化数据结构和索引–文件缓存(IO操作)–Memcached(当时最热门的技术)

分库分表+水平拆分+M有SQL集群
使用缓存解决了读的问题,用分集群的方式解决读的问题,数据存在不同的库中

早些年,MySAM使用 表锁,读取一行要将整个表锁起来,十分影响效率,高并发下会出现严重问题

后来转为innodb 行锁

慢慢的就开始使用分库分表来解决写的压力,将表拆分,不同的业务使用不同表,存放在不同的数据库中,不同的业务使用单独的数据库,结合微服务,mysql推出了表分区,但是很少公司使用

Mysql的集群已经满足了那个年代的需求

最近的年代
定位也是一种数据,Mysql的关系型数据库已经不够用了,数据量多,变化快,

非关系型数据库

  • Json
  • Bson
  • 图型数据库

放在缓存中一段时间之后再进行持久化的操作,来保证效率和安全

存储很大的文件,会导致数据表很大效率会变低, 如果有一种专门的数据库来处理这种数据Mysql压力就会变得十分小,研究如何处理这些问题

如果数据量变大,要在对数据库进行更改,增加一行是很难的

用户先访问企业防火墙,到负载均衡的主机,到App服务器,到mysql实例,然后是独立功能的服务器

最后总结为什么要用NoSQL

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

NoSQL=Not Only SQL 不仅仅是SQL

泛指非关系型数据库,Web2.0的诞生,传统的关系型数据库很难对付Web2.0时代,尤其是超大规模的高并发的社区,

NoSQL在大数据环境下发展十分迅速

要存储的数据不是固定格式,以键值对来控制,Map< String , Object >,数据之间没有关系就很好扩展,Java现在要做面向接口编程也是为了解耦,

使用Redis也是为了高性能,官方的数据读取速度每秒11w次,写8w次

数据类型是多样性的,不需要事先设计数据库,不需要设计键值对,随取随用

传统的RDBMS和NoSQL

传统的RDBMS

  • 结构化组织
  • sql
  • 数据和关系都存储在表中 row column
  • 数据定义语言
  • 严格的一致性
  • 基础的事务
  • 。。。。

NoSQL

  • 不仅仅是数据
  • 没有固定查询语言
  • 很多的存储方式,列存储、文档存储、图形存储(社交关系)
  • 可以不用满足严格一致性,数据是可以有误差的,要保证的是最终一致性
  • CAP定理,和BASE理论
  • 高性能、高可用、高可扩展

大数据时代的3V和3高
大数据时代的3V,主要是描述问题的

  • 海量的Volume
  • 多样的Variety
  • 实时Velocity

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

  • 高并发
  • 高可拓(随时水平拆分,机器不足,可用扩展来)
  • 高性能

真正在公司中实践一定是:NoSQL+RDBMS一起使用才是最好的

阿里巴巴框架演进


第五代架构改进

敏捷开发
极限编程

  • 业务快速增长,每天都要上线大量的小需求
  • 应用系统日益膨胀,耦合恶化,架构越来越复杂,带来更高的开发成本,如何保持业务开发的敏捷性

开放,提升网站的开放性,吸引第三方开发者加入网站的建设
体验,网站并发压力快速增长,用户对体验提出了更高的要求

使用了各种数据库,这么多种类型的数据库导致数据架构非常复杂,要简化架构,增加一层就行,像是jdbc一样,
商品中的信息存在不同的数据库中
商品的基本信息
        名称、价格、商家信息:

  • 关系型数据库就可以解决 MySQL / Oracle
  • 淘宝早些年就去IOE了,去掉IBM小型机,Oracle数据库,EMC存储设备

商家的描述、评论(文字比较多)

  • 文档型数据库,MongoDB,

图片

  • 分布式文件系统,FastDFS
  • 淘宝自己的TFS
  • Google的GFS
  • Hadoop HDFS
  • 阿里云的 oss

商品的关键字
        搜索引擎

  • solr
  • elasticsearch
  • 淘宝用的是 Isearch

商品热门的波段信息

  • 内存数据库
  • Redis 、Tair 、Memacache

商品的交易,外部支付接口

  • 第三方应用

大型互联网应用问题:

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

阿里的解决方案

​ 统一数据服务层UDSL,在网站应用集群和底层数据源之间,构建一层代理,统一数据层

        模型数据映射

  • 实现 业务模型 各属性 与 底层不同类型数据源的模型数据映射

        统一的查询和更新API

  • 提供了基于业务模型的统一的查询和更新的API,简化网站应用跨不同数据源的开发模式
  • 性能优化

设计了一套统一的DSL,提供了统一的增删改查的API,开发速度问题是解决了,但是性能还是问题

网站数据庞大,只能缓存热点数据,解决方案,开发热点缓存平台,提供UDSL作为缓存系统

以上都是NoSQL入门概述

NoSQL的四大分类

KV键值对

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

文档型数据库(Bson格式和Json格式)

  • MongoDB

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

列存储数据库

  • HBase
  • 分布式文件系统

图形关系数据库

  • 不是用来存放图形的,是用来寸关系的,朋友圈社交,广告推荐
  • Neo4j,InfoGrid

Redis入门

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

开源、使用C语言编写,支持网络、基于内存可持久化的日志型,Key-Value数据库,提供多种语言的API,可以用多种语言调用 ,NoSQL技术之一,也被称之为结构化数据库之一

读的速度是11w,写的速度是8w

Redis能干嘛

  • 内存存储,持久化,内存是断电即失的,持久化很重要, 持久化有两种机制(RBD,AOF)
  • 效率高,可以用于高速缓存
  • 发布订阅系统
  • 地图信息分析
  • 计数器,(浏览量)
  • 。。。

特性

  • 多样的数据类型
  • 持久化
  • 集群
  • 事务
  • 。。

常用网站

  • Redis官网 https://redis.io
  • Redis中文网https://www.redis.net.cn

windows启动Redis

  • 安装Redis

brew install redis
//安装redis
brew info redis
//查看软件详细信息,以来关系,注意事项等
brew list redis
安装包所在的位置
  • 启动Redis

redis-server

  • 启动之后不要关闭

  • 连接测试

redis-cli
  • 默认端口是6379

  • 基本操作

  • Redis推荐使用Linux开发

  • 好吧这里加入了Linux的知识,要开始学习Linux

  • 退出Redis

redis-cli shutdown

CentOS7 安装Redis

常用的命令

netstat -lnpt |grep 6379
//查看6379=端口占用情况,netstat CentOS不自带,需要另外安装
yum install -y net-toolskill -9 [PID]
//结束对应PID进程ln -s /usr/local/redis/bin/redis-cli /usr/bin/redis
//创建链接,使之可以直接使用/bin之中的命令,这创建应该回到~目录进行创建,称为命令软链接cp /usr/local/redis-5.0.3/redis.conf /usr/local/redis/bin/
//复制操作make install PREFIX=/usr/local/redis
//安装到指定目录tar -zxvf redis-5.0.3.tar.gz//解压wget http://download.redis.io/releases/redis-5.0.3.tar.gz//通过链接下载

主要操作

不同版本的redis有不同的操作,选择高版本的redis,基本就只是解压,安装,选择配置文件启动

make install
make uninstall

Linux启动

退出

Redis测试性能

在/usr/local/bin 下有很多工具

benchmark压力测试工具,官方自带的性能测试工具

测试100个并发,每个并发100000个请求redis-benchmark  -p 6379 -c 100 -n 10000
不加-h 就默认本机

解读测试数据

基础知识
Redis是单线程的,Redis是基于内存操作的,CPU不是Redis的瓶颈,根据机器的内存和网络带宽的,可以用单线程实现

每秒10w+的QPS,完全不比使用key-value的Memecache差

单线程Redis

Redis五大数据类型

Redis官方介绍

Redis是内存中的数据结构存储系统,可以作用数据库、缓存、消息中间MQ
支持多种数据类型

Redis内置了主从复制replication、LUAscripting脚本,LRU 驱动事件,transactions事务,和不同级别的磁盘持久化persistence
通过Redis哨兵Sentinel,和自动分区Cluster,提供高可用性high availability
Redis

redis-server &
#后台启动
redis-server   /etc/local/bin/redis.conf
#指定文件启动
-p
#指定端口启动

String

append 追加value

追加不存在的key会set key
strlen 查看value长度

incr xc 自增1,可以用作增加浏览量increase

decr xc 递减1

incrby 能设置自增量的自增     incrby xc 12   decrby xc 12

getrange 截取范围,下标从0开始   getrange xc 0 2    取xc第一到第三的数   getrange xc 0 -1 全部

setrange 修改范围的值   setrange xc 1 22   123456变122456

setex (set wth expire) 设置key时顺便设置生存时间    setex xc aaa  30

setnx ( set if not exist )参数不存在就设置,在分布式锁经常使用,如果存在就创建失败.

setex xc  aa

mset 批量设置,空格间隔   mset xc1 1 xc2 2 xc3 3

mget    mget xc1 xc2 xc3

msetnx 原子性操作,批量设置,要么都成功或失败,nx如果存在就创建失败

对象操作


设置一个对象,值为json字符串来保存一个对象

user:{id}:{filed}

getset 先get再set,规则就按getset来,无论key存不存在,都按getset来

数据结构是相同的

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

List

在Redis中可以将List作为、栈、队列、阻塞队列

消息排队、消息队列、堆、栈

Set

集合中的值不能重复,set是无需不重复原则

string和list的元素都是value,set中是member

Hash

map集合,key - map 本质和string类型没有太大区别,还是一个简单的key - value,像是增加了一层

  • hset hset key field value  存值
  • hget                      取值

  • hmset   存多个数据
  • hmget   取多个数据
  • hgetall   查全部数据

  • Hdel 删除  hdel myhash xc   删除myhash里的xc数据
  • hlen    hlen  myhash  获取myhash字段长度
  • hexists 判断hash中指定字段是否存在    hexists  myhash  xc
  • hkeys 获取key所有的field
  • hvals 获取所有的value

  • hincrby 递增,参数设置为负数就是递减
  • hsetnx 是否存在,不存在就创建设置,存在就不设置

  • 做用户信息保存,比用string类型好点,存储经常变动的信息,hash更适合存储对象,

ZSet(有序集合)

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

可以作为一个排行榜功能,进场刷新,或者任务等级排序之类的,都可以做

  • 排序
  • zrangebyscore

  • zcard salary  获取salary集合的个数
  • zconut     zcount myset 1 3 获取myset1-3区间的数量

三种特殊数据类型

  • geospatial
  • hyperloglog
  • bitmaps

Geospatial

可以推算地理位置的信息,两地之间的距离,方圆几公里之内的人

需要注意:

  • 地球南北两极无法直接添加,
  • 一般会下载城市数据,直接通过Java程序一次性导入
  • 有效经纬度范围从-180到180,超出范围时会返回错误,
  • key由(纬度,经度,名称)构成

查看官网,一共有六个相关命令

  • add、dist、hash、pos、radius、rediusbymember

  • geodist 返回两个给定位置之间的距离

  • geohash 返回geohash对位置进行的编码,用于内部调试,一般用不到
  • geopos 返回指定member的经纬度信息

  • georadius : 根据半径查找,需要给定中心点数据

 

  • georadiusbymember : 也是根据半径查找,但是中心点是已经存在的member

  • zrange 遍历member
  • zrem 移除member
  • 纬度经度,member名称,geoadd可以一次添加多个

  • 可以使用geopos读取地理位置

  • geodist,输出两地距离,加上unit单位,设置输出距离单位

    • m、km、mi 英里、ft 英尺

  • 我附近的人功能,通过半径来查询,获取附近的人的定位地址

  • georadius命令来实现,longitude纬度、latitude经度、radius半径 单位,输入查询位置的经纬度就能从key集合中找到在半径范围的元素

  • 后面跟了几个参数,withdist、withcoord、withhash、asc、desc、count

    • Withdist:返回元素位置的同时,把与中心之间相差的距离一同返回

    • withcoord : 将元素经纬度一同返回

    • withhash : 返回经过geohash编码的有序集合分值,主要用于底层调试,作用不大
    • asc : 根据中心的位置,从近到远的方式返回位置元素
    • desc:从远到近的方式返回元素
    • count :获取前n个匹配元素,对于提高效率是有效的

HyperLogLog

是一种概率数据结构,计数唯一事物,从技术上讲估计一个集合的基数,通常计数唯一项需要使用成比例的内存,因为需要记录使用过的元素,以免多次记录,但是hyperloglog的算法可以用内存换精度,虽然有误差,但是误差小于1%,算法的神奇之处在于只需要很小的内存,最大也不超过12k,类似集合的功能,能记录2^64的计数,从内存的角度来说,hyperloglog是首选

能用在网页的UV

  • 传统方式,set保存用户的id,用户可能是uuid,这样可能占用巨大的内存
  • 但是只是需要计数功能并不需要保存用户的id
  • pfadd
  • pfcount 计数
  • pfmarge 合并 //合并到第一个key

Bitmaps

统计用户信息,活跃与不活跃,登录未登录只有两种状态的数据,可以使用BitMaps

可以用作打卡功能实现,到达一定数目之后进行统计,判断预期数目与统计得出的数目是否达到预期

  • setbit 中的offset是偏移量,可以看作下标,value只能是0或1

  • getbit

  • bitcount 统计key offset 为1的个数    bitcount sign

  • bitpos 查看 key offset 为0或1的位置,并且可以设置range

  • bitop 对一个或多个保存二进制位的字符串key进行位元操作,将结果保存在deskkey上

    • and、or、not、xor

    • 除了not之外,其他都能加入多个key进行运算

事务transition

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

一次性、顺序性、排他性,一次执行多个指令,其他客户端提交的命令请求不会插入到事务执行命令序列中

单条命令是原子性执行的,但事务不保证原子性,且没有回滚,事务中任意命令执行失败,其余命令仍会被执行

  • 开启事务(multi)
  • 命令入队(。。。)
  • 执行事务(exec)

  • 取消事务(discard)
  • 监视、加锁(watch)
  • 取消监视、解锁(unwatch)

乐观锁应该适用于读多写少的情况,悲观锁应该适用于写多读少的情况

悲观锁

Jedis

官方推荐的操作Redis的中间件,SpringBoot已经有整合RedisTemplate

  • 导入包
  • 建立连接
  • 操作
  • 可关闭连接,或者直接就使用jedis连接池
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.3.0</version>
</dependency>

测试

package com.xc;import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;/*** 星晨*/
public class TestPing {public static void main(String[] args) {//1.new Jedis对象即可Jedis jedis = new Jedis("127.0.0.1",6379);System.out.println(jedis.ping());//连接成功打印PONGjedis.flushDB();//清空当前数据JSONObject jsonObject = new JSONObject();jsonObject.put("hello","world");jsonObject.put("name","xingchen");//开启事务Transaction multi = jedis.multi();String result = jsonObject.toJSONString();try {multi.set("user1",result);multi.set("user2",result);
//          int i = 1/0;//代码抛出异常事务,执行失败!multi.exec();//执行事务} catch (Exception e){multi.discard();//放弃事务e.printStackTrace();} finally {System.out.println(jedis.get("user1"));System.out.println(jedis.get("user2"));jedis.close();//关闭连接}}
}

SpringBoot整合

SpringBoot操作数据:是封装在Spring-data中的,jpa、jdbc、mongodb、redis

在SpringBoot2.x以后与原来使用的jedis被替换成来看lettuce,底层已经不使用jedis了

  • jedis:采用的直连,多个线程操作的话,不安全,要提高安全性要使用jedis pool连接池 BIO
  • lettuce:采用netty,高性能网络框架,异步请求,实例在多线程中可以共享,不存在线程不安全的情况,dubbo底层也是用netty,可以减少线程数量,更像NIO模式
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

原理讲解

SpringBoot所有配置类,都会有一个自动配置类

自动配置类都会绑定一个properties配置文件

RedisAutoConfiguation

启动配置类中有一个RedisProperties配置类

里面有很多以前缀spring.redis开头的配置,可以在application中配置

如host、password、、配置

RedisAutoConfiguation中封装了两个Bean

  • RedisTemplate
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException {RedisTemplate<Object, Object> template = new RedisTemplate<>();template.setConnectionFactory(redisConnectionFactory);return template;
}
  • 没有过多的设置,Redis的对象都是需要序列化的

  • 两个泛型都是object,后面使用需要强制转换

  • 靠自己重写config来替换这个template

  • StringRedisTamplate

    • 大部分情况下String类型是最常用的,就会多一个stringRedisTemplate
@ConditionalOnMissingBean(name = "redisTemplate")
//重写一个redisTemplate就能替换掉这个bean

整合实现

  • 导入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 配置连接
# 配置Redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
  • 测试
package com.xc;import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;@SpringBootTest
class Redis02SpringBootApplicationTests {@Autowiredprivate RedisTemplate redisTemplate;@Testvoid contextLoads() {//redisTemplate  操作不同的数据类型,api和我们的指令是一样的//opsForValue 操作字符串 类似String//opsForList  操作List  类似List//opsForSet//opsForHash//opsForZSet//opsForGeo//opsForHyperLogLog//除了基本的操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务和基本的CRUD//获取redis的连接对象
//        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//        connection.flushDb();
//        connection.flushAll();redisTemplate.opsForValue().set("mykey","xingchen");System.out.println(redisTemplate.opsForValue().get("mykey"));}}

Redis工具类

package com.xc.utils;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;@Component
public final class RedisUtil {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;// =============================common============================/*** 指定缓存失效时间** @param key  键* @param time 时间(秒)*/public boolean expire(String key, long time, TimeUnit timeUnit) {try {if (time > 0) {redisTemplate.expire(key, time, timeUnit);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 根据key 获取过期时间** @param key 键 不能为null* @return 时间(秒) 返回0代表为永久有效*/public long getExpire(String key) {return redisTemplate.getExpire(key, TimeUnit.SECONDS);}/*** 判断key是否存在** @param key 键* @return true 存在 false不存在*/public boolean hasKey(String key) {try {return redisTemplate.hasKey(key);} catch (Exception e) {e.printStackTrace();return false;}}/*** 删除缓存** @param key 可以传一个值 或多个*/@SuppressWarnings("unchecked")public void del(String... key) {if (key != null && key.length > 0) {if (key.length == 1) {redisTemplate.delete(key[0]);} else {redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));}}}// ============================String=============================/*** 普通缓存获取** @param key 键* @return 值*/public Object get(String key) {return key == null ? null : redisTemplate.opsForValue().get(key);}/*** 普通缓存放入** @param key   键* @param value 值* @return true成功 false失败*/public boolean set(String key, Object value) {try {redisTemplate.opsForValue().set(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 普通缓存放入并设置时间** @param key   键* @param value 值* @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期* @return true成功 false 失败*/public boolean set(String key, Object value, long time) {try {if (time > 0) {redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);} else {set(key, value);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 递增** @param key   键* @param delta 要增加几(大于0)*/public long incr(String key, long delta) {if (delta < 0) {throw new RuntimeException("递增因子必须大于0");}return redisTemplate.opsForValue().increment(key, delta);}/*** 递减** @param key   键* @param delta 要减少几(小于0)*/public long decr(String key, long delta) {if (delta < 0) {throw new RuntimeException("递减因子必须大于0");}return redisTemplate.opsForValue().increment(key, -delta);}// ================================Map=================================/*** HashGet** @param key  键 不能为null* @param item 项 不能为null*/public Object hget(String key, String item) {return redisTemplate.opsForHash().get(key, item);}/*** 获取hashKey对应的所有键值** @param key 键* @return 对应的多个键值*/public Map<Object, Object> hmget(String key) {return redisTemplate.opsForHash().entries(key);}/*** HashSet** @param key 键* @param map 对应多个键值*/public boolean hmset(String key, Map<String, Object> map) {try {redisTemplate.opsForHash().putAll(key, map);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** HashSet 并设置时间** @param key  键* @param map  对应多个键值* @param time 时间(秒)* @return true成功 false失败*/public boolean hmset(String key, Map<String, Object> map, long time, TimeUnit timeUnit) {try {redisTemplate.opsForHash().putAll(key, map);if (time > 0) {expire(key, time, timeUnit);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 向一张hash表中放入数据,如果不存在将创建** @param key   键* @param item  项* @param value 值* @return true 成功 false失败*/public boolean hset(String key, String item, Object value) {try {redisTemplate.opsForHash().put(key, item, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 向一张hash表中放入数据,如果不存在将创建** @param key   键* @param item  项* @param value 值* @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间* @return true 成功 false失败*/public boolean hset(String key, String item, Object value, long time, TimeUnit timeUnit) {try {redisTemplate.opsForHash().put(key, item, value);if (time > 0) {expire(key, time, timeUnit);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 删除hash表中的值** @param key  键 不能为null* @param item 项 可以使多个 不能为null*/public void hdel(String key, Object... item) {redisTemplate.opsForHash().delete(key, item);}/*** 判断hash表中是否有该项的值** @param key  键 不能为null* @param item 项 不能为null* @return true 存在 false不存在*/public boolean hHasKey(String key, String item) {return redisTemplate.opsForHash().hasKey(key, item);}/*** hash递增 如果不存在,就会创建一个 并把新增后的值返回** @param key  键* @param item 项* @param by   要增加几(大于0)*/public double hincr(String key, String item, double by) {return redisTemplate.opsForHash().increment(key, item, by);}/*** hash递减** @param key  键* @param item 项* @param by   要减少记(小于0)*/public double hdecr(String key, String item, double by) {return redisTemplate.opsForHash().increment(key, item, -by);}// ============================set=============================/*** 根据key获取Set中的所有值** @param key 键*/public Set<Object> sGet(String key) {try {return redisTemplate.opsForSet().members(key);} catch (Exception e) {e.printStackTrace();return null;}}/*** 根据value从一个set中查询,是否存在** @param key   键* @param value 值* @return true 存在 false不存在*/public boolean sHasKey(String key, Object value) {try {return redisTemplate.opsForSet().isMember(key, value);} catch (Exception e) {e.printStackTrace();return false;}}/*** 将数据放入set缓存** @param key    键* @param values 值 可以是多个* @return 成功个数*/public long sSet(String key, Object... values) {try {return redisTemplate.opsForSet().add(key, values);} catch (Exception e) {e.printStackTrace();return 0;}}/*** 将set数据放入缓存** @param key    键* @param time   时间(秒)* @param values 值 可以是多个* @return 成功个数*/public long sSetAndTime(String key, long time, TimeUnit timeUnit, Object... values) {try {Long count = redisTemplate.opsForSet().add(key, values);if (time > 0) {expire(key, time, timeUnit);}return count;} catch (Exception e) {e.printStackTrace();return 0;}}/*** 获取set缓存的长度** @param key 键*/public long sGetSetSize(String key) {try {return redisTemplate.opsForSet().size(key);} catch (Exception e) {e.printStackTrace();return 0;}}/*** 移除值为value的** @param key    键* @param values 值 可以是多个* @return 移除的个数*/public long setRemove(String key, Object... values) {try {Long count = redisTemplate.opsForSet().remove(key, values);return count;} catch (Exception e) {e.printStackTrace();return 0;}}// ===============================list=================================/*** 获取list缓存的内容** @param key   键* @param start 开始* @param end   结束 0 到 -1代表所有值*/public List<Object> lGet(String key, long start, long end) {try {return redisTemplate.opsForList().range(key, start, end);} catch (Exception e) {e.printStackTrace();return null;}}/*** 获取list缓存的长度** @param key 键*/public long lGetListSize(String key) {try {return redisTemplate.opsForList().size(key);} catch (Exception e) {e.printStackTrace();return 0;}}/*** 通过索引 获取list中的值** @param key   键* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推*/public Object lGetIndex(String key, long index) {try {return redisTemplate.opsForList().index(key, index);} catch (Exception e) {e.printStackTrace();return null;}}/*** 将list放入缓存** @param key   键* @param value 值*/public boolean lSet(String key, Object value) {try {redisTemplate.opsForList().rightPush(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 将list放入缓存** @param key   键* @param value 值* @param time  时间(秒)*/public boolean lSet(String key, Object value, long time,TimeUnit timeUnit) {try {redisTemplate.opsForList().rightPush(key, value);if (time > 0) {expire(key, time,timeUnit);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 将list放入缓存** @param key   键* @param value 值* @return*/public boolean lSet(String key, List<Object> value) {try {redisTemplate.opsForList().rightPushAll(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 将list放入缓存** @param key   键* @param value 值* @param time  时间(秒)* @return*/public boolean lSet(String key, List<Object> value, long time,TimeUnit timeUnit) {try {redisTemplate.opsForList().rightPushAll(key, value);if (time > 0) {expire(key, time,timeUnit);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 根据索引修改list中的某条数据** @param key   键* @param index 索引* @param value 值* @return*/public boolean lUpdateIndex(String key, long index, Object value) {try {redisTemplate.opsForList().set(key, index, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 移除N个值为value** @param key   键* @param count 移除多少个* @param value 值* @return 移除的个数*/public long lRemove(String key, long count, Object value) {try {Long remove = redisTemplate.opsForList().remove(key, count, value);return remove;} catch (Exception e) {e.printStackTrace();return 0;}}// ===============================HyperLogLog=================================public long pfadd(String key, String value) {return redisTemplate.opsForHyperLogLog().add(key, value);}public long pfcount(String key) {return redisTemplate.opsForHyperLogLog().size(key);}public void pfremove(String key) {redisTemplate.opsForHyperLogLog().delete(key);}public void pfmerge(String key1, String key2) {redisTemplate.opsForHyperLogLog().union(key1, key2);}}

Redis.conf详解

Redis持久化

持久化RDB、AOF,重点

Redis是内存数据库,断电即失去,只要是内存数据库就一定会有持久化操作

RDB(Redis DataBase)

在指定的时间 间隔内将内存中的数据集快照写入到磁盘中,Snapshot快照,恢复时将快照文件直接读到内存中

  • 单独创建一个子进程,fork分支
  • 将内存内容写入临时RDB文件
  • 再用临时文件替换上次持久化完成的文件

整个过程主进程不进行任何io操作,保证了性能,如果进行大规模数据恢复,RDB和AOP都可以进行数据恢复,RDB数据恢复完整性不敏感,RDB更加高效,缺点时最后一次持久化后的数据可能丢失,默认使用的就是RDB,一般情况不需要修改这个配置

RDB保存的文件是dump.rdb

AOF保存的文件是appendonly.aof

配置快照在snapshots配置区域下

dump.rdb文件

  • 通过Redis config get dir 获取

config get dir
  • 触发机制

    • save规则触发
    • 执行flushall命令
    • 关闭redis
  • 备份会自动生成dump.rdb文件

如何恢复备份文件
只要将rdb文件放在redis规定的目录,redis启动时会自动检查dump.rdb文件恢复数据

查看位置,config get dir

在生产环境中最好对dump.rdb文件进行备份

RDB优缺点
优点:

  • 父进程正常处理用户请求,fork分支一个子进程进行备份
  • 适合大规模的数据恢复,如果服务器宕机了,不要删除rdb文件,重启自然在目录下,自动会读取

缺点:

  • 需要一定的时间间隔,可以自行修改设置
  • 如果redis意外宕机,最后一次的修改数据会丢失
  • fork进程的时候,会占用一定的内存空间

AOF(Append Only File)

将所执行的所有命令都记录下来,处读操作以外,恢复时重新执行一次,如果是大数据就需要写很久

aof默认是文件无限追加,大小会不断扩张

在主从复制中,rdb是备用的,在从机上使用,aof一般不使用

  1. fork分支出子进程
  2. 根据内存中的数据子进程创建临时aof文件
  3. 父进程执行的命令存放在缓存中,并且写入原aof文件
  4. 子进程完成新aof文件通知父进程
  5. 父进程将缓存中的命令写入临时文件
  6. 父进程用临时文件替换旧aof文件并重命名
  7. 后面的命令都追加到新的aof文件中

开启AOF

配置文件Append Only Modo区块中设置

appendonly no
#默认关闭appendonly 手动设置yes开启appendfilename "appendonly.aof"
#默认名字# appendfsync always
appendfsync everysec
# appendfsync no
#每次都进行修改
#每秒钟都进行修改
#不进行修改no-appendfsync-on-rewrite no
#是否进行重写auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
#percentage重写百分比
#重写时文件最小的体积#一般保持默认,一般只需要开启

这些配置也能在连接redis后在redis中通过config set 进行更改

与RDB类似的触发机制,也能生成配置文件

进行了一些操作,如list在同一个key上覆盖值操作,aof是一同操作的,把之前的值进行了覆盖,但是保存的并不是最新的值,而是把全部进行的操作保存了下来,lpush lpop,当从aof文件中恢复数据时,不管最新的值是什么都重新的进行一遍操作,这样在时间上和效率上并不是最优的,但是能保证在每次的操作能进行备份,保证数据不丢失,如果出于绝对的安全考虑可以开启aof

aof文件损坏情况

  • 人为测试aof文件损坏,aof文件是根据文件的大小进行比对,判断文件是否损坏,使用

  • haoyun@HAOYUN ~ % redis-check-aof --fix /usr/local/var/db/redis/appendonly.aof
    AOF analyzed: size=23, ok_up_to=23, diff=0
    AOF is valid
    
  • 损坏的aof会导致redis无法打开

  • 这个修复真垃圾,给我数据删没了,删除规律数据不好修复,但是加入明显没有逻辑的错误,还是能修复

  • redis-check-rdb 能修复rdb文件

 优缺点

优点:

  • 可设置文件修改每次都同步备份,文件完整性更好,但是消耗性能
  • 设置每秒同步一次可能会丢失一秒的数据
  • 从不同步效率最高

缺点

  • 对于数据文件,aof远远大于rdb,修复速度也比rdb慢
  • aof是io操作,所以默认是aof
  • aof文件会无限扩大

扩展

  • rdb持久化方式能够在指定的时间间隔内对数据进行快照存储

  • aof持久化方式记录每次对服务器写的操作,服务器重启时,重新执行命令来恢复原始数据,追加在文件末尾,能对aof文件进行重写,避免体积过大

  • 如果只做缓存不需要使用任何持久化

  • 同时开启两种持计划方式

    • 重启时优先载入aof文件来恢复数据
    • 只会找aof文件,但是推荐只使用rdb用于备份,能快速重启,并且不会有aof可能潜在的bug
  • 性能建议

    • rdb文件只做后背用途,建议只在slave上持久化rdb文件,15分钟备份一次,使用save 900 1 规则

    • 使用aof,即便在最恶劣的环境下也不会丢失超过2秒的数据

      • 代价:持续的io
      • rewrite 过程中产生的新数据写到新文件造成的阻塞不可避免,尽量减少rewrite的频率
      • 不使用aof,也可以通过Master-Slave Replication 实现高可用性也可以,能省去一大笔io,减少rewrite带来的系统波动
      • 代价:如果Master-Slave 同时倒掉,回丢失十几分钟的数据,启动脚本也要比较Master-Slave中的rdb文件,选择最新的文件,载入新的,微博就是这种架构

Redis订阅发布

Redis主从复制

一个Master有多个slave,将一台redis服务器数据,复制到其他的redis服务器,前者称为主节点(masterleader),后者称为从节点(slave、follower),数据是单向的,只能从主节点到从节点,Master以写为主,Slave以读为主

默认情况下,每台redis服务器都是主节点,一个Master可以有多少Slave或没有从节点,一个从节点只能有一个主节点

主从复制作用包括:

  • 数据冗余

    • 实现了数据的热备份,是持久化之外的一种数据冗余方式
  • 故障恢复
    • 主节点出现问题,从节点可以提供服务,实现快速的故障恢复,实际上是一种服务的冗余
  • 负载均衡
    • 在主从复制的基础上,配合读写分离,主节点提供写服务,从节点提供读服务,写redis数据时连接主节点,读redis数据连接从节点,分担服务器负载,尤其在写少读多的场景下通过,多个从节点分担负载,可以提高redis性能
  • 高可用(集群)基石
    • 哨兵、集群,能够实施的基础,主从复制时高可用的基础

不能只使用一台redis的原因:

  • 从结构上讲,单个redis服务器会发生单点故障,一台服务器需要处理所有请求,压力大
  • 从容量上讲,单个redis服务器内存容量有限,并且不能完全使用全部的内存,单台redis的最大内存不应该超过20g压力过大

通常的电商网站都是一次上传吗,无数次浏览,读多写少 ,主从复制,读写分离,80%的情况都在进行读操作,起码一主二从

需要配置的config选项

  • daemonize
  • port
  • pidfile
  • logfile
  • dbfilename
  • rdb-del-sync-files

 Redis replication实现

 查看当前服务

ps -ef|grep redis

一主二从

默认情况下,每台Redis服务器都是主节点;我们一般情况下只用配置从机就好了

认老大,一主(6379)、二从(6380、6381)

  • 真实的主从配置要在配置文件中配置,在redis-cli中配置的是暂时的

  • 配置在redis.conf文件中replication区块下

replicaof <masterip> <masterport>
replicaof 127.0.0.1 6379
  • 配置文件设置好,启动时就不用重新设置

  • 根据读写分离的原则,主机只能写,从机只能读

  • slave 会自动write master中的数据,但是不能往slave中写数据

哨兵模式

当master宕机时让slave变为master

slaveof no one
#让自己变为主机

这种设置是手动的,使用哨兵模式将自动选取master

此时master恢复后使用slaveof no one 的主机也还会继续当master,要重新作为slave只能重新配置

单哨兵模式、多哨兵模式

概述切换技术的方法是,当master服务器宕机后,需要人工切换,费事,更多时候选择优先考虑是哨兵模式,redis2.8 开始正确提供sentinel(哨兵 )

能够监控后台的主机是否故障,根据投票自动将从库专为主库

哨兵模式是一种特殊模式,哨兵是一个独立的进程,作为进程独立运行,原理是哨兵通过发送命令,等待redis服务器响应,从而监控多个redis实例

像每台发送信息确定主机是否存活,优点类似于springcloud的心跳检测

这种图成为单机哨兵,当单个哨兵也宕机也会有风险,创建多个哨兵是个不错的选择,称为多哨兵模式

当哨兵模式检测到master宕机,会自动将slave切换成master,通过发布订阅模式通知其他的从服务器,修改配置文件,让他们切换主机

多哨兵模式

  • 假设master宕机,sentinel先检测到这个结果,系统并不会马上进行failover(故障切换、失效备援)这个现象称为主观下线,当后面的哨兵也检测到主服务器不可用,sentinel之间会发起一次投票,投票的结果由随机一个sentinel发起,进行failover操作,得到sentinel票数多的slave能成功切换为master,切换成功后,通过发布订阅模式,让各个哨兵把自己监控的服务器实现切换主机,这个过程称为客观下线

多哨兵模式实现

1、配置sentinel.conf

sentinel monitor  被监控的名称   主机地址  端口  1

sentinel monitor mymaster 127.0.0.1 6379 1

后面的这个数字1,代表主机挂了,slave(从机)投票看让谁接替成为主机,票数最多的就会成为主机。

2、启动哨兵

  • 然后启动就行

  • 默认端口为26379,默认pid为69427

  • 当master 79 宕机,sentinel选举了81为newmaster

  • master节点断开,这时候从slave中选择一个座位master,其中有投票算法,自行了解

  • 当79重新启动后,是以80作为master的slave role存在

主机宕机之后哨兵会重新在从机中选出一个从机做为新主机,如果旧主机重新启动回来了,只能归并到新的主机下,当作从机,这就是哨兵模式的规则。

哨兵模式优缺点

优点

  • 基于集群,基于主从复制,所有的主从配置的优点,它全有
  • 主从可以切换,故障可以切换,系统的可用性提高
  • 哨兵模式就是主从模式的升级,手动到自动,更加健壮

缺点

  • redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
  • 哨兵模式需要很多配置
    • 多哨兵,多端口配置复杂,一般由运维来配置

Redis缓存穿透、击穿、雪崩(面试高频,工作常用)

都是服务的三高问题

  • 高并发
  • 高可用
  • 高性能

面试高频,工作常用

redis缓存的使用极大的提升了应用程序的性能和效率,特别是数据查询方面,但同时,它也带来了一些问题,数据一致性问题,严格意义上来讲,问题无解,对一致性要求极高,不推荐使用缓存

布隆过滤器、缓存空对象

缓存穿透

用户查询一个数据,redis数据库中没有,也就是缓存没命中,于是向持久层数据库查询,发现也没有,于是查询失败,用户很多的时候,缓存都没有命中,都请求持久层数据库,给持久层数据库造成巨大压力,称为缓存穿透

在直达持久层的路径上加上过滤器、或者缓存中专门增加一个为空的请求

布隆过滤器

  • 布隆过滤器是一种数据结构,对所有可能的查询参数以hash形式存储,在控制层进行校验,不符合则丢弃,从而避免了对底层存储系统查询压力

缓存空对象(查不到)

  • 当持久化层不命中后,将返回的空对象存储起来,同时设置一个过期时间,之后再访问这个数据就从缓存中获取,保护持久层数据源
  • 需要面临的问题

    • 存储空的key也需要空间
    • 对空值设置了过期时间,还会存在缓存层和存储层的数据有一段时间窗口不一致,对于需要保持一致性的业务会有影响

缓存击穿(量太大了,缓存过期)

例子微博服务器热搜,巨大访问量访问同一个key

一个key非常热点,不停扛着大并发,集中对一个点进行访问,当个key失效的瞬间,持续大并发导致穿破缓存,直接请求数据库

某个key在过期的瞬间,大量的访问会同时访问数据库来查询最新的数据,并且回写缓存,导致数据库瞬间压力过大

解决方案

  • 设置热点数据不过期

    • 一直缓存也会浪费空间
  • 加互斥锁
    • 分布式锁:使用分布式锁,保证对于每个key同时只有一个线程查询后端服务,其他线程没有获得分布式锁的权限,只需要等待即可,这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大

缓存雪崩

在某一个时间段,缓存集中过期失效,redis宕机

产生雪崩的原因之一,设置缓存的存活时间较短,大并发访问时刚好都过期,直接访问了数据库,对数据库而言,会产生周期性压力波峰,暴增时数据库可能会宕机

双十一时会停掉一些服务,保证主要的一些服务可用,springcloud中说明过

解决方案:

  • 增加集群中服务器数量

    • 异地多活
  • 限流降级
    • 缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,对某个key只允许一个线程查询数据和写缓存,其他线程等待
  • 数据预热
    • 正式部署之前,把可能的数据提前访问一遍,可能大量访问的数据就会加载到缓存中,加载不同的key,设置不同的过期时间,让缓存时间尽量均匀

狂胜——Redis学习笔记相关推荐

  1. Redis学习笔记 - 数据类型与API(1)Key

    Redis学习笔记 - 数据类型与API(1)Key Key相关命令 1. 常用命令 命令 含义 时间复杂度 keys 查找所有符合给定模式 pattern 的 key O(N), N 为数据库中 k ...

  2. Redis学习笔记~Redis在windows环境下的安装

    Redis是一个key-value的存储系统,它最大的特点就是可以将数据序列化到文件中. redis存储在服务器的内存或者文件中,它不是session,不是cookies,它只是个更安全,更稳定,更可 ...

  3. redis学习笔记-持久化

    redis学习笔记-持久化 前言 redis持久化有两种方式:RDB和AOF.分别对应着全量复制和增量复制.深刻理解各自的实现方式及适用场景对redis的使用和运维十分重要.下面就分别介绍. RDB持 ...

  4. StackExchange.Redis学习笔记(五) 发布和订阅

    StackExchange.Redis学习笔记(五) 发布和订阅 原文:StackExchange.Redis学习笔记(五) 发布和订阅 Redis命令中的Pub/Sub Redis在 2.0之后的版 ...

  5. Redis学习笔记~分布式的Pub/Sub模式

    redis的客户端有很多,这次用它的pub/sub发布与订阅我选择了StackExchange.Redis,发布与订阅大家应该很清楚了,首先一个订阅者,订阅一个服务,服务执行一些处理程序(可能是写个日 ...

  6. Redis学习笔记——SpringDataRedis的使用

    与Spring集成 我需要哪些jar包? <dependency><groupId>org.springframework.data</groupId><ar ...

  7. Redis学习笔记(五)——持久化及redis.conf配置文件叙述

    对于日常使用来说,学习完SpringBoot集成Redis就够我们工作中使用了,但是既然学习了,我们就学习一些Redis的配置及概念,使我们可以更深层次的理解Redis,以及增强我们的面试成功概率,接 ...

  8. Redis学习笔记1-理论篇

    目录 1,Redis 数据类型的底层结构 1.1,Redis 中的数据类型 1.2,全局哈希表 1.3,数据类型的底层结构 1.4,哈希冲突 1.5,rehash 操作 2,Redis 的 IO 模型 ...

  9. Redis学习笔记(实战篇)(自用)

    Redis学习笔记(实战篇)(自用) 本文根据黑马程序员的课程资料与百度搜索的资料共同整理所得,仅用于学习使用,如有侵权,请联系删除 文章目录 Redis学习笔记(实战篇)(自用) 1.基于Sessi ...

最新文章

  1. php singleton()
  2. RDKit | 基于SMILES查找化合物的MACCS密钥
  3. MXNet 安装 Windows
  4. windows和linux多进程效率,Windows程序效率 (转)
  5. SpringCloud系列之服务消费Ribbon和Feign区别
  6. Java黑皮书课后题第2章:2.5(金融应用:计算小费)编写一个程序,读入一笔费用与小费利率,计算小费和总钱数
  7. 第17章:图像分割提取
  8. 技术人员要分出2成精力防范内斗及斗争方法
  9. android根据银行卡卡号判断银行
  10. python websockets 网络聊天室V1
  11. 大众点评数据爬虫思路[更新版]
  12. 如何实现1分钟写一个API接口
  13. SQL零基础入门学习(一)
  14. vue 播放器视频插件 之 vue-video-player 铺满全屏自动播放
  15. 如何基于4P理论做运营?
  16. LeetCode 2409. 统计共同度过的日子数
  17. 中国联通与用友签署合作框架协议 多领域展开全面合作
  18. scholarscope不显示影响因子_帮研网—科研共享平台——新版Pubmed显示影响因子并设置筛选过滤器...
  19. 国产单片机有盗版吗? 侵权了吗?
  20. 装备科研项目过程通用控制要求

热门文章

  1. docker拉取镜像时,报错:no matching manifest
  2. 【ENOVIA 服务包】知识重用解决方案 | 达索系统百世慧®
  3. 使用Mixamo_Converter重新定义根骨骼导入UE4
  4. 【附源码】计算机毕业设计java校园食堂订餐系统设计与实现
  5. 软件测试需要什么思维,做软件测试需要学习什么
  6. Python 10个项目创意
  7. Android 各版本对应的SDK版本
  8. (Get the office2019)Download Office Tool
  9. 【Alpha阶段】第三次scrum meeting
  10. mysql通过正则表达式根据手机号判断运营商