为人尽量保持低调。尊重别人,不要随便去打断别人的发言,认真的听别人的言语。不要老是想着把话题抢过来抓在自己手里。不能放开的人是没法获得的。
| 首页 | | 发新文章 | 联系 | 聚合 | 管理

2012年12月10日

Redis 数据库的键值设计

丰富的数据结构使得redis的设计非常的有趣。不像关系型数据库那样,DEV和DBA需要深度沟通,review每行sql语句,也不像memcached那样,不需要DBA的参与。redis的DBA需要熟悉数据结构,并能了解使用场景。

下面举一些常见适合kv数据库的例子来谈谈键值的设计,并与关系型数据库做一个对比,发现关系型的不足之处。

用户登录系统

记录用户登录信息的一个系统, 我们简化业务后只留下一张表。

关系型数据库的设计

mysql> select * from login;
+---------+----------------+-------------+---------------------+
| user_id | name           | login_times | last_login_time     |
+---------+----------------+-------------+---------------------+
|       1 | ken thompson   |           5 | 2011-01-01 00:00:00 |
|       2 | dennis ritchie |           1 | 2011-02-01 00:00:00 |
|       3 | Joe Armstrong  |           2 | 2011-03-01 00:00:00 |
+---------+----------------+-------------+---------------------+

user_id表的主键,name表示用户名,login_times表示该用户的登录次数,每次用户登录后,login_times会自增,而last_login_time更新为当前时间。

REDIS的设计

关系型数据转化为KV数据库,我的方法如下:

key 表名:主键值:列名

value 列值

一般使用冒号做分割符,这是不成文的规矩。比如在php-admin for redis系统里,就是默认以冒号分割,于是user:1 user:2等key会分成一组。于是以上的关系数据转化成kv数据后记录如下:

Set login:1:login_times 5
Set login:2:login_times 1
Set login:3:login_times 2

Set login:1:last_login_time 2011-1-1
Set login:2:last_login_time 2011-2-1
Set login:3:last_login_time 2011-3-1

set login:1:name ”ken thompson“
set login:2:name “dennis ritchie”
set login:3:name ”Joe Armstrong“

这样在已知主键的情况下,通过get、set就可以获得或者修改用户的登录次数和最后登录时间和姓名。

一般用户是无法知道自己的id的,只知道自己的用户名,所以还必须有一个从name到id的映射关系,这里的设计与上面的有所不同。

set "login:ken thompson:id"      1
set "login:dennis ritchie:id"    2
set "login: Joe Armstrong:id"    3

这样每次用户登录的时候业务逻辑如下(python版),r是redis对象,name是已经获知的用户名。

view source
print ?
1 #获得用户的id
2 uid = r.get( "login:%s:id" % name)
3 #自增用户的登录次数
4 ret = r.incr( "login:%s:login_times" % uid)
5 #更新该用户的最后登录时间
6 ret = r. set ( "login:%s:last_login_time" % uid, datetime.datetime.now())

如果需求仅仅是已知id,更新或者获取某个用户的最后登录时间,登录次数,关系型和kv数据库无啥区别。一个通过btree pk,一个通过hash,效果都很好。

假设有如下需求,查找最近登录的N个用户。开发人员看看,还是比较简单的,一个sql搞定。

view source
print ?
1 select * from login order by last_login_time desc limit N

DBA了解需求后,考虑到以后表如果比较大,所以在last_login_time上建个索引。执行计划从索引leafblock 的最右边开始访问N条记录,再回表N次,效果很好。

过了两天,又来一个需求,需要知道登录次数最多的人是谁。同样的关系型如何处理?DEV说简单

view source
print ?
1 select * from login order by login_times desc limit N

DBA一看,又要在login_time上建立一个索引。有没有觉得有点问题呢,表上每个字段上都有素引。

关系型数据库的数据存储的的不灵活是问题的源头,数据仅有一种储存方法,那就是按行排列的堆表。统一的数据结构意味着你必须使用索引来改变sql的访问路径来快速访问某个列的,而访问路径的增加又意味着你必须使用统计信息来辅助,于是一大堆的问题就出现了。

没有索引,没有统计计划,没有执行计划,这就是kv数据库。

redis里如何满足以上的需求呢? 对于求最新的N条数据的需求,链表的后进后出的特点非常适合。我们在上面的登录代码之后添加一段代码,维护一个登录的链表,控制他的长度,使得里面永远保存的是最近的N个登录用户。

view source
print ?
1 #把当前登录人添加到链表里
2 ret = r.lpush( "login:last_login_times" , uid)
3 #保持链表只有N位
4 ret = redis.ltrim( "login:last_login_times" , 0 , N - 1 )

这样需要获得最新登录人的id,如下的代码即可

view source
print ?
1 last_login_list = r.lrange( "login:last_login_times" , 0 , N - 1 )

另外,求登录次数最多的人,对于排序,积分榜这类需求,sorted set非常的适合,我们把用户和登录次数统一存储在一个sorted set里。

zadd login:login_times 5 1
zadd login:login_times 1 2
zadd login:login_times 2 3

这样假如某个用户登录,额外维护一个sorted set,代码如此

view source
print ?
1 #对该用户的登录次数自增1
2 ret = r.zincrby( "login:login_times" , 1 , uid)

那么如何获得登录次数最多的用户呢,逆序排列取的排名第N的用户即可

view source
print ?
1 ret = r.zrevrange( "login:login_times" , 0 , N - 1 )

可以看出,DEV需要添加2行代码,而DBA不需要考虑索引什么的。

TAG系统

tag在互联网应用里尤其多见,如果以传统的关系型数据库来设计有点不伦不类。我们以查找书的例子来看看redis在这方面的优势。

关系型数据库的设计

两张表,一张book的明细,一张tag表,表示每本的tag,一本书存在多个tag。

mysql> select * from book;
+------+-------------------------------+----------------+
| id   | name                          | author         |
+------+-------------------------------+----------------+
|    1 | The Ruby Programming Language | Mark Pilgrim   |
|    1 | Ruby on rail                  | David Flanagan |
|    1 | Programming Erlang            | Joe Armstrong  |
+------+-------------------------------+----------------+

mysql> select * from tag;
+---------+---------+
| tagname | book_id |
+---------+---------+
| ruby    |       1 |
| ruby    |       2 |
| web     |       2 |
| erlang  |       3 |
+---------+---------+

假如有如此需求,查找即是ruby又是web方面的书籍,如果以关系型数据库会怎么处理?
view source
print ?
1 select b. name , b.author  from tag t1, tag t2, book b
2 where t1.tagname = 'web' and t2.tagname = 'ruby' and t1.book_id = t2.book_id and b.id = t1.book_id

tag表自关联2次再与book关联,这个sql还是比较复杂的,如果要求即ruby,但不是web方面的书籍呢?

关系型数据其实并不太适合这些集合操作。

REDIS的设计

首先book的数据肯定要存储的,和上面一样。

set book:1:name    ”The Ruby Programming Language”
Set book:2:name     ”Ruby on rail”
Set book:3:name     ”Programming Erlang”

set book:1:author    ”Mark Pilgrim”
Set book:2:author     ”David Flanagan”
Set book:3:author     ”Joe Armstrong”

tag表我们使用集合来存储数据,因为集合擅长求交集、并集

sadd tag:ruby 1
sadd tag:ruby 2
sadd tag:web 2
sadd tag:erlang 3

那么,即属于ruby又属于web的书?

inter_list = redis.sinter("tag.web", "tag:ruby")

即属于ruby,但不属于web的书?

inter_list = redis.sdiff("tag.ruby", "tag:web")

属于ruby和属于web的书的合集?

inter_list = redis.sunion("tag.ruby", "tag:web")

简单到不行阿。

从以上2个例子可以看出在某些场景里,关系型数据库是不太适合的,你可能能够设计出满足需求的系统,但总是感觉的怪怪的,有种生搬硬套的感觉。

尤其登录系统这个例子,频繁的为业务建立索引。放在一个复杂的系统里,ddl(创建索引)有可能改变执行计划。导致其它的sql采用不同的执行计 划,业务复杂的老系统,这个问题是很难预估的,sql千奇百怪。要求DBA对这个系统里所有的sql都了解,这点太难了。这个问题在oracle里尤其严 重,每个DBA估计都碰到过。对于MySQL这类系统,ddl又不方便(虽然现在有online ddl的方法)。碰到大表,DBA凌晨爬起来在业务低峰期操作,这事我没少干过。而这种需求放到redis里就很好处理,DBA仅仅对容量进行预估即可。

未来的OLTP系统应该是kv和关系型的紧密结合。

来源:www.hoterran.info

posted @ 2012-12-10 14:03 (北京)木木 阅读(127) | 评论 (0) | 编辑 收藏
 
Redis示例配置文件 2.4
#  Redis示例配置文件

#  注意单位问题:当需要设置内存大小的时候,可以使用类似1k、5GB、4M这样的常见格式:
#
#
 1k => 1000 bytes
#
 1kb => 1024 bytes
#
 1m => 1000000 bytes
#
 1mb => 1024*1024 bytes
#
 1g => 1000000000 bytes
#
 1gb => 1024*1024*1024 bytes
#
#
 单位是大小写不敏感的,所以1GB 1Gb 1gB的写法都是完全一样的。

#  Redis默认是不作为守护进程来运行的。你可以把这个设置为"yes"让它作为守护进程来运行。
#
 注意,当作为守护进程的时候,Redis会把进程ID写到 /var/run/redis.pid
daemonize no

#  当以守护进程方式运行的时候,Redis会把进程ID默认写到 /var/run/redis.pid。你可以在这里修改路径。
pidfile  / var / run / redis.pid

#  接受连接的特定端口,默认是6379。
#
 如果端口设置为0,Redis就不会监听TCP套接字。
port  6379

#  如果你想的话,你可以绑定单一接口;如果这里没单独设置,那么所有接口的连接都会被监听。
#
#
 bind 127.0.0.1

#  指定用来监听连接的unxi套接字的路径。这个没有默认值,所以如果你不指定的话,Redis就不会通过unix套接字来监听。
#
#
 unixsocket /tmp/redis.sock
#
 unixsocketperm 755

# 一个客户端空闲多少秒后关闭连接。(0代表禁用,永不关闭)
timeout 0

#  设置服务器调试等级。
#
 可能值:
#
 debug (很多信息,对开发/测试有用)
#
 verbose (很多精简的有用信息,但是不像debug等级那么多)
#
 notice (适量的信息,基本上是你生产环境中需要的程度)
#
 warning (只有很重要/严重的信息会记录下来)
loglevel verbose

#  指明日志文件名。也可以使用"stdout"来强制让Redis把日志信息写到标准输出上。
#
 注意:如果Redis以守护进程方式运行,而你设置日志显示到标准输出的话,那么日志会发送到 /dev/null
logfile stdout

#  要使用系统日志记录器很简单,只要设置 "syslog-enabled" 为 "yes" 就可以了。
#
 然后根据需要设置其他一些syslog参数就可以了。
#
 syslog-enabled no

#  指明syslog身份
#
 syslog-ident redis

#  指明syslog的设备。必须是一个用户或者是 LOCAL0 ~ LOCAL7 之一。
#
 syslog-facility local0

#  设置数据库个数。默认数据库是 DB 0,你可以通过SELECT <dbid> WHERE dbid(0~'databases' - 1)来为每个连接使用不同的数据库。
databases  16

# ############################### 快照 #################################

#
#
 把数据库存到磁盘上:
#
#
   save <seconds> <changes>
#
   
#
   会在指定秒数和数据变化次数之后把数据库写到磁盘上。
#
#
   下面的例子将会进行把数据写入磁盘的操作:
#
   900秒(15分钟)之后,且至少1次变更
#
   300秒(5分钟)之后,且至少10次变更
#
   60秒之后,且至少10000次变更
#
#
   注意:你要想不写磁盘的话就把所有 "save" 设置注释掉就行了。

save 
900   1
save 
300   10
save 
60   10000

#  当导出到 .rdb 数据库时是否用LZF压缩字符串对象。
#
 默认设置为 "yes",所以几乎总是生效的。
#
 如果你想节省CPU的话你可以把这个设置为 "no",但是如果你有可压缩的key的话,那数据文件就会更大了。
rdbcompression yes

#  数据库的文件名
dbfilename dump.rdb

#  工作目录
#
#
 数据库会写到这个目录下,文件名就是上面的 "dbfilename" 的值。
#
 
#
 累加文件也放这里。
#
 
#
 注意你这里指定的必须是目录,不是文件名。
dir . /

# ################################ 同步 #################################

#
#
 主从同步。通过 slaveof 配置来实现Redis实例的备份。
#
 注意,这里是本地从远端复制数据。也就是说,本地可以有不同的数据库文件、绑定不同的IP、监听不同的端口。
#
#
 slaveof <masterip> <masterport>

#  如果master设置了密码(通过下面的 "requirepass" 选项来配置),那么slave在开始同步之前必须进行身份验证,否则它的同步请求会被拒绝。
#
#
 masterauth <master-password>

#  当一个slave失去和master的连接,或者同步正在进行中,slave的行为有两种可能:
#
#
 1) 如果 slave-serve-stale-data 设置为 "yes" (默认值),slave会继续响应客户端请求,可能是正常数据,也可能是还没获得值的空数据。
#
 2) 如果 slave-serve-stale-data 设置为 "no",slave会回复"正在从master同步(SYNC with master in progress)"来处理各种请求,除了 INFO 和 SLAVEOF 命令。
#
slave - serve - stale - data yes

#  slave根据指定的时间间隔向服务器发送ping请求。
#
 时间间隔可以通过 repl_ping_slave_period 来设置。
#
 默认10秒。
#
#
 repl-ping-slave-period 10

#  下面的选项设置了大块数据I/O、向master请求数据和ping响应的过期时间。
#
 默认值60秒。
#
#
 一个很重要的事情是:确保这个值比 repl-ping-slave-period 大,否则master和slave之间的传输过期时间比预想的要短。
#
#
 repl-timeout 60

# ################################# 安全 ###################################

#  要求客户端在处理任何命令时都要验证身份和密码。
#
 这在你信不过来访者时很有用。
#
#
 为了向后兼容的话,这段应该注释掉。而且大多数人不需要身份验证(例如:它们运行在自己的服务器上。)
#
 
#
 警告:因为Redis太快了,所以居心不良的人可以每秒尝试150k的密码来试图破解密码。
#
 这意味着你需要一个高强度的密码,否则破解太容易了。
#
#
 requirepass foobared

#  命令重命名
#
#
 在共享环境下,可以为危险命令改变名字。比如,你可以为 CONFIG 改个其他不太容易猜到的名字,这样你自己仍然可以使用,而别人却没法做坏事了。
#
#
 例如:
#
#
 rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
#
#
 甚至也可以通过给命令赋值一个空字符串来完全禁用这条命令:
#
#
 rename-command CONFIG ""

# ################################## 限制 ####################################

#
#
 设置最多同时连接客户端数量。
#
 默认没有限制,这个关系到Redis进程能够打开的文件描述符数量。
#
 特殊值"0"表示没有限制。
#
 一旦达到这个限制,Redis会关闭所有新连接并发送错误"达到最大用户数上限(max number of clients reached)"
#
#
 maxclients 128

#  不要用比设置的上限更多的内存。一旦内存使用达到上限,Redis会根据选定的回收策略(参见:maxmemmory-policy)删除key。
#
#
 如果因为删除策略问题Redis无法删除key,或者策略设置为 "noeviction",Redis会回复需要更多内存的错误信息给命令。
#
 例如,SET,LPUSH等等。但是会继续合理响应只读命令,比如:GET。
#
#
 在使用Redis作为LRU缓存,或者为实例设置了硬性内存限制的时候(使用 "noeviction" 策略)的时候,这个选项还是满有用的。
#
#
 警告:当一堆slave连上达到内存上限的实例的时候,响应slave需要的输出缓存所需内存不计算在使用内存当中。
#
 这样当请求一个删除掉的key的时候就不会触发网络问题/重新同步的事件,然后slave就会收到一堆删除指令,直到数据库空了为止。
#
#
 简而言之,如果你有slave连上一个master的话,那建议你把master内存限制设小点儿,确保有足够的系统内存用作输出缓存。
#
 (如果策略设置为"noeviction"的话就不无所谓了)
#
#
 maxmemory <bytes>

#  内存策略:如果达到内存限制了,Redis如何删除key。你可以在下面五个策略里面选:
#
 
#
 volatile-lru -> 根据LRU算法生成的过期时间来删除。
#
 allkeys-lru -> 根据LRU算法删除任何key。
#
 volatile-random -> 根据过期设置来随机删除key。
#
 allkeys->random -> 无差别随机删。
#
 volatile-ttl -> 根据最近过期时间来删除(辅以TTL)
#
 noeviction -> 谁也不删,直接在写操作时返回错误。
#
 
#
 注意:对所有策略来说,如果Redis找不到合适的可以删除的key都会在写操作时返回一个错误。
#
#
       这里涉及的命令:set setnx setex append
#
       incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
#
       sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
#
       zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
#
       getset mset msetnx exec sort
#
#
 默认值如下:
#
#
 maxmemory-policy volatile-lru

#  LRU和最小TTL算法的实现都不是很精确,但是很接近(为了省内存),所以你可以用样例做测试。
#
 例如:默认Redis会检查三个key然后取最旧的那个,你可以通过下面的配置项来设置样本的个数。
#
#
 maxmemory-samples 3

# ############################# 纯累加模式 ###############################

#  默认情况下,Redis是异步的把数据导出到磁盘上。这种情况下,当Redis挂掉的时候,最新的数据就丢了。
#
 如果不希望丢掉任何一条数据的话就该用纯累加模式:一旦开启这个模式,Redis会把每次写入的数据在接收后都写入 appendonly.aof 文件。
#
 每次启动时Redis都会把这个文件的数据读入内存里。
#
#
 注意,异步导出的数据库文件和纯累加文件可以并存(你得把上面所有"save"设置都注释掉,关掉导出机制)。
#
 如果纯累加模式开启了,那么Redis会在启动时载入日志文件而忽略导出的 dump.rdb 文件。
#
#
 重要:查看 BGREWRITEAOF 来了解当累加日志文件太大了之后,怎么在后台重新处理这个日志文件。

appendonly no

#  纯累加文件名字(默认:"appendonly.aof")
#
 appendfilename appendonly.aof

#  fsync() 请求操作系统马上把数据写到磁盘上,不要再等了。
#
 有些操作系统会真的把数据马上刷到磁盘上;有些则要磨蹭一下,但是会尽快去做。
#
#
 Redis支持三种不同的模式:
#
#
 no:不要立刻刷,只有在操作系统需要刷的时候再刷。比较快。
#
 always:每次写操作都立刻写入到aof文件。慢,但是最安全。
#
 everysec:每秒写一次。折衷方案。
#
#
 默认的 "everysec" 通常来说能在速度和数据安全性之间取得比较好的平衡。
#
 如果你真的理解了这个意味着什么,那么设置"no"可以获得更好的性能表现(如果丢数据的话,则只能拿到一个不是很新的快照);
#
 或者相反的,你选择 "always" 来牺牲速度确保数据安全、完整。
#
#
 如果拿不准,就用 "everysec"

#  appendfsync always
appendfsync everysec
#  appendfsync no

#  如果AOF的同步策略设置成 "always" 或者 "everysec",那么后台的存储进程(后台存储或写入AOF日志)会产生很多磁盘I/O开销。
#
 某些Linux的配置下会使Redis因为 fsync() 而阻塞很久。
#
 注意,目前对这个情况还没有完美修正,甚至不同线程的 fsync() 会阻塞我们的 write(2) 请求。
#
#
 为了缓解这个问题,可以用下面这个选项。它可以在 BGSAVE 或 BGREWRITEAOF 处理时阻止 fsync()。
#
 
#
 这就意味着如果有子进程在进行保存操作,那么Redis就处于"不可同步"的状态。
#
 这实际上是说,在最差的情况下可能会丢掉30秒钟的日志数据。(默认Linux设定)
#
 
#
 如果你有延迟的问题那就把这个设为 "yes",否则就保持 "no",这是保存持久数据的最安全的方式。
no - appendfsync - on - rewrite no

#  自动重写AOF文件
#
#
 如果AOF日志文件大到指定百分比,Redis能够通过 BGREWRITEAOF 自动重写AOF日志文件。
#
 
#
 工作原理:Redis记住上次重写时AOF日志的大小(或者重启后没有写操作的话,那就直接用此时的AOF文件),
#
           基准尺寸和当前尺寸做比较。如果当前尺寸超过指定比例,就会触发重写操作。
#
#
 你还需要指定被重写日志的最小尺寸,这样避免了达到约定百分比但尺寸仍然很小的情况还要重写。
#
#
 指定百分比为0会禁用AOF自动重写特性。

auto
- aof - rewrite - percentage  100
auto
- aof - rewrite - min - size 64mb

# ################################# 慢查询日志 ###################################

#  Redis慢查询日志可以记录超过指定时间的查询。运行时间不包括各种I/O时间。
#
 例如:连接客户端,发送响应数据等。只计算命令运行的实际时间(这是唯一一种命令运行线程阻塞而无法同时为其他请求服务的场景)
#
 
#
 你可以为慢查询日志配置两个参数:一个是超标时间,单位为微妙,记录超过个时间的命令。
#
 另一个是慢查询日志长度。当一个新的命令被写进日志的时候,最老的那个记录会被删掉。
#
#
 下面的时间单位是微秒,所以1000000就是1秒。注意,负数时间会禁用慢查询日志,而0则会强制记录所有命令。
slowlog - log - slower - than  10000

#  这个长度没有限制。只要有足够的内存就行。你可以通过 SLOWLOG RESET 来释放内存。(译者注:日志居然是在内存里的Orz)
slowlog - max - len  128

# ############################### 虚拟内存 ###############################

# ## 警告!虚拟内存在Redis 2.4是反对的。
#
## 非常不鼓励使用虚拟内存!!

#  虚拟内存可以使Redis在内存不够的情况下仍然可以将所有数据序列保存在内存里。
#
 为了做到这一点,高频key会调到内存里,而低频key会转到交换文件里,就像操作系统使用内存页一样。
#
#
 要使用虚拟内存,只要把 "vm-enabled" 设置为 "yes",并根据需要设置下面三个虚拟内存参数就可以了。

vm
- enabled no
#  vm-enabled yes

#  这是交换文件的路径。估计你猜到了,交换文件不能在多个Redis实例之间共享,所以确保每个Redis实例使用一个独立交换文件。
#
#
 最好的保存交换文件(被随机访问)的介质是固态硬盘(SSD)。
#
#
 *** 警告 *** 如果你使用共享主机,那么默认的交换文件放到 /tmp 下是不安全的。
#
 创建一个Redis用户可写的目录,并配置Redis在这里创建交换文件。
vm - swap - file  / tmp / redis.swap

#  "vm-max-memory" 配置虚拟内存可用的最大内存容量。
#
 如果交换文件还有空间的话,所有超标部分都会放到交换文件里。
#
#
 "vm-max-memory" 设置为0表示系统会用掉所有可用内存。
#
 这默认值不咋地,只是把你能用的内存全用掉了,留点余量会更好。
#
 例如,设置为剩余内存的60%-80%。
vm - max - memory 0

#  Redis交换文件是分成多个数据页的。
#
 一个可存储对象可以被保存在多个连续页里,但是一个数据页无法被多个对象共享。
#
 所以,如果你的数据页太大,那么小对象就会浪费掉很多空间。
#
 如果数据页太小,那用于存储的交换空间就会更少(假定你设置相同的数据页数量)
#
#
 如果你使用很多小对象,建议分页尺寸为64或32个字节。
#
 如果你使用很多大对象,那就用大一些的尺寸。
#
 如果不确定,那就用默认值 :)
vm - page - size  32

#  交换文件里数据页总数。
#
 根据内存中分页表(已用/未用的数据页分布情况),磁盘上每8个数据页会消耗内存里1个字节。
#
#
 交换区容量 = vm-page-size * vm-pages
#
#
 根据默认的32字节的数据页尺寸和134217728的数据页数来算,Redis的数据页文件会占4GB,而内存里的分页表会消耗16MB内存。
#
#
 为你的应验程序设置最小且够用的数字比较好,下面这个默认值在大多数情况下都是偏大的。
vm - pages  134217728

#  同时可运行的虚拟内存I/O线程数。
#
 这些线程可以完成从交换文件进行数据读写的操作,也可以处理数据在内存与磁盘间的交互和编码/解码处理。
#
 多一些线程可以一定程度上提高处理效率,虽然I/O操作本身依赖于物理设备的限制,不会因为更多的线程而提高单次读写操作的效率。
#
#
 特殊值0会关闭线程级I/O,并会开启阻塞虚拟内存机制。
vm - max - threads  4

# ############################## 高级配置 ###############################

#  当有大量数据时,适合用哈希编码(需要更多的内存),元素数量上限不能超过给定限制。
#
 你可以通过下面的选项来设定这些限制:
hash - max - zipmap - entries  512
hash
- max - zipmap - value  64

#  与哈希相类似,数据元素较少的情况下,可以用另一种方式来编码从而节省大量空间。
#
 这种方式只有在符合下面限制的时候才可以用:
list - max - ziplist - entries  512
list
- max - ziplist - value  64

#  还有这样一种特殊编码的情况:数据全是64位无符号整型数字构成的字符串。
#
 下面这个配置项就是用来限制这种情况下使用这种编码的最大上限的。
set - max - intset - entries  512

#  与第一、第二种情况相似,有序序列也可以用一种特别的编码方式来处理,可节省大量空间。
#
 这种编码只适合长度和元素都符合下面限制的有序序列:
zset - max - ziplist - entries  128
zset
- max - ziplist - value  64

#  哈希刷新,每100个CPU毫秒会拿出1个毫秒来刷新Redis的主哈希表(顶级键值映射表)。
#
 redis所用的哈希表实现(见dict.c)采用延迟哈希刷新机制:你对一个哈希表操作越多,哈希刷新操作就越频繁;
#
 反之,如果服务器非常不活跃那么也就是用点内存保存哈希表而已。
#
 
#
 默认是每秒钟进行10次哈希表刷新,用来刷新字典,然后尽快释放内存。
#
#
 建议:
#
 如果你对延迟比较在意的话就用 "activerehashing no",每个请求延迟2毫秒不太好嘛。
#
 如果你不太在意延迟而希望尽快释放内存的话就设置 "activerehashing yes"。
activerehashing yes

# ################################# 包含 ###################################

#  包含一个或多个其他配置文件。
#
 这在你有标准配置模板但是每个redis服务器又需要个性设置的时候很有用。
#
 包含文件特性允许你引人其他配置文件,所以好好利用吧。
#
#
 include /path/to/local.conf
#
 include /path/to/other.conf
posted @ 2012-12-10 13:57 (北京)木木 阅读(126) | 评论 (0) | 编辑 收藏
 

2010年11月12日

JVM监控工具介绍
JVM监控工具介绍
jstatd
启动jvm监控服务。它是一个基于rmi的应用,向远程机器提供本机jvm应用程序的信息。默认端口1099。
实例:jstatd -J-Djava.security.policy=my.policy

my.policy文件需要自己建立,内如如下:
grant codebase "file:$JAVA_HOME/lib/tools.jar" {
 permission java.security.AllPermission;
};
这是安全策略文件,因为jdk对jvm做了jaas的安全检测,所以我们必须设置一些策略,使得jstatd被允许作网络操作

jps
列出所有的jvm实例
实例:
jps
列出本机所有的jvm实例

jps 192.168.0.77
列出远程服务器192.168.0.77机器所有的jvm实例,采用rmi协议,默认连接端口为1099
(前提是远程服务器提供jstatd服务)

输出内容如下:
jones@jones:~/data/ebook/java/j2se/jdk_gc$ jps
6286 Jps
6174  Jstat

jconsole
一个图形化界面,可以观察到java进程的gc,class,内存等信息。虽然比较直观,但是个人还是比较倾向于使用jstat命令(在最后一部分会对jstat作详细的介绍)。

jinfo(linux下特有)
观察运行中的java程序的运行环境参数:参数包括Java System属性和JVM命令行参数
实例:jinfo 2083
其中2083就是java进程id号,可以用jps得到这个id号。
输出内容太多了,不在这里一一列举,大家可以自己尝试这个命令。

jstack(linux下特有)
可以观察到jvm中当前所有线程的运行情况和线程当前状态
jstack 2083
输出内容如下:


jmap(linux下特有,也是很常用的一个命令)
观察运行中的jvm物理内存的占用情况。
参数如下:
-heap
:打印jvm heap的情况
-histo:打印jvm heap的直方图。其输出信息包括类名,对象数量,对象占用大小。
-histo:live :同上,但是只答应存活对象的情况
-permstat:打印permanent generation heap情况

命令使用:
jmap -heap 2083
可以观察到New Generation(Eden Space,From Space,To Space),tenured generation,Perm Generation的内存使用情况
输出内容:


jmap -histo 2083 | jmap -histo:live 2083
可以观察heap中所有对象的情况(heap中所有生存的对象的情况)。包括对象数量和所占空间大小。
输出内容:

写个脚本,可以很快把占用heap最大的对象找出来,对付内存泄漏特别有效。

jstat
最后要重点介绍下这个命令。
这是jdk命令中比较重要,也是相当实用的一个命令,可以观察到classloader,compiler,gc相关信息
具体参数如下:
-class:统计class loader行为信息
-compile:统计编译行为信息
-gc:统计jdk gc时heap信息
-gccapacity:统计不同的generations(不知道怎么翻译好,包括新生区,老年区,permanent区)相应的heap容量情况
-gccause:统计gc的情况,(同-gcutil)和引起gc的事件
-gcnew:统计gc时,新生代的情况
-gcnewcapacity:统计gc时,新生代heap容量
-gcold:统计gc时,老年区的情况
-gcoldcapacity:统计gc时,老年区heap容量
-gcpermcapacity:统计gc时,permanent区heap容量
-gcutil:统计gc时,heap情况
-printcompilation:不知道干什么的,一直没用过。

一般比较常用的几个参数是:
jstat -class 2083 1000 10 (每隔1秒监控一次,一共做10次)
输出内容含义如下:
LoadedNumber of classes loaded.
BytesNumber of Kbytes loaded.
UnloadedNumber of classes unloaded.
BytesNumber of Kbytes unloaded.
TimeTime spent performing class load and unload operations.








jstat -gc 2083 2000 20(每隔2秒监控一次,共做10)
输出内容含义如下:
S0C Current survivor space 0 capacity (KB).
EC Current eden space capacity (KB).
EU Eden space utilization (KB).
OC Current old space capacity (KB).
OU Old space utilization (KB).
PC Current permanent space capacity (KB).
PU Permanent space utilization (KB).
YGC Number of young generation GC Events.
YGCT Young generation garbage collection time.
FGC Number of full GC events.
FGCT Full garbage collection time.
GCT Total garbage collection time.


















输出内容:


如果能熟练运用这些命令,尤其是在linux下,那么完全可以代替jprofile等监控工具了,谁让它收费呢。呵呵。
用命令的好处就是速度快,并且辅助于其他命令,比如grep gawk sed等,可以组装多种符合自己需求的工具。
posted @ 2010-11-12 13:29 (北京)木木 阅读(246) | 评论 (0) | 编辑 收藏
 

2010年11月10日

悟
滚滚长江东逝水,
浪花淘尽英雄。
是非成败转头空,
青山依旧在,
几度夕阳红。

白发渔樵江渚上,
惯看秋月春风。
一壶浊酒喜相逢,
古今多少事,
都付笑谈中。

又一兄弟要离开了,怎么说呢。百感交集~~~

或许人都有更高的追求吧。

呜呼~~
posted @ 2010-11-10 13:49 (北京)木木 阅读(144) | 评论 (0) | 编辑 收藏
 

2010年11月2日

8种代码臭味
原文地址:http://www.jdon.com/jivejdon/thread/37355

千里之行,始于足下,千里之堤,毁于蚁穴,做好设计的第一步就是写好你的代码,博文8 Signs your code sucks总结了代码中第一个感觉的臭味,让你能够于细微之处发现软件的质量问题。

1.方法内代码超过一个电脑屏幕:
一个方法只应该执行特定的任务,一个方法不应该包含一些这样的逻辑,例如判断用户名字段包哈巴的数据是否有效,是否存在等。如果一个方法代码大得超过一个屏幕,那么这是表明它做了太多的事情,需要切分。

2.你在重用变量:
除非你工作于嵌入式领域,否则内存是便宜的,不要做内存的守财奴,要注重可维护性。

3.你直接访问request/session :
这和具体应用服务器环境绑定,难于测试,所有应用数据应该直接解耦Session/request,保存到Bean中,通过 bean的 getters 和 setters方法, 创建使用者访问数据的合约,这将大大帮助代码的可维护性,个人补充,不要把类中的Collection字段直接通过Collection getCollection来暴露给外界,通过方法封装对Collection的操作。


4.你需要使用注解来解释代码如何使用:
代码应该自己能够解释它如何使用,易于可读,如果你发现你自己都需要注解专门解释如何使用,那么就要重构你的代码。这里注解不是指javadoc等必要文档。

5.一个exception系列错误没有返回最原始的错误:
你不应该吃掉exception错误,在catch一个exception时,要打印出其出错tack trace. 如果不知道错误来源,如何纠正错误呢?

6.你的代码是一堆泥球:
代码粘在一起,机会没有分离分层,代码应该是模块化,这样易于维护和重用。 MVC是关注用户View视图界面发生的事情,控制器是关注程序流程和数据的校验,而处理业务逻辑是领域模型的事情,只有模型可以和数据库访问直接进行交互。

7.难于单元测试
如果你发现Bug,那么些一段新的代码片段,它会花去你一些时间,但是这样代码就能处理更加复杂的事情了。
posted @ 2010-11-02 17:35 (北京)木木 阅读(168) | 评论 (0) | 编辑 收藏
 

2010年7月21日

Java多线程编程总结
http://lavasoft.blog.51cto.com/62575/27069
Java多线程编程总结
 
下面是Java线程系列博文的一个编目:
 
Java线程:概念与原理
Java线程:创建与启动
Java线程:线程栈模型与线程的变量
Java线程:线程状态的转换
Java线程:线程的同步与锁
Java线程:线程的交互
Java线程:线程的调度-休眠
Java线程:线程的调度-优先级
Java线程:线程的调度-让步
Java线程:线程的调度-合并
Java线程:线程的调度-守护线程
Java线程:线程的同步-同步方法
Java线程:线程的同步-同步块
Java线程:并发协作-生产者消费者模型
Java线程:并发协作-死锁
Java线程:volatile关键字
Java线程:新特征-线程池
Java线程:新特征-有返回值的线程
Java线程:新特征-锁(上)
Java线程:新特征-锁(下)
Java线程:新特征-信号量
Java线程:新特征-阻塞队列
Java线程:新特征-阻塞栈
Java线程:新特征-条件变量
Java线程:新特征-原子量
Java线程:新特征-障碍器
Java线程:大总结
posted @ 2010-07-21 11:41 (北京)木木 阅读(312) | 评论 (0) | 编辑 收藏
 
Dbutils 详解
     摘要: Common Dbutils是操作数据库的组件,对传统操作数据库的类进行二 次封装,可以把结果集转化成List。DBUtils包括3个包:org.apache.commons.dbutils org.apache.commons.dbutils.handlers org.apache.commons.dbutils.wrappersDBUtils封装了对JDBC的操作,简化了JDBC操作,可以少...  阅读全文
posted @ 2010-07-21 10:55 (北京)木木 阅读(3906) | 评论 (1) | 编辑 收藏
 

2010年7月8日

一套完整的javascript面试题--答案
     摘要: 一、 单选题 1 、以下哪条语句会产生运行错误:( a ) A.var obj = ();// 语法错误 B.var obj = [];// 创建数组 C.var obj = {};// 创建对象 D...  阅读全文
posted @ 2010-07-08 12:02 (北京)木木 阅读(359) | 评论 (0) | 编辑 收藏
 
一套完整的javascript面试题--试题

一、 单选题
1
、以下哪条语句会产生运行错误:( )
A.var obj = ();//
语法错误
B.var obj = [];//
创建数组
C.var obj = {};//
创建对象
D.var obj = //;
2、以下哪个单词不属于javascript保留字:()
A.with
B.parent
C.class
D.void
3、请选择结果为真的表达式:()
A.null instanceof Object
(if(!(nullinstanceof Object))是真的)
B.null === undefined
C.null == undefined
D.NaN == NaN

二、不定项选择题

4
、请选择对javascript理解有误的:()
A.JScript
是javascript的简称
B.javascript
是网景公司开发的一种Java脚本语言,其目的是为了简化Java的开发难度
C.FireFox
和IE存在大量兼容性问题的主要原因在于他们对javascript的支持不同上
D.AJAX
技术一定要使用javascript技术
5
、foo对象有att属性,那么获取att属性的值,以下哪些做法是可以的:()
A.foo.att
B.foo(“att”)
C.foo[“att”]
D.foo{“att”}
E.foo[“a”+”t”+”t”]
6
、在不指定特殊属性的情况下,哪几种HTML标签可以手动输入文本:()
A.<TEXTAREA></TEXTAREA>
B.<INPUT type=”text”/>
C.<INPUT type=”hidden”/>
D.<DIV></DIV>

7
、以下哪些是javascript的全局函数:()
A.escape
B.parseFloat
C.eval
D.setTimeout
E.alert
8
、关于IFrame表述正确的有:()
A.
通过IFrame,网页可以嵌入其他网页内容,并可以动态更改
B.
在相同域名下,内嵌的IFrame可以获取外层网页的对象
C.
在相同域名下,外层网页脚本可以获取IFrame网页内的对象
D.
可以通过脚本调整IFrame的大小
9
、关于表格表述正确的有:()
A.
表格中可以包含TBODY元素
B.
表格中可以包含CAPTION元素
C.
表格中可以包含多个TBODY元素
D.
表格中可以包含COLGROUP元素
E.
表格中可以包含COL元素
10
、关于IE的window对象表述正确的有:()
A.window.opener
属性本身就是指向window对象
B.window.reload()
方法可以用来刷新当前页面
C.window.location=”a.html”
和window.location.href=”a.html”的作用都是把当前页面替换成a.html页面
D.
定义了全局变量g;可以用window.g的方式来存取该变量
三、问答题:
1
、谈谈javascript数组排序方法sort()的使用,重点介绍sort()参数的使用及其内部机制
2、简述DIV元素和SPAN元素的区别。
3、结合text这段结构,谈谈innerHTML outerHTML innerText之间的区别。
4、说几条XHTML规范的内容(至少3条)
5、对Web标准化(或网站重构)知道哪些相关的知识,简述几条你知道的Web标准?
四、程序题:
1
、完成foo()函数的内容,要求能够弹出对话框提示当前选中的是第几个单选框。

< html >

< head >

< script >

 
function  foo() 

 
{

   
//  在此处添加代码

   
 }


</ script >



</ head >

< body >

    
< form  name ="form1"  onsubmit ="return foo();" >

        
< input  type ="radio"  name ="radioGroup"   />

        
< input  type ="radio"  name ="radioGroup"   />

        
< input  type ="radio"  name ="radioGroup"   />

        
< input  type ="radio"  name ="radioGroup"   />

        
< input  type ="radio"  name ="radioGroup"   />

        
< input  type ="radio"  name ="radioGroup"   />

        
< input  type ="submit"   />

    
</ form >

</ body >

</ html >
2、填充注释部分的函数体,使得foo()函数调用弹出”成功”的对话框。代码应尽量简短
<html>

<head>

 

 
<script>

function foo() 

{

   
var str = reverse('a,b,c,d,e,f,g');

      alert(str);

   
if (str == 'g,f,e,d,c,b,a') 

      alert('成功');

   
else alert('失败');

}


function reverse(str)

{

   
// 在此处加入代码,完成字符串翻转功能

    
}


    
</script>

</head>

 

    
<body>

        
<input type="button" value="str" onclick="foo()" />

    
</body>

</html>
posted @ 2010-07-08 12:02 (北京)木木 阅读(2440) | 评论 (0) | 编辑 收藏
 

2010年6月26日

JAVA基础知识精华总结
        1、对象的初始化

  (1)非静态对象的初始化

  在创建对象时,对象所在类的所有数据成员会首先进行初始化。

  基本类型:int型,初始化为0。

  如果为对象:这些对象会按顺序初始化。

  ※在所有类成员初始化完成之后,才调用本类的构造方法创建对象。

  构造方法的作用就是初始化。

  (2)静态对象的初始化

  程序中主类的静态变量会在main方法执行前初始化。

  不仅第一次创建对象时,类中的所有静态变量都初始化,并且第一次访问某类(注意此时未创建此类对象)的静态对象时,所有的静态变量也要按它们在类中的顺序初始化。

  2、继承时,对象的初始化过程 

  (1)主类的超类由高到低按顺序初始化静态成员,无论静态成员是否为private。

  (2)主类静态成员的初始化。

  (3)主类的超类由高到低进行默认构造方法的调用。注意,在调用每一个超类的默认构造方法前,先进行对此超类进行非静态对象的初始化。

  (4)主类非静态成员的初始化。

  (5)调用主类的构造方法。

  3、关于构造方法

  (1)类可以没有构造方法,但如果有多个构造方法,就应该要有默认的构造方法,否则在继承此类时,需要在子类中显式调用父类的某一个非默认的构造方法了。

  (2)在一个构造方法中,只能调用一次其他的构造方法,并且调用构造方法的语句必须是第一条语句。

  4、有关public、private和protected

  (1)无public修饰的类,可以被其他类访问的条件是:a.两个类在同一文件中,b.两个类在同一文件夹中,c.两个类在同一软件包中。

  (2)protected:继承类和同一软件包的类可访问。

  (3)如果构造方法为private,那么在其他类中不能创建该类的对象。

  5、抽象类

  (1)抽象类不能创建对象。

  (2)如果一个类中一个方法为抽象方法,则这个类必须为abstract抽象类。

  (3)继承抽象类的类在类中必须实现抽象类中的抽象方法。

  (4)抽象类中可以有抽象方法,也可有非抽象方法。抽象方法不能为private。

  (5)间接继承抽象类的类可以不给出抽象方法的定义。

  6、final关键字

  (1)一个对象是常量,不代表不能转变对象的成员,仍可以其成员进行操作。

  (2)常量在使用前必须赋值,但除了在声明的同时初始化外,就只能在构造方法中初始化。

  (3)final修饰的方法不能被重置(在子类中不能出现同名方法)。

  (4)如果声明一个类为final,则所有的方法均为final,无论其是否被final修饰,但数据成员可为final也可不是。

  7、接口interface(用implements来实现接口)

  (1)接口中的所有数据均为static和final即静态常量。尽管可以不用这两个关键字修饰,但必须给常量赋初值。

  (2)接口中的方法均为public,在实现接口类中,实现方法必须可public关键字。

  (3)如果使用public来修饰接口,则接口必须与文件名相同。

  8、多重继承 

  (1)一个类继承了一个类和接口,那么必须将类写在前面,接口写在后面,接口之间用逗号分隔。

  (2)接口之间可多重继承,注意使用关键字extends。

  (3)一个类虽只实现了一个接口,但不仅要实现这个接口的所有方法,还要实现这个接口继承的接口的方法,接口中的所有方法均须在类中实现。

  9、接口的嵌入

  (1)接口嵌入类中,可以使用private修饰。此时,接口只能在所在的类中实现,其他类不能访问。

  (2)嵌入接口中的接口一定要为public。 

        10、类的嵌入 

  (1)类可以嵌入另一个类中,但不能嵌入接口中。

  }

  (2)在静态方法或其他方法中,不能直接创建内部类对象,需通过手段来取得。

  手段有两种:

  class A { class B {} B getB() { B b = new B(); return b; } } static void m() { A a = new A(); A.B ab = a.getB(); // 或者是 A.B ab = a.new B(); }

  (3)一个类继承了另一个类的内部类,因为超类是内部类,而内部类的构造方法不能自动被调用,这样就需要在子类的构造方法中明确的调用超类的构造方法。接上例:

  class C extends A.B { C() { new A().super(); // 这一句就实现了对内部类构造方法的调用。 } }

  构造方法也可这样写:

  C(A a) { a.super(); } // 使用这个构造方法创建对象,要写成C c = new C(a); a是A的对象。

  11、异常类JAVA中除了RunTimeException类,其他异常均须捕获或抛出。

posted @ 2010-06-26 15:04 (北京)木木 阅读(164) | 评论 (0) | 编辑 收藏
 
仅列出标题  
随笔:8 文章:23 评论:45 引用:0
<2025年6月>
日一二三四五六
25262728293031
1234567
891011121314
15161718192021
22232425262728
293012345

留言簿(5)

  • 给我留言
  • 查看公开留言
  • 查看私人留言

文章分类(25)

  • html(2) (rss)
  • java(15) (rss)
  • javascript(7) (rss)
  • 正则表达式(1) (rss)

最新随笔

  • 1.  Redis 数据库的键值设计
  • 2. Redis示例配置文件 2.4
  • 3. JVM监控工具介绍
  • 4. 悟
  • 5. 8种代码臭味
  • 6. Java多线程编程总结
  • 7. Dbutils 详解
  • 8. 一套完整的javascript面试题--答案
  • 9. 一套完整的javascript面试题--试题
  • 10. JAVA基础知识精华总结

搜索

  •  

积分与排名

  • 积分 - 38773
  • 排名 - 1188

最新评论

  • 1. re: Dbutils 详解
  • 学习中
  • --江鸽
  • 2. re: 原创【整合iis+tomcat(成功)】
  • 为什么我没成功!运行很慢
  • --hailan
  • 3. re: 原创【整合iis+tomcat(成功)】
  • 通过你的表述。 结果有用了。 但是貌似读取文件好慢。还是多谢了!!!
  • --ff
  • 4. re: 原创【整合iis+tomcat(成功)】
  • 谢谢
  • --mens moncler coats
  • 5. re: java 图片切割,缩放,转换类型
  • 垃圾自己检查下
  • --飞速度放松

Powered by: 博客园
模板提供:沪江博客
Copyright ©2025 (北京)木木