什么是Redis

面试的时候,考官会经常问你,有没有用过什么缓存框架或者什么缓存中间件,其实就是想问 MemberCached、Redis 等等,或者你可以手写一个 LRU 缓存。

今天我们要讨论下 Redis,Redis 是一个非关系型内存数据库,也叫 NoSQL,因为它是直接在内存上操作所以叫内存数据库,速度非常快,QPS 达到 10w+。

平时我们用的 MySQL、Oracle、SQL Server、PostgreSQL 这些属于关系型数据库。通俗来说,以前我们的数据库,会有固定的表结构、主键外键等等,表与表之间可以通过 SQL 进行关联查询,但表与表之间多了便会复杂(建议一般不要超过三个表,超过就要进行插表了),这种复杂的关系查询会导致在高并发海量数据读写的时候,给硬盘 IO 带来很大的压力,有时候即使我们写的 SQL 天衣无缝,也避免不了数据库的性能瓶颈。

而 Redis 这种非关系型的数据库就是专门为高并发读写频繁场景而生的,它数据结构简单,跟 Hashmap 集合用法差不多,简单的 put、get、value。

以后一说到高并发读写频繁场景,我们就会想起有这么个好用的中间件 Redis。在我们实际应用场景下它可以为我们做点什么呢?下面是自己在工作中常用到的一些场景:

  • 缓存,经常访问但有一段时间不会变的字段,例如保存短信验证
  • 各种排行榜
  • 签到功能
  • 点赞功能
  • 简单的消息队列,异步队列
  • Session 服务器
  • 分布式锁

下面是Redis的思维导图,如果你熟悉Redis的话,估计只看导图就可以了。

为什么是 Redis

缓存数据库那么多,为什么是Redis?

  • Redis 自身纯内存操作,速度快
  • 支持多种数据类型,String、List、set、Sorted Set、Hash、Stream
  • 支持持久化,RDB、AOF
  • 单线程,避免多线程上下文切换和竞争锁问题
  • 采用 epoll 多路复用非阻塞 IO,提高工作效率
  • 支持事务

那么 IO 多路复用机制是什么?epoll、select、poll 又有什么不一样呢?

复用,指的是复用同一个线程或者进程

多路 IO 复用是指在同一个线程中,会有多个 IO 流事件发生,我们可以同时去处理这些 IO 事件,不会影响到我们线程的任务。

就好像快递员发快递,他可以同时去处理顺丰,圆通,申通每一家公司的快递,只要他们快递到了快递员就会去送快递。select、poll 模式就是每次都一个个公司去询问,是否有快递到了。epoll 模式,顺丰会给你打电话,说小伙子某某的快递到了,你可以过来拣货去送快递了,不需要每次都去询问公司,公司会主动找你。这样你就有了更多的空闲时间,去做点其他事情,人生不只是送快递。

epoll、select、poll 的区别:

  • select:服务端会一直在轮询、监听,如果有客户端链接上,就进行处理,此过程会一直循环,消耗资源大
  • poll:跟 select 差不多,是其加强版,select 连接数是 1024,而 poll 没有限制了
  • epoll:服务端也会监测 IO 事件,但是它不会循环遍历的去监听,当有客户端发来连接,会去通知服务端,然后服务端才会进行处理,这就大大提高了效率。

小时候,老师提问,有时候会一个个去问学生,轮流问,那就是 select,poll 模式,估计这样问完,一节课快要下课了;此时,老师为了提高效率,就跟大家说,知道问题的答案就举手回答,那样就可以知道谁真正掌握了这个问题。这就是 epoll,并且它们本质上都是阻塞 IO。

有人会说 Memcached 也是内存数据库,为什么要用 Redis?

  • Redis 支持丰富的数据类型,基础数据类型 String、list、set、zset、hash、Stream,高级数据类型,例如 bitmap、geo、HyperLogLog 等数据结构的存储。Memcache 只支持简单的数据类型,String。
  • Redis 支持数据的持久化,例如 RDB 和 AOF,一些重要的数据可以保持在磁盘中,当遇到突然停电宕机这种情况,可以用来进行灾难恢复。而 Memecache 把数据全部存在内存之中。
  • Redis 支持集群模式,哨兵模式,主从复制,具有高可用。
  • Redis 使用单线程的多路 IO 复用模型,单线程能保证其安全性。Memcached 是多线程。

Redis 的基本数据类型和高级数据类型

五种基本数据类型

String

List

Hash

Set

SoredSet(也叫 zSET)

高级数据类型

Bitmap

用来存储状态,是或否。举个栗子:

有一天老板突然说,我们系统需要增加一个每天签到送积分的功能。这个可愁死我了。按照以前的逻辑我会创建一个用户表,表中用 ID 和用户签到状态 status,用户签到一天我们会新增一条记录。当时以极快的效率做出来了,受到了老板的表演,小伙子可以啊,头发多还有实力哦。好景不长,我们的系统随着业务的高速发展,用户量暴增,达到了千万级别。

有一天发现系统突然卡死宕机了,原来是我们系统数据库挂了,一直连不到数据库。由于数据库硬盘存储不够了,而导致我们存储不够的原因,是因为用户签到表数据量太大了。你想想一个用户一天一条数据,一年 365 条,用户达到了上亿的系统,这数据量可怕啊。此时我赶紧连夜赶工,把 Bug 修复了。

这个时候 Redis 的 bitmap 就被我派上用场了。

用户是否签到可以用 0/1 来表示,0 代表用户不签到,1 表示签到,那么 1bit 就可以表示用户是否签到。

大概预估了下一亿个用户一个月的数据量也就是几百兆。此时的我,头发迎风飘扬。

HyperLogLog

是基于基数来进行估算的,误差率大概 0.81,输入的元素越小,误差率有时候会比较大,所以当我们需要统计一些大量重复的数据 ,而这些误差可以忽略不计时候可以用它。

当你输入的数据数据量非常大的时候,亿万级,它需要的存储也不大,固定的而且每个键值只要 12K 大小,但它可以存储的 2^64 个不同的基数,大大节约了存储空间。如果我们用集合 hashmap 虽然也能实现这功能,但需要的存储成本肯定差很多。

虽然 HyperLogLog 在统计海量数据方面优势肉眼可见的优势,但因为 HyperLogLog 只会根据输入数据来计算基数,而不会储存输入元素本身,所以 使用 HyperLogLog 如果你想获取集合中的元素,这个功能是不能实现的。

当数据量越大的时候,值会不太准确。所以它只适合那些对精确度要求没那么高的场景。

Geo

Pub/Sub(发布订阅)

举个栗子:

  • 只发msg1

    publish msg1  hello
    (integer) 2
    发布 msg1 频道消息
    返回 int 2 表示有 2 个客户端收到了 msg1 频道消息
  • 只发msg2

    publish msg2  hello
    (integer) 2
    发布 msg2 频道消息
    返回 int 2 表示有 2 个客户端收到了 msg2 频道消息
  • 客户端 1 在监听 msg1 频道:

    subscribe msg1
    Reading messages... (press Ctrl-C to quit)
    1) "subscribe" # 显示订阅成功
    2) "msg1" # 订阅的频道(通道)名称
    3) (integer) 1 # 自己订阅的频道数量
  • 客户端 2 在监听 tv1 和 tv2 多个频道:

    subscribe msg1 msg2
    Reading messages... (press Ctrl-C to quit)
    1) "subscribe"
    2) "msg1 "
    3) (integer) 1
    1) "subscribe"
    2) "msg2"
    3) (integer) 2

分布式锁的实现

说到分布式锁,很多人可能都知道它的原理,但并没有实际的项目经验。下面说的是我工作中对分布式锁的实践运用。

分布式锁的三种方式:

  • 1. 数据库 (一般不用)
  • 2. Redis
    • 优点:性能比较高
    • 缺点:需要手动实现复杂的代码
  • 3. zookeeper
    • 优点:具备高可用、可重入、阻塞锁特性,可解决失效死锁问题,实现简单(临时节点的创建和取消)
    • 缺点:因为需要频繁的创建和删除节点,性能上不如 Redis 方式

原理

分布式锁应具备以下特点:

  • 在分布式系统环境下,一个方法在同一时间,只能被一个机器的一个线程执行
  • 高可用的获取锁以及释放锁
  • 高性能的获取锁以及释放锁
  • 具备可重入特性
  • 具备锁失效机制,防止死锁
  • 具备非阻塞锁特性,在没有获取到锁的时候,直接返回获取锁失败

分布式锁设计遇到的坑:

  • 加锁时候,如果没有加超时时间,机子突然宕机了,会导致死锁,其他机子会一直拿不到锁
  • 加锁的时候,应该使用原子命令
  • 任务没执行完成,就释放锁了,可以对锁续时,Redisson 客户端已经帮我我们把这个功能实现
  • 解锁的时候不要删错锁

场景

有一个订单,我们会对订单进行业务处理,例如分仓(就是生成包裹),如果只是一台服务器去执行的话,不会出现什么问题。但在高并发的场景下,会有多台服务器同时去处理这个订单。这样就会导致重复分仓,生成了多个包裹,此时老板和领导就会过来找你喝茶……

RDB 和 AOF 的理解

RDB

定义&原理:

指定的时间间隔内生成数据集的时间点快照,例如 save 30 1000 表示 30 秒内如果至少有 1000 个 key 的值变化,则保存 save。

RDB 触发机制有三种:

  • save,同步,阻塞进程
  • bgsave,异步操作,消耗资源
  • 配置文件

优点:服务器突然宕机了,此时肯定是首先要把服务器快速恢复。此时候 RDB 就能当此重任,它能快速进行灾难恢复,速度比 AOF 快很多,而且在进行数据恢复的时候,RDB 模式可以异步进行,主进程会 fork 一个子进程来进行数据恢复,不会影响主进程。

缺点: 会丢失部分数据数据。

AOF

文件追加模式

三种触发机制

  • always:每次有数据发生变化都会进行同步,这样能保证数据的完整性但牺牲了系统的性能,同步持久化
  • everysec:默认配置,每秒都会进行数据保存,如果碰巧不小心一秒内停电宕机,也只会丢失这一秒的数据,异步
  • no:从不同步数据

优点:

  • AOF 模式数据完整性更强更加安全,最多只会让系统丢失一秒的数据。
  • AOF 模式写入性能高,不会频繁进行磁盘 IO,而且文件损坏相对来说概率小。
  • AOF 模式当数据达到一个阈值的时候,可以对文件进行压缩,减小文件大小,异步 fork 另一个进程,不影响主进程 。
  • AOF 模式适合最紧急性灾难数据恢复。如果有同事错用 flushall 命令导致所有数据都被他删除了,前提是服务器后台重写还没有开始,这个同事还是能被救的哦。此时我们应该马上复制一份 AOF 文件,并将最后一次使用的 flushall 命令删除掉,然后再拷贝的文件重新放回去便可。

缺点:

  • AOF 日志文件比 RDB 数据快照文件更大,速度没 RDB 快
  • AOF 写 QPS 相对来说会比 RDB 支持的写 QPS 低

使用管道技术提高服务器性能

什么是管道技术

以前我们没发一个请求,会等服务端给我们一个响应,然后才能进行下一步操作。但我们想不等服务端给我们回应就不能做其他的事情了么?不能再给服务器发请求了么?虽然我们可以用异步去实现这个功能,今天跟大家说的 Redis 管道技术就是很好地帮我们解决了这个问题。

Redis 管道技术可以一次性读取客服端发来的请求。例如,Redis 上有一个 key age=18,当客户端 client 连续发送 incr age. 总共三次,服务端会一次性去接收这三条命令,并且处理好响应,看到的返回结果是 age=21。频繁地操作 Redis,对效率会有很大的影响。虽然这管道技术在处理请求上效率提高了,减少了链路上时间的消耗,但只能是要求实时性不是那么高的系统。如果系统要求实时性十分高,就好像我每次操作都要看到结果,管道技术就不太适合。

使用场景

  • 群发消息,如果每发一条消息就去操作链接 Redis,一千万的话,那不是要一千万次操作,可怕,每个链接耗时 2 或者 3ms,总耗时也是很可怕的。
  • 获取或者处理批量数据。

异步队列和延时队列的实现

异步队列

Redis 是用来做轻量级消息队列的(Disque 是新出的特性,消息队列系统),如果是重量级的,kafka、rocketMq、RabbitMQ 等消息中间件是更好的选择。

延时队列

场景

  • 订单下单后没有支付成功,订单自动取消
  • 订单完成后,系统自动评价

原理

Redis 的基本数据类型 soredSet(Zset)可以用来实现延时队列 用户下单后生成订单并记录其下单时间作为 score,我们希望 30 分钟后,如果支付不成功,我们系统便可以对它进行自动取消订单。操作。每个用户订单我们都会放到 soredSet 数据结构的列表,然后按时间大小进行排序。我们会有定时任务不断地从列表获取订单,如果当前时间和订单下单时间对比,如果大于 30 分钟以上,我们就会对它完成取消订单的操作。

Redis cluster 集群

什么是集群?

Redis 集群主要是解决单个实例在高并发场景压力过大的问题,减压分压。集群的数据会根据不同的 key 分到不同的槽中。

集群和哨兵模式和主从复制的区别?

  • 哨兵模式:主要是为了保证服务器的高可用,哨兵会监控集群里面的每一台服务器,如果出现问题了,它就会重新进行选举,选出 master。
  • 主从模式:是为了防止主节点突然宕机,数据丢失了,它会复制一份数据到另外一台机子上,就是备份。

搭建集群的时候,如果没有配置哨兵模式,如果 master 服务器宕机了,每次都需要手动去操作:slaveof IP,选择另外一台从服务器作为 master。有了哨兵模式后,就能自动选举,不用手动进行。

Redis 集群,需要至少 3 个主节点,既然有 3 个主节点,而一个主节点搭配至少一个从节点,因此至少得 6 台 Redis 服务器。

集群的一些常用配置,以后有机会可以再分享。

分片技术

假如领导需要看下订单的半年数据报表,而这些订单的数据量大,而且获取这些报表数据除了要关联多张表,还要调用第三方接口进行统计进行复杂的逻辑的计算。如果是用正常的方式去处理数据,一天之内完成任务是不可能。

这个时候我们的 Redis 分片技术就可以帮我们解决这个问题。 利用这个技术,我们可以用更多的服务器去实现这个功能,就好像,以前我一个人干的事,现在来了 6 个人帮我,效率肯定会比之前快多。

解决方案:我们会按时间进行分区,例如 1 月份、2 月份、3 月份,每个月份代表一个区间,一个 Redis 实例会处理一个区间的数据,当所有区间的事务都处理完了就可以了。

下面是我们设置的分区,每个会根据订单的下单时间查询出所有订单,然后对订单进行一些业务处理。

首先我们会把分区信息缓存到 Redis,然后启动多台实例进行处理数据:

 	[{
"pageSize": 1000,
"startTime": "2018-03-02 00:00:00",
"endTime": "2019-01-02 00:00:00"
}, {
"pageSize": 1000,
"startTime": "2019-01-02 00:00:00",
"endTime": "2019-03-02 00:00:00"
}, {
"pageSize": 1000,
"startTime": "2019-03-02 00:00:00",
"endTime": "2019-06-02 00:00:00"
}, {
"pageSize": 1000,
"startTime": "2019-06-02 00:00:00",
"endTime": "2019-09-02 00:00:00"
}, {
"pageSize": 1000,
"startTime": "2019-09-02 00:00:00",
"endTime": "2019-10-02 00:00:00"
}, {
"pageSize": 1000,
"startTime": "2019-10-02 00:00:00",
"endTime": "2059-09-01 00:00:00"
}]

strings、strconv:让你高效的处理字符串的更多相关文章

  1. 【转自CSDN】深入 Microsoft.VisualBasic.Strings.StrConv 簡繁轉換

    深入 Microsoft.VisualBasic.Strings.StrConv 簡繁轉換 昨天又遇到一個簡繁轉換的需求, 雖然這個問題以前已經處理過了, 但是以前是用自己建立的 b52gb 和 gb ...

  2. GO开发[三]:fmt,strings,strconv,指针,控制结构

    一.fmt包 %v 值的默认格式表示.当输出结构体时,扩展标志(%+v)会添加字段名 %#v 值的Go语法表示 %T 值的类型的Go语法表示 %% 百分号 %t 单词true或false %b 表示为 ...

  3. JS下高效拼装字符串的几种方法比较与测试代码

    在使用Ajax提交信息时,我可能常常需要拼装一些比较大的字符串通过XmlHttp来完成POST提交.尽管提交这样大的信息的做法看起来并不优雅,但有时我们可能不得不面对这样的需求.那么JavaScrip ...

  4. 最优雅,高效的javascript字符串拼接

    这种方式是es6的语法.使用键盘1左边的那个字符 `` 拼接, 再加上js自带的模板引擎拼接字符串非常快速.这东西也没什么高深的,看几个例子就懂了. console.log(`<xml> ...

  5. 使用StringBuilder更高效的处理字符串

    前言 本文介绍一个java中处理字符串的技巧 - 使用StringBuilder提高字符串处理效率. StringBuilder 当程序要频繁的进行字符串拼接的时候,直接使用String效率会比较低. ...

  6. iOS开发:Swift/Objective-C高效生成随机字符串

    原文连接 Objective-C版 // 随机生成字符串(由大小写字母.数字组成) + (NSString *)random: (int)len { char ch[len]; for (int in ...

  7. Go中的字符串使用----strings和strconv

    Go中的字符串操作 字符串是工作中最常用的,值得我们专门的练习一下.在Go中使用strings包来操作字符串,这也是内置的包哈,不像Java中要么手写,要么引入common-lang 或者 别的第三方 ...

  8. golang(3):strings和strconv使用 & 时间和日期类型 & 指针类型 & 流程控制 & 函数

    strings和strconv使用 . strings.HasPrefix(s string, prefix string) bool: // 判断字符串s是否以prefix开头 . . string ...

  9. Go开发之路 -- strings以及strconv的使用

    strings的使用 HasPrefix 语法: strings.HasPrefix(s, prefix string) bool // 判断字符串s是否以prefix开头 // 判断一个url是否以 ...

随机推荐

  1. 七十六:flask.Restful之flask-Restful插件的基本使用

    安装:flask 0.8以上.python2.6或者3.3以上:pip install flask-restful 使用方法:1.从flask_restful中导入Api,来创建对象 2.写一个视图函 ...

  2. mysql数据库为什么要分表和分区?

    一般下载的源码都带了MySQL数据库的,做个真正意义上的网站没数据库肯定不行. 数据库主要存放用户信息(注册用户名密码,分组,等级等),配置信息(管理权限配置,模板配置等),内容链接(html ,图片 ...

  3. 闲记Windows 取证艺术

         是不是很好奇,别人能够在你电脑上查看你运行程序历史,文档使用痕迹,浏览器浏览历史种种历史痕迹,没错,通过简单的系统了解以及配合相对应的工具,这一切就是这么的简单,看起来很高大上的操作,其实是 ...

  4. 【Spring Cloud】Spring Cloud使用总结

    项目概要 项目环境信息 IDEA ultimate 2018.3.2 springboot 2.1.7.RELEASE springCloud Greenwich.SR2 Eureka 介绍 基于ne ...

  5. Windows系统中CMD wmic查看硬盘、内存、CPU、BIOS、网卡等信息。(附带脚本)

    目录 Windows系统中CMD wmic查看硬盘.内存.CPU.BIOS.网卡等信息. 查看内存相关 查看内存主板数量(两条命令均可) 查看物理内存 查看逻辑内存 查看缓存 查看虚拟内存 查看网络相 ...

  6. Oracle网络相关概念与常用配置文件

    监听器(Listener) 监听器是Oracle基于服务端的一种网络服务,主要用于监听客户端向数据库服务器提出的链接请求. 本地服务名(Tnsname) Oracle客户端与服务器端的链接是通过客户端 ...

  7. dual Oracle兼容

    CREATE OR REPLACE VIEW dual ASSELECT NULL::"unknown"WHERE 1 = 1;

  8. 谷歌官方颜色库 MaterialDesignColor

    谷歌官方颜色库 MaterialDesignColor

  9. Django中ORM操作提升性能

    提升orm操作性能注意的点 优化一:尽量不查对象,能用values就是用values 直接使用对象查询的结果是5条sql语句 def youhua(request): # 使用对象查 obj_list ...

  10. MySQL中的SQL的常见优化策略

    MySQL中的SQL的常见优化策略 MySQL中的索引优化 MySQL中的索引简介 1 避免全表扫描对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索 ...