redis入门指南读书笔记

以下介绍使用redis版本为5.0.4

简介

redis(remote dictionary server 远程字典服务器)是一个开源高性能的键值对数据库,通过提供多种键值数据类型来适应不同场景下的存储需求,并借助高层次的接口来胜任缓存、队列等角色。

功能

以字典结构存储数据,并允许其他应用通过tcp连接来读写字典中的内容。

支持的键值类型

  • 字符串
  • 散列类型
  • 列表
  • 集合
  • 有序集合

相对于mysql等二维表形式存储数据的关系型数据库有点

  • 存储数据更接近于程序中的数据,操作数据更方便
  • 提供简洁、高效的操作
  • 数据存储于内存中,相对于硬盘存储更为高效(提供有异步持久化写入硬盘)

缓存系统

redis提供有丰富的功能,通过给键值设置ttl(time to live)生存时间,可以作为缓存系统来实现,并且支持持久化和多种数据类型,因此与memcached缓存系统存在竞争关系。

redis是单线程,memcached是多线程,所以在多核服务器上,memcached的性能占优势,但是redis是高性能的,所以一般不会存在性能瓶颈,所以在不考虑性能的情况下,两者的使用选择,主要看使用场景,例如redis提供有多种数据类型和持久化功能,如果需要使用这些高级数据类型或者持久化能力,则可以选择redis

redis的列表类型键支持阻塞式读取,可以实现优先级队列。并且在更高层面支持“发布、订阅”的消息模式,因此可以构建聊天室等系统。

启动配置

通过redis-server命令可以在启动配置相关参数,例如--port <port>配置redis服务的端口号,如果参数较多,可以在redis-server后加配置文件路径,redis-server /redis/path/config,在配置文件后加参数可以覆盖配置文件中的配置 redis-server /redis/path/config --loglevel warning,在redis目录下存在一个配置模板

部分参数在启动后可以通过config命令动态修改,例如:config set loglevel warning

实例下数据库多个字典

redis服务器下有多个字典,类似于一个数据库实例可以有多个数据库,字典以编号排列,不能进行命名,从0开始,默认为16个。而且多个字典面向同一个客户端,即一个redis实例面向一个客户端,多个字典对于一个客户端,要么可以全部访问,要么一个都不能访问。所以建议一个redis实例对应存储一个应用的数据,可以将不同数据存储于多个字典中,不建议一个redis实例存储多个应用的数据,因为这些字典,或数据库不存在隔离性。

符号 含义
? 匹配一个字符
* 匹配任意个(包括0个)字符
[] 匹配括号内任一字符,使用“-”可以匹配范围
\x 匹配字符x,\用作转义

基础命令

命令 作用
keys <keyName> 返回键名,keys *,返回所有键名
exists <keyName> 判断键是否存在,存在返回1,不存在返回0
del <name1 name2 ..> 删除键,可以为多个,返回删除的个数
type <keyName> 返回键值的类型,可能是字符串string、散列hash、列表list、集合set、有序集合zset

del命令不支持通配符删除,可以通过keys命令拿到多个键名来作为输入进行一次删除,示例:
./redis-cli -p 6380 del `./redis-cli -p 6380 keys \*`

字符串类型

字符串作为redis中的基础类型,可以存储任意类型的字符串,包括二进制字符串,或者json化的对象,甚至一张图片,字符串类型键允许存储的最大容量是512M。

字符串类型是其他几种类型的基础,其他类型与字符串类型的不同只是组织字符串方式的差异,例如list列表类型只是以列表的形式来组织字符串,集合只是以集合的方式来组织字符串。

设置、获取键值

命令 作用
set <keyName> <value> 设置键值
get <keyName> 获取键值,当键不存在,返回空(nil)

redis对于键的命名无要求,但推荐使用 对象类型:对象id:对象属性 来命名一个键,例如 user:1:friends 来存储用户id为1的好友列表,多个单词则使用.符号进行分隔

incr与decr

命令 作用
incr <keyName> 递增键值
incrby <keyName> <increment> 指定步长递增键值
decr <keyName> 递减键值
decrby <keyName> <increment> 指定步长递减键值
incrbyfloat <keyName> <increment> 指定浮点数步长递增键值

append添加字符串

命令 作用
append <keyName> <value> 增加字符串,返回添加后字符串长度

strlen返回字符串长度

命令 作用
strlen <keyName> 返回字符串长度

redis可以保存二进制数据,可以保存任何类型的字符串,对于中文,使用utf-8进行编码保存,一个中文字符对应三个字节长度

同时设置、获取多个键值

命令 作用
mset k1 v1 k2 v2 ... 同时设置多个键值
mget k1 k2 ... 同时获取多个键值

二进制位操作

命令 作用
setbit <keyName> <pos> <value> 设置键值对应位的二进制,返回该位置的旧值
getbit <keyName> <pos> 获取键值对应位的二进制
bitcount <keyName> 获取键值二进制中1的个数
bitop [or|xor|and|not] <result> <key1> <key2> 二进制运算,并将结果赋予result

散列类型

redis使用键值对形式的字典结构,散列类型也是一种键值对形式的字典结构,存储字段到字段值的映射,但字段值只能是字符串,不能是其他类型,即不支持嵌套类型,一个散列类型的键最多可以有2^{32}-1个字段。

redis中其他类型同样不支持嵌套类型,例如集合中元素只能是字符串,不能是其他集合或列表类型

散列类型适合存储对象,使用对象和id作为键名,字段名作为属性,字段值作为属性值。该结构相对于关系型数据库的优点是,可以随意增删一个对象的属性,而不用像二维表结构那样修改表结构。

设置、获取属性值

命令 作用
hset <keyName> <field> <value> 设置属性值
hget <keyName> <field> 获取属性值

散列类型不区分插入和更新,插入属性操作返回1,更新操作返回0,如果键不存在,则会自动创建

设置、获取多个属性值

命令 作用
hmset <keyName> <field> <value> <field1> <value1> ... 设置多个属性值
hmget <keyName> <field> <field1> ... 获取多个属性值
hgetall <keyName> 获取所有属性值

判断属性是否存在

命令 作用
hexists <keyName> <field> 属性存在返回1,不存在返回0

属性不存在时赋值

命令 作用
hsetnx <keyName> <field> 属性不存在时赋值,存在不作操作

属性值增加数字

命令 作用
hincrby <keyName> <field> <increment> 属性值增加数字,不存在则创建

删除属性

命令 作用
hdel <keyName> <field> 删除属性,存在返回1,不存在返回0

获取键属性名、属性值

命令 作用
hkeys <keyName> 获取所有属性名
hvals <keyName> 获取所有属性值
hlen <keyName> 获取所有属性个数

列表类型

redis列表内部使用双向链表实现,所以无论列表大小是多大,从头尾获取一定长度的数据速度很快。可以用来保存新鲜事或者日志,不用考虑列表本身有多大,只需要从一端获取数据即可。最大列表项为2^{32}-1个。

添加元素

命令 作用
lpush <keyName> <value> 从左侧添加元素,返回列表长度
rpush <keyName> <value> 从右侧添加元素,返回列表长度

弹出元素

命令 作用
lpop <keyName> 从左侧弹出元素
rpop <keyName> 从右侧弹出元素

redis列表的双向链表特性,可以用于实现栈和队列,作为栈使用时,lpush+lpop或rpush+rpop;作为队列使用时,lpush+rpop或rpush+lpop。

列表长度

命令 作用
llen <keyName> 返回列表长度

列表切片

命令 作用
lrange <keyName> <start> <end> 返回指定范围的列表元素,包括终点位置,可以为负数

常用lrange 0 -1获取列表所有元素

列表中删除指定个数的元素值

命令 作用
lrem <keyName> <count> <value> 从列表中删除count表示个数的元素值,返回删除的个数
  • 当count为正数时,从左侧开始删除count个value元素
  • 当count为负数时,从右侧开始删除|count|个value元素
  • 当count为0时,删除列表中全部value元素

获取、设置指定位置元素

命令 作用
lindex <keyName> <index> 返回指定位置列表元素
lset <keyName> <index> <value> 设置指定位置列表元素值

删除列表指定范围之外的所有元素

命令 作用
ltrim <keyName> <start> <end> 删除列表指定范围之外的所有元素

可用于保存指定数量的日志信息,在lpush添加新日志记录后,使用ltrim 0 99保存指定个数的日志记录。

在指定元素旁插入元素

命令 作用
linsert <keyName> <before after> <pivot> <value> 在指定元素前后插入元素

将元素从一个列表移动到另一个列表

命令 作用
rpoplpush <source> <destination> 将source列表右侧元素弹出,在destination列表左侧插入

当source和destination相同时,可以用于监控程序,循环执行该命令,在不影响新元素加入的情况下,可以对元素进行循环检测

集合类型

集合类型存储不重复的元素,元素唯一,但无需,内部使用值为空的散列表实现,所以查询元素的时间复杂度为O(n),一个集合最多存储2^{32}-1个元素。

集合常见的操作,除了加入、删除和判断元素是否存在外,还能提供灵活的交集、并集和差集操作。

添加、删除集合元素

命令 作用
sadd <keyName> <value> <value2> ... 添加集合元素,返回成功添加的元素个数
srem <keyName> <value> <value2> ... 删除集合元素,返回成功删除的元素个数

返回集合中所有元素

命令 作用
smembers <keyName> 返回集合中所有元素

判断元素是否在集合中

命令 作用
sismember <keyName> <value> 元素在集合中返回1,不在返回0

集合间操作

命令 作用
sdiff <seta> <setb> 元素在seta中,且不在setb中,集合的差集,支持传入多个集合
sinter <seta> <setb> 元素在seta中,且在setb中,集合的交集,支持传入多个集合
sunion <seta> <setb> 元素在seta中,或在setb中,集合的并集,支持传入多个集合

获得集合中元素个数

命令 作用
scard <keyName> 返回集合中元素个数

集合运算并存储结果

命令 作用
sdiffstore <des> <seta> <setb> 将seta与setb的差集赋予des
sinterstore <des> <seta> <setb> 将seta与setb的交集赋予des
sunionstore <des> <seta> <setb> 将seta与setb的并集赋予des

随机获得集合中元素

命令 作用
srandmember <keyName> count 根据count值,随机返回集合中元素
  • 当count为正数时,随机返回count个不重复元素,当count大于集合中元素个数时,返回所有元素
  • 当count为负数时,随机|count|个元素,可能重复

因为redis集合内部是散列表的实现,如果存在散列冲突,则以链表形式存储元素,在链表上随机获取元素,所以对于不冲突的元素,可能srandmember返回的概率更高一些。

从集合弹出随机元素

命令 作用
spop <keyName> 从集合弹出随机元素

有序集合类型

有序集合相对于集合,给元素增加了一个关联的分数,以此提供获得最高或最低的N个元素,或者指定分数范围的元素等操作。

有序集合通过散列表和跳跃表实现,相对于集合和列表更耗费内存。

向有序集合添加元素

命令 作用
zadd <keyName> <score> <value> <score1> <value1> ... 向有序集合添加元素

获取有序集合元素的分数

命令 作用
zscore <keyName> <value> 返回元素的分数

根据分数排名获得在某个下标范围的元素列表

命令 作用
zrange <keyName> <start> <end> 获取排名在某个范围的元素列表,包括首尾位置

该命令与lrange返回指定下标范围的列表元素很相似,并且提供附加参数withscores,返回元素与分数。zrevrange与zrange相似,只是按照分数由大到小输出。

获得在某个分数范围的元素列表

命令 作用
zrangebyscore <keyName> <start> <end> 获取分数在某个范围的元素列表,包括首尾位置

可以通过(符号来表示分数小于该分数值,同样支持正负无穷,-inf和+inf,withscores参数用法同上

获得在某个分数范围内指定偏移量的元素列表

命令 作用
zrangebyscore <keyName> <start> <end> limit <offset> <count> 获取分数在某个范围内,从offset开始的count个元素列表,包括首尾位置

逆序的偏移量使用方式为zrevrangebyscore方式,start和end为由大到小方式

给元素增加分数

命令 作用
zincrby <keyName> <increment> <value> 给元素增加分数

获得集合中元素的个数

命令 作用
zcard <keyName> 返回元素的个数

获得集合中指定范围的元素个数

命令 作用
zcount <keyName> <start> <end> 返回集合中指定范围的元素个数

默认包括首尾数值,通过(符号来不包含首尾值

删除集合中元素

命令 作用
zrem <keyName> <value> 返回删除成功的元素个数

按照排名范围删除集合中元素

命令 作用
zremrangebyrank <keyName> <start> <end> 返回删除成功的元素个数

按照分数范围删除集合中元素

命令 作用
zremrangebyscore <keyName> <start> <end> 返回删除成功的元素个数

获得元素排名

命令 作用
zrank <keyName> <value> 返回元素按照分数正序排名
zrevrank <keyName> <value> 返回元素按照分数逆序排名

有序集合间操作

命令 作用
zinterstore <desSet> <numkeys> <set1> ... 集合间交集运算,元素分数默认为相加
zunionstore <desSet> <numkeys> <set1> ... 集合间并集运算,元素分数默认为相加

通过添加weights <key1Weight> <key2Weight> 可以给集合元素分数设置不同权重,通过添加aggregate sum|min|max可以对元素分数取不同操作

事务

事务是一组命令的集合,同命令一样,都是redis的最小执行单元。事务中的命令,要么都执行,要么全部不执行。

redis事务的执行原理,是发送一个事务命令,然后将待执行命令存储于事务队列之中,然后发送exec命令执行队列中的命令。

事务操作

命令 作用
multi 标志事务块开始
exec 执行事务中命令

redis中不提供回滚的功能,如果是语法错误,在发送exec命令后,redis会识别错误,并放弃执行所有命令,如果是运行错误,则redis会执行所有能执行的命令,因为在执行前并不能识别出哪些命令可以执行,哪些不可以执行。需要用户对这些执行错误的命令进行修复。

watch

多线程环境中,对键值的非原子操作可能存在竞态条件,例如先判断键值,再修改键值,这种非原子操作在并发情况下可能得到一些非预想结果。通过watch命令可以设置对键值的监听,后续的操作根据监听来选择是否执行。

watch命令对一个或多个键值进行监听,当一个或多个键值发生变化时,则后续的一个事务取消执行;若监听的键值都没有发生变化,则执行事务,exec后,取消对键值的监听。

unwatch命令可以取消对键值的监听,事务中通过discard命令取消事务,也可以做到取消对键值的监听。

expire

expire命令可以设置键的生存时间,单位为秒,过期后删除该键。通过ttl命令可以查看键的剩余生存时间,如果没有对键设置生存时间,则返回-1,如果键不存在或到期后被删除,则返回-2。

通过set命令对键进行修改,相当于设置键的生存时间为永久,即相当于没有设置生存时间。生存时间的操作可以应用于一些限制访问频率的场景中。

缓存

为了提高网站的负载能力,往往将一些cpu或内存消耗较高的操作结果存入redis,并设置过期时间,以此来提供缓存的功能。如果生存时间设置较长,则可能存在内存一直被占用的问题,如果生存时间设置较短,则可能导致命中率过低的问题。

对redis中缓存键的淘汰有如下几种规则:

规则 说明
volatile-lru 使用lru算法清除一个键(对于设置了生存时间的键)
allkeys-lru 使用lru算法清除一个键
volatile-random 随机清除一个键(对于设置了生存时间的键)
allkeys-random 随机清除一个键
volatile-ttl 删除ttl时间最少的一个键
noeviction 不删除键,返回错误

sort

sort命令提供对集合、有序集合、列表的排序功能,默认将元素转为双精度浮点数进行递增排序,通过alpha参数可以按照字典序进行排序,通过desc参数可以进行递减排序,通过limit offset count参数可以获取指定偏移量的count个元素。

对有序集合的排序,是按照元素自身来排序的,与分数无关。

如果使用by参考键来进行排序,则排序操作不依赖自身元素字典值,而是将自身元素替换掉参考键的第一个*符号,并取其值作为排序依据进行排序。

示例:
集合tag:ruby:posts,存储文章的id,post:<id>哈希键,存储文章对象的多个属性,例如time、id、title等,此处对集合tag:ruby:posts进行排序,排序的依据是文章的更新时间降序排列

sort tag:ruby:posts by post:*->time desc

该命令作用为使用文章对象的time属性降序排列文章的id集合

get

get命令可以搭配sort命令,获取排序后的属性值,同样使用*符号替换属性名
示例:
在依据文章的时间对id集合进行排序后,根据id获取文章的title

sort tag:ruby:posts by post:*->time desc get post:*->title

可以填写多个get,同时获取多个属性值

获取待排序集合自身,可以使用get #

store

sort执行的结果默认直接返回,也可以将结果存储为一个键,作为结果集使用

sort tag:ruby:posts by post:*->time desc get post:*->title store sort.result

排序后的结果解store后为list类型,常用的方式为搭配expire命令,作为sort后的缓存结果使用

expire sort.result <seconds>

sort命令的时间复杂度为O(n+mlogm),其中n为待排序列表(集合或有序集合)的元素个数,m为待返回的元素个数。为了避免sort命令称为性能瓶颈,使用时需要注意sort的使用方式,常见的优化方式为:
1)减少待排序元素
2)使用limit获取尽量少的元素
3)数据量较大时,使用store+expire建立缓存

任务队列

使用列表可以实现任务队列,例如lpush+rpop,可以使用rpop循环获取列表中元素,如果元素存在则处理,不存在则等待一定时间继续从队列中获取元素。

redis中提供有brpop/blpop命令来阻塞获取队列中元素

brpop <queue> <time>

time为阻塞时间,当time为0时,表示一直阻塞。

brpop/blpop阻塞等待命令可以同时监控多个队列,所以可以将不同优先级的任务放在不同队列中,优先级高的队列放在前面,这样当高优先级的队列中有元素时,则取元素并执行任务。

brpop que1 que2 0

发布、订阅模式

除了任务队列外,redis提供有发布/订阅模式来实现进程间通信。订阅者可以订阅若干个频道,发布者可以向指定频道发送消息,所有订阅次频道的订阅者都可以接收到该消息。

发布到某频道的消息不会进行持久化,即订阅者只能收到订阅此频道之后发布到该频道的消息。

publish <channel> <message>
subscribe <channel> ...

publish命令会返回接收该消息的订阅者数量,subscribe有三种返回类型:

  • 第一个值为subscribe类型,第二个值是订阅频道名称,第三个值是当前订阅者订阅的频道数量
  • 第一个值为message类型,第二个值是频道名称,第三个值是消息内容
  • 第一个值为unsubscribe类型,第二个值是取消订阅的频道名称,第三个值是当前订阅者订阅的频道数量

通过psubscribe可以使用通配符形式订阅频道

psubscribe <channel> 

接收publish消息的返回会多一项通配符形式的频道:第一个值为message类型,第二个值是通配符频道名称,第三个是具体的频道名称,第四个值是消息内容

punsubscribe用于退订psubscribe订阅的频道,unsubscribe用于退订subscribe订阅的频道,对应使用。

管道

客户端与redis的通信过程是通过tcp连接进行的,命令的传输与结果的返回都是存在网络传输时延的,当要执行的命令较多时,如果每次传输执行一条命令,后续的每条命令都等待前一条命令执行结束后,才能进行传输执行,时延造成的性能影响较大。

redis底层通信对管道(pipelining)提供了支持,如果多个命令的结果不相互依赖,可以通过管道一次传输多个命令,并将结果一次返回,通过降低通信次数来减少传输时延。

节省空间

redis的数据是保存在内存之中的,所以优化存储,减少空间的占用对成本控制是很重要的。

精简键名和键值

例如very.import.person:20 可以精简为 VIP:20,male和female可以改为m和f。

内部编码优化

redis未每种数据类型提供了两种内部编码方式,以散列类型为例,散列类型以散列表实现,实现 O(1) 时间复杂度查找和赋值操作,但是当键中元素数较少时,散列类型会以一种紧凑但性能较差的内部编码方式。当数据量较少时,O(1)O(n) 相差不大。

查询键的内部编码方式

object encoding <key>

持久化

redis的高性能在于其数据保存于内存中,并且为了避免重启后丢失数据,提供了将数据同步到硬盘的方式,即持久化。redis提供有两种方式的持久化,rdb和aof,可以使用其中一种或两种结合使用。

rdb(redis database)

rdb方式的持久化是通过快照完成的,当符合一定条件会自动将内存中数据进行快照并存储在硬盘中。执行快照的条件是在配置文件中自定义的,包括两个参数:时间和改动的键个数。当在指定时间内,改动的键个数达到条件后会触发快照。

rdb是redis默认的持久化方式,在redis.conf配置文件中预置了三个条件:

save 900 1
save 300 10
save 60 10000

save参数指定了快照条件,多个条件之间是“或”的关系。

redis 默认快照存储位置为当前目录的dump.rdb文件中,通过dir指定目录,通过dbfilename指定文件名。快照位置和快照条件都可以在redis.conf文件中进行修改。

快照过程:

  1. redis使用fork函数复制当前进程(父进程)的副本(子进程)
  2. 父进程继续接收并处理客户端命令,子进程将内存中数据写入硬盘的临时文件中
  3. 子进程写入完毕,将该临时文件替换旧的rdb文件

fork函数执行使用写时复制(copy on write)策略,父、子进程共享同一内存空间,当父进程执行写命令时,操作系统会将数据复制一份给子进程,避免子进程数据受影响。所以rdb文件存储的是fork函数执行那一刻的数据,如果redis异常退出,则会丢失最后一次备份之后的修改。

处理自动备份外,还可以手动执行save、bgsave使用主进程或fork子进程进行备份。

aop(append only file)

redis默认没有开启aop持久化,通过修改appendonly参数为yes开启该持久化功能。aof的持久化文件目录与rdb相同,同样有dir参数配置,文件名由appendfilename参数配置。

启用aof后,每条修改数据的命令都会被记录。这里其实并不是直接记录到硬盘文件中,而是写入硬盘缓存,通过appendfsync参数配置同步缓存到硬盘文件的时间,默认为everysec,即每秒钟都会进行同步,no表示由操作系统进行同步,即每30秒进行同步,always表示每次执行命令都会进行同步。

如果同时启动了rdb和aof,则启动redis时会根据aof文件进行恢复数据。

如果配置文件中设置appendonly为yes,没有aof文件生成,需要执行如下命令:
redis-cli config set appendonly yes
redis-cli config set save ""

复制

通过持久化功能,redis保证了即使服务器重启,也不会损失多少数据,但是当服务器硬盘故障时,仍然会导致数据丢失。为了避免这种单点故障的情况,需要将数据存储在多个服务器上,当一台服务器上redis更新数据时,通过复制功能将数据同步到其他服务器上。

配置

同步数据库存在两种角色,提供读写功能的主数据库,提供只读功能的从数据库,在从数据库的配置文件中加入如下配置即可,主数据库无需配置。

slaveof <master ip> <master port>

该命令可以在启动redis服务时指定--slaveof ,也可以在运行时指定/更新主数据库

复制原理

从数据库启动后,向主数据库发送sync命令,主数据库接收到sync命令后,开始后台保存快照(rdb持久化过程),并将快照期间接收到的命令缓存起来。快照完成后,redis将快照文件和所有缓存命令发送给从数据库。从数据库收到后,会载入快照文件并执行收到的缓存的命令。若主从断开后,会重新执行上述快照、缓存命令、发送快照和缓存命令方式,不支持断点续传。

从数据库会将接收到的内容写入硬盘临时文件中,当写入完成后会用该临时文件替换rdb快照文件,然后根据快照文件恢复数据。从数据库在同步期间并不会阻塞,可以继续接收客户端命令。默认情况为使用同步前的数据对命令进行响应。可以配置 slave-server-stale-data参数为no,在同步完成前对客户端命令回复错误"sync with master in progress"。

无论是否启用了rdb持久化方式(删除save参数),redis启动时都会尝试读取dir和dbfilename参数指定的rdb快照文件恢复数据。

读写分离

在常见的场景中,读的频率大于写,当单机的redis无法应付大量的读请求时,可以通过复制功能建立多个从数据库,主数据库只进行写操作,从数据库负责读操作,即实现读写分离来提供服务器的负载能力。

从数据库持久化

持久化操作相对较为耗时,为了提供性能,可以通过复制功能建立一个或多个从数据库,并在从数据库上启用持久化,同时在主数据库上禁用持久化(删除save配置条件)。当从数据库崩溃后恢复,可以由主数据库同步数据;当主数据库崩溃时,从数据库使用slaveof no one命令提升为新的主数据库提供服务,恢复后的原主数据库使用slaveof命令变为从数据库,并同步回数据。

安全

redis以简洁为美,在安全层面没有太多的工作。redis设计前提为运行在可信环境中,所以redis默认会接收来自任何地址发送来的请求,可以通过配置bind参数,限制只允许指定地址的连接。

通信协议

redis支持两种通信协议,二进制安全的统一请求协议(unified request protocol)和较为直观的便于在telnet程序中输入的简单协议。两种协议在命令格式上有差异,命令的返回值格式是一样的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350