Python을 사용하여 NoSQL 데이터베이스 샘플 코드 공유를 완료하는 방법

黄舟
풀어 주다: 2017-07-18 11:12:57
원래의
1836명이 탐색했습니다.

NoSQL은 최근 몇 년 동안 널리 사용되는 용어입니다. 그런데 "NoSQL"은 정확히 무엇을 의미합니까? 이 기사에서는 어떻게 그리고 왜 그렇게 유용한가요? 구조화된 의사 코드") NoSQL 데이터베이스를 작성하여 이러한 질문에 답하세요.

OldSQL

많은 경우 SQL은 "데이터베이스"의 동의어가 되었습니다. 실제로 SQLStrctured Query Language의 약어입니다. 데이터베이스 기술 자체를 말하는 것입니다. 오히려 RDBMS(Relational Database Management System)에서 데이터를 검색하는 언어를 말합니다. MySQL, MS SQL Server 및 Oracle은 모두 RDBMS의 R입니다. "관계형"(관계, 연관)이 가장 풍부한 부분입니다. 데이터는 테이블

을 통해 구성되며, 각 테이블은

유형으로 연결된 으로 구성됩니다. 모든 테이블의 유형, 열 및 해당 클래스를 호출합니다. 데이터베이스의 schema(스키마 또는 스키마)이 전달됩니다. 각 테이블의 설명 정보는 데이터베이스의 구조를 완전히 설명합니다. 예를 들어 Car라는 테이블에는 다음 열이 있을 수 있습니다. :

  • Make: 문자열
  • Car 的表可能有以下一些列:
    • Make: a string

    • Model: a string

    • Year: a four-digit number; alternatively, a date

    • Color: a string

    • VIN(Vehicle Identification Number): a string

    在一张表中, 每个单一的条目叫做一 行 (row), 或者一条 记录 (record). 为了区分每条记录, 通常会定义一个 主键 (primary key). 表中的 主键 是其中一列 , 它能够唯一标识每一行. 在表 Car中, VIN 是一个天然的主键选择, 因为它能够保证每辆车具有唯一的标识. 两个不同的行可能会在 Make, Model, Year 和 Color 列上有相同的值, 但是对于不同的车而言, 肯定会有不同的 VIN. 反之, 只要两行拥有同一个 VIN, 我们不必去检查其他列就可以认为这两行指的的就是同一辆车.

    Querying

    SQL 能够让我们通过对数据库进行 query (查询) 来获取有用的信息. 查询 简单来说, 查询就是用一个结构化语言向 RDBMS 提问, 并将其返回的行解释为问题的答案. 假设数据库表示了美国所有的注册车辆, 为了获取 所有的 记录, 我们可以通过在数据库上进行如下的 SQL 查询 :

    SELECT Make, Model FROM Car;
    로그인 후 복사

    将 SQL 大致翻译成中文:

    • “SELECT”: “向我展示”

    • “Make, Model”: “Make 和 Model 的值”

    • “FROM Car”: “对表 Car 中的每一行”

    也就是, “向我展示表 Car 每一行中 Make 和 Model 的值”. 执行查询后, 我们将会得到一些查询的结果, 其中每个都是 Make 和 Model. 如果我们仅关心在 1994 年注册的车的颜色, 那么可以:

    SELECT Color FROM Car WHERE Year = 1994;
    로그인 후 복사

    此时, 我们会得到一个类似如下的列表:

    Black
    Red
    Red
    White
    Blue
    Black
    White
    Yellow
    로그인 후 복사

    最后, 我们可以通过使用表的 (primary key) 主键 , 这里就是 VIN 来指定查询一辆车:

    SELECT * FROM Car WHERE VIN = '2134AFGER245267'
    로그인 후 복사

    上面这条查询语句会返回所指定车辆的属性信息.

    主键被定义为唯一不可重复的. 也就是说, 带有某一指定 VIN 的车辆在表中至多只能出现一次. 这一点非常重要,为什么? 来看一个例子:

    Relations

    假设我们正在经营一个汽车修理的业务. 除了其他一些必要的事情, 我们还需要追踪一辆车的服务历史, 即在该辆车上所有的修整记录. 那么我们可能会创建包含以下一些列的 ServiceHistoryModel: 문자열

  • Year: 4자리 또는 날짜
  • 색상: 문자열VIN(차량 식별 번호): 문자열테이블 , 각 단일 항목을 또는 항목 이라고 합니다. 각 레코드를 구별하기 위해 일반적으로 테이블의 가 열 중 하나입니다. 이는 각 행을 고유하게 식별할 수 있습니다. Car</ code> 테이블에서 VIN은 각 차량이 고유한 식별자를 갖도록 보장하므로 Make에서 동일한 값을 가질 수 있습니다. 모델, 연도 및 색상 열은 다르지만 자동차의 경우 VIN이 분명히 다를 것입니다. 반대로 두 행의 VIN이 동일한 경우 다른 열을 확인하지 않고도 두 행이 동일한 자동차를 참조한다고 생각할 수 있습니다. <td></td>Querying<td></td>SQL을 사용하면 데이터베이스에서 <td>query</td>를 수행하여 유용한 정보를 얻을 수 있습니다. <td>Query</td> 간단히 말해서 쿼리는 구조화된 언어를 사용하여 RDBMS에 질문하고 반환되는 행을 해석합니다. 질문에 대한 답은 데이터베이스가 미국에 등록된 모든 차량에 대해 </tr>모든</tbody> 기록을 얻기 위해 데이터베이스에서 다음 </table>SQL 쿼리🎜를 수행할 수 있다는 것을 가정합니다. 🎜<div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:sql;toolbar:false;">SELECT Vehicle.Model, Vehicle.Year FROM Vehicle, ServiceHistory WHERE Vehicle.VIN = ServiceHistory.VIN AND ServiceHistory.Price &gt; 75.00;</pre><div class="contentsignin">로그인 후 복사</div></div><div class="contentsignin">로그인 후 복사</div></div>🎜대략 SQL을 중국어로 번역합니다. :🎜<ul class="list-paddingleft-2" ><li>🎜"SELECT": "Show me"🎜</li><li>🎜"Make, Model": "Make and Model value"🎜</ li><li>🎜"FROM Car": "Car 테이블의 각 행에 대해" 🎜</li></ul>🎜 즉, "테이블의 각 행에 있는 제조사 및 모델의 값을 보여줍니다. Car". 쿼리를 실행하면 각각 제조사와 모델인 몇 가지 쿼리 결과를 얻게 됩니다. 1994년에 등록된 자동차의 색상에만 관심이 있다면 다음을 수행할 수 있습니다.🎜<div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:sql;toolbar:false;">COMMAND; [KEY]; [VALUE]; [VALUE TYPE]</pre><div class="contentsignin">로그인 후 복사</div></div><div class="contentsignin">로그인 후 복사</div></div>🎜이 시점에서 우리는 다음과 유사한 목록을 얻습니다.🎜<div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:python;toolbar:false;">&quot;&quot;&quot;NoSQL database written in Python&quot;&quot;&quot; # Standard library imports import socket HOST = &amp;#39;localhost&amp;#39; PORT = 50505 SOCKET = socket.socket(socket.AF_INET, socket.SOCK_STREAM) STATS = { &amp;#39;PUT&amp;#39;: {&amp;#39;success&amp;#39;: 0, &amp;#39;error&amp;#39;: 0}, &amp;#39;GET&amp;#39;: {&amp;#39;success&amp;#39;: 0, &amp;#39;error&amp;#39;: 0}, &amp;#39;GETLIST&amp;#39;: {&amp;#39;success&amp;#39;: 0, &amp;#39;error&amp;#39;: 0}, &amp;#39;PUTLIST&amp;#39;: {&amp;#39;success&amp;#39;: 0, &amp;#39;error&amp;#39;: 0}, &amp;#39;INCREMENT&amp;#39;: {&amp;#39;success&amp;#39;: 0, &amp;#39;error&amp;#39;: 0}, &amp;#39;APPEND&amp;#39;: {&amp;#39;success&amp;#39;: 0, &amp;#39;error&amp;#39;: 0}, &amp;#39;DELETE&amp;#39;: {&amp;#39;success&amp;#39;: 0, &amp;#39;error&amp;#39;: 0}, &amp;#39;STATS&amp;#39;: {&amp;#39;success&amp;#39;: 0, &amp;#39;error&amp;#39;: 0}, }</pre><div class="contentsignin">로그인 후 복사</div></div><div class="contentsignin">로그인 후 복사</div></div>🎜마지막으로 테이블의 🎜(기본 키) 기본 키🎜를 사용할 수 있습니다. 여기에 자동차에 대한 쿼리를 지정하는 VIN이 있습니다.🎜<div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:python;toolbar:false;">COMMAND_HANDERS = { &amp;#39;PUT&amp;#39;: handle_put, &amp;#39;GET&amp;#39;: handle_get, &amp;#39;GETLIST&amp;#39;: handle_getlist, &amp;#39;PUTLIST&amp;#39;: handle_putlist, &amp;#39;INCREMENT&amp;#39;: handle_increment, &amp;#39;APPEND&amp;#39;: handle_append, &amp;#39;DELETE&amp;#39;: handle_delete, &amp;#39;STATS&amp;#39;: handle_stats, } DATA = {} def main(): &quot;&quot;&quot;Main entry point for script&quot;&quot;&quot; SOCKET.bind(HOST, PORT) SOCKET.listen(1) while 1: connection, address = SOCKET.accept() print(&amp;#39;New connection from [{}]&amp;#39;.format(address)) data = connection.recv(4096).decode() command, key, value = parse_message(data) if command == &amp;#39;STATS&amp;#39;: response = handle_stats() elif command in (&amp;#39;GET&amp;#39;, &amp;#39;GETLIST&amp;#39;, &amp;#39;INCREMENT&amp;#39;, &amp;#39;DELETE&amp;#39;): response = COMMAND_HANDERS[command](key) elif command in ( &amp;#39;PUT&amp;#39;, &amp;#39;PUTLIST&amp;#39;, &amp;#39;APPEND&amp;#39;, ): response = COMMAND_HANDERS[command](key, value) else: response = (False, &amp;#39;Unknown command type {}&amp;#39;.format(command)) update_stats(command, response[0]) connection.sandall(&amp;#39;{};{}&amp;#39;.format(response[0], response[1])) connection.close() if __name__ == &amp;#39;__main__&amp;#39;: main()</pre><div class="contentsignin">로그인 후 복사</div></div><div class="contentsignin">로그인 후 복사</div></div>🎜위 쿼리 문은 다음을 반환합니다. 🎜🎜기본 키는 고유하고 반복 불가능한 것으로 정의됩니다. 즉, 특정 VIN을 가진 차량은 테이블에 최대 한 번만 나타날 수 있습니다. 이것이 왜 중요한가요? 예를 들어보세요: 🎜🎜Relations🎜🎜우리가 자동차 수리 사업을 운영하고 있다고 가정해 보세요. 또한 자동차의 서비스 기록, 즉 자동차의 모든 수리 기록을 추적해야 합니다. 다음 열이 포함된 code>ServiceHistory 테이블: 🎜🎜🎜🎜🎜 VIN🎜🎜Make🎜🎜Model🎜🎜Year🎜🎜Color🎜🎜Service Performed🎜🎜Mechanic🎜🎜Price🎜🎜Date🎜 🎜🎜🎜

    이런 식으로 차량을 수리할 때마다 테이블에 새 행을 추가하고 서비스 중에 수행한 작업, 수리공이 누구인지, 비용은 얼마인지, 서비스 시간 등을 기록합니다.

    그런데 잠깐만요. 한눈에 우리 모두는 동일한 차량에 대해 차량 자체 정보와 관련된 모든 열이 변경되지 않음을 알고 있습니다. 즉, 내 Black 2014 Lexus RX 350을 10번 개조하면 제조사, 모델, 연식, 색상 정보가 변경되지 않더라도 정보가 매번 반복적으로 기록됩니다. 합리적인 접근 방식은 이러한 정보를 한 번만 저장하고 필요할 때 쿼리하는 것입니다.

    그럼 어떻게 해야 할까요? 다음 열이 있는 Vehicle이라는 두 번째 테이블을 만들 수 있습니다. ServiceHistory 테이블을 다음 열로 단순화할 수 있습니다. Vehicle , 它有如下一些列:

  • row(row)Record기본 키
    VINMakeModelYearColor

    这样一来, 对于 ServiceHistory

    VINService PerformedMechanicPriceDate

    你可能会问,为什么 VIN 会在两张表中同时出现? 因为我们需要有一个方式来确认在 ServiceHistory 表的 辆车指的就是 Vehicle 表中的 辆车, 也就是需要确认两张表中的两条记录所表示的是同一辆车。 这样的话,我们仅需要为每辆车的自身信息存储一次即可. 每次当车辆过来维修的时候, 我们就在 ServiceHistory 表中创建新的一行, 而不必在 Vehicle表中添加新的记录。 毕竟, 它们指的是同一辆车。

    我们可以通过 SQL 查询语句来展开 VehicleServiceHistory 两张表中包含的隐式关系:

    SELECT Vehicle.Model, Vehicle.Year FROM Vehicle, ServiceHistory WHERE Vehicle.VIN = ServiceHistory.VIN AND ServiceHistory.Price > 75.00;
    로그인 후 복사
    로그인 후 복사

    该查询旨在查找维修费用大于 $75.00 的所有车辆的 Model 和 Year. 注意到我们是通过匹配 VehicleServiceHistory 表中的 VIN 值来筛选满足条件的记录. 返回的将是两张表中符合条件的一些记录, 而 “Vehicle.Model” 与 “Vehicle.Year” , 表示我们只想要 Vehicle 表中的这两列.

    如果我们的数据库没有 索引 (indexes) (正确的应该是 indices), 上面的查询就需要执行 表扫描 (table scan) 来定位匹配查询要求的行。 table scan 是按照顺序对表中的每一行进行依次检查, 而这通常会非常的慢。 实际上, table scan 实际上是所有查询中最慢的。

    可以通过对列加索引来避免扫描表。 我们可以把索引看做一种数据结构, 它能够通过预排序让我们在被索引的列上快速地找到一个指定的值 (或指定范围内的一些值). 也就是说, 如果我们在 Price 列上有一个索引, 那么就不需要一行一行地对整个表进行扫描来判断其价格是否大于 75.00, 而是只需要使用包含在索引中的信息 “跳” 到第一个价格高于 75.00 的那一行, 并返回随后的每一行(由于索引是有序的, 因此这些行的价格至少是 75.00)。

    当应对大量的数据时, 索引是提高查询速度不可或缺的一个工具。当然, 跟所有的事情一样,有得必有失, 使用索引会导致一些额外的消耗: 索引的数据结构会消耗内存,而这些内存本可用于数据库中存储数据。这就需要我们权衡其利弊,寻求一个折中的办法, 但是为经常查询的列加索引是 非常 常见的做法。

    The Clear Box

    得益于数据库能够检查一张表的 schema (描述了每列包含了什么类型的数据), 像索引这样的高级特性才能够实现, 并且能够基于数据做出一个合理的决策。 也就是说, 对于一个数据库而言, 一张表其实是一个 “黑盒” (或者说透明的盒子) 的反义词?

    当我们谈到 NoSQL 数据库的时候要牢牢记住这一点。 当涉及 query 不同类型数据库引擎的能力时, 这也是其中非常重要的一部分。

    Schemas

    我们已经知道, 一张表的 schema , 描述了列的名字及其所包含数据的类型。它还包括了其他一些信息, 比如哪些列可以为空, 哪些列不允许有重复值, 以及其他对表中列的所有限制信息。 在任意时刻一张表只能有一个 schema, 并且 表中的所有行必须遵守 schema 的规定

    这是一个非常重要的约束条件。 假设你有一张数据库的表, 里面有数以百万计的消费者信息。 你的销售团队想要添加额外的一些信息 (比如, 用户的年龄), 以期提高他们邮件营销算法的准确度。 这就需要来 alter (更改) 现有的表 – 添加新的一列。 我们还需要决定是否表中的每一行都要求该列必须有一个值。 通常情况下, 让一个列有值是十分有道理的, 但是这么做的话可能会需要一些我们无法轻易获得的信息(比如数据库中每个用户的年龄)。因此在这个层面上,也需要有些权衡之策。

    此外,对一个大型数据库做一些改变通常并不是一件小事。为了以防出现错误,有一个回滚方案非常重要。但即使是如此,一旦当 schema 做出改变后,我们也并不总是能够撤销这些变动。 schema 的维护可能是 DBA 工作中最困难的部分之一。

    Key/Value Stores

    在 “NoSQL” 这个词存在前, 像 memcached 这样的 键/值 数据存储 (Key/Value Data Stores) 无须 table schema 也可提供数据存储的功能。 实际上, 在 K/V 存储时, 根本没有 “表 (table)” 的概念。 只有 键 (keys)值 (values) . 如果键值存储听起来比较熟悉的话, 那可能是因为这个概念的构建原则与 Python 的 dictset 相一致: 使用 hash table (哈希表) 来提供基于键的快速数据查询。 一个基于 Python 的最原始的 NoSQL 数据库, 简单来说就是一个大的字典 (dictionary) .

    为了理解它的工作原理,亲自动手写一个吧! 首先来看一下一些简单的设计想法:

    • 一个 Python 的 dict 作为主要的数据存储

    • 仅支持 string 类型作为键 (key)

    • 支持存储 integer, string 和 list

    • 一个使用 ASCLL string 的简单 TCP/IP 服务器用来传递消息

    • 一些像 INCREMENT, DELETE , APPENDSTATS 这样的高级命令 (command)

    有一个基于 ASCII 的 TCP/IP 接口的数据存储有一个好处, 那就是我们使用简单的 telnet 程序即可与服务器进行交互, 并不需要特殊的客户端 (尽管这是一个非常好的练习并且只需要 15 行代码即可完成)。

    对于我们发送到服务器及其它的返回信息,我们需要一个 “有线格式”。下面是一个简单的说明:

    Commands Supported

    • PUT

      • 参数: Key, Value

      • 目的: 向数据库中插入一条新的条目 (entry)

    • GET

      • 参数: Key

      • 目的: 从数据库中检索一个已存储的值

    • PUTLIST

      • 参数: Key, Value

      • 目的: 向数据库中插入一个新的列表条目

    • APPEND

      • 参数: Key, Value

      • 目的: 向数据库中一个已有的列表添加一个新的元素

    • INCREMENT

      • 参数: key

      • 目的: 增长数据库的中一个整型值

    • DELETE

      • 参数: Key

      • 目的: 从数据库中删除一个条目

    • STATS

      • 参数: 无 (N/A)

      • 目的: 请求每个执行命令的 成功/失败 的统计信息

    现在我们来定义消息的自身结构。

    Message Structure

    Request Messages

    一条 请求消息 (Request Message) 包含了一个命令(command),一个键 (key), 一个值 (value), 一个值的类型(type). 后三个取决于消息类型,是可选项, 非必须。; 被用作是分隔符。即使并没有包含上述可选项, 但是在消息中仍然必须有三个 ; 字符。

    COMMAND; [KEY]; [VALUE]; [VALUE TYPE]
    로그인 후 복사
    로그인 후 복사
    • COMMAND 是上面列表中的命令之一

    • KEY 是一个可以用作数据库 key 的 string (可选)

    • VALUE 是数据库中的一个 integer, list 或 string (可选)

      • list 可以被表示为一个用逗号分隔的一串 string, 比如说, “red, green, blue”

    • VALUE TYPE 描述了 VALUE 应该被解释为什么类型

      • 可能的类型值有:INT, STRING, LIST

    Examples

    • "PUT; foo; 1; INT"

    • "GET; foo;;"

    • "PUTLIST; bar; a,b,c ; LIST"

    • "APPEND; bar; d; STRING"

    • "GETLIST; bar; ;"

    • STATS; ;;

    • INCREMENT; foo;;

    • DELETE; foo;;

    Reponse Messages

    一个 响应消息 (Reponse Message) 包含了两个部分, 通过 ; 进行分隔。第一个部分总是 True|False , 它取决于所执行的命令是否成功。 第二个部分是命令消息 (command message), 当出现错误时,便会显示错误信息。对于那些执行成功的命令,如果我们不想要默认的返回值(比如 PUT), 就会出现成功的信息。 如果我们返回成功命令的值 (比如 GET), 那么第二个部分就会是自身值。

    Examples

    • True; Key [foo] set to [1]

    • True; 1

    • True; Key [bar] set to [[&#39;a&#39;, &#39;b&#39;, &#39;c&#39;]]

    • True; Key [bar] had value [d] appended

    • True; [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;]

    • True; {&#39;PUTLIST&#39;: {&#39;success&#39;: 1, &#39;error&#39;: 0}, &#39;STATS&#39;: {&#39;success&#39;: 0, &#39;error&#39;: 0}, &#39;INCREMENT&#39;: {&#39;success&#39;: 0, &#39;error&#39;: 0}, &#39;GET&#39;: {&#39;success&#39;: 0, &#39;error&#39;: 0}, &#39;PUT&#39;: {&#39;success&#39;: 0, &#39;error&#39;: 0}, &#39;GETLIST&#39;: {&#39;success&#39;: 1, &#39;error&#39;: 0}, &#39;APPEND&#39;: {&#39;success&#39;: 1, &#39;error&#39;: 0}, &#39;DELETE&#39;: {&#39;success&#39;: 0, &#39;error&#39;: 0}}

    Show Me The Code!

    我将会以块状摘要的形式来展示全部代码。 整个代码不过 180 行,读起来也不会花费很长时间。

    Set Up

    下面是我们服务器所需的一些样板代码:

    """NoSQL database written in Python"""
    
    # Standard library imports
    import socket
    
    HOST = &#39;localhost&#39;
    PORT = 50505
    SOCKET = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    STATS = {
        &#39;PUT&#39;: {&#39;success&#39;: 0, &#39;error&#39;: 0},
        &#39;GET&#39;: {&#39;success&#39;: 0, &#39;error&#39;: 0},
        &#39;GETLIST&#39;: {&#39;success&#39;: 0, &#39;error&#39;: 0},
        &#39;PUTLIST&#39;: {&#39;success&#39;: 0, &#39;error&#39;: 0},
        &#39;INCREMENT&#39;: {&#39;success&#39;: 0, &#39;error&#39;: 0},
        &#39;APPEND&#39;: {&#39;success&#39;: 0, &#39;error&#39;: 0},
        &#39;DELETE&#39;: {&#39;success&#39;: 0, &#39;error&#39;: 0},
        &#39;STATS&#39;: {&#39;success&#39;: 0, &#39;error&#39;: 0},
        }
    로그인 후 복사
    로그인 후 복사

    很容易看到, 上面的只是一个包的导入和一些数据的初始化。

    Set up(Cont’d)

    接下来我会跳过一些代码, 以便能够继续展示上面准备部分剩余的代码。 注意它涉及到了一些尚不存在的一些函数, 不过没关系, 我们会在后面涉及。 在完整版(将会呈现在最后)中, 所有内容都会被有序编排。 这里是剩余的安装代码:

    COMMAND_HANDERS = {
        &#39;PUT&#39;: handle_put,
        &#39;GET&#39;: handle_get,
        &#39;GETLIST&#39;: handle_getlist,
        &#39;PUTLIST&#39;: handle_putlist,
        &#39;INCREMENT&#39;: handle_increment,
        &#39;APPEND&#39;: handle_append,
        &#39;DELETE&#39;: handle_delete,
        &#39;STATS&#39;: handle_stats,
    }
    
    DATA = {}
    
    def main():
        """Main entry point for script"""
        SOCKET.bind(HOST, PORT)
        SOCKET.listen(1)
        while 1:
            connection, address = SOCKET.accept()
            print(&#39;New connection from [{}]&#39;.format(address))
            data = connection.recv(4096).decode()
            command, key, value = parse_message(data)
            if command == &#39;STATS&#39;:
                response = handle_stats()
            elif command in (&#39;GET&#39;, &#39;GETLIST&#39;, &#39;INCREMENT&#39;, &#39;DELETE&#39;):
                response = COMMAND_HANDERS[command](key)
            elif command in (
                    &#39;PUT&#39;,
                    &#39;PUTLIST&#39;,
                    &#39;APPEND&#39;, ):
                response = COMMAND_HANDERS[command](key, value)
            else:
                response = (False, &#39;Unknown command type {}&#39;.format(command))
            update_stats(command, response[0])
            connection.sandall(&#39;{};{}&#39;.format(response[0], response[1]))
            connection.close()
    
    if __name__ == &#39;__main__&#39;:
        main()
    로그인 후 복사
    로그인 후 복사

    我们创建了 COMMAND_HANDLERS, 它常被称为是一个 查找表 (look-up table) . COMMAND_HANDLERS 的工作是将命令与用于处理该命令的函数进行关联起来。 比如说, 如果我们收到一个 GET 命令, COMMAND_HANDLERS[command](key) 就等同于说 handle_get(key) . 记住,在 Python 中, 函数可以被认为是一个值,并且可以像其他任何值一样被存储在一个 dict中。

    在上面的代码中, 虽然有些命令请求的参数相同,但是我仍决定分开处理每个命令。 尽管可以简单粗暴地强制所有的 handle_ 函数接受一个 key 和一个 value , 但是我希望这些处理函数条理能够更加有条理, 更加容易测试,同时减少出现错误的可能性。

    注意 socket 相关的代码已是十分极简。 虽然整个服务器基于 TCP/IP 通信, 但是并没有太多底层的网络交互代码。

    最后还须需要注意的一小点: DATA 字典, 因为这个点并不十分重要, 因而你很可能会遗漏它。 DATA 就是实际用来存储的 key-value pair, 正是它们实际构成了我们的数据库。

    Command Parser

    下面来看一些 命令解析器 (command parser) , 它负责解释接收到的消息:

    def parse_message(data):
        """Return a tuple containing the command, the key, and (optionally) the
        value cast to the appropriate type."""
        command, key, value, value_type = data.strip().split(&#39;;&#39;)
        if value_type:
            if value_type == &#39;LIST&#39;:
                value = value.split(&#39;,&#39;)
            elif value_type == &#39;INT&#39;:
                value = int(value)
            else:
                value = str(value)
        else:
            value = None
        return command, key, value
    로그인 후 복사

    这里我们可以看到发生了类型转换 (type conversion). 如果希望值是一个 list, 我们可以通过对 string 调用 str.split(&#39;,&#39;) 来得到我们想要的值。 对于 int, 我们可以简单地使用参数为 string 的 int() 即可。 对于字符串与 str() 也是同样的道理。

    Command Handlers

    下面是命令处理器 (command handler) 的代码. 它们都十分直观,易于理解。 注意到虽然有很多的错误检查, 但是也并不是面面俱到, 十分庞杂。 在你阅读的过程中,如果发现有任何错误请移步 这里 进行讨论.

    def update_stats(command, success):
        """Update the STATS dict with info about if executing *command* was a
        *success*"""
        if success:
            STATS[command][&#39;success&#39;] += 1
        else:
            STATS[command][&#39;error&#39;] += 1
    
    def handle_put(key, value):
        """Return a tuple containing True and the message to send back to the
        client."""
        DATA[key] = value
        return (True, &#39;key [{}] set to [{}]&#39;.format(key, value))
    
    def handle_get(key):
        """Return a tuple containing True if the key exists and the message to send
        back to the client"""
        if key not in DATA:
            return (False, &#39;Error: Key [{}] not found&#39;.format(key))
        else:
            return (True, DATA[key])
    
    def handle_putlist(key, value):
        """Return a tuple containing True if the command succeeded and the message
        to send back to the client."""
        return handle_put(key, value)
    
    def handle_putlist(key, value):
        """Return a tuple containing True if the command succeeded and the message
        to send back to the client"""
        return handle_put(key, value)
    
    def handle_getlist(key):
        """Return a tuple containing True if the key contained a list and the
        message to send back to the client."""
        return_value = exists, value = handle_get(key)
        if not exists:
            return return_value
        elif not isinstance(value, list):
            return (False, &#39;ERROR: Key [{}] contains non-list value ([{}])&#39;.format(
                key, value))
        else:
            return return_value
    
    def handle_increment(key):
        """Return a tuple containing True if the key&#39;s value could be incremented
        and the message to send back to the client."""
        return_value = exists, value = handle_get(key)
        if not exists:
            return return_value
        elif not isinstance(list_value, list):
            return (False, &#39;ERROR: Key [{}] contains non-list value ([{}])&#39;.format(
                key, value))
        else:
            DATA[key].append(value)
            return (True, &#39;Key [{}] had value [{}] appended&#39;.format(key, value))
    
    def handle_delete(key):
        """Return a tuple containing True if the key could be deleted and the
        message to send back to the client."""
        if key not in DATA:
            return (
                False,
                &#39;ERROR: Key [{}] not found and could not be deleted.&#39;.format(key))
        else:
            del DATA[key]
    
    def handle_stats():
        """Return a tuple containing True and the contents of the STATS dict."""
        return (True, str(STATS))
    로그인 후 복사

    有两点需要注意: 多重赋值 (multiple assignment) 和代码重用. 有些函数仅仅是为了更加有逻辑性而对已有函数的简单包装而已, 比如 handle_get 和 handle_getlist . 由于我们有时仅仅是需要一个已有函数的返回值,而其他时候却需要检查该函数到底返回了什么内容, 这时候就会使用 多重赋值 。

    handle_append를 살펴보겠습니다. handle_get을 호출하려고 시도했지만 키가 존재하지 않으면 간단히 handle_get에서 반환된 콘텐츠를 반환합니다. >. 또한 handle_get이 반환한 튜플을 별도의 반환 값으로 참조할 수 있기를 원합니다. 그런 다음 키가 존재하지 않으면 간단히 return return_value를 사용할 수 있습니다.handle_append . 如果我们尝试调用 handle_get 但是 key 并不存在时, 那么我们简单地返回 handle_get 所返回的内容。 此外, 我们还希望能够将 handle_get 返回的 tuple 作为一个单独的返回值进行引用。 那么当 key 不存在的时候, 我们就可以简单地使用 return return_value .

    如果它 确实存在 , 那么我们需要检查该返回值。并且, 我们也希望能够将 handle_get 的返回值作为单独的变量进行引用。 为了能够处理上述两种情况,同时考虑需要分开处理结果的情形,我们使用了多重赋值。 如此一来, 就不必书写多行代码, 同时能够保持代码清晰。 return_value = exists, list_value = handle_get(key) 能够显式地表明我们将要以至少两种不同的方式引用 handle_get 的返回值。

    How Is This a Database?

    上面的程序显然并非一个 RDBMS, 但却绝对称得上是一个 NoSQL 数据库。它如此易于创建的原因是我们并没有任何与 数据 (data) 的实际交互。 我们只是做了极简的类型检查,存储用户所发送的任何内容。 如果需要存储更加结构化的数据, 我们可能需要针对数据库创建一个 schema 用于存储和检索数据。

    既然 NoSQL 数据库更容易写, 更容易维护,更容易实现, 那么我们为什么不是只使用 mongoDB 就好了? 当然是有原因的, 还是那句话,有得必有失, 我们需要在 NoSQL 数据库所提供的数据灵活性 (data flexibility) 基础上权衡数据库的可搜索性 (searchability).

    Querying Data

    假如我们上面的 NoSQL 数据库来存储早前的 Car 数据。 那么我们可能会使用 VIN 作为 key, 使用一个列表作为每列的值, 也就是说, 2134AFGER245267 = ['Lexus', 'RX350', 2013, Black] . 当然了, 我们已经丢掉了列表中每个索引的 涵义 (meaning) . 我们只需要知道在某个地方索引 1 存储了汽车的 Model , 索引 2 存储了 Year.

    糟糕的事情来了, 当我们想要执行先前的查询语句时会发生什么? 找到 1994 年所有车的颜色将会变得噩梦一般。 我们必须遍历 DATA키가 존재한다면

    , 반환 값을 확인해야 합니다. 또한 handle_get의 반환 값을 별도의 변수로 참조할 수 있기를 바랍니다. 위의 두 가지 상황을 처리하고 결과를 별도로 처리해야 하는 상황을 고려하기 위해 다중 할당을 사용합니다. 이렇게 하면 코드를 명확하게 유지하면서 여러 줄의 코드를 작성할 필요가 없습니다. return_value = 존재, list_value = handler_get(key)는 최소한 두 가지 다른 방법으로 handle_get의 반환 값을 참조할 것임을 명시적으로 나타낼 수 있습니다.

    이것이 어떻게 데이터베이스인가요?

    위 프로그램은 분명히 RDBMS는 아니지만 확실히 NoSQL 데이터베이스라고 부를 수 있습니다. 만들기가 너무 쉬운 이유는

    data

    와 실제 상호 작용이 없기 때문입니다. 우리는 최소한의 유형 검사를 수행하고 사용자가 보내는 모든 것을 저장합니다. 더 많은 구조화된 데이터를 저장해야 하는 경우 데이터를 저장하고 검색하기 위해 데이터베이스에 대한 스키마를 생성해야 할 수도 있습니다.

    NoSQL 데이터베이스는 작성하기 쉽고, 유지 관리하기 쉽고, 구현하기도 쉽기 때문에 그냥 mongoDB를 사용하는 것이 어떨까요? 물론 이유가 있습니다. NoSQL 데이터베이스가 제공하는 데이터 유연성을 기반으로 데이터베이스의 검색 가능성을 평가해야 합니다.

    데이터 쿼리

    가정해 보세요. 위의 NoSQL 데이터베이스를 사용하여 이전 자동차 데이터를 저장합니다. 그런 다음 VIN을 키로 사용하고 목록을 각 열의 값으로 사용할 수 있습니다. 즉, 2134AFGER245267 = ['Lexus', 'RX350', 2013, Black] 입니다. 목록에 있는 각 인덱스의
    의미
    가 손실되었습니다. 인덱스 1이 자동차 모델을 저장하고 인덱스 2가 연도를 저장한다는 것만 알면 됩니다.
    실행하려고 할 때 나쁜 점은 발생합니다. 이전 쿼리 문이 사용됩니까? 모든 1994년 자동차의 색상을 찾는 것은 악몽이 될 것입니다. DATA
    각 값을 탐색하여 이 값이 자동차 데이터를 저장하는지 아니면 단순히 다른 관련 없는 데이터를 저장하는지 확인해야 합니다. 예를 들어, 인덱스 2의 값이 1994와 같은지 확인하고, 그런 다음 계속해서 인덱스 3의 값을 가져옵니다. 이는 모든 데이터 행을 스캔할 뿐만 아니라 쿼리에 응답하기 위해 몇 가지 복잡한 규칙을 적용해야 하기 때문에 테이블 스캔보다 더 나쁩니다. 🎜NoSQL 데이터베이스 작성자는 쿼리가 매우 유용한 기능이라는 점을 고려하여 이러한 문제를 확실히 인식하고 있으며 쿼리가 "접근할 수 없는" 문제를 덜 해결하는 몇 가지 방법도 알아냈습니다. 한 가지 접근 방식은 JSON과 같이 사용되는 데이터를 구조화하여 다른 행에 대한 참조가 관계를 나타낼 수 있도록 하는 것입니다. 동시에 대부분의 NoSQL 데이터베이스에는 네임스페이스라는 개념이 있습니다. 단일 유형의 데이터는 데이터베이스의 해당 유형에 고유한 "섹션"에 저장될 수 있으며, 이를 통해 쿼리 엔진은 데이터의 "모양"을 활용할 수 있습니다. 쿼리할 데이터 정보입니다. 🎜🎜물론, 쿼리 가능성을 높이기 위해 좀 더 정교한 방법이 존재하고 구현되었지만, 더 적은 양의 스키마를 저장하는 것과 쿼리 가능성을 향상시키는 것 사이의 절충은 항상 피할 수 없는 문제입니다. 이 예에서 데이터베이스는 키별 쿼리만 지원합니다. 더 풍부한 쿼리를 지원해야 한다면 상황은 훨씬 더 복잡해집니다. 🎜🎜요약🎜🎜이 시점에서 “NoSQL”의 개념이 아주 명확해졌으면 좋겠습니다. 우리는 SQL에 대해 조금 배웠고 RDBMS가 어떻게 작동하는지 보았습니다. 우리는 RDBMS에서 데이터를 검색하는 방법(SQL 🎜query🎜 사용)을 살펴보았습니다. 장난감 수준의 NoSQL 데이터베이스를 구축함으로써 쿼리 가능성과 단순성 사이에 직면한 몇 가지 문제에 대해 알아보고 일부 데이터베이스 작성자가 이를 처리하기 위해 사용하는 몇 가지 방법에 대해 논의했습니다. 이러한 문제로. 🎜🎜🎜🎜🎜🎜🎜🎜

    위 내용은 Python을 사용하여 NoSQL 데이터베이스 샘플 코드 공유를 완료하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    관련 라벨:
    원천:php.cn
    본 웹사이트의 성명
    본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
    인기 튜토리얼
    더>
    최신 다운로드
    더>
    웹 효과
    웹사이트 소스 코드
    웹사이트 자료
    프론트엔드 템플릿
    회사 소개 부인 성명 Sitemap
    PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!