redis为什么那么快
# 1. redis为什么那么快?
1.1 基于内存存储实现
- 内存读写是比在磁盘快很多的,Redis基于内存存储实现的数据库,相对于数据存在磁盘的MySQL数据库,省去磁盘1/0的消耗。
1.2 高效的数据结构
redis 数据结构和内部编码
SDS简单动态字符串
字符串长度处理:Redis获取字符串长度,时间复杂度为0(1),而C语言中,需要从头开始遍历,复杂度为0(n);
字典
redis 是一种key-value内存数据库, 搜友的键值都是用字典来存储的.
1.3 合理的数据编码
Redis 支持多种数据类型,每种基本类型,可能对多种数据结构。什么时候,使用什么样数据结构,使用什么样 编码,是redis设计者总结优化的结果。
string:如果存储数字的话,是用int类型的编码;如果存储非数宇,小于等于39字节的字符串,是embstr;大于39个字节,则是raw编码。
list:如果列表的元素个数小于512个,列表每个元素的值都小于64字节(默认),使用ziplist编码,否则使用linkedlist编码
Hash:哈希类型元素个数小于512个,所有值小于64字节的话,使用ziplist编码,否则使用hashtable编码。
set:如果集合中的元素都是整数且元素个数小于512个,使用intset编码,否则使用hashtable编码、
zset:当有序集合的元素个数小于128个,每个元素的值小于64字节时,使用ziplist编码,否则使用skiplist(跳跃表)编码
1.4 合理的io线程模型(io多路复用)
- 多路1/0复用技术可以让单个线程高效的处理多个连接请求,而Redis使用用epol作为1/0多路复用技术的实现。并且,Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络1/0上浪费过多的时间
# 2. Redis有几种数据结构,底层分别是怎么存储的?
- 基础数据类型
- string
- list
- set
- zset
- hash
2.1 String(字符串)
- 简介:String是Redis最基础的数据结构类型,它是二进制安全的,可以存储图片或者序列化的对象,值最大存储为512M
- 简单使用举例:set key value、get key等
- 应用场景:共享session、分布式锁,计数器、限流。
- 内部编码有3种,int (8字节长整型)/embstr(小于等于39字节字符串)/raw(大于39个字节字符串)
2.2 Hash(哈希)
- 简介:在Redis中,哈希类型是指v(值)本身又是一个键值对(k-v)结构
- 简单使用举例:hset key field value、hget key field
- 内部编码:ziplist(压缩列表)、hashtable(哈希表)
- 应用场景:缓存用户信息等。
2.3 List(列表)
- 简介:列表 (list)类型是用来存储多个有序的字符串,一个列表最多可以存储2^32-1个元素。
- 简单实用举例:lpush key value Ivalue ..J、lrange key start end
- 内部编码:ziplist(压缩列表)、linkedlist (链表)
- 应用场景:消息队列,文章列表
2.4 Set(集合)
- 简介:集合(set)类型也是用来保存多个的字符串元素,,但是不允许重复元素
- 简单使用举例:sadd key element [element ..]smembers key
- 内部编码:intset (整数集合)、hashtable(哈希)
- 使用场景:用户标签,生成随机数抽奖、社交需求。
2.5 Zset(有序集合)
- 简介:已排序的字符串集合,同时元素不能重复
- 简单格式举例:zadd key score member [score member…], zrank key member
- 底层内部编码:ziplist(压缩列表)、skiplist(跳跃表)
- 应用场景:排行榜,社交需求(如用户点赞)
# 3. go 如何调用lua脚本执行redis命令
package main
import (
"fmt"
"github.com/gomodule/redigo/redis"
"io/ioutil"
"time"
)
func main() {
// 测试redis连接是否正常
dial, err := redis.Dial("tcp", "10.157.89.90:8379")
if err != nil {
panic(err)
return
}
dial.Close()
pool := &redis.Pool{
Dial: func() (redis.Conn, error) {
c, err := redis.Dial("tcp", "10.157.89.90:8379")
if err != nil {
fmt.Println(err)
return nil, err
}
return c, nil
},
DialContext: nil,
TestOnBorrow: nil,
MaxIdle: 10,
MaxActive: 10,
IdleTimeout: 10 * time.Second,
Wait: false,
MaxConnLifetime: 0,
}
conn := pool.Get()
defer conn.Close()
file, err := ioutil.ReadFile("script/tokenbucket.lua")
if err != nil {
panic(err)
return
}
lua := redis.NewScript(1, string(file))
// 初始化提前加载脚本
err = lua.Load(conn)
if err != nil {
panic(err)
}
// 调用脚本,如果之前没有加载过脚本,Do 方法也会加载脚本
ret, err := lua.Do(conn, "lua", 0, 0, 0, 0, 0)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%#v", string(ret.([]byte)))
// Send 方法不会等待返回结果,只是将命令写到缓存,需要调用redis.Conn的Flush 方法
err = lua.Send(conn, "lua2", 0, 0, 0, 0, 0)
if err != nil {
fmt.Println(err)
return
}
conn.Flush()
// conn.Close()内部会执行一次 Do 方法,可相当于Flush
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
上次更新: 2023/04/16, 18:35:33