python之路[11] - kafka、mq队列、redis缓存 - 迁

Kafka

安装 

pip install kafka-python

  

============单节点=============
cd /root/kafka_2.11-2.3.0
启动zookeeper
bin/zookeeper-server-start.sh

再启动kafka
bin/kafka-server-start.sh

============集群===============
advertised.listeners=PLAINTEXT://<ip_addr>:9092 # 这里一定要改,不然客户端会hang住没有返回

 

###创建主题
bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 3 --partitions 8 --topic huored

###查看主题
bin/kafka-topics.sh --list --zookeeper localhost:2181
__consumer_offsets
cluster1
cluster2
cluster3
hello
testa
testtopic

###删除主题
bin/kafka-topics.sh --delete --topic hello --zookeeper localhost:2181

###启动生产者(等待输入)
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test

###启动消费者(监听柱塞)
bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic test

  

伪集群

server.properties
       broker.id=0
       listeners=PLAINTEXT://:9092
       log.dirs=/tmp/kafka-logs-0

server_1.properties
       broker.id=1
       listeners=PLAINTEXT://:9093
       log.dirs=/tmp/kafka-logs-1
 

server_2.properties
       broker.id=2
       listeners=PLAINTEXT://:9094
       log.dirs=/tmp/kafka-logs-2

 

并启动kafka
bin/kafka-server-start.sh config/server.properties

bin/kafka-server-start.sh config/server_1.properties

bin/kafka-server-start.sh config/server_2.properties
 

  

producer

from kafka import KafkaConsumer, TopicPartition, KafkaProducer
import json
# consumer = KafkaConsumer(bootstrap_servers=['10.4.232.1:9092'])

# consumer.subscribe(['cluster3'])
# for msg in consumer:
#     print(msg.value)

producer = KafkaProducer(bootstrap_servers=['10.4.232.1:9092'], acks=1)
# producer = KafkaProducer(bootstrap_servers=['10.4.230.90:9092'], acks=1)
_msg = {'test':123}
msg = json.dumps(_msg)
result = producer.send('cluster3', key=b'foo', value=b'bar')
print('1111111')

record = result.get(timeout=1)
print(record.topic)
print(record.partition)
print(record.offset)
========================》
1111111
cluster3
0
23

  

consumer

from kafka import KafkaConsumer, TopicPartition

# consumer = KafkaConsumer(bootstrap_servers=['10.4.230.90:9092'])
consumer = KafkaConsumer(bootstrap_servers=['10.4.232.1:9092'])

consumer.subscribe(['cluster3'])
for msg in consumer:
    print(msg.value)


# for msg in consumer:
#     print ("%s:%d:%d: key=%s value=%s" % (message.topic, message.partition,
#                                         message.offset, message.key,
#                           
#                                                       message.value))

   

RabbitMQ队列 

安装 

下载rabbitmq-server_3.6.14-1_all.deb文件,dpkg提示缺包

apt install erlang

查看队列状态:

rabbitmqctl list_queues
Listing queues
hello	1

 

# rabbitmqctl -q status
[{pid,5614},
 {running_applications,
     [{rabbit,"RabbitMQ","3.6.14"},
      {mnesia,"MNESIA  CXC 138 12","4.15"},
      {rabbit_common,
          "Modules shared by rabbitmq-server and rabbitmq-erlang-client",
          "3.6.14"},
      {compiler,"ERTS  CXC 138 10","7.1"},
      {ranch,"Socket acceptor pool for TCP protocols.","1.3.0"},
      {ssl,"Erlang/OTP SSL application","8.2"},
      {public_key,"Public key infrastructure","1.4.1"},
      {asn1,"The Erlang ASN1 compiler version 5.0","5.0"},
      {crypto,"CRYPTO","4.0"},
      {recon,"Diagnostic tools for production use","2.3.2"},
      {xmerl,"XML parser","1.3.15"},
      {os_mon,"CPO  CXC 138 46","2.4.2"},
      {syntax_tools,"Syntax tools","2.1.2"},
      {sasl,"SASL  CXC 138 11","3.0.4"},
      {stdlib,"ERTS  CXC 138 10","3.4.1"},
      {kernel,"ERTS  CXC 138 10","5.3"}]},
 {os,{unix,linux}},
 {erlang_version,
     "Erlang/OTP 20 [erts-9.0.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:64] [kernel-poll:true]\n"},
 {memory,
     [{connection_readers,0},
      {connection_writers,0},
      {connection_channels,0},
      {connection_other,0},
      {queue_procs,19640},
      {queue_slave_procs,0},
      {plugins,0},
      {other_proc,18658560},
      {metrics,184496},
      {mgmt_db,0},
      {mnesia,63096},
      {other_ets,1595176},
      {binary,47208},
      {msg_index,42480},
      {code,21448198},
      {atom,900041},
      {other_system,9720457},
      {allocated_unused,16805192},
      {reserved_unallocated,0},
      {total,61435904}]},
 {alarms,[]},
 {listeners,[{clustering,25672,"::"},{amqp,5672,"::"}]},
 {vm_memory_calculation_strategy,rss},
 {vm_memory_high_watermark,0.4},
 {vm_memory_limit,6595198976},
 {disk_free_limit,50000000},
 {disk_free,365812633600},
 {file_descriptors,
     [{total_limit,924},{total_used,2},{sockets_limit,829},{sockets_used,0}]},
 {processes,[{limit,1048576},{used,159}]},
 {run_queue,0},
 {uptime,647},
 {kernel,{net_ticktime,60}}]
rabbitMQ status

 

  

安装python rabbitMQ module  ,注意下面说的各种模型都是基于rabbitMQ队列服务器

pip install pika
or
easy_install pika
or
源码
  
https://pypi.python.org/pypi/pika

  

 

实现最简单的队列通信

 

 

发送端,生产者

#!/usr/bin/env python
# set coding: utf-8
__author__ = "richardzgt"

import pika


credentials = pika.PlainCredentials('richard', 'richard') #activemq认证
# connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) #本地
connection = pika.BlockingConnection(\
    pika.ConnectionParameters('127.0.0.1',5672,'/',credentials))

channel = connection.channel()

# 声明queue
channel.queue_declare(queue='hello')
msg = "Used rabbitMQ"
# RabbitMQ a message can never be sent directly to the queue, it always needs to go through an exchange.
channel.basic_publish(exchange='',
                      routing_key='hello',
                      body=msg)
print("[x] Sent:",msg)
connection.close()

 

接收端,消费者

#!/usr/bin/env python
# set coding: utf-8
__author__ = "richardzgt"


import pika
credentials = pika.PlainCredentials('richard', 'richard')
# connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
connection = pika.BlockingConnection(\
    pika.ConnectionParameters('127.0.0.1',5672,'/',credentials))

channel = connection.channel()

#You may ask why we declare the queue again ‒ we have already declared it in our previous code.
# We could avoid that if we were sure that the queue already exists. For example if send.py program
#was run before. But we're not yet sure which program to run first. In such cases it's a good
# practice to repeat declaring the queue in both programs.
channel.queue_declare(queue='hello')


def callback(ch, method, properties, body):
    print(" [x] Received %r" % body)


channel.basic_consume(callback,
                      queue='hello',
                      no_ack=True)

print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()

  

远程连接rabbitmq server的话,需要配置权限

首先在rabbitmq server上创建一个用户

 

rabbitmqctl add_user richard richard
rabbitmqctl set_permissions -p / richard ".*" ".*" ".*"

  

 Work Queues

 

在这种模式下,RabbitMQ会默认把p发的消息依次分发给各个消费者(c),跟负载均衡差不多

消息提供者代码

import pika
import time
connection = pika.BlockingConnection(pika.ConnectionParameters(
    'localhost'))
channel = connection.channel()
 
# 声明queue
channel.queue_declare(queue='task_queue')
 
# n RabbitMQ a message can never be sent directly to the queue, it always needs to go through an exchange.
import sys
 
message = ' '.join(sys.argv[1:]) or "Hello World! %s" % time.time()
channel.basic_publish(exchange='',
                      routing_key='task_queue',
                      body=message,
                      properties=pika.BasicProperties(
                          delivery_mode=2,  # make message persistent
                      )
                      )
print(" [x] Sent %r" % message)
connection.close()

  

消费者代码

#!/usr/bin/env python
# set coding: utf-8
__author__ = "richardzgt"

import pika, time

connection = pika.BlockingConnection(pika.ConnectionParameters(
    'localhost'))
channel = connection.channel()


def callback(ch, method, properties, body):
    print(" [x] Received %r" % body)
    print(" [x] Done")
    print("method.delivery_tag", method.delivery_tag)
    ch.basic_ack(delivery_tag=method.delivery_tag)


channel.basic_consume(callback,
                      queue='task_queue',
                      no_ack=True
                      )

print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()

  

此时,先启动消息生产者,然后再分别启动3个消费者,通过生产者多发送几条消息,你会发现,这几条消息会被依次分配到各个消费者身上

 

保证消息高可用,不会在消费机宕机的时候丢失消息,rabbitmq提供了ack机制。这样当处理节点挂掉,任务会重新分配(requeue)给已存活的消费机上

客户端

def callback(ch, method, properties, body):
    print " [x] Received %r" % (body,)
    time.sleep( body.count('.') )
    print " [x] Done"
    ch.basic_ack(delivery_tag = method.delivery_tag)
 
channel.basic_consume(callback,
                      queue='hello')
channel.basic_consume(callback,
queue='task_queue',
# no_ack=True
)

  

消息持久化

当生产机宕机,需要用磁盘持久化方式把消息保留下来

channel.queue_declare(queue='task_queue', durable=True)
直接加上 durable是不行的,因为不能对已存在的队列进行修改

pika.exceptions.ChannelClosed: (406, "PRECONDITION_FAILED - inequivalent arg 'durable' for queue 'task_queue' in vhost '/': received 'true' but current is 'false'")

 换一个队列名称就可以了

 

消息公平分发

如果Rabbit只管按顺序把消息发到各个消费者身上,不考虑消费者负载的话,很可能出现,一个机器配置不高的消费者那里堆积了很多消息处理不完,同时配置高的消费者却一直很轻松。为解决此问题,可以在各个消费者端,配置perfetch=1,意思就是告诉RabbitMQ在我这个消费者当前消息还没处理完的时候就不要再给我发新消息了

channel.basic_qos(prefetch_count=1)

带消息持久化+公平分发的完整代码

生产者端

#!/usr/bin/env python
import pika
import sys
 
connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='localhost'))
channel = connection.channel()
 
channel.queue_declare(queue='task_queue', durable=True)
 
message = ' '.join(sys.argv[1:]) or "Hello World!"
channel.basic_publish(exchange='',
                      routing_key='task_queue',
                      body=message,
                      properties=pika.BasicProperties(
                         delivery_mode = 2, # make message persistent
                      ))
print(" [x] Sent %r" % message)
connection.close()

消费者端

#!/usr/bin/env python
import pika
import time
 
connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='localhost'))
channel = connection.channel()
 
channel.queue_declare(queue='task_queue', durable=True)
print(' [*] Waiting for messages. To exit press CTRL+C')
 
def callback(ch, method, properties, body):
    print(" [x] Received %r" % body)
    time.sleep(body.count(b'.'))
    print(" [x] Done")
    ch.basic_ack(delivery_tag = method.delivery_tag)
 
channel.basic_qos(prefetch_count=1)
channel.basic_consume(callback,
                      queue='task_queue')
 
channel.start_consuming()

  

Publish\Subscribe(消息发布\订阅) 

之前的例子都基本都是1对1的消息发送和接收,即消息只能发送到指定的queue里,但有些时候你想让你的消息被所有的Queue收到,类似广播的效果,这时候就要用到exchange了,

exchange一方面从所有producers接受消息,另一方面又将消息push到队列中。Exchange在定义的时候是有类型的,以决定到底是哪些Queue符合条件,可以接收消息:

fanout: 所有bind到此exchange的queue都可以接收消息
direct: 通过routingKey和exchange决定的那个唯一的queue可以接收消息
topic:所有符合routingKey(此时可以是一个表达式)的routingKey所bind的queue可以接收

   表达式符号说明:#代表一个或多个字符,*代表任何字符
      例:#.a会匹配a.a,aa.a,aaa.a等
          *.a会匹配a.a,b.a,c.a等
     注:使用RoutingKey为#,Exchange Type为topic的时候相当于使用fanout 

headers: 通过headers 来决定把消息发给哪些queue

 

fanout默认全部推送

 

消息publisher

#!/usr/bin/env python
# set coding: utf-8
__author__ = "richardzgt"

import pika
import sys

connection = pika.BlockingConnection(pika.ConnectionParameters(
    host='localhost'))
channel = connection.channel()

channel.exchange_declare(exchange='logs',
                         exchange_type='fanout')

message = ' '.join(sys.argv[1:]) or "info: Hello World!"
channel.basic_publish(exchange='logs',
                      routing_key='',
                      body=message)
print(" [x] Sent %r" % message)
connection.close()

  

 

消息subscriber

#!/usr/bin/env python
# set coding: utf-8
__author__ = "richardzgt"

import pika

connection = pika.BlockingConnection(pika.ConnectionParameters(
    host='localhost'))
channel = connection.channel()

channel.exchange_declare(exchange='logs',exchange_type='fanout')

result = channel.queue_declare(exclusive=True)  # 不指定queue名字,rabbit会随机分配一个名字,exclusive=True会在使用此queue的消费者断开后,自动将queue删除
queue_name = result.method.queue

channel.queue_bind(exchange='logs',
                   queue=queue_name)

print(' [*] Waiting for logs. To exit press CTRL+C')


def callback(ch, method, properties, body):
    print(" [x] %r" % body)


channel.basic_consume(callback,
                      queue=queue_name,
                      no_ack=True)

channel.start_consuming()

  

  

有选择的接收消息(exchange type=direct) 

RabbitMQ还支持根据关键字发送,即:队列绑定关键字,发送者将数据根据关键字发送到消息exchange,exchange根据 关键字 判定应该将数据发送至指定队列。

 

事例: 根据不同的routing_key绑定到不同的queue队列

publisher

#!/usr/bin/env python
# set coding: utf-8
__author__ = "richardzgt"

import pika
import sys

connection = pika.BlockingConnection(pika.ConnectionParameters(
    host='localhost'))
channel = connection.channel()

channel.exchange_declare(exchange='direct_logs',
                         exchange_type='direct')

severity = sys.argv[1] if len(sys.argv) > 1 else 'info'
message = ' '.join(sys.argv[2:]) or 'Hello World!'
channel.basic_publish(exchange='direct_logs',
                      routing_key=severity,
                      body=message)
print(" [x] Sent %r:%r" % (severity, message))
connection.close()
$ python 订阅publisher.py error
 [x] Sent 'error':'Hello World!'

$ python 订阅publisher.py
 [x] Sent 'info':'Hello World!'

  

 

subscriber

#!/usr/bin/env python
# set coding: utf-8
__author__ = "richardzgt"
import pika
import sys

connection = pika.BlockingConnection(pika.ConnectionParameters(
    host='localhost'))
channel = connection.channel()

channel.exchange_declare(exchange='direct_logs',
                         exchange_type='direct')

result = channel.queue_declare(exclusive=True)
queue_name = result.method.queue

severities = sys.argv[1:]
if not severities:
    sys.stderr.write("Usage: %s [info] [warning] [error]\n" % sys.argv[0])
    sys.exit(1)

for severity in severities:
    channel.queue_bind(exchange='direct_logs',
                       queue=queue_name,
                       routing_key=severity)

print(' [*] Waiting for logs. To exit press CTRL+C')


def callback(ch, method, properties, body):
    print(" [x] %r:%r" % (method.routing_key, body))


channel.basic_consume(callback,
                      queue=queue_name,
                      no_ack=True)

channel.start_consuming()

 

$ python 订阅subscriber.py error
 [*] Waiting for logs. To exit press CTRL+C
 [x] 'error':'Hello World!'

$ python 订阅subscriber.py info
 [*] Waiting for logs. To exit press CTRL+C
 [x] 'info':'Hello World!'

  

更细致的消息过滤

虽然通过direct exchange的方式能丰富我们系统的分发规则,但是它依然在多准则情景下有局限性。

上面的例子中,我们的日志系统可能需要订阅多个维度的日志,比如同时订阅info和warn及auth和cron多个设备的输出。

publisher

import pika
import sys
 
connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='localhost'))
channel = connection.channel()
 
channel.exchange_declare(exchange='topic_logs',
                         exchange_type='topic')
 
routing_key = sys.argv[1] if len(sys.argv) > 1 else 'anonymous.info'
message = ' '.join(sys.argv[2:]) or 'Hello World!'
channel.basic_publish(exchange='topic_logs',
                      routing_key=routing_key,
                      body=message)
print(" [x] Sent %r:%r" % (routing_key, message))
connection.close()

subscriber

import pika
import sys
 
connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='localhost'))
channel = connection.channel()
 
channel.exchange_declare(exchange='topic_logs',
                         exchange_type='topic')
 
result = channel.queue_declare(exclusive=True)
queue_name = result.method.queue
 
binding_keys = sys.argv[1:]
if not binding_keys:
    sys.stderr.write("Usage: %s [binding_key]...\n" % sys.argv[0])
    sys.exit(1)
 
for binding_key in binding_keys:
    channel.queue_bind(exchange='topic_logs',
                       queue=queue_name,
                       routing_key=binding_key)
 
print(' [*] Waiting for logs. To exit press CTRL+C')
 
def callback(ch, method, properties, body):
    print(" [x] %r:%r" % (method.routing_key, body))
 
channel.basic_consume(callback,
                      queue=queue_name,
                      no_ack=True)
 
channel.start_consuming()

 

使用:

To receive all the logs run:

python receive_logs_topic.py "#"

To receive all logs from the facility "kern":

python receive_logs_topic.py "kern.*"

Or if you want to hear only about "critical" logs:

python receive_logs_topic.py "*.critical"

You can create multiple bindings:

python receive_logs_topic.py "kern.*" "*.critical"

And to emit a log with a routing key "kern.critical" type:

python emit_log_topic.py "kern.critical" "A critical kernel error"

 

 

Remote procedure call (RPC)

模拟一个rpc服务:类似用本地客户端调用远端程序的类,用以灵活的扩展和调用。不需要写api接口,是柱塞试的。

fibonacci_rpc = FibonacciRpcClient()
result = fibonacci_rpc.call(4)
print("fib(4) is %r" % result)

RPC server

#_*_coding:utf-8_*_
__author__ = 'Alex Li'
import pika
import time
connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='localhost'))
 
channel = connection.channel()
 
channel.queue_declare(queue='rpc_queue')
 
def fib(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)
 
def on_request(ch, method, props, body):
    n = int(body)
 
    print(" [.] fib(%s)" % n)
    response = fib(n)
 
    ch.basic_publish(exchange='',
                     routing_key=props.reply_to,
                     properties=pika.BasicProperties(correlation_id = \
                                                         props.correlation_id),
                     body=str(response))
    ch.basic_ack(delivery_tag = method.delivery_tag)
 
channel.basic_qos(prefetch_count=1)
channel.basic_consume(on_request, queue='rpc_queue')
 
print(" [x] Awaiting RPC requests")
channel.start_consuming()

  

RPC client

import pika
import uuid
 
class FibonacciRpcClient(object):
    def __init__(self):
        self.connection = pika.BlockingConnection(pika.ConnectionParameters(
                host='localhost'))
 
        self.channel = self.connection.channel()
 
        result = self.channel.queue_declare(exclusive=True)
        self.callback_queue = result.method.queue
 
        self.channel.basic_consume(self.on_response, no_ack=True,
                                   queue=self.callback_queue)
 
    def on_response(self, ch, method, props, body):
        if self.corr_id == props.correlation_id:
            self.response = body
 
    def call(self, n):
        self.response = None
        self.corr_id = str(uuid.uuid4())
        self.channel.basic_publish(exchange='',
                                   routing_key='rpc_queue',
                                   properties=pika.BasicProperties(
                                         reply_to = self.callback_queue,
                                         correlation_id = self.corr_id,
                                         ),
                                   body=str(n))
        while self.response is None:
            self.connection.process_data_events()
        return int(self.response)
 
fibonacci_rpc = FibonacciRpcClient()
 
print(" [x] Requesting fib(30)")
response = fibonacci_rpc.call(30)
print(" [.] Got %r" % response)

 

AcitiveMQ

服务器设置

    <persistenceAdapter>
       <jdbcPersistenceAdapter dataDirectory="${activemq.data}" dataSource="#oracle-ds"/>
    </persistenceAdapter>
    <systemUsage>
            <systemUsage>
                <memoryUsage>
                    <memoryUsage limit="256 mb"/>
                </memoryUsage>
                <storeUsage>
                    <storeUsage limit="20 gb"/>
                </storeUsage>
                <tempUsage>
                    <tempUsage limit="10 gb"/>
                </tempUsage>
            </systemUsage>
        </systemUsage>

    <transportConnectors>
       <transportConnector name="stomp" uri="stomp://0.0.0.0:61616"/>
    </transportConnectors>
  </broker>
 
 <!-- Oracle DataSource Sample Setup -->
 
  <bean id="oracle-ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
    <property name="url" value="jdbc:oracle:thin:@xxxxxx:1521/test"/>
    <property name="username" value="msg"/>
    <property name="password" value="msg"/>
    <property name="maxActive" value="200"/>
    <property name="poolPreparedStatements" value="true"/>
  </bean> 

</beans>
ativemq.conf

 

其中transportConnector可以多种选择:

        <transportConnectors>
            <!-- DOS protection, limit concurrent connections to 1000 and frame size to 100MB -->
            <transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
            <transportConnector name="amqp" uri="amqp://0.0.0.0:5672?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
            <transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
            <transportConnector name="mqtt" uri="mqtt://0.0.0.0:1883?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
            <transportConnector name="ws" uri="ws://0.0.0.0:61614?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
        </transportConnectors>
View Code

 

排错

java.io.IOException: Unknown data type: 69
使用python stomp接口 61613

   

# -*- coding: utf-8 -*-
# @Author: richard
# @Date:   2017-12-14 14:44:50
# @Last Modified by:   richard
# @Last Modified time: 2017-12-14 15:29:02
# Purpose: 
# 

import time
import sys
import stomp

class MyListener(object):
    def on_error(self, headers, message):
        print('received an error %s' % message)
    def on_message(self, headers, message):
        print('received a message %s' % message)

#连接
conn = stomp.Connection10([('10.0.170.10',61616),('10.0.170.11',61616)])  
conn.set_listener('', MyListener())
conn.start()
conn.connect()

#注意,官方示例这样发送消息是有问题的
#conn.send(body='hello,garfield! this is '.join(sys.argv[1:]), destination='/queue/test')
# 持久化消息写在header里面
conn.send(body=b'hello,garfield!', destination='/queue/testTopic' , headers={'persistent': 'true'})

#订阅消息
# conn.subscribe(destination='/queue/testTopic', id=1, ack='auto')

time.sleep(2)
conn.disconnect()

  

持久化之后,数据保存在activemq_msgs里面

 

 


 

缓存数据库

NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”

NoSQL数据库的四大分类

键值(Key-Value)存储数据库

这一类数据库主要会使用到一个哈希表,这个表中有一个特定的键和一个指针指向特定的数据。Key/value模型对于IT系统来说的优势在于简单、易部署。但是如果DBA只对部分值进行查询或更新的时候,Key/value就显得效率低下了。[3]  举例如:Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB.
 
列存储数据库。
这部分数据库通常是用来应对分布式存储的海量数据。键仍然存在,但是它们的特点是指向了多个列。这些列是由列家族来安排的。如:Cassandra, HBase, Riak.
 
文档型数据库
文档型数据库的灵感是来自于Lotus Notes办公软件的,而且它同第一种键值存储相类似。该类型的数据模型是版本化的文档,半结构化的文档以特定的格式存储,比如JSON。文档型数据库可 以看作是键值数据库的升级版,允许之间嵌套键值。而且文档型数据库比键值数据库的查询效率更高。如:CouchDB, MongoDb. 国内也有文档型数据库SequoiaDB,已经开源。
 
图形(Graph)数据库
图形结构的数据库同其他行列以及刚性结构的SQL数据库不同,它是使用灵活的图形模型,并且能够扩展到多个服务器上。NoSQL数据库没有标准的查询语言(SQL),因此进行数据库查询需要制定数据模型。许多NoSQL数据库都有REST式的数据接口或者查询API。[2]  如:Neo4J, InfoGrid, Infinite Graph.
因此,我们总结NoSQL数据库在以下的这几种情况下比较适用:1、数据模型比较简单;2、需要灵活性更强的IT系统;3、对数据库性能要求较高;4、不需要高度的数据一致性;5、对于给定key,比较容易映射复杂值的环境。

 

Redis

redis是业界主流的key-value nosql 数据库之一。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步 

安装

$sudo apt-get update
$sudo apt-get install redis-server
这将在您的计算机上安装Redis
启动 Redis

$redis-server
查看 redis 是否还在运行

$redis-cli
这将打开一个 Redis 提示符,如下图所示:
redis 127.0.0.1:6379>
在上面的提示信息中:127.0.0.1 是本机的IP地址,6379是 Redis 服务器运行的端口。现在输入 PING 命令,如下图所示:
redis 127.0.0.1:6379> ping
PONG
这说明现在你已经成功地在计算机上安装了 Redis。

  

python安装redis模块

sudo pip install redis
or
sudo easy_install redis
or
源码安装
  
详见:https://github.com/WoLpH/redis-py

 

在线文档  

https://redis-py.readthedocs.io/en/stable/

 

 

redis-py 的API的使用可以分类为:

  • 连接方式
  • 连接池
  • 操作
    • String 操作
    • Hash 操作
    • List 操作
    • Set 操作
    • Sort Set 操作
  • 管道
  • 发布订阅

 

连接方式

1、操作模式

redis-py提供两个类Redis和StrictRedis用于实现Redis的命令,StrictRedis用于实现大部分官方的命令,并使用官方的语法和命令,Redis是StrictRedis的子类,用于向后兼容旧版本的redis-py。 

import redis
r = redis.Redis(host='127.0.0.1',port=6379)
r.set('gaotao', '666')
    True
print(r.get('gaotao'))
    666

  在3.0 之后的版本,两者相同了

StrictRedis = Redis

  

除了上面的更改之外,Redis类(StrictRedis的子类)会覆盖其他几个命令,以提供与旧版本redis-py的向后兼容性:

LREM:'num'和'value'参数的顺序颠倒过来,'num'可以提供默认值零。

ZADD:Redis在'value'之前指定'score'参数。

SETEX:'时间'和'价值'参数的顺序颠倒了。

 

注意:

默认使用redis lpush到列表会产生bytes类型的list列表,需要指定连接字符集连接

r = redis.Redis('127.0.0.1', 6379, charset="utf-8", decode_responses=True)

    

2、连接池

redis-py使用connection pool来管理对一个redis server的所有连接,避免每次建立、释放连接的开销。默认,每个Redis实例都会维护一个自己的连接池。可以直接建立一个连接池,然后作为参数Redis,这样就可以实现多个Redis实例共享一个连接池。

 

操作

1. String操作

redis中的String在在内存中按照一个name对应一个value来存储。如图:

 

set(name, value, ex=None, px=None, nx=False, xx=False)

在Redis中设置值,默认,不存在则创建,存在则修改
参数:
     ex,过期时间(秒)
     px,过期时间(毫秒)
     nx,如果设置为True,则只有name不存在时,当前set操作才执行
     xx,如果设置为True,则只有name存在时,当前set操作才执行

  

setnx(name, value)  #设置值,只有name不存在时,执行设置操作(添加)
setex(name, value, time)  # time,过期时间(数字秒 或 timedelta对象)
psetex(name, time_ms, value)  # time_ms,过期时间(数字毫秒 或 timedelta对象)
mset(*args, **kwargs)  #批量设置值 mset(k1='v1', k2='v2') 或 mget({'k1': 'v1', 'k2': 'v2'})
get(name)  #获取值
mget(keys, *args)  #批量获取 mget('ylr', 'wupeiqi') 或r.mget(['ylr', 'wupeiqi'])
getset(name, value) #设置新值并获取原来的值
getrange(key, start, end)  #获取子序列(根据字节获取,非字符),name,Redis 的 name,start,起始位置(字节),end,结束位置(字节),如: "武沛齐" ,0-3表示 "武"
setrange(name, offset, value)  # 修改字符串内容,从指定字符串索引开始向后替换(新值太长时,则向后添加) 参数:offset,字符串的索引,字节(一个汉字三个字节) value,要设置的值
getbit(name, offset)  # 获取name对应的值的二进制表示中的某位的值 (0或1)
bitcount(key, start=None, end=None)  # 获取name对应的值的二进制表示中 1 的个数, 参数:key,Redis的name ; start,位起始位置 ,end,位结束位置
strlen(name)  # 返回name对应值的字节长度(一个汉字3个字节)
incr(self, name, amount=1) # 自增 name对应的值,当name不存在时,则创建name=amount,否则,则自增。参数:name,Redis的name , amount,自增数(必须是整数)注:同incrby
incrbyfloat(self, name, amount=1.0) # 自增 name对应的值,当name不存在时,则创建name=amount,否则,则自增。 参数: name,Redis的name ,amount,自增数(浮点型)
decr(self, name, amount=1)  # 自减 name对应的值,当name不存在时,则创建name=amount,否则,则自减。 参数:name,Redis的name ; amount,自减数(整数)
append(key, value) # # 在redis name对应的值后面追加内容, 参数: key, redis的name ; value, 要追加的字符串

  

setbit(name, offset, value)

*用途举例,用最省空间的方式,存储在线用户数及分别是哪些用户在线

# 对name对应值的二进制表示的位进行操作
 
# 参数:
    # name,redis的name
    # offset,位的索引(将值变换成二进制后再进行索引)
    # value,值只能是 1 或 0
 
# 注:如果在Redis中有一个对应: n1 = "foo",
        那么字符串foo的二进制表示为:01100110 01101111 01101111
    所以,如果执行 setbit('n1', 7, 1),则就会将第7位设置为1,
        那么最终二进制则变成 01100111 01101111 01101111,即:"goo"
 
# 扩展,转换二进制表示:
 
    # source = "武沛齐"
    source = "foo"
 
    for i in source:
        num = ord(i)
        print bin(num).replace('b','')
 
    特别的,如果source是汉字 "武沛齐"怎么办?
    答:对于utf-8,每一个汉字占 3 个字节,那么 "武沛齐" 则有 9个字节
       对于汉字,for循环时候会按照 字节 迭代,那么在迭代时,将每一个字节转换 十进制数,然后再将十进制数转换成二进制
        11100110 10101101 10100110 11100110 10110010 10011011 11101001 10111101 10010000
        -------------------------- ----------------------------- -----------------------------
                    武                         沛                           齐

  

2. Hash操作

hash表现形式上有些像pyhton中的dict,可以存储一组关联性较强的数据 , redis中Hash在内存中的存储格式如下图:

hset(name, key, value)  # name对应的hash中设置一个键值对(不存在,则创建;否则,修改)参数:name,redis的name; key,name对应的hash中的key ; value,name对应的hash中的value , 
     # 注: hsetnx(name, key, value),当name对应的hash中不存在当前key时则创建(相当于添加)
hmset(name, mapping) # 在name对应的hash中批量设置键值对, 参数:name,redis的name ; mapping,字典,如:{'k1':'v1', 'k2': 'v2'}  如: r.hmset('xx', {'k1':'v1', 'k2': 'v2'})
hget(name,key)  # 在name对应的hash中获取根据key获取value
hmget(name, keys, *args) # 在name对应的hash中获取多个key的值  参数: name,reids对应的name ; keys,要获取key集合,如:['k1', 'k2', 'k3']  ; *args,要获取的key,
     # 如:k1,k2,k3  如: r.mget('xx', ['k1', 'k2'])  或 print r.hmget('xx', 'k1', 'k2')
hget(name,key)   # 在name对应的hash中获取根据key获取value
hmget(name, keys, *args)  # 在name对应的hash中获取多个key的值,  参数:name,reids对应的name ; keys,要获取key集合,如:['k1', 'k2', 'k3'] args,要获取的key,如:k1,k2,k3  
     # 如:r.mget('xx', ['k1', 'k2']) 或  print r.hmget('xx', 'k1', 'k2')
hgetall(name) # 获取name对应hash的所有键值
hlen(name)  # 获取name对应的hash中键值对的个数
hkeys(name)  # 获取name对应的hash中所有的key的值
hvals(name)   # 获取name对应的hash中所有的value的值
hexists(name, key)  # 检查name对应的hash是否存在当前传入的key
hdel(name,*keys)  # 将name对应的hash中指定key的键值对删除
hincrby(name, key, amount=1)   # 自增name对应的hash中的指定key的值,不存在则创建key=amount 参数: name,redis中的name  ; key, hash对应的key ; amount,自增数(整数)
hincrbyfloat(name, key, amount=1.0)  # 自增name对应的hash中的指定key的值,不存在则创建key=amount . 参数: name,redis中的name ; key, hash对应的key ; amount,自增数(浮点数)
   # 自增name对应的hash中的指定key的值,不存在则创建key=amount

  

hscan(name, cursor=0, match=None, count=None)

# 增量式迭代获取,对于数据大的数据非常有用,hscan可以实现分片的获取数据,并非一次性将数据全部获取完,从而放置内存被撑爆
 
# 参数:
    # name,redis的name
    # cursor,游标(基于游标分批取获取数据)
    # match,匹配指定key,默认None 表示所有的key
    # count,每次分片最少获取个数,默认None表示采用Redis的默认分片个数
 
# 如:
    # 第一次:cursor1, data1 = r.hscan('xx', cursor=0, match=None, count=None)
    # 第二次:cursor2, data1 = r.hscan('xx', cursor=cursor1, match=None, count=None)
    # ...
    # 直到返回值cursor的值为0时,表示数据已经通过分片获取完毕

  

hscan_iter(name, match=None, count=None)

# 利用yield封装hscan创建生成器,实现分批去redis中获取数据
  
# 参数:
    # match,匹配指定key,默认None 表示所有的key
    # count,每次分片最少获取个数,默认None表示采用Redis的默认分片个数
  
# 如:
    # for item in r.hscan_iter('xx'):
    #     print item

  

 

3. list

List操作,redis中的List在在内存中按照一个name对应一个List来存储。如图:

lpush(name,values)

# 在name对应的list中添加元素,每个新的元素都添加到列表的最左边如:
    # r.lpush('oo', 11,22,33)
    # 保存顺序为: 33,22,11
 
# 扩展:
    # rpush(name, values) 表示从右向左操作

  

lpushx(name,value)

# 在name对应的list中添加元素,只有name已经存在时,值添加到列表的最左边
 
# 更多:
    # rpushx(name, value) 表示从右向左操作

 

llen(name)

# name对应的list元素的个数

 

linsert(name, where, refvalue, value))

# 在name对应的列表的某一个值前或后插入一个新值
 
# 参数:
    # name,redis的name
    # where,BEFORE或AFTER
    # refvalue,标杆值,即:在它前后插入数据
    # value,要插入的数据

  

r.lset(name, index, value)

# 对name对应的list中的某一个索引位置重新赋值
 
# 参数:
    # name,redis的name
    # index,list的索引位置
    # value,要设置的值

  

r.lrem(name, num, value)

# 在name对应的list中删除指定的值
 
# 参数:
    # name,redis的name
    # num,  num=0,删除列表中所有的指定值;
           # num=2,从前到后,删除2个;
           # num=-2,从后向前,删除2个
    # value,要删除的值

  r.lrem('channel_name',0,'specific.IjxfqmwI!zlqTtaNjLCGG')

  

lpop(name)

# 在name对应的列表的左侧获取第一个元素并在列表中移除,返回值则是第一个元素
 
# 更多:
    # rpop(name) 表示从右向左操作

  

lindex(name, index)

在name对应的列表中根据索引获取列表元素

  

lrange(name, start, end)

# 在name对应的列表分片获取数据
# 参数:
    # name,redis的name
    # start,索引的起始位置
    # end,索引结束位置

  

ltrim(name, start, end)

# 在name对应的列表中移除没有在start-end索引之间的值
# 参数:
    # name,redis的name
    # start,索引的起始位置
    # end,索引结束位置

  

rpoplpush(src, dst)

# 从一个列表取出最右边的元素,同时将其添加至另一个列表的最左边
# 参数:
    # src,要取数据的列表的name
    # dst,要添加数据的列表的name

  

blpop(keys, timeout)

# 将多个列表排列,按照从左到右去pop对应列表的元素
 
# 参数:
    # keys,redis的name的集合
    # timeout,超时时间,当元素所有列表的元素获取完之后,阻塞等待列表内有数据的时间(秒), 0 表示永远阻塞
 
# 更多:
    # r.brpop(keys, timeout),从右向左获取数据

  

brpoplpush(src, dst, timeout=0)

# 从一个列表的右侧移除一个元素并将其添加到另一个列表的左侧
 
# 参数:
    # src,取出并要移除元素的列表对应的name
    # dst,要插入元素的列表对应的name
    # timeout,当src对应的列表中没有数据时,阻塞等待其有数据的超时时间(秒),0 表示永远阻塞

  

4.set集合操作

Set操作,Set集合就是不允许重复的列表

sadd(name,values)  # name对应的集合中添加元素
scard(name)   # 获取name对应的集合中元素个数
sdiff(keys, *args)  #在第一个name对应的集合中且不在其他name对应的集合的元素集合
sdiffstore(dest, keys, *args)  # 获取第一个name对应的集合中且不在其他name对应的集合,再将其新加入到dest对应的集合中
sinter(keys, *args)  # 获取多一个name对应集合的并集
sinterstore(dest, keys, *args) # 获取多一个name对应集合的并集,再讲其加入到dest对应的集合中
sismember(name, value)  # 检查value是否是name对应的集合的成员
smembers(name)  # 获取name对应的集合的所有成员
smove(src, dst, value)  # 将某个成员从一个集合中移动到另外一个集合
spop(name)  #  从集合的右侧(尾部)移除一个成员,并将其返回
srandmember(name, numbers)  # 从name对应的集合中随机获取 numbers 个元素
srem(name, values)  # 在name对应的集合中删除某些值
sunion(keys, *args)  # 获取多一个name对应的集合的并集
sunionstore(dest,keys, *args)  # 获取多一个name对应的集合的并集,并将结果保存到dest对应的集合中
sscan(name, cursor=0, match=None, count=None) # 同字符串的操作,用于增量迭代分批获取元素,避免内存消耗太大
sscan_iter(name, match=None, count=None)  

  

有序集合,在集合的基础上,为每元素排序;元素的排序需要根据另外一个值来进行比较,所以,对于有序集合,每一个元素有两个值,即:值和分数,分数专门用来做排序。

zadd(name, *args, **kwargs)  # 在name对应的有序集合中添加元素, 如: zadd('zz', 'n1', 1, 'n2', 2)  或  zadd('zz', n1=11, n2=22)
zcard(name)  # 获取name对应的有序集合元素的数量
zcount(name, min, max)  # 获取name对应的有序集合中分数 在 [min,max] 之间的个数
zincrby(name, value, amount)  	 # 自增name对应的有序集合的 name 对应的分数

zrange( name, start, end, desc=False, withscores=False, score_cast_func=float) # 按照索引范围获取name对应的有序集合的元素
# 参数:
    # name,redis的name
    # start,有序集合索引起始位置(非分数)
    # end,有序集合索引结束位置(非分数)
    # desc,排序规则,默认按照分数从小到大排序
    # withscores,是否获取元素的分数,默认只获取元素的值
    # score_cast_func,对分数进行数据转换的函数
 
# 更多:
    # 从大到小排序
    # zrevrange(name, start, end, withscores=False, score_cast_func=float)
 
    # 按照分数范围获取name对应的有序集合的元素
    # zrangebyscore(name, min, max, start=None, num=None, withscores=False, score_cast_func=float)
    # 从大到小排序
    # zrevrangebyscore(name, max, min, start=None, num=None, withscores=False, score_cast_func=float)

zrank(name, value) # 获取某个值在 name对应的有序集合中的排行(从 0 开始)。更多: zrevrank(name, value),从大到小排序
zrem(name, values) # 删除name对应的有序集合中值是values的成员, 如:zrem('zz', ['s1', 's2'])
zremrangebyrank(name, min, max)  # 根据排行范围删除
zremrangebyscore(name, min, max) # 根据分数范围删除
zscore(name, value)  # 获取name对应有序集合中 value 对应的分数
zinterstore(dest, keys, aggregate=None)  # 获取两个有序集合的交集,如果遇到相同值不同分数,则按照aggregate进行操作 aggregate的值为:  SUM  MIN  MAX
zunionstore(dest, keys, aggregate=None) # 获取两个有序集合的并集,如果遇到相同值不同分数,则按照aggregate进行操作  aggregate的值为:  SUM  MIN  MAX
zscan(name, cursor=0, match=None, count=None, score_cast_func=float) # 同字符串相似,相较于字符串新增score_cast_func,用来对分数进行操作
zscan_iter(name, match=None, count=None,score_cast_func=float) 

  

 

5、其他常用操作

delete(*names)  # 根据删除redis中的任意数据类型
exists(name) # 检测redis的name是否存在
keys(pattern='*')  # 根据模型获取redis的name
# 更多:
    # KEYS * 匹配数据库中所有 key 。
    # KEYS h?llo 匹配 hello , hallo 和 hxllo 等。
    # KEYS h*llo 匹配 hllo 和 heeeeello 等。
    # KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo

expire(name ,time) # 为某个redis的某个name设置超时时间
rename(src, dst)  # 对redis的name重命名为
move(name, db))  # 将redis的某个值移动到指定的db下
randomkey()    # 随机获取一个redis的name(不删除)
type(name)      # 获取name对应值的类型
scan(cursor=0, match=None, count=None) # 同字符串操作,用于增量迭代获取key
scan_iter(match=None, count=None)

  

管道

redis-py默认在执行每次请求都会创建(连接池申请连接)和断开(归还连接池)一次连接操作,如果想要在一次请求中指定多个命令,则可以使用pipline实现一次请求指定多个命令,并且默认情况下一次pipline 是原子性操作。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
import redis
 
pool = redis.ConnectionPool(host='10.211.55.4', port=6379)
 
r = redis.Redis(connection_pool=pool)
 
# pipe = r.pipeline(transaction=False)
pipe = r.pipeline(transaction=True)
 
pipe.set('name', 'alex')
pipe.set('role', 'sb')
 
pipe.execute()

  

发布订阅

 

#!/usr/bin/env python
# set coding: utf-8
__author__ = "richardzgt"

import redis

class RedisHelper(object):

    def __init__(self):
        self.__conn = redis.Redis(host='127.0.0.1')
        self.chan_sub = 'fm104.5'
        self.chan_pub = 'fm104.5'

    def public(self, msg):
        self.__conn.publish(self.chan_pub, msg)
        return True

    def subscribe(self):
        pub = self.__conn.pubsub()
        pub.subscribe(self.chan_sub)
        pub.parse_response()
        return pub

  

订阅者:

#!/usr/bin/env python
# set coding: utf-8
__author__ = "richardzgt"
from redis_help import RedisHelper


obj = RedisHelper()
redis_sub = obj.subscribe()
while True:
    msg = redis_sub.parse_response()
    print(msg)

  

发布者

#!/usr/bin/env python
# set coding: utf-8
__author__ = "richardzgt"
from redis_help import RedisHelper

obj = RedisHelper()
obj.public("hello world")

  


 

通过redis的订阅功能和gevent的异步处理话单

#!/bin/env python

import gevent.monkey
gevent.monkey.patch_all()

import gevent
from geventhttpclient import HTTPClient, URL
from redis import Redis
import urllib
import json
import logging
import logging.handlers

REDIS_HOST = '1.1.1.1'
POST_URL = 'XXXXXX'

logger = logging.getLogger("cdrposter")
logger.setLevel(logging.DEBUG)


class CdrPoster(object):

    def __init__(self, redis_host):
        self.red = Redis(redis_host)

    def post_cdr(self, cdr):
        data = {'id':cdr['uniqueid'], 'fromMobile':cdr['username'], 'toMobile':cdr['dest'], 'type':cdr['hangcause'], 
                'callTime':cdr['start_time'], 'talkTime':cdr['billsec'], 'direct':cdr['call_type']}
        if cdr.get('media_time'):
            data['media_time'] = cdr['media_time']
        body = urllib.urlencode(data)
        u = URL(POST_URL)
        http = HTTPClient.from_url(u, headers={'Content-Type': 'application/x-www-form-urlencoded'})
        response = http.post(u.request_uri, body)
        return response.status_code == 200, response.read()

    def start(self):
        pubsub = self.red.pubsub()
        pubsub.subscribe('fs:cdrs')
        for msg in pubsub.listen():
            if msg.get('type') == 'message':
                try:
                    cdr = json.loads(msg['data'])
                    code, content = self.post_cdr(cdr)
                    logger.info("post cdr %s, response code %s, content %s" % (cdr['uniqueid'], code, content))
                except Exception, err:
                    logger.info("post cdr %s failed: %s" % (cdr['uniqueid'], str(err)))


if __name__ == '__main__':
    poster = CdrPoster(REDIS_HOST)
    gevent.spawn(poster.start).join()

  

 

什么时候用关系型数据库,什么时候 用NoSQL?

Go for legacy relational databases (RDBMS) when:

The data is well structured, and lends itself to a tabular arrangement (rows and columns) in a relational database. Typical examples: bank account info, customer order info, customer info, employee info, department info etc etc.
Another aspect of the above point is : schema oriented data model. When you design a data model (tables, relationships etc) for a potential use of RDBMS, you need to come up with a well defined schema: there will be these many tables, each table having a known set of columns that store data in known typed format (CHAR, NUMBER, BLOB etc).
Very Important: Consider whether the data is transactional in nature. In other words, whether the data will be stored, accessed and updated in the context of transactions providing the ACID semantics or is it okay to compromise some/all of these properties.
Correctness is also important and any compromise is _unacceptable_. This stems from the fact that in most NoSQL databases, consistency is traded off in favor of performance and scalability (points on NoSQL databases are elaborated below).
There is no strong/compelling need for a scale out architecture ; a database that linearly scales out (horizontal scaling) to multiple nodes in a cluster.
The use case is not for “high speed data ingestion”.
If the client applications are expecting to quickly stream large amounts of data in/out of the database then relational database may not be a good choice since they are not really designed for scaling write heavy workloads.
In order to achieve ACID properties, lots of additional background work is done especially in writer (INSERT, UPDATE, DELETE) code paths. This definitely affects performance.
The use case is not for “storing enormous amounts of data in the range of petabytes”.
 

Go for NoSQL databases when:

There is no fixed (and predetermined) schema that data fits in:
Scalability, Performance (high throughput and low operation latency), Continuous Availability are very important requirements to be met by the underlying architecture of database.
Good choice for “High Speed Data Ingestion”. Such applications (for example IoT style) which generate millions of data points in a second and need a database capable of providing extreme write scalability.
The inherent ability to horizontally scale allows to store large amounts of data across commodity servers in the cluster. They usually use low cost resources, and are able to linearly add compute and storage power as the demand grows.
View Code

 

 

附赠redis性能测试

  准备环境:

  因为找不到可用的1000M网络机器,使用一根直通线将两台笔记本连起来组成1000M Ethernet网。没错,是直通线现在网卡都能自适应交叉线、直通线,速度不受影响,用了一段时间机器也没出问题。

  服务端:T420 i5-2520M(2.5G)/8G ubuntu 11.10

  客户端:Acer i5-2430M(2.4G)/4G mint 11

  redis版本:2.6.9

  测试脚本:./redis-benchmark -h xx -p xx -t set -q -r 1000 -l -d 20

 

长度 速度/sec 带宽(MByte/s) 发送+接收 CPU CPU Detail
20Byte 17w 24M+12M 98.00% Cpu0 : 21.0%us, 40.7%sy, 0.0%ni, 4.3%id, 0.0%wa, 0.0%hi, 34.0%si, 0.0%st
100Byte 17w 37M+12M 97.00% Cpu0 : 20.3%us, 37.9%sy, 0.0%ni, 7.0%id, 0.0%wa, 0.0%hi, 34.9%si, 0.0%st
512Byte 12w 76M+9M 87.00% Cpu0 : 20.9%us, 33.2%sy, 0.0%ni, 25.6%id, 0.0%wa, 0.0%hi, 20.3%si, 0.0%st
1K 9w 94M+8M 81.00% Cpu0 : 19.9%us, 30.2%sy, 0.0%ni, 34.2%id, 0.0%wa, 0.0%hi, 15.6%si, 0.0%st
2K 5w 105M+6M 77.00% Cpu0 : 18.0%us, 32.0%sy, 0.0%ni, 34.7%id, 0.0%wa, 0.0%hi, 15.3%si, 0.0%st
5K 2.2w 119M+3.2M 77.00% Cpu0 : 22.5%us, 32.8%sy, 0.0%ni, 32.8%id, 0.0%wa, 0.0%hi, 11.9%si, 0.0%st
10K 1.1w 119M+1.7M 70.00% Cpu0 : 18.2%us, 29.8%sy, 0.0%ni, 42.7%id, 0.0%wa, 0.0%hi, 9.3%si, 0.0%st
20K 0.57w 120M+1M 58.00% Cpu0 : 17.8%us, 26.4%sy, 0.0%ni, 46.2%id, 0.0%wa, 0.0%hi, 9.6%si, 0.0%st

  value 在1K以上时,1000M网卡轻松的被跑慢,而且redis-server cpu连一个核心都没占用到,可见redis高效,redis的服务也不需要太高配置,瓶颈在网卡速度。

  整理看redis的us都在20%左右,用户层代码资源占用比例都很小。

 

 

 

 

  

参数
说明(解释)
broker.id =0
每一个broker在集群中的唯一表示,要求是正数。当该服务器的IP地址发生改变时,broker.id没有变化,则不会影响consumers的消息情况
log.dirs=/data/kafka-logs
kafka数据的存放地址,多个地址的话用逗号分割/data/kafka-logs-1,/data/kafka-logs-2
port =9092
broker server服务端口
message.max.bytes =6525000
表示消息体的最大大小,单位是字节
num.network.threads =4
broker处理消息的最大线程数,一般情况下不需要去修改
num.io.threads =8
broker处理磁盘IO的线程数,数值应该大于你的硬盘数
background.threads =4
一些后台任务处理的线程数,例如过期消息文件的删除等,一般情况下不需要去做修改
queued.max.requests =500
等待IO线程处理的请求队列最大数,若是等待IO的请求超过这个数值,那么会停止接受外部消息,应该是一种自我保护机制。
host.name
broker的主机地址,若是设置了,那么会绑定到这个地址上,若是没有,会绑定到所有的接口上,并将其中之一发送到ZK,一般不设置
socket.send.buffer.bytes=100*1024
socket的发送缓冲区,socket的调优参数SO_SNDBUFF
socket.receive.buffer.bytes =100*1024
socket的接受缓冲区,socket的调优参数SO_RCVBUFF
socket.request.max.bytes =100*1024*1024
socket请求的最大数值,防止serverOOM,message.max.bytes必然要小于socket.request.max.bytes,会被topic创建时的指定参数覆盖
log.segment.bytes =1024*1024*1024
topic的分区是以一堆segment文件存储的,这个控制每个segment的大小,会被topic创建时的指定参数覆盖
log.roll.hours =24*7
这个参数会在日志segment没有达到log.segment.bytes设置的大小,也会强制新建一个segment会被 topic创建时的指定参数覆盖
log.cleanup.policy = delete
日志清理策略选择有:delete和compact主要针对过期数据的处理,或是日志文件达到限制的额度,会被 topic创建时的指定参数覆盖
log.retention.minutes=3days
数据存储的最大时间超过这个时间会根据log.cleanup.policy设置的策略处理数据,也就是消费端能够多久去消费数据
log.retention.bytes和log.retention.minutes任意一个达到要求,都会执行删除,会被topic创建时的指定参数覆盖
log.retention.bytes=-1
topic每个分区的最大文件大小,一个topic的大小限制 =分区数*log.retention.bytes。-1没有大小限log.retention.bytes和log.retention.minutes任意一个达到要求,都会执行删除,会被topic创建时的指定参数覆盖
log.retention.check.interval.ms=5minutes
文件大小检查的周期时间,是否处罚 log.cleanup.policy中设置的策略
log.cleaner.enable=false
是否开启日志压缩
log.cleaner.threads = 2
日志压缩运行的线程数
log.cleaner.io.max.bytes.per.second=None
日志压缩时候处理的最大大小
log.cleaner.dedupe.buffer.size=500*1024*1024
日志压缩去重时候的缓存空间,在空间允许的情况下,越大越好
log.cleaner.io.buffer.size=512*1024
日志清理时候用到的IO块大小一般不需要修改
log.cleaner.io.buffer.load.factor =0.9
日志清理中hash表的扩大因子一般不需要修改
log.cleaner.backoff.ms =15000
检查是否处罚日志清理的间隔
log.cleaner.min.cleanable.ratio=0.5
日志清理的频率控制,越大意味着更高效的清理,同时会存在一些空间上的浪费,会被topic创建时的指定参数覆盖
log.cleaner.delete.retention.ms =1day
对于压缩的日志保留的最长时间,也是客户端消费消息的最长时间,同log.retention.minutes的区别在于一个控制未压缩数据,一个控制压缩后的数据。会被topic创建时的指定参数覆盖
log.index.size.max.bytes =10*1024*1024
对于segment日志的索引文件大小限制,会被topic创建时的指定参数覆盖
log.index.interval.bytes =4096
当执行一个fetch操作后,需要一定的空间来扫描最近的offset大小,设置越大,代表扫描速度越快,但是也更好内存,一般情况下不需要搭理这个参数
log.flush.interval.messages=None
log文件”sync”到磁盘之前累积的消息条数,因为磁盘IO操作是一个慢操作,但又是一个”数据可靠性"的必要手段,所以此参数的设置,需要在"数据可靠性"与"性能"之间做必要的权衡.如果此值过大,将会导致每次"fsync"的时间较长(IO阻塞),如果此值过小,将会导致"fsync"的次数较多,这也意味着整体的client请求有一定的延迟.物理server故障,将会导致没有fsync的消息丢失.
log.flush.scheduler.interval.ms =3000
检查是否需要固化到硬盘的时间间隔
log.flush.interval.ms = None
仅仅通过interval来控制消息的磁盘写入时机,是不足的.此参数用于控制"fsync"的时间间隔,如果消息量始终没有达到阀值,但是离上一次磁盘同步的时间间隔达到阀值,也将触发.
log.delete.delay.ms =60000
文件在索引中清除后保留的时间一般不需要去修改
log.flush.offset.checkpoint.interval.ms =60000
控制上次固化硬盘的时间点,以便于数据恢复一般不需要去修改
auto.create.topics.enable =true
是否允许自动创建topic,若是false,就需要通过命令创建topic
default.replication.factor =1
是否允许自动创建topic,若是false,就需要通过命令创建topic
num.partitions =1
每个topic的分区个数,若是在topic创建时候没有指定的话会被topic创建时的指定参数覆盖
 
 
以下是kafka中Leader,replicas配置参数
 
controller.socket.timeout.ms =30000
partition leader与replicas之间通讯时,socket的超时时间
controller.message.queue.size=10
partition leader与replicas数据同步时,消息的队列尺寸
replica.lag.time.max.ms =10000
replicas响应partition leader的最长等待时间,若是超过这个时间,就将replicas列入ISR(in-sync replicas),并认为它是死的,不会再加入管理中
replica.lag.max.messages =4000
如果follower落后与leader太多,将会认为此follower[或者说partition relicas]已经失效
##通常,在follower与leader通讯时,因为网络延迟或者链接断开,总会导致replicas中消息同步滞后
##如果消息之后太多,leader将认为此follower网络延迟较大或者消息吞吐能力有限,将会把此replicas迁移
##到其他follower中.
##在broker数量较少,或者网络不足的环境中,建议提高此值.
replica.socket.timeout.ms=30*1000
follower与leader之间的socket超时时间
replica.socket.receive.buffer.bytes=64*1024
leader复制时候的socket缓存大小
replica.fetch.max.bytes =1024*1024
replicas每次获取数据的最大大小
replica.fetch.wait.max.ms =500
replicas同leader之间通信的最大等待时间,失败了会重试
replica.fetch.min.bytes =1
fetch的最小数据尺寸,如果leader中尚未同步的数据不足此值,将会阻塞,直到满足条件
num.replica.fetchers=1
leader进行复制的线程数,增大这个数值会增加follower的IO
replica.high.watermark.checkpoint.interval.ms =5000
每个replica检查是否将最高水位进行固化的频率
controlled.shutdown.enable =false
是否允许控制器关闭broker ,若是设置为true,会关闭所有在这个broker上的leader,并转移到其他broker
controlled.shutdown.max.retries =3
控制器关闭的尝试次数
controlled.shutdown.retry.backoff.ms =5000
每次关闭尝试的时间间隔
leader.imbalance.per.broker.percentage =10
leader的不平衡比例,若是超过这个数值,会对分区进行重新的平衡
leader.imbalance.check.interval.seconds =300
检查leader是否不平衡的时间间隔
offset.metadata.max.bytes
客户端保留offset信息的最大空间大小
kafka中zookeeper参数配置
 
zookeeper.connect = localhost:2181
zookeeper集群的地址,可以是多个,多个之间用逗号分割hostname1:port1,hostname2:port2,hostname3:port3
zookeeper.session.timeout.ms=6000
ZooKeeper的最大超时时间,就是心跳的间隔,若是没有反映,那么认为已经死了,不易过大
zookeeper.connection.timeout.ms =6000
ZooKeeper的连接超时时间
zookeeper.sync.time.ms =2000
ZooKeeper集群中leader和follower之间的同步实际那
 

————————————————版权声明:本文为CSDN博主「星辰大海的风景」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/zhongwumao/article/details/81171143

posted @ 2017-11-02 13:33  richardzgt  阅读(578)  评论(0编辑  收藏  举报