Redis(四):独立功能的实现
发布与订阅
Redis 的发布与订阅功能有PUBLISH
命令,SUBSCRIBE
命令,PSUBSCRIBE
命令,PUBSUB
命令等组成。
客户端可以通过SUBSCRIBE
命令订阅一个或多个频道,当其它客户端向被订阅的频道发送消息时,频道所有的订阅者都会收到这消息。
频道的订阅与退订
Redis会在redisServer
中用pubsub_channels
字典来记录订阅的客户端和频道的关系。其中字典的键是被订阅的频道,而字典的值是一个客户端链表,保存了订阅这个频道的所有客户端。
比如有一个客户端执行了SUBSCRIBE HEllO
,另一个客户端执行SUBSCRIBE HELLO WORLD
,那么此时redisServer
中pubsub_channels
的结构如下:
订阅频道
当客户端执行SUBSCRIBE <channel1> <channel2...>
命令时,服务器会现在pubsub_channels
字典中查询是否有对应的键,如果存在,则将客户端添加到键对应的链表的末端,如果不存在,则在字典中添加键,并关联新的链表,然后将客户端加入链表。
退订频道
当客户端执行UNSUBSCRIBE
命令时,服务器会在pubsub_channels
的字典中找到对应的键,然后遍历链表,找到客户端未自身的节点移除。如果移除完节点后,链表为空,那么会在字典中删除该键。
模式的订阅与退订
Redis会在redisServer
中用pubsub_patterns
链表保存客户端模式订阅的关系。其中链表的一个节点是一个pubsubPattern
:
typedef struct pubsubPattern {
client *client;
robj *pattern;
} pubsubPattern;
其中client
指向客户端,而pattern
代表订阅的模式。
其结构大概如下:
订阅模式
当客户端执行PSUBSCRIBE
命令时,服务器会把客户端创建一个新的pubsubPattern
结构,用来记录客户端和模式,并添加到链表的末端。
退订模式
当客户端执行PUNSUBCRIBE
命令时,服务器会在pubsub_patterns
链表中遍历查找客户端和模式都符合的pubsubPattern
节点,并从链表中移除。
消息发送
当服务器收到来自客户端的PUBLISH <channel> <message>
命令时,
- 首先服务器会先从
pubsub_channels
字典中找对应的键,然后遍历链表中的客户端,发送message
消息。 - 之后服务器再遍历
pubsub_patterns
链表,对符合channel
的模式的客户端发送消息。
查询订阅信息
Redis 提供了PUBSUB
命令用来查询订阅信息。PUBSUB
一共有三个子命令,PUBSUB CHANNELS <pattern>
,PUBSUB NUMSUB <channels>
,PUBSUB NUMPAT
(即pubsub_channels
的size
)。
PUBSUB CHANNELS <pattern>
:用来查询服务器当前有哪些符合模式(``pattern)的频道,如果不加
pattern,则列出所有的
channel(即
pubsub_channels中每个键对应链表的
size`)PUBSUB NUMSUB <channels>
:用来统计有多少客户端在订阅指定的频道,如果不加channels
,则统计所有的channel
PUBSUB NUMPAT
:用来统计有多少客户端在订阅模式(既pubsub_patterns
的size
)
事务
Redis 通过MULTI
,EXEC
,WATCH
,DISCARD
命令来实现事物的功能。一个事务会将多个命令打包,一次性,顺序的执行这些命令,且中间不会执行其他客户端请求的命令。
事务的实现
一个事务从开始到结束一般分为三个阶段:
- 事务开始
- 命令入队
- 事物执行/丢弃
事务开始
一个事务的开始是通过MULTI
命令来实现的,当客户端请求MULTI
命令,那么服务器会打开该客户端的flags
属性中的REDIS_MULTI
标识,表示该客户端由非事务状态切换为事务状态。
命令入队
当事务状态中的客户端向服务器发送命令时,如果发送的命令不为WATCH
,MUTLI
,EXEC
,DISCARD
,那么服务器会将命令入队,并向客户端放回QUEUED
。
在redisClient
结构中,有一个multiState
,其结构定义如下:
typedef struct multiState {
//multiCmd数组
multiCmd *commands; /* Array of MULTI commands */
//命令数量
int count; /* Total number of MULTI commands */
int cmd_flags; /* The accumulated command flags OR-ed together.
So if at least a command has a given flag, it
will be set in this field. */
int minreplicas; /* MINREPLICAS for synchronous replication */
time_t minreplicas_timeout; /* MINREPLICAS timeout as unixtime. */
} multiState;
其中multiCmd
结构如下:
typedef struct multiCmd {
//用来保存命令参数
robj **argv;
//参数个数
int argc;
//指向对应的命令函数
struct redisCommand *cmd;
} multiCmd;
假设有一个客户端在发送了如下命令:
192.168.1.102:6379> MULTI
OK
192.168.1.102:6379> SET TEST HAHAH
QUEUED
192.168.1.102:6379> GET TEST
QUEUED
192.168.1.102:6379> SET TEST HEIHEIHEI
QUEUED
192.168.1.102:6379>
那么该客户端对应的结构显示为:
事务结束
当客户端向服务器发送DISCARD
命令时,服务器会清楚客户端的事务状态,并且丢掉任务队列中入队的命令。
执行事务
当处于事务的客户端向服务器发送EXEC
命令时,EXEC
会立即被服务器执行,并且服务器会遍历命令队列中的命令,依次执行,然后将全部结果返回给客户端。
例如,针对上述的命令输入,当客户端执行了EXEC
后,获得的回复如下:
192.168.1.102:6379> EXEC
1) OK
2) "HAHAH"
3) OK
192.168.1.102:6379>
WATCH命令
WATCH
命令可以在执行EXEC
命令之前,监视某些数据库键,如果在EXEC
命令执行时,被监视的键被其他客户端修改,那么服务器将拒绝事务的执行。
WATCH命令的实现
当客户端提交WATCH
命令时,客户端会将监视的键和客户端保存在redisDb
的watched_keys
中,watched_keys
是一个字典,其中键表示被监视的数据库键,而值则是一个链表,其中的每个节点都指向监视该键的客户端。
当服务器在执行完某些修改数据库的命令后,不如SET
,SADD
等,会触发一次multi.c/touchWatchKey
函数,该函数会在数据库中watched_keys
中查询对应的键是否存在,如果存在,则修改对应链表中的客户端,打开REDIS_DIRTY_CAS
标志
当服务器收到一个客户端发的EXEC
命令时,会先检查客户端的REDIS_DIRTY_CAS
标志是否打开。如果打开则拒绝执行事务。
Redis事务的ACID性质
传统的数据库中,用 ACID 表示事务的可靠性和安全性。ACID是指原子性(Atomicity
),一致性(Consistency
),隔离性(lsolcation
)和耐久性(Durablity
)。
原子性:
是指事务要么全部执行,要么全部不执行。由于 Redis 事务会将命令打包,统一执行,因此Redis事物具有原子性。
一致性:
是指数据库在执行数据前后,数据库并不会存在非法或是错误的数据。
Redis 通过入队命令检测拦截非法命令,在执行时,即使遇到错误命令,也会继续执行之后的命令。
隔离性:
是指事务执行过程中,其他事务或操作的执行不会互相收到影响。由于 Redis 通过单线程执行命令,因此保证了事务与事务执行的顺序一定是串行的,由此确保了隔离性。
耐久性
是指事物的执行结构能够得到保存。Redis 事务是否具备耐久性和持久化策略相关。只有在开启AOF
模式,并且appendfsync
值为true
时,才具备耐久性。
Lua脚本
Redis客户端可以使用Lua脚本原子的执行多个任务(比如,用在分布式锁原子性的释放上)。
Lua环境的创建过程
在 Redis 服务器启动的过程中,initServer
方法会调用scriptingInit
方法。
scripting.c/scriptingInit
的代码如下:
void scriptingInit(void) {
//创建Lua环境
lua_State *lua = lua_open();
//载入函数库,移除其中不支持的函数
luaLoadLibraries(lua);
luaRemoveUnsupportedFunctions(lua);
//创建lua_scripts字典,用来保存执行或载入过的脚本
server.lua_scripts = dictCreate(&shaScriptObjectDictType,NULL);
//创建全局表格,并添加函数
lua_newtable(lua);
/* redis.call */
lua_pushstring(lua,"call");
lua_pushcfunction(lua,luaRedisCallCommand);
lua_settable(lua,-3);
/* redis.pcall */
lua_pushstring(lua,"pcall");
lua_pushcfunction(lua,luaRedisPCallCommand);
lua_settable(lua,-3);
/* redis.log and log levels. */
lua_pushstring(lua,"log");
lua_pushcfunction(lua,luaLogCommand);
lua_settable(lua,-3);
lua_pushstring(lua,"LOG_DEBUG");
lua_pushnumber(lua,REDIS_DEBUG);
lua_settable(lua,-3);
lua_pushstring(lua,"LOG_VERBOSE");
lua_pushnumber(lua,REDIS_VERBOSE);
lua_settable(lua,-3);
lua_pushstring(lua,"LOG_NOTICE");
lua_pushnumber(lua,REDIS_NOTICE);
lua_settable(lua,-3);
lua_pushstring(lua,"LOG_WARNING");
lua_pushnumber(lua,REDIS_WARNING);
lua_settable(lua,-3);
/* redis.sha1hex */
lua_pushstring(lua, "sha1hex");
lua_pushcfunction(lua, luaRedisSha1hexCommand);
lua_settable(lua, -3);
/* redis.error_reply and redis.status_reply */
lua_pushstring(lua, "error_reply");
lua_pushcfunction(lua, luaRedisErrorReplyCommand);
lua_settable(lua, -3);
lua_pushstring(lua, "status_reply");
lua_pushcfunction(lua, luaRedisStatusReplyCommand);
lua_settable(lua, -3);
/* Finally set the table as 'redis' global var. */
lua_setglobal(lua,"redis");
//替换部分函数
lua_getglobal(lua,"math");
lua_pushstring(lua,"random");
lua_pushcfunction(lua,redis_math_random);
lua_settable(lua,-3);
lua_pushstring(lua,"randomseed");
lua_pushcfunction(lua,redis_math_randomseed);
lua_settable(lua,-3);
lua_setglobal(lua,"math");
//创建辅助函数
{
char *compare_func = "function __redis__compare_helper(a,b)\n"
" if a == false then a = '' end\n"
" if b == false then b = '' end\n"
" return a<b\n"
"end\n";
luaL_loadbuffer(lua,compare_func,strlen(compare_func),"@cmp_func_def");
lua_pcall(lua,0,0,0);
}
{
char *errh_func = "function __redis__err__handler(err)\n"
" local i = debug.getinfo(2,'nSl')\n"
" if i and i.what == 'C' then\n"
" i = debug.getinfo(3,'nSl')\n"
" end\n"
" if i then\n"
" return i.source .. ':' .. i.currentline .. ': ' .. err\n"
" else\n"
" return err\n"
" end\n"
"end\n";
luaL_loadbuffer(lua,errh_func,strlen(errh_func),"@err_handler_def");
lua_pcall(lua,0,0,0);
}
//创建伪客户端
if (server.lua_client == NULL) {
server.lua_client = createClient(-1);
server.lua_client->flags |= REDIS_LUA_CLIENT;
}
//全局变量保护
scriptingEnableGlobalsProtection(lua);
//将lua环境变量保存到服务器中
server.lua = lua;
}
对应上述过程总结如下:
- 创建 Lua 环境(
lua_open
) - 载入 Lua 的函数库
- 在服务器中创建
lua_scripts
字典,其中键为脚本的 SHA1 值,值为执行或载入过的 Lua 脚本 - 创建全局表格用来保存基本函数(如
call
,pcall
等,可以通过 Lua 执行 Redis 的命令) - 替换 Lua 的随机函数(保持数据库的一致性)
- 创建辅助函数
- 创建执行Redis命令的伪客户端
- 设置全局变量保护(避免执行脚本时,攻击全局变量)
- 将Lua环境保存到
redisServer
中
环境协作组件
伪客户端
Lua 环境中创建了伪客户端(没有TCP连接的客户端,和启动时载入AOF
文件的客户端相似),用来执行Redis命令。
当Lua调用redis.call
或是redis.pcall
函数时,函数中需要执行的 Redis 命令将传给伪客户端,伪客户端又将命令交给命令执行器执行,并向 Lua 环境返回结果。
lua_scripts 字典
redisServer.lua_scripts
字典是用来保存该服务器执行或是载入过的Lua脚本的。其中键是脚本的SHA1
校验和,而值是Lua
脚本。
EVAL命令的实现
EVAL命令格式
EVAL script numskey <key> <key...> <arg> <arg...>
其中script
是我们要执行的脚本,numskey
表示键名参数的个数,key
表示键名参数,arg
表示附加参数,这些参数都可以在script
中通过KEYS[]
和ARGV[]
被引用(其中基准下标为1)。
例如我们执行如下Lua脚本
等同于执行了SET HELLO WORLD
命令
192.168.1.102:6379> EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 HELLO WORLD
OK
EVAL命令执行过程
- 定义脚本函数:服务器为客户端发送的脚本创建一个对应的函数,其中函数名为
f_
开头,并加上脚本的SHA1
校验和,函数体就是脚本本身。 - 将脚本保存至
lua_scripts
字典 - 执行脚本函数
EVALSHA命令的实现
EVALSHA
命令就是通过SHA1
值从lua_scripts
查找对应的脚本是否存在,如果存在,则执行通过f_
加SHA1
校验和确认函数名,直接执行函数。
脚本管理命令
SCRIPT FLUSH
可以清楚lua_scripts
字典保存的脚本,并重新创建 lua 环境。
SCRIPT EXISTS
通过脚本的SHA1
校验和确认脚本是否存在于服务器
SCRIPT LOAD
上传脚本,但是不执行(只进行EVAL
过程的前两步)
SCRIPT KILL
当脚本处理超时时,可以通过该命令关闭脚本
脚本复制
当服务器处于复制模式下时,具有写性质的脚本命令(EVAL
,EVALSHA
,SCRIPT FLUSH
,SCRIPT
)也需要被复制到从服务器。
EVAL
,SCRIPT FLUSH
,SCRIPT LOAD
的复制
这三种命令复制不会存在主从服务器执行结果不一致的情况,因此复制时只需要简单的命令传播即可实现。
EVALSHA
命令
由于可能存在主从服务器lua_scrips
中保存的脚本不一致的问题,会发生主服务器执行EVALSHA
时,脚本确实存在,而从服务却不存在,EVALSHA
执行失败,导致主从数据不一致的问题。
例如,主服务器A
先执行了一个EVAL LOAD
命令,载入了一个脚本,而后,服务器B
上线,并成为A
的从服务器。 此时A
在执行EVALSHA
命令,运行刚载入的脚本,并将命令传播给从服务器B
,由于B
不存在该脚本,EVALSHA
命令就会执行失败。
为了避免这种情况,redisServer
服务器会通过repl_scriptcache_dict
字典保存已经复制给全部从服务器的命令(也就是说,当出现一个新的从服务器,字典需要清空),其中键为脚本的SHA1
,而值为NULL
。当执行EVALSHA
命令的复制过程时,如果repl_scriptcache_dict
中可以找到该脚本,那么直接命令传播,如果找不到,那么服务器将根据lua_scripts
中脚本的内容,将EVALSHA
转换成等价的EVAL
命令,再传播,并添加到repl_scriptcache_dict
字典中。
排序
Redis 通过SORT
命令实现对给定列表,集合,有序集合key
中的元素进行排序的功能。
排序默认以数组为权重,值被解释为双精度浮点数,然后进行排序。
SORT
命令格式如下:
SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC | DESC] [ALPHA] [STORE destination]
上述命令中小写的均为参数,每个[]
表示一个选项,[]
内大写的部分为具体选项,后面小写的部分为选项参数,GET
选项可以同时使用多个。
验证适合用SORT
命令的数据类型
在客户端中做如下测试:
192.168.1.102:6379[15]> set stringtest 1
OK
192.168.1.102:6379[15]> sort stringtest
(error) WRONGTYPE Operation against a key holding the wrong kind of value
192.168.1.102:6379[15]> lpush listtest 1 2 3
(integer) 3
192.168.1.102:6379[15]> sort listtest
1) "1"
2) "2"
3) "3"
192.168.1.102:6379[15]> hmset hashtest key1 1 key2 2 key3 3
OK
192.168.1.102:6379[15]> sort hashtest
(error) WRONGTYPE Operation against a key holding the wrong kind of value
192.168.1.102:6379[15]> sadd settest 1 2 3
(integer) 3
192.168.1.102:6379[15]> sort settest
1) "1"
2) "2"
3) "3"
192.168.1.102:6379[15]> zadd zsettest 1 1 2 2 3 3
(integer) 3
192.168.1.102:6379[15]> sort zsettest
1) "1"
2) "2"
3) "3"
可以看到,字符串和哈希表无法使用SORT
命令,而链表,集合和有序集合都可以使用SORT
命令。
各选项的说明
ALPHA
ALPHA
选项可以让SORT
命令从默认的以数字为权重的排序改成以字母为权重。
假设有一个保存字符串的链表:
192.168.1.102:6379[15]> lpush alphatest a b c
(integer) 3
192.168.1.102:6379[15]>
当我们不添加ALPHA
选项直接执行SORT
命令时,会报错:
192.168.1.102:6379[15]> sort alphatest
(error) ERR One or more scores can't be converted into double
192.168.1.102:6379[15]>
说明SORT
命令默认以双精度浮点数做权重进行排序,针对不能转换为双精度的浮点数的值,会运行出错。
当我们带上ALPHA
选项后在运行SORT
命令:
192.168.1.102:6379[15]> sort alphatest ALPHA
1) "a"
2) "b"
3) "c"
命令可以正常执行,且是以字母顺序排序。
LIMIT
LIMIT
选项可以控制排序后的结果输出个数,接受offset
和count
两个参数,其中offset
表示跳过几个结果,count
表示输出几个结果。
同样以上面的例子,我们增加LIMIT
选项,让其输出第二和第三个结果:
192.168.1.102:6379[15]> sort alphatest ALPHA LIMIT 1 2
1) "b"
2) "c"
ASC | DESC
ASC
表示以升序排列,DESC
表示以降序排列,SORT
命令默认以升序排列,当需要降序结果时,可以添加DESC
选项。
上面的例子再增加DESC
选项后,观察下输出的结果:
192.168.1.102:6379[15]> sort alphatest ALPHA LIMIT 1 2 DESC
1) "b"
2) "a"
从输出结果中,可以确认DESC
能够降序输出,而且LIMIT
是在排序完之后再控制输出个数。
BY
BY
选项可以外部的KEY作为权重,代替默认以键值为权重的排序方式。
首先我们增加一些键值对作为辅助的排序权重:
192.168.1.102:6379[15]> mset a_weight 1 b_weight 2 c_weight 3
OK
然后针对一开始的链表,我们增加BY选项,在进行排序:
192.168.1.102:6379[15]> sort alphatest BY *_weight
1) "a"
2) "b"
3) "c"
可以发现,虽然我们并没有加ALPHA
选项,但是通过BY
选项,我们实际的权重是*_weight
的键值,能够被正常转为双精度浮点型,因此也可以正常排序。
GET选项
GET
选项是通过排序结果在去查询键值。
比如我们继续增加一些键值对:
192.168.1.102:6379[15]> mset a_toUpperSize A b_toUpperSize B c_toUpperSize C
OK
然后通过GET
选项将输出的结果,作为键去查询:
192.168.1.102:6379[15]> sort alphatest ALPHA GET *_toUpperSize
1) "A"
2) "B"
3) "C"
再做个测试,假设此时我们删除了c_toUpperSize
,然后再去SORT
和GET
,会发生什么?
192.168.1.102:6379[15]> del c_toUpperSize
(integer) 1
192.168.1.102:6379[15]> sort alphatest ALPHA GET *_toUpperSize
1) "A"
2) "B"
3) (nil)
发现c对应输出结果变成了nil
,结果等价于直接mget
三个 key:
192.168.1.102:6379[15]> mget a_toUpperSize b_toUpperSize c_toUpperSize
1) "A"
2) "B"
3) (nil)
STORE
STORE
选项可以将排序后的结果集存在一个新的键中。
192.168.1.102:6379[15]> sort alphatest ALPHA STORE sorted_result
(integer) 3
192.168.1.102:6379[15]> TYPE sorted_result
list
192.168.1.102:6379[15]> lrange sorted_result 0 -1
1) "a"
2) "b"
3) "c"
可以发现结果集被存在了新的键中。而且键的类型是链表。
SORT命令的实现
与SORT
命令相关的数据结构是redis.h/_redisSortObject
。
typedef struct _redisSortObject {
//*obj指针指向被排序的数据库键
robj *obj;
//u用来记录分值,默认用score记录双精度浮点型,而使用BY或是ALPHA的情况下会使用cmpobj
union {
double score;
robj *cmpobj;
} u;
} redisSortObject;
SORT
执行步骤:
- 当服务器执行
SORT
命令时,首先会根据被执行的数据库键的大小创建一个同等长度的_redisSortObject
数组,然后遍历数组,将*obj
指针指向数据库键中的每一项, - 然后根据
SORT
命令的选项,确定u.score
或是u.cmpobj
。在根据u
进行快速排序。 - 根据
LIMIT
和ASC|DESC
选项确定输出数组中哪些结果。 - 如果还有
GET
选项,服务器将根据排序的结果去数据库中查找对应数据库键。 - 如果存在
STORE
选项,则保存结果集。
二进制位数组
位数组的表示
Redis 中使用SDS
对象表示数组,其中sdshdr.len
表示保存了几个字节长度的数组(最后一个字节是\0
)。
GETBIT命令
GETBIT
命令用来获取某个二进制位数组中指定位的二进制值。
SETBIT命令
SETBIT
命令用来设置某个二进制位数组中指定位的二进制值。
BITCOUNT命令
BITCOUNT
命令用来统计二进制位数组总一共存在多少个1
的位。实现方式参考查表法和汉明重量。
BITTOP命令
BITTOP
命令可以用来对多个二进制位数组计算按位与,按位或,按位异或运算。或者可以对某个二进制进行取反。
慢日志查询
Redis 慢日志查询功能用来记录执行超过给定时长的命令请求,命令请求会被保存在一个链表中。有两个相关的配置:slowlog-log-slower-than
(时长阈值)和slow-log-max-len
(链表长度,先进先出)。
SLOWLOG GET
命令可以获取保存在服务器上的慢查询日志。
监视器
普通Redis客户端可以通过发送MONITOR
命令,成为服务器的监视器,当服务器在收到命令后,会向监视器发送命令信息。
Redis(四):独立功能的实现的更多相关文章
- Redis笔记(4)独立功能的实现
1.前言 本节记录一下redis的一些功能上的实现,包括发布订阅.事务.Lua脚本.排序.二进制位数组.慢查询日志和监视器. 2.发布订阅 上一章介绍sentinel的时候说到了sentinel会订阅 ...
- Redis的各项功能解决了哪些问题?
先看一下Redis是一个什么东西.官方简介解释到:Redis是一个基于BSD开源的项目,是一个把结构化的数据放在内存中的一个存储系统,你可以把它作为数据库,缓存和消息中间件来使用.同时支持string ...
- Redis的事务功能详解
Redis的事务功能详解 MULTI.EXEC.DISCARD和WATCH命令是Redis事务功能的基础.Redis事务允许在一次单独的步骤中执行一组命令,并且可以保证如下两个重要事项: >Re ...
- Redis实现排行榜功能(实战)
需求前段时间,做了一个世界杯竞猜积分排行榜.对世界杯64场球赛胜负平进行猜测,猜对+1分,错误+0分,一人一场只能猜一次.1.展示前一百名列表.2.展示个人排名(如:张三,您当前的排名106579). ...
- Redis实现聊天功能
在学习了Redis做为消息队列之后研究 了redis聊天的功能. 其实用关系型数据库也可以实现消息功能,自己就曾经用mysql写过一个简单的消息的功能.RDB中思路如下: ** 在实际中可以完全借助m ...
- 4个点让你彻底明白Redis的各项功能
前言 先看一下Redis是一个什么东西.官方简介解释到: Redis是一个基于BSD开源的项目,是一个把结构化的数据放在内存中的一个存储系统,你可以把它作为数据库,缓存和消息中间件来使用.同时支持st ...
- 【转】Redis的各项功能解决了哪些问题?
作者:Blackheart 出处:http://linianhui.cnblogs.com 先看一下Redis是一个什么东西.官方简介解释到:Redis是一个基于BSD开源的项目,是一个把结构化的数据 ...
- Redis 分析部分功能所解决的问题
前言:说到缓存,大家都会想到redis,而redis中又有各种眼花缭乱的功能,今天就来看看这些功能能解决的问题. Redis官方简介 Redis是一个基于BSD开源的项目,是一个把结构化的数据放在内存 ...
- Redis的各项功能解决了哪些问题?(转)
http://www.cnblogs.com/linianhui/p/what-problem-does-redis-solve.html 先看一下Redis是一个什么东西.官方简介解释到:Redis ...
- Redis多机功能介绍
Redis多机功能目的:以单台Redis服务器过渡到多台Redis服务器 Redis单机在生产环境中存在的问题 1.内存容量不足 Redis使用内存来存书数据库中的数据,但是对于一台机器来说,硬件的内 ...
随机推荐
- PTA数据结构与算法题目集(中文) 7-26
PTA数据结构与算法题目集(中文) 7-26 7-26 Windows消息队列 (25 分) 消息队列是Windows系统的基础.对于每个进程,系统维护一个消息队列.如果在进程中有特定事件发生, ...
- Java中如何通过try优雅地释放资源?
时间紧迫,长话短说,今天,小明给大家同步一个知识点,使用try-with-resources来优雅地关闭资源. 1. 背景 其实,在JDK 7就已经引入了对try-with-resources的支持, ...
- Pytest系列(13)- 重复执行用例插件之pytest-repeat的详细使用
如果你还想从头学起Pytest,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1690628.html 前言 平常在做功能测试的时候,经常 ...
- JavaScript中||和&&的运算
一般来讲 && 运算和 | | 运算得到的结果都是 true 和 false ,但是 js 中的有点不太一样.当进行 a&&b 和 a| |b (如 1&&am ...
- JavaScript 入门 (一)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- spark error Caused by: java.io.NotSerializableException: org.apache.hadoop.hdfs.DistributedFileSystem
序列化问题多事rdd遍历过程中使用了没有序列化的对象. 1.将未序列化的变量定义到rdd遍历内部.如定义入数据库连接池. 2.常量定义里包含了未序列化对象 ,提出去吧 如下常量要放到main里,不能放 ...
- python3(二十五) getClassInfo
""" """ __author__ = 'shaozhiqi' # 如何知道这个对象是什么类型,使用type() print(type(1 ...
- 使用StopWatch类来计时 (perf4j-0.9.16.jar 包里的类)
public class StopWatch { static public int AN_HOUR = 60 * 60 * 1000; static public int A_MINUTE = 60 ...
- CSS3 制作正方体
一.预备知识 变形属性 2D变形属性 transform:他是css3中的变形属性: 通过transform(变形) 来实现2d 或者3d 转换,其中2d 有,缩放 scale(x, y) ,移动 t ...
- css定位有哪几种方式
一.position 属性规定元素的定位类型,它一般有以下四个值: 默认static 相对定位relative 绝对定位absolute 固定定位fixed 元素可以使用的顶部,底部,左侧和右侧属性定 ...