vlambda博客
学习文章列表

选 Redis 还是 Memcached?请听我说...

卫河公园推荐搜索
redis
memcached

Redis 介绍

选 Redis 还是 Memcached?请听我说...
redis-logo

简介

Redis (Remote Dictionary Server),即远程字典服务。是一种 NoSQL 数据库。

官网介绍:

Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.

翻译:

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

Redis 和 Memcached 的比较

相同点

  • 都支持存储 Key-Value 数据
  • 都支持集群部署
  • 数据存放在内存中
  • 基于内存操作,存取速度快

不同点

  • Redis 可以支持更多的数据类型,如 lists、sets等
  • Redis 支持事务,Memcached 不支持
  • Redis 支持持久化,断电数据不丢失。Memcached 不支持持久化,数据易丢失
  • Redis 使用单线程IO模型,Memcached使用多线程IO模型

Redis 6 正式支持多线程IO模型

趋势对比

  • 百度趋势

    选 Redis 还是 Memcached?请听我说...
    baidu-trend-redis-memcached
  • 谷歌趋势

    选 Redis 还是 Memcached?请听我说...
    google-trend-redis-memcached

通过对比发现,Redis 热度已遥遥领先 Memcached,技术选型首选 Redis 方案

数据结构

key-value-data-stores

字符串(Strings)

字符串是一种最基本的Redis值类型,一个字符串类型的值最多能存储512M字节的内容。

列表(Lists)

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

集合(Sets)

Redis集合是一个无序的字符串合集。你可以以O(1) 的时间复杂度(无论集合中有多少元素时间复杂度都为常量)完成 添加,删除以及测试元素是否存在的操作。Redis集合有着不允许相同成员存在的优秀特性。

有序集合(Sorted Sets)

Redis有序集合和Redis集合类似,是不包含 相同字符串的合集。它们的差别是,每个有序集合 的成员都关联着一个评分,这个评分用于把有序集 合中的成员按最低分到最高分排列。

哈希(Hashes)

Redis Hashes是字符串字段和字符串值之间的映射,所以它们是完美的表示对象(eg:一个有名,姓,年龄等属性的用户)的数据类型。

还有 Bit arrays 、HyperLogLogs 等不常用结构,此处不再过多介绍。

二进制安全就是,字符串不是根据某种特殊的标志来解析的,无论输入是什么,总能保证输出是处理的原始输入而不是根据某种特殊格式来处理。

在 C 语言中,字符串可以用一个 \0 结尾的 char 数组来表示

比如说, hello world 在 C 语言中就可以表示为 "hello world\0"

判断字符串"1234\0123"的长度,看下面代码:

# c语言

str="1234\0123";

strlen(str)=4

# redis

127.0.0.1:6379set str 1234\0123

OK

127.0.0.1:6379strlen str

(integer) 9

关于key的几条规则:

  • 合理的键长:不易太长或太短,易阅读
  • 固定模式:例如:”object-type:id:field”就是个不错的注意,像这样”user:1000:password”

持久化

RDB

在指定的时间间隔能对你的数据进行快照存储。默认持久化方式

AOF

记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据。需要手工开启

RDB 和 AOF 比较

redis-rdb-aof
  • Redis 默认开启RDB持久化方式,在指定的时间间隔内,执行指定次数的写操作,则将内存中的数据写入到磁盘中。

  • RDB 持久化适合大规模的数据恢复但它的数据一致性和完整性较差。

  • Redis 需要手动开启AOF持久化方式,默认是每秒将写操作日志追加到AOF文件中。

  • AOF 的数据完整性比RDB高,但记录内容多了,会影响数据恢复的效率。

  • Redis 针对 AOF文件大的问题,提供重写的瘦身机制。

  • 若只打算用Redis 做缓存,可以关闭持久化。

  • 若打算使用Redis 的持久化。建议RDB和AOF都开启。其实RDB更适合做数据的备份,留一后手。AOF出问题了,还有RDB。

集群

主从复制

在 Redis 复制的基础上,使用和配置主从复制非常简单,能使得从 Redis 服务器(下文称 slave)能精确得复制主 Redis 服务器(下文称 master)的内容。每次当 slave 和 master 之间的连接断开时, slave 会自动重连到 master 上,并且无论这期间 master 发生了什么, slave 都将尝试让自身成为 master 的精确副本。

高可用

Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务:

  • 监控(Monitoring):Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
  • 提醒(Notification):当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
  • 自动故障迁移(Automatic failover):当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器;当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。

redis-sentinel , 实际上它只是一个运行在特殊模式下的 Redis 服务器, 你可以在启动一个普通 Redis 服务器时通过给定 –sentinel 选项来启动 Redis Sentinel

常用命令

常用管理命令

启动Redis

> redis-server [--port 6379]

通过配置文件来启动Redis

redis-server [xx/xx/redis.conf]

连接Redis

./redis-cli [-h 127.0.0.1 -p 6379]

关闭连接

127.0.0.1:6379> quit

输入密码

# 123456 为密码
127.0.0.1:6379> auth 123456

停止Redis

> redis-cli shutdown

>
 kill redis-pid

测试连通性

127.0.0.1:6379> ping
PONG

key 操作命令

获取所有键

127.0.0.1:6379> keys *
1) "javastack"

获取键总数

127.0.0.1:6379> dbsize
(integer) 6

查询键是否存在

127.0.0.1:6379> exists javastack java
(integer) 2

删除键

127.0.0.1:6379> del java javastack
(integer) 1

移动键

# 如把javastack移到2号数据库
127.0.0.1:6379> move javastack 2
(integer) 1
127.0.0.1:6379> select 2
OK
127.0.0.1:6379[2]> keys *
1) "javastack"

查询key的生命周期(秒)

# -1:永远不过期。
127.0.0.1:6379[2]> ttl javastack
(integer) -1

设置过期时间

# expire 秒,pexpire 毫秒
127.0.0.1:6379[2]> expire javastack 60
(integer) 1
127.0.0.1:6379[2]> ttl javastack
(integer) 55

设置永不过期

127.0.0.1:6379[2]> persist javastack
(integer) 1

更改键名称

127.0.0.1:6379[2]> rename javastack javastack123
OK

字符串(Strings)操作命令

存放键值

语法:set key value [EX seconds] [PX milliseconds] [NX|XX]

nx:如果key不存在则建立,xx:如果key存在则修改其值,也可以直接使用setnx/setex命令。

# 
127.0.0.1:6379> set javastack 666
OK

获取键值

127.0.0.1:6379[2]> get javastack
"666"

值递增/递减

如果字符串中的值是数字类型的,可以使用incr命令每次递增,不是数字类型则报错。

一次想递增N用incrby命令,如果是浮点型数据可以用incrbyfloat命令递增。

同样,递减使用decr、decrby命令。

127.0.0.1:6379[2]> incr javastack
(integer) 667

批量存放键值

127.0.0.1:6379[2]> mset java1 1 java2 2 java3 3
OK

批量获取键值

Redis接收的是UTF-8的编码,如果是中文一个汉字将占3位返回。

127.0.0.1:6379[2]> mget java1 java2
1) "1"
2) "2"

获取值长度

127.0.0.1:6379[2]> strlen javastack
(integer) 3

追加内容

# 向键值尾部添加,如上命令执行后由666变成666hi
127.0.0.1:6379[2]> append javastack hi
(integer) 5

获取部分字符

getrange key start end

start 为开始位置

end 为结束位置

> 127.0.0.1:6379[2]> getrange javastack 0 4
"javas"

列表(Lists)操作命令

存储值

# 左端存值语法:lpush key value [value …]
127.0.0.1:6379> lpush list lily sandy
(integer) 2

#
 右端存值语法:rpush key value [value …]
127.0.0.1:6379> rpush list tom kitty
(integer) 4

#
 索引存值语法:lset key index value
127.0.0.1:6379> lset list 3 uto
OK

弹出元素

# 左端弹出语法:lpop key
127.0.0.1:6379> lpop list
"sandy"

#
 右端弹出语法:rpop key
127.0.0.1:6379> rpop list
"kitty"

获取元素个数

127.0.0.1:6379> llen list
(integer) 2

获取列表元素

两边获取语法:lrange key start stop

127.0.0.1:6379> lpush users tom kitty land pony jack maddy
(integer) 6

127.0.0.1:6379> lrange users 0 3
1) "maddy"
2) "jack"
3) "pony"
4) "land"

#
 获取所有
127.0.0.1:6379> lrange users 0 -1
1) "maddy"
2) "jack"
3) "pony"
4) "land"
5) "kitty"
6) "tom"

#
 从右端索引
127.0.0.1:6379> lrange users -3 -1
1) "land"
2) "kitty"
3) "tom"

索引获取语法:lindex key index

127.0.0.1:6379> lindex list 2
"ketty"

// 从右端获取
127.0.0.1:6379> lindex list -5
"sady"

删除元素

根据值删除语法:lrem key count value

127.0.0.1:6379> lpush userids 111 222 111 222 222 333 222 222
(integer) 8

// count=0 删除所有
127.0.0.1:6379> lrem userids 0 111
(integer) 2

// count > 0 从左端删除前count个
127.0.0.1:6379> lrem userids 3 222
(integer) 3

// count < 0 从右端删除前count个
127.0.0.1:6379> lrem userids -3 222
(integer) 2

范围删除语法:ltrim key start stop

# 只保留2-4之间的元素
127.0.0.1:6379> ltrim list 2 4
OK

集合(Sets)操作命令

存储值

# 这里有8个值(2个java),只存了7个
127.0.0.1:6379> sadd langs java php c++ go ruby python kotlin java
(integer) 7

获取元素

127.0.0.1:6379> smembers langs
1) "php"
2) "kotlin"
3) "c++"
4) "go"
5) "ruby"
6) "python"
7) "java"

随机获取元素

随机获取语法:srandmember langs count

count 几个元素

127.0.0.1:6379> srandmember langs 3
1) "c++"
2) "java"
3) "php"

判断集合是否存在元素

127.0.0.1:6379> sismember langs go
(integer) 1

获取集合元素个数

127.0.0.1:6379> scard langs
(integer) 7

删除集合元素

127.0.0.1:6379> srem langs ruby kotlin
(integer) 2

弹出元素

语法:SPOP key [count] 随机弹出并返回一个或多个元素

count参数将在更高版本中提供,但是在2.6、2.8、3.0中不可用。

127.0.0.1:6379> spop langs 2
1) "go"
2) "java"

有序(Sorted Sets)集合

存储值

语法:zadd key [NX|XX] [CH] [INCR] score member [score member …]

127.0.0.1:6379> zadd footCounts 16011 tid 20082 huny 2893 nosy
(integer) 3

获取元素分数

语法:zscore key member

127.0.0.1:6379> zscore footCounts tid
"16011"

获取排名范围

排名语法:zrange key start stop [WITHSCORES]

# 获取所有,没有分数
127.0.0.1:6379> zrange footCounts 0 -1
1) "nosy"
2) "tid"
3) "huny"

#
 获取所有及分数
127.0.0.1:6379> zrange footCounts 0 -1 Withscores
1) "nosy"
2) "2893"
3) "tid"
4) "16011"
5) "huny"
6) "20082"

获取指定分数范围排名

语法:zrangebyscore key min max [WITHSCORES] [LIMIT offset count]

127.0.0.1:6379> zrangebyscore footCounts 3000 30000 withscores limit 0 1
1) "tid"
2) "16011"

增加指定元素分数

语法:zincrby key increment member

127.0.0.1:6379> zincrby footCounts 2000 tid
"18011"

获取集合元素个数

127.0.0.1:6379> zcard footCounts
(integer) 3

获取指定范围分数个数

语法:zcount key min max

127.0.0.1:6379> zcount footCounts 2000 20000
(integer) 2

删除指定元素

语法:zrem key member [member …]

127.0.0.1:6379> zrem footCounts huny
(integer) 1

获取元素排名

语法:zrank key member

127.0.0.1:6379> zrank footCounts tid
(integer) 1

哈希(Hashes)操作命令

存放键值

单个语法:hset key field value

127.0.0.1:6379> hset user name javastack
(integer) 1

多个语法:hmset key field value [field value …]

127.0.0.1:6379> hmset user name javastack age 20 address china
OK

不存在时语法:hsetnx key field value

# tall 字段不存在
127.0.0.1:6379> hsetnx user tall 180
(integer) 1

#
 tall 字段存在
127.0.0.1:6379> hsetnx user tall 180
(integer) 0

获取字段值

单个语法:hget key field

127.0.0.1:6379> hget user age
"20"

多个语法:hmget key field [field …]

127.0.0.1:6379> hmget user name age address
1) "javastack"
2) "20"
3) "china"

获取所有键与值语法:hgetall key

127.0.0.1:6379> hgetall user
1) "name"
2) "javastack"
3) "age"
4) "20"
5) "address"
6) "china"

获取所有字段语法:hkeys key

127.0.0.1:6379> hkeys user
1) "name"
2) "address"
3) "tall"
4) "age"

获取所有值语法:hvals key

127.0.0.1:6379> hvals user
1) "javastack"
2) "china"
3) "170"
4) "20"

判断字段是否存在

语法:hexists key field

127.0.0.1:6379> hexists user address
(integer) 1

获取字段数量

语法:hlen key

127.0.0.1:6379> hlen user
(integer) 4

递增/减

语法:hincrby key field increment

127.0.0.1:6379> hincrby user tall -10
(integer) 170

删除字段

语法:hdel key field [field …]

127.0.0.1:6379> hdel user age
(integer) 1

这么多的命令,不需要全部记住,一般人一时半会也不可能记得住。仔细观察,发现都遵循 CRUD (增删改查)的分类,查的方式可能更多一些。实际开发中也是通过第三方工具来操作 Redis ,不是通过命令行来操作的,都是封装的工具类或方法来调用的,什么样的数据用什么样的结构来存储才是需要开发者着重考虑清楚的事情。

附录:

Redis 官网 [https://redis.io/]

Redis 中文网[http://www.redis.cn/]

Redis 维基百科[https://en.wikipedia.org/wiki/Redis]

Memcached 官网[http://memcached.org/]

Memcached 中文网[http://www.memcached.org.cn/]

Memcached 维基百科[https://en.wikipedia.org/wiki/Memcached]