Redis learning

199 阅读11分钟

News flash

1.VueConf 2019

Vue Conf 2019 上海于 2019 年 6 月 8 日成功举办,会议中 Vue 作者尤雨溪做了题为“State of Vue”的演讲,介绍了 Vue 的现状和未来 Vue 3.0 的进化,包括:更小、更快、加强 TypeScript 支持、Function-based API、提高自身可维护性以及开放更多底层功能。🔗

www.yuque.com/vueconf/201…

2.Chrome 75 发布,带来了一些新特性,包括:能减少 canvas 延迟的 desynchronized 选项、使用 Web Share API 分享文件以及提供了更好数字可读性的下划线数字分隔符。🔗

3.全球大前端技术大会

gmtc2019.geekbang.org/schedule

4.Apple WebKit 开发者在 Twitter 上透露,在解决了一些遗留的限制之后,Safari 在 iOS 和 macOS 上统一了引擎,与此同时开发者无需使用 CSS 属性-webkit-overflow-scrolling: touch即可获得平滑滚动的特性。🔗

点评:iOS 13 中的 Safari 从此摆脱旧的 iPhone,宛如新生。

5.TypeScript 3.5 发布,在编译器、语言和工具上带来了一些新特性,还有一些关键功能包括速度改进,Omit 辅助类型,改进的多余属性检查等。🔗



Redis 是什么?

Redis 是一个开源的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings)散列(hashes)列表(lists)集合(sets)有序集合(sorted sets)

redis的特点

1.速度快


横轴是连接数,纵轴是每秒查询数。1w个客户端同时请求,1秒可响应12w个请求。


2.为什么快?

Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。

数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的。类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)。

采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。

使用多路I/O复用模型,非阻塞IO,单线程轮训多个流。


redis 怎么安装?

wget http://download.redis.io/releases/redis-5.0.5.tar.gz
tar xzf redis-5.0.5.tar.gz

mv redis-5.0.5 /usr/local/cd redis-5.0.5
sudo make install

#以默认配置启动redisServer
redis-server

#以指定配置文件启动redisServer
redis-server /usr/local/redis-5.0.5/redis.conf
#启动redis客户端连接到server
redis-cli
ping
>pong



redis配置文件

#绑定的主机地址bind 127.0.0.1      #指定 Redis 监听端口,默认端口为 6379port 6379      #日志文件位置logfile /usr/local/redis-5.0.5/log-redis.log  #地址的相对路径dir ./   #指定本地数据库文件名,默认值为 dump.rdbdbfilename dump.rdb    #分别表示 900 秒(15 分钟)内有 1 个更改,300 秒(5 分钟)内有 10 个更改以及 60 秒内有 10000 个更改save 900 1save 300 10save 60 10000





redis 怎么用?

通用的redis命令

[命令] [键名]

#查看所有的键
keys *
#前数据库中所有键的总数
dbsize
#检查key是否存在,存在返回1,不存在0
exists key
#论任何数据结构类型,del都可以将其删除
del key
#对键添加过期时间,当超过时间后键会被自动删除
expire key time
#查看键的数据类型
type key



redis的五种数据结构

分别是string(字符串),hash(哈希),list(列表),set(集合),zset(有序集合)


  • 字符串(string)

字符串类型是redis最基础的数据类型,其他几种数据结构都是在字符串基础上建立的.字符串的值可以是字符串、数字、二进制,值最大不可以超过512mb

  • 哈希(hash)
hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象


// 设置键为website    
 website: {         
    google: "google.com",       
    baidu: "baidu.com",        
    nio: "nio.com",       
    apollo: "apollo.com",       
    zeus: "zeus.com"      
  }


键值本身又是一个键值对结构,例如:value={{field1,value1},....,{fieldN,valueN}}

  • 列表(list)
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)


消息队列:redislpush-brpop命令组合即可实现阻塞队列,生产者客户端使用lpush命令向列表插入元素.消费者客户端使用brpop命令阻塞式的"抢"列表中的尾部元素.多个客户端保证消息的负载均衡与可用性.

列表的使用场景有很多如: lpush+lpop=Stack(栈)、lpush+rpop=queue(队列)、lpush+brpop=message queue(消息队列)、lpush+ltrim=Capped Collection(有限集合)

取最新N个数据的操作

记录前N个最新登陆的用户Id列表,超出的范围可以从数据库中获得。

//把当前登录人添加到链表里
ret = r.lpush("login:last_login_times", uid)

//保持链表只有N位
ret = redis.ltrim("login:last_login_times", 0, N-1)

//获得前N个最新登陆的用户Id列表
last_login_list = r.lrange("login:last_login_times", 0, N-1)
  • 集合(set)

集合(set)类型也是用来保存多个的字符串元素,但和列表不同的是:它的元素是无序且不可重复的,不能通过索引获取元素.如下图,集合user:1:follows中包含着"his"、"it"、"sports"、"music"四个元素,一个集合最多可以存储(2的32次方-1)个元素.


在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。

  • 有序集合(Sorted Set)

有序集合相对于哈希、列表、集合来说会有一点陌生,但既然叫有序集合,那么它和集合必然是有着联系,它保留了集合不能重复元素的特性.但不同的是,有序集合是可排序的.但是他和列表使用索引下标进行排序依据不同的是,它给每个元素设置一个分数(score)作为排序的依据, 其中成员的位置按score值递增(从小到大)来排序.

当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构,比如twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。



列表、集合、有序结合的异同点


事务

Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:

  • 批量操作在发送 EXEC 命令前被放入队列缓存。
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

一个事务从开始到执行会经历以下三个阶段:

  • 开始事务。
  • 命令入队。
  • 执行事务。


单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。

事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。

比如把命令用在了错误的类型上。sadd是执行成功的,但lpop执行错误了。



发布订阅

Redis提供了发布订阅功能,可以用于消息的传输,Redis的发布订阅机制包括三个部分,发布者,订阅者和Channel.

作为例子, 下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2client5client1 之间的关系:


当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:


编程示例

client 向news、music频道推送消息

var Redis = require("ioredis");var pub = new Redis();
pub.publish("news", "Hello world news!");
pub.publish("music", "Hello again music!");

监听news music频道

var Redis = require("ioredis");var redis = new Redis();
# 开始监听news music频道

redis.subscribe("news", "music", function(err, count) {});# 监听接受的消息redis.on("message", function(channel, message) {    console.log(`Receive message ${message} from channel ${channel}`);});


键空间通知(keyspace notification)

键空间通知是基于发布订阅功能的。默认是关闭状态,开启则需要修改redis.conf文件或通过CONFIG SET来开启或关闭该功能。这里我们使用CONFIG SET来开启:

redis-cli config set notify-keyspace-events Ex

这里有人会问了, Ex 是什么意思呢?这是notify-keyspace-events的参数,完整的参数列表看看下面的表格:

字符发送的通知
K键空间通知,所有通知以 keyspace@<db> 为前缀
E键事件通知,所有通知以 keyevent@<db> 为前缀
gDEL 、 EXPIRE 、 RENAME 等类型无关的通用命令的通知
$字符串命令的通知
l列表命令的通知
s集合命令的通知
h哈希命令的通知
z有序集合命令的通知
x过期事件:每当有过期键被删除时发送
e驱逐(evict)事件:每当有键因为 maxmemory 政策而被删除时发送

可以看出,我们只开启了键事件通知和过期事件。因为我们实现延时任务只需要这两个就足够了


redis使用场景有哪些?

1.redis缓存最新的项目列表

下面这个语句常用来显示最新项目,随着数据多了,查询毫无疑问会越来越慢。

SELECT * FROM foo WHERE ... ORDER BY time DESC LIMIT 10   

 在Web应用中,“列出最新的回复”之类的查询非常普遍,这通常会带来可扩展性问题。这令人沮丧,因为项目本来就是按这个顺序被创建的,但要输出这个顺序却不得不进行排序操作。类似的问题就可以用Redis来解决。比如说,我们的一个Web应用想要列出用户贴出的最新20条评论。在最新的评论边上我们有一个“显示全部”的链接,点击后就可以获得更多的评论。我们假设数据库中的每条评论都有一个唯一的递增的ID字段。使用Redis,每次新评论发表时,我们会将它的ID添加到一个Redis列表:

LPUSH latest.comments <ID>   

我们将列表裁剪为指定长度,因此Redis只需要保存最新的5000条评论:

LTRIM latest.comments 0 5000 

每次我们需要获取最新评论的项目范围时,尝试先看看redis能否满足数据量,小于5000的id都可以直接从redis读(使用伪代码):

FUNCTION get_latest_comments(start, num_items):  
    id_list = redis.lrange("latest.comments",start,start+num_items - 1)  
    IF id_list.length < num_items  
        id_list = SQL_DB("SELECT ... ORDER BY time LIMIT ...")  
    END  
    RETURN id_list  
END 

只有在start/count参数超出了这个范围的时候,才需要去访问数据库。

2.排行榜应用,取TOP N操作

 一个很普遍的需求是各种数据库的数据并非存储在内存中,因此在按得分排序以及实时更新这些几乎每秒钟都需要更新的功能上数据库的性能不够理想。典型的比如那些在线游戏的排行榜,比如一个Facebook的游戏,根据得分你通常想要:

- 列出前100名高分选手

- 列出某用户当前的全球排名

这些操作对于Redis来说小菜一碟,即使你有几百万个用户,每分钟都会有几百万个新的得分。模式是这样的,每次获得新得分时,我们用这样的代码:

ZADD leaderboard  <score>  <username>

你可能用userID来取代username,这取决于你是怎么设计的。得到前100名高分用户很简单:

ZREVRANGE leaderboard 0 99

用户的全球排名也相似,只需要:

ZRANK leaderboard <username>

3.redis作为分布式锁

基于Redis实现的锁机制,主要是依赖redis自身的原子操作,例如:

SETNX job "programmer"
EXPIRE job 1

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写

当redis中不存在job这个键的时候,才会去设置一个user_key键,并且给这个键的值设置为 programmer,且这个键的存活时间为1s

为什么这个命令可以帮我们实现锁机制呢?

因为这个命令是只有在某个key不存在的时候,才会执行成功。那么当多个进程同时并发的去设置同一个key的时候,就永远只会有一个进程成功。

当某个进程设置成功之后,就可以去执行业务逻辑了,等业务逻辑执行完毕之后,再去进行解锁。

解锁很简单,只需要删除这个key就可以了,不过删除之前需要判断,这个key对应的value是当初自己设置的那个。

blog.51cto.com/13732225/21…

4.redis共享多个实例的服务器程序的session

使用Redis进行会话缓存。例如,将web session存放在Redis中。

5.redis-订阅发布模式-实现延迟任务

xcoder.in/2015/06/05/…