工作,学习,生活,这里将会有一些记录. 备用域名:http://meisw.wdlinux.cn 注册 | 登陆
浏览模式: 标准 | 列表2017年12月的文章

使用Redis的五个注意事项

 下面内容来源于Quora上的一个提问,问题是使用Redis需要避免的五个问题。而回答中超出了五个问题的范畴,描述了五个使用Redis的注意事项。如果你在使用或者考虑使用Redis,可能你可以学习一下下面的一些建议,避免一下提到的问题。
1.使用key值前缀来作命名空间虽然说Redis支持多个数据库(默认32个,可以配置更多),但是除了默认的0号库以外,其它的都需要通过一个额外请求才能使用。所以用前缀作为命名空间可能会更明智一点。
另外,在使用前缀作为命名空间区隔不同key的时候,最好在程序中使用全局配置来实现,直接在代码里写前缀的做法要严格避免,这样可维护性实在太差了。
2.创建一个类似 ”registry” 的key用于标记key使用情况为了更好的管理你的key值的使用,比如哪一类key值是属于哪个业务的,你通常会在内部wiki或者什么地方创建一个文档,通过查询这个文档,我们能够知道Redis中的key都是什么作用。
与之结合,一个推荐的做法是,在Redis里面保存一个registry值,这个值的名字可以类似于 __key_registry__ 这样的,这个key对应的value就是你文档的位置,这样我们在使用Redis的时候,就能通过直接查询这个值获取到当前Redis的使用情况了。
3.注意垃圾回收Redis是一个提供持久化功能的内存数据库,如果你不指定上面值的过期时间,并且也不进行定期的清理工作,那么你的Redis内存占用会越来越大,当有一天它超过了系统可用内存,那么swap上场,离性能陡降的时间就不远了。所以在Redis中保存数据时,一定要预先考虑好数据的生命周期,这有很多方法可以实现。
比如你可以采用Redis自带的过期时间为你的数据设定过期时间。但是自动过期有一个问题,很有可能导致你还有大量内存可用时,就让key过期去释放内存,或者是内存已经不足了key还没有过期。
如果你想更精准的控制你的数据过期,你可以用一个ZSET来维护你的数据更新程度,你可以用时间戳作为score值,每次更新操作时更新一下score,这样你就得到了一个按更新时间排序序列串,你可以轻松地找到最老的数据,并且从最老的数据开始进行删除,一直删除到你的空间足够为止。
4.设计好你的Sharding机制Redis目前并不支持Sharding,但是当你的数据量超过单机内存时,你不得不考虑Sharding的事(注意:Slave不是用来做Sharding操作的,只是数据的一个备份和读写分离而已)。
所以你可能需要考虑好数据量大了后的分片问题,比如你可以在只有一台机器的时候就在程序上设定一致性hash机制,虽然刚开始所有数据都hash到一台机器,但是当你机器越加越多的时候,你就只需要迁移少量的数据就能完成了。
5.不要有个锤子看哪都是钉子当你使用Redis构建你的服务的时候,一定要记住,你只是找了一个合适的工具来实现你需要的功能。而不是说你在用Redis构建一个服务,这是很不同的,你把Redis当作你很多工具中的一个,只在合适使用的时候再使用它,在不合适的时候选择其它的方法。

redis info 命令查看redis使用情况

 用客户端连接redis服务器:  redis-cli >> info  :

  • server : 一般 Redis 服务器信息,包含以下域:

    • redis_version : Redis 服务器版本
    • redis_git_sha1 : Git SHA1
    • redis_git_dirty : Git dirty flag
    • os : Redis 服务器的宿主操作系统
    • arch_bits : 架构(32 或 64 位)
    • multiplexing_api : Redis 所使用的事件处理机制
    • gcc_version : 编译 Redis 时所使用的 GCC 版本
    • process_id : 服务器进程的 PID
    • run_id : Redis 服务器的随机标识符(用于 Sentinel 和集群)
    • tcp_port : TCP/IP 监听端口
    • uptime_in_seconds : 自 Redis 服务器启动以来,经过的秒数
    • uptime_in_days : 自 Redis 服务器启动以来,经过的天数
    • lru_clock : 以分钟为单位进行自增的时钟,用于 LRU 管理
  • clients : 已连接客户端信息,包含以下域:

    • connected_clients : 已连接客户端的数量(不包括通过从属服务器连接的客户端)
    • client_longest_output_list : 当前连接的客户端当中,最长的输出列表
    • client_longest_input_buf : 当前连接的客户端当中,最大输入缓存
    • blocked_clients : 正在等待阻塞命令(BLPOP、BRPOP、BRPOPLPUSH)的客户端的数量
  • memory : 内存信息,包含以下域:

    • used_memory : 由 Redis 分配器分配的内存总量,以字节(byte)为单位
    • used_memory_human : 以人类可读的格式返回 Redis 分配的内存总量
    • used_memory_rss : 从操作系统的角度,返回 Redis 已分配的内存总量(俗称常驻集大小)。这个值和top 、 ps 等命令的输出一致。
    • used_memory_peak : Redis 的内存消耗峰值(以字节为单位)
    • used_memory_peak_human : 以人类可读的格式返回 Redis 的内存消耗峰值
    • used_memory_lua : Lua 引擎所使用的内存大小(以字节为单位)
    • mem_fragmentation_ratio :used_memory_rss 和 used_memory 之间的比率
    • mem_allocator : 在编译时指定的, Redis 所使用的内存分配器。可以是 libc 、 jemalloc 或者 tcmalloc 。
    在理想情况下, used_memory_rss 的值应该只比used_memory 稍微高一点儿。
    当 rss > used ,且两者的值相差较大时,表示存在(内部或外部的)内存碎片。
    内存碎片的比率可以通过 mem_fragmentation_ratio 的值看出。
    当 used > rss 时,表示 Redis 的部分内存被操作系统换出到交换空间了,在这种情况下,操作可能会产生明显的延迟。

    Because Redis does not have control over how its allocations are mapped to memory pages, highused_memory_rss is often the result of a spike in memory usage.

    当 Redis 释放内存时,分配器可能会,也可能不会,将内存返还给操作系统。
    如果 Redis 释放了内存,却没有将内存返还给操作系统,那么 used_memory 的值可能和操作系统显示的 Redis 内存占用并不一致。
    查看 used_memory_peak 的值可以验证这种情况是否发生。
  • persistence :RDB 和 AOF 的相关信息

  • stats : 一般统计信息

  • replication : 主/从复制信息

  • cpu : CPU 计算量统计信息

  • commandstats : Redis 命令统计信息

  • cluster : Redis 集群信息

  • keyspace : 数据库相关的统计信息

除上面给出的这些值以外,参数还可以是下面这两个:

  • all : 返回所有信息
  • default : 返回默认选择的信息

当不带参数直接调用 INFO 命令时,使用 default 作为默认参数。

不同版本的 Redis 可能对返回的一些域进行了增加或删减。

因此,一个健壮的客户端程序在对 INFO 命令的输出进行分析时,应该能够跳过不认识的域,并且妥善地处理丢失不见的域。

Redis 数据操作命令

 

1、连接操作相关的命令

  • quit:关闭连接(connection)
  • auth:简单密码认证

2、对value操作的命令

  • exists(key):确认一个key是否存在
  • del(key):删除一个key
  • type(key):返回值的类型
  • keys(pattern):返回满足给定pattern的所有key
  • randomkey:随机返回key空间的一个key
  • rename(oldname, newname):将key由oldname重命名为newname,若newname存在则删除newname表示的key
  • dbsize:返回当前数据库中key的数目
  • expire:设定一个key的活动时间(s)
  • ttl:获得一个key的活动时间
  • select(index):按索引查询
  • move(key, dbindex):将当前数据库中的key转移到有dbindex索引的数据库
  • flushdb:删除当前选择数据库中的所有key
  • flushall:删除所有数据库中的所有key

3、对String操作的命令

  • set(key, value):给数据库中名称为key的string赋予值value
  • get(key):返回数据库中名称为key的string的value
  • getset(key, value):给名称为key的string赋予上一次的value
  • mget(key1, key2,…, key N):返回库中多个string(它们的名称为key1,key2…)的value
  • setnx(key, value):如果不存在名称为key的string,则向库中添加string,名称为key,值为value
  • setex(key, time, value):向库中添加string(名称为key,值为value)同时,设定过期时间time
  • mset(key1, value1, key2, value2,…key N, value N):同时给多个string赋值,名称为key i的string赋值value i
  • msetnx(key1, value1, key2, value2,…key N, value N):如果所有名称为key i的string都不存在,则向库中添加string,名称key i赋值为value i
  • incr(key):名称为key的string增1操作
  • incrby(key, integer):名称为key的string增加integer
  • decr(key):名称为key的string减1操作
  • decrby(key, integer):名称为key的string减少integer
  • append(key, value):名称为key的string的值附加value
  • substr(key, start, end):返回名称为key的string的value的子串

4、对List操作的命令

  • rpush(key, value):在名称为key的list尾添加一个值为value的元素
  • lpush(key, value):在名称为key的list头添加一个值为value的 元素
  • llen(key):返回名称为key的list的长度
  • lrange(key, start, end):返回名称为key的list中start至end之间的元素(下标从0开始,下同)
  • ltrim(key, start, end):截取名称为key的list,保留start至end之间的元素
  • lindex(key, index):返回名称为key的list中index位置的元素
  • lset(key, index, value):给名称为key的list中index位置的元素赋值为value
  • lrem(key, count, value):删除count个名称为key的list中值为value的元素。count为0,删除所有值为value的元素,count>0从头至尾删除count个值为value的元素,count<0从尾到头删除|count|个值为value的元素。 lpop(key):返回并删除名称为key的list中的首元素 rpop(key):返回并删除名称为key的list中的尾元素 blpop(key1, key2,… key N, timeout):lpop命令的block版本。即当timeout为0时,若遇到名称为key i的list不存在或该list为空,则命令结束。如果timeout>0,则遇到上述情况时,等待timeout秒,如果问题没有解决,则对keyi+1开始的list执行pop操作。
  • brpop(key1, key2,… key N, timeout):rpop的block版本。参考上一命令。
  • rpoplpush(srckey, dstkey):返回并删除名称为srckey的list的尾元素,并将该元素添加到名称为dstkey的list的头部

5、对Set操作的命令

  • sadd(key, member):向名称为key的set中添加元素member
  • srem(key, member) :删除名称为key的set中的元素member
  • spop(key) :随机返回并删除名称为key的set中一个元素
  • smove(srckey, dstkey, member) :将member元素从名称为srckey的集合移到名称为dstkey的集合
  • scard(key) :返回名称为key的set的基数
  • sismember(key, member) :测试member是否是名称为key的set的元素
  • sinter(key1, key2,…key N) :求交集
  • sinterstore(dstkey, key1, key2,…key N) :求交集并将交集保存到dstkey的集合
  • sunion(key1, key2,…key N) :求并集
  • sunionstore(dstkey, key1, key2,…key N) :求并集并将并集保存到dstkey的集合
  • sdiff(key1, key2,…key N) :求差集
  • sdiffstore(dstkey, key1, key2,…key N) :求差集并将差集保存到dstkey的集合
  • smembers(key) :返回名称为key的set的所有元素
  • srandmember(key) :随机返回名称为key的set的一个元素

6、对zset(sorted set)操作的命令

  • zadd(key, score, member):向名称为key的zset中添加元素member,score用于排序。如果该元素已经存在,则根据score更新该元素的顺序。
  • zrem(key, member) :删除名称为key的zset中的元素member
  • zincrby(key, increment, member) :如果在名称为key的zset中已经存在元素member,则该元素的score增加increment;否则向集合中添加该元素,其score的值为increment
  • zrank(key, member) :返回名称为key的zset(元素已按score从小到大排序)中member元素的rank(即index,从0开始),若没有member元素,返回“nil”
  • zrevrank(key, member) :返回名称为key的zset(元素已按score从大到小排序)中member元素的rank(即index,从0开始),若没有member元素,返回“nil”
  • zrange(key, start, end):返回名称为key的zset(元素已按score从小到大排序)中的index从start到end的所有元素
  • zrevrange(key, start, end):返回名称为key的zset(元素已按score从大到小排序)中的index从start到end的所有元素
  • zrangebyscore(key, min, max):返回名称为key的zset中score >= min且score <= max的所有元素 zcard(key):返回名称为key的zset的基数 zscore(key, element):返回名称为key的zset中元素element的score zremrangebyrank(key, min, max):删除名称为key的zset中rank >= min且rank <= max的所有元素 zremrangebyscore(key, min, max) :删除名称为key的zset中score >= min且score <= max的所有元素
  • zunionstore / zinterstore(dstkeyN, key1,…,keyN, WEIGHTS w1,…wN, AGGREGATE SUM|MIN|MAX):对N个zset求并集和交集,并将最后的集合保存在dstkeyN中。对于集合中每一个元素的score,在进行AGGREGATE运算前,都要乘以对于的WEIGHT参数。如果没有提供WEIGHT,默认为1。默认的AGGREGATE是SUM,即结果集合中元素的score是所有集合对应元素进行SUM运算的值,而MIN和MAX是指,结果集合中元素的score是所有集合对应元素中最小值和最大值。

7、对Hash操作的命令

  • hset(key, field, value):向名称为key的hash中添加元素field<—>value
  • hget(key, field):返回名称为key的hash中field对应的value
  • hmget(key, field1, …,field N):返回名称为key的hash中field i对应的value
  • hmset(key, field1, value1,…,field N, value N):向名称为key的hash中添加元素field i<—>value i
  • hincrby(key, field, integer):将名称为key的hash中field的value增加integer
  • hexists(key, field):名称为key的hash中是否存在键为field的域
  • hdel(key, field):删除名称为key的hash中键为field的域
  • hlen(key):返回名称为key的hash中元素个数
  • hkeys(key):返回名称为key的hash中所有键
  • hvals(key):返回名称为key的hash中所有键对应的value
  • hgetall(key):返回名称为key的hash中所有的键(field)及其对应的value

8、持久化

  • save:将数据同步保存到磁盘
  • bgsave:将数据异步保存到磁盘
  • lastsave:返回上次成功将数据保存到磁盘的Unix时戳
  • shundown:将数据同步保存到磁盘,然后关闭服务

9、远程服务控制

  • info:提供服务器的信息和统计
  • monitor:实时转储收到的请求
  • slaveof:改变复制策略设置
  • config:在运行时配置Redis服务器

从Redis+Lua到Goroutine,日均10亿次的股票行情计算

 股票行情数据是一种典型的时序数据(Time-series Data),在一般的IT系统中,日志数据其实也是一种时序数据,在大数据的世界里,也有大量应用是基于时序数据处理的,可以说时间序列的数据无处不在。所以,哪怕是不炒股、不熟悉金融世界的工程师,从本文也可以了解一些具有普遍性的技术思考,例如在大规模、高密度的数据处理中,是把数据快照搬到计算节点作运算还是把计算能力放到数据节点中“就地计算”?协程(co-routine)在这类计算密集型系统中又有何作用?

实时行情服务是券商的基础服务,给普通投资者描绘出风云变幻的动态市场画面,也给量化投资者提供最重要的建模基础数据和下单信号,时间就是金钱,行情服务必须要快。证券交易系统行情指标很丰富,最基础常见的包括:行情报价、分笔数据、分时数据、分钟K线、日周月年K线、各类财务技术指标、多维度排序、多维度统计等等,这些指标需要由交易所的行情数据流结合时间流进行统计计算。本文分享广发证券行情服务并行化计算演进的过程,包括五个部分:

  • 股票行情是怎么回事

  • 日均10亿次的行情指标计算

  • 基于Redis+Lua的方案:“就地计算”

  • 引入Goroutine的方案:“海量算子”

  • 孰优孰劣

一、不炒股没关系,股票行情科普在这里

任何交易在达成之前,通常都有一个讨价还价的僵持过程,或者买方让价,或者卖方让价,两不相让的时候需要第三方介入撮合取个“平均价”,这个过程就是定价。证券股票交易也不例外,不同的是买卖双方互不可见,双方通过券商渠道把自己的价量报给交易所,交易所通常按照达成最大成交量的原则撮合定价。撮合涉及order book和tick data两个概念。交易所维护两个“账本”分别记录买方和卖方申报的价量,实时或按照一定频率进行撮合定价成交,申报、成交时对“账本”进行增、改、删操作。

这两个“账本”就称作order book,对order book增改删操作引起数据变化,每个变化的快照就称作tick data。国内股民数超 1.2 亿户,A股上市公司近3000家,可以想见的到order book是一个大“账本”,tick data瞬息万变,一个交易日产生的tick data量更是惊人。国内交易所目前采用抓取快照的方式,抓取order book的前五/十档价量,剔除”无用的”其他档价量数据,统计交易当日开市时点到目前时点的最高、最低、成交量、成交额数据,我们将这种数据称为原始行情数据(如图1)。

交易所将原始行情数据近实时的发送给券商,券商行情系统对这份原始数据进一步处理,比如按时间区间统计出5分钟、10分钟、15分钟、30分钟、1小时、1天、1周、1月、1季度、1年这十个周期时间段K线蜡烛图的高、开、低、收价格及成交量、成交额数据。

券商将处理好的行情数据揭示给投资者做再报价参考。可见这类数据量大、变化频繁、中间统计计算耗时,一旦延迟,以后就再也跟不上“实时”的节奏了。又快又准,是股票行情服务的基本要求,任何误差、延迟,都可以引起交易者的经济损失,导致投诉甚至社会事件。系统该如何设计才能高速的处理和展示这类实时行情数据,是个很大的技术挑战。

图1

二、你们股炒的爽,我们指标算的酸爽

券商行情系统可以分为行情原始数据接收解析、中间指标统计计算处理、行情数据请求响应三个模块。原始数据接收解析模块只需要做好与不同交易所的行情消息格式适配即可,行情数据请求响应模块要达到原始行情报价及各类行情统计指标的快速展现需要减少中间处理步骤,在行情原始数据接收解析、统计指标计算完成后立即推送给终端用户,同时存一份到内存数据库中以便终端用户再次查询读取,推送与查询两路相结合,达到在数据落地之前即已展示给用户的效果。

剩下要做的就是要尽量减少中间指标统计计算的处理时延。A股上市公司3000多家,基金加债券数更是上万,但它们之间是无关联的,在统计计算时完全可以分片、并行处理,存储上采用内存数据库,redis内置多种数据结构的支持满足多统计指标存储的需求、容易分片部署便于并行计算,如图2。

图 2

以最简单的十个周期时间段的K线统计指标,证券数保守算1w只,10*1w=10w,也就是说分分秒秒就有近10w的计算量,国内沪深交易所一个交易日开市4个小时,按照交易所行情数据每3秒更新一次,可得出日计算量就有4.8亿,加上其他指标计算如市盈率、涨跌幅、换手率、委比委差、多板块多指标排序,日计算量已突破10亿。

三、“就地计算”的方案 – 把计算能力放在数据里

好在不同的证券可以分片并行、同样各周期段的K线指标也可以并行统计计算,实时处理这么大的计算量,我们显然需要高度并行处理。

我们最早的方案基于Redis,因为Redis是一个非常好的承载时间序列数据的高性能内存存储技术。Redis除了内置多种数据结构,还内置了Lua语言解释器,这意味着Redis除了具备数据存储能力也拥有了数据计算的能力。一个Redis存储集群中,每个节点加载Lua脚本,这基本上就是一个分布式并行计算集群了。

我们剩下要做的是事情是实现一个行情收集器,接收来自不同交易所的行情原始数据(node.js善于处理io型事务,很适合用来实现收集器,细节不是本文焦点,不在此详述),发送eval lua指令到Redis集群。 Redis收到指令后执行我们用Lua实现的指标计算逻辑完成指标计算,之后通过Redis pub将行情数据推送出去。如图3通过将计算挪到Redis存储节点,我们避免了复杂易出错的多进程管理问题,也大大简化了开发的工作量。

图 3

这个方案的一个优点,是技术架构比较简单,从数据存储到运算处理,都在Redis上,我们仅专注于Redis集群的性能优化、高可用方案实现、容灾备份、数据复制。对于运维来说,运维一套相对单一的技术系统,“零部件”(moving parts)越少越好,出现故障、单点失败的环节也少了很多。

四、“海量算子”- 把数据快照挪到技术节点

上述Redis+Lua的方案,早期也服务了我们的市场与客户,但是当指标计算量不断增大后,其不足也日益显著,主要体现在高度密集的计算导致影响存储集群的性能而产生延迟,并且在交易期间对存储集群作动态扩容是非常困难的。

有鉴于此,我们很自然的只能把指标计算任务从数据存储中回收,放弃一个数据与计算一体化的相对简单的架构,把计算的职责交给存储之外的专用运算节点。此时Redis变成单纯的内存存储。这种实现将计算与存储分离,计算所需的数据不再能从”本地”获取到了,对于Redis而言,运算服务算是“out of process”(进程外),所以计算指标所需的数据,必须以一份数据快照的方式从Redis传递到运算节点,用以计算和更新,计算结果最终回写到Redis,如图4。

这个对于Redis而言“out of process”的计算能力,我们称之为运算节点(相对于Redis的数据节点),是一系列非常容易水平扩容的、高度并发的程序,我们采用了Golang来实现。Golang这个语言,天生支持协程(Goroutine) – 在一个运行的Golang程序中,可轻易启动上万的协程,所消耗的资源远小于线程,天生适合并行计算。

当接收到交易所tick数据时, 在一个运算节点中对每只证券解析及每类指标计算启动单独的goroutine,每个goroutine在它的生命周期中只做一件事就结束(存活时间毫秒级),这很像数学上的函数执行过程完全没有副作用,goroutine就是一个闭包算子,相互之间毫无影响。 

Golang的内存回收是并行的,数万个goroutine启动到销毁对性能的影响很小,另外tick data本身是有间隔的从交易所发过来的,在goroutine销毁那一刻往往是tick data空闲的间隔时间,这个空闲时机点用来做goroutine回收是合适的。当然也有一些常驻goroutine用来将指标结果数据落地到Redis。程序语言本身是有各自不同的设计哲学的,Golang正是这样一种语言,其协程机制让我们能够更细粒度的处理可分而治之的系统,同时免去了复杂易出错的多进程、多线程问题。

图 4

五、方案比较 - 孰优孰劣

两种方案的对比:

1、Redis既负责存储也负责计算

  • 我们通过搭建Redis集群来进行分布式并行计算,Redis集群本身有主备节点,这就涉及到主备同步的问题,虽然Redis自己解决了同步问题并且也支持增量同步,但通过eval lua指令在存储节点上计算时,同步的不是计算结果而是计算本身,也就是说同一个指标计算在主节点和备节点都需要各自计算一次。Redis又是单线程处理,对于复杂的指标计算通过查看SLOWLOG会有上百毫秒的延迟,这会造成阻塞,对于要求快速的行情服务是不可接受的,为了减少阻塞,就需要尽可能多的进行分片,这意味着需要起更多的redis节点。

  • Redis集群一旦搭建,节点缩扩容需要人工干预。对于证券行情服务这种目前9:30-15:00业务高峰,其他时段空闲的系统来说,高峰时无法弹性扩容,低峰时Redis节点进程无法回收造成资源浪费。

2、Redis负责存储,海量Goroutine做算子

  • 将指标计算从Redis拿出来交给goroutine,Redis仅作存储节点,主备节点同步的是指标计算结果,这样降低了Redis进程的cpu使用率,也不再有SLOWLOG记录,同时仅需要很少的分片,大大减少了Redis节点数。

  • Redis集群规模的大小仅需要根据业务数据量确定。指标计算量的变化可以轻易通过启动更多的goroutine来进行方便的弹性扩容,goroutine数也随投资者活跃程度变化,对交易频繁的股票只需要启动更多的goroutine,不会像方案1那样造成部分Redis节点成为热点。在休市时段goroutine完全回收释放。

图 5

从Redis+Lua的数据存储与运算一体化,过度到数据存储与并行运算分离,也大大减少了系统对硬件资源的要求,Redis集群规模减少十倍,如图表1。采用Redis+Lua方案实现上比较简单,开发工作量较小,收集器与Redis数据交换量小,这种方案更适合简单的逻辑计算比如计数器;goroutine的实现方案,虽然开发上复杂,与Redis数据交换量大了一些,但更适合像行情指标计算这类复杂的应用场景。

方案 机器数(1个部署单元) Redis集群 总CPU核数 总内存 计算最大延迟
In-process:数据“就地计算”(利用Lua) 5台高配 90个节点 128核 128G 几十毫秒
Out of  process:数据快照挪到运算节点(基于goroutine) 4台中配 9个节点 32核 64G 几毫秒

Redis: 为行情数据库设计键值

   如果说用传统关系数据库如MSSQL,MYSQL,PGSQL来设计一个行情数据库,除表名外,就是列字段了。

       比如,表名:600036.SH_1min,  (这里假定每个个股设计一张表,避免把所有的个股放在一张表中,导致数据以亿计,查询效率太低)

       举例而言,其中一条记录(虚拟)

        code :600036.SH Date :2014-10-09 DateTime: 930 Open: 9.60 High:10.08 Close:10.02 Low :9.54 Volume: 123456 Amount :78964531 Factor: 5.123

        另外,成份股的指数权属作为另外一张表而存在,不放在这里,否则太大,也浪费空间。

        如果以内存数据库Redis来设计key-value,相对的key 就会比较长,但value比较简单。

         因为key的唯一性,

         比如,同样以上面的记录而言,可能就要设计一个唯一的KEY:

         key1:  600036.SH_1min:2014-10-09:0930:Open  => value1 :9.60

         key2:  600036.SH_1min:2014-10-09:0930:High    => value 2:10.08

 

         key3:  600036.SH_1min:2014-10-09:0930:Close  => value3 :10.02

         key4:  600036.SH_1min:2014-10-09:0930:Low     => value4 :9.54

          ......

          也就是说

          set   600036.SH_1min:2014-10-09:0930:Open  9.60

          set   600036.SH_1min:2014-10-09:0930:High  10.08

          set   600036.SH_1min:2014-10-09:0930:Close   10.02

          set   600036.SH_1min:2014-10-09:0930:Low  9.54

           .......

          这样,就可以建立一系列的KEY-VALUE结构的非结构化数据了。

         下面,针对历史行情数据库中,典型的日度数据,N分钟数据,TICK数据,我们要进行一个整体上的数据库设计。

         (1) 类型的设计

              我个人认为,用hash的方式来设历史行情库,可能会更好。

         (2) KEY 的设计

              一般而言,而行情数据库的KEY要包含代码名称(比如,600036.SH).但是,因为分钟数据和日度数据的粒度相差较大,如何统一考虑?

             其中,不同类型的数据的文件名应有唯一性。

              A、分钟数据:

                           文件变量名:"600036.sh_1min“,假设有6年的数据,6*250*270  约40万条左右的数据记录源(注意KEY个数还要乘以记录的字段数,下同。)

                            KEY : “600036.sh_1min_2014-10-09_930”  => 开盘价,收盘价,最高价,最低价,成交量,成交金额,复权因子....

              B、TICK数据:

                           文件变量名:”600036.sh_Tick_2014-10-09“  

                                                  #  把每天的TICK,有数千条(股票)甚至数万条(期货)放到一个变量中。

                           KEY: ”600036.sh_Tick_2014-10-09 _930_01.001“ =>

              C、日度数据:

                          文件变量名:”600036.sh_1day“,假设6年数据,此文件大约会储存 6*250 =1500条左右数据源。

                          KEY: “ 600036.sh_1day_2014-10-09” =>

             另外,数据中应考虑不同类类型的数据,可能具有不同的特征,如 股票:复权因子,期货:未平仓合约量 。

        (3)性能比较

             数据如何设计,关键要看性能和维度容易程度。这块需要后续实证了。

redis应用,nginx,lua

nginx + lua + redis 防刷和限流

 

http://blog.csdn.net/fenglvming/article/details/51996406
 

利用redis + lua解决抢红包高并发的问题

http://blog.csdn.net/hengyunabc/article/details/19433779
 
Go实战--golang中使用redis(redigo和go-redis/redis)
http://blog.csdn.net/wangshubo1989/article/details/75050024

redis的常用命令

 redis的常用命令主要分为两个方面、一个是键值相关命令、一个是服务器相关命令

1、键值相关命令
      keys * 取出当前所有的key
      exists name 查看n是否有name这个key
      del name 删除key name
      expire confirm 100 设置confirm这个key100秒过期
      ttl confirm 获取confirm 这个key的有效时长
      select 0 选择到0数据库 redis默认的数据库是0~15一共16个数据库
      move confirm 1 将当前数据库中的key移动到其他的数据库中,这里就是把confire这个key从当前数据库中移动到1中
      persist confirm 移除confirm这个key的过期时间
      randomkey 随机返回数据库里面的一个key
      rename key2 key3 重命名key2 为key3
      type key2 返回key的数据类型
2、服务器相关命令
      ping PONG返回响应是否连接成功
      echo 在命令行打印一些内容
      select 0~15 编号的数据库
      quit  /exit 退出客户端
      dbsize 返回当前数据库中所有key的数量
      info 返回redis的相关信息
      config get dir/* 实时传储收到的请求
      flushdb 删除当前选择数据库中的所有key
      flushall 删除所有数据库中的数据库

Redigo--用池管理redis连接

在golang的项目中,若要频繁的用redis(或者其他类似的NoSQL)来存取数据,最好用redigo自带的池来管理连接。

 

不然的话,每当要操作redis时,建立连接,用完后再关闭,会导致大量的连接处于TIME_WAIT状态(redis连接本质上就是tcp)。

 

注:TIME_WAIT,也叫TCP半连接状态,会继续占用本地端口。

 

以下为redis连接池的golang实现:

 

 

import (

"github.com/garyburd/redigo/redis"

"github.com/astaxie/beego"

"time"

)

 

var (

// 定义常量

RedisClient     *redis.Pool

REDIS_HOST string

REDIS_DB   int

)

 

func init() {

// 从配置文件获取redis的ip以及db

REDIS_HOST = beego.AppConfig.String("redis.host")

REDIS_DB, _ = beego.AppConfig.Int("redis.db")

// 建立连接池

RedisClient = &redis.Pool{

// 从配置文件获取maxidle以及maxactive,取不到则用后面的默认值

MaxIdle:     beego.AppConfig.DefaultInt("redis.maxidle", 1),

MaxActive:   beego.AppConfig.DefaultInt("redis.maxactive", 10),

IdleTimeout: 180 * time.Second,

Dial: func() (redis.Conn, error) {

c, err := redis.Dial("tcp", REDIS_HOST)

if err != nil {

return nil, err

}

// 选择db

c.Do("SELECT", REDIS_DB)

return c, nil

},

}

}

其中,各参数的解释如下:

MaxIdle:最大的空闲连接数,表示即使没有redis连接时依然可以保持N个空闲的连接,而不被清除,随时处于待命状态。

 

MaxActive:最大的激活连接数,表示同时最多有N个连接

 

IdleTimeout:最大的空闲连接等待时间,超过此时间后,空闲连接将被关闭

 

Dial:建立连接

 

使用连接池时的代码:

 

 

// 从池里获取连接

rc := RedisClient.Get()

// 用完后将连接放回连接池

defer rc.Close()

以上就是连接池的用法了,很简单吧。

Records:24123