├── .idea
├── .gitignore
├── modules.xml
└── vcs.xml
├── README.md
├── SDCS_ Simple Distributed Cache System.pdf
├── sdcs_grpc
├── .idea
│ ├── .gitignore
│ ├── inspectionProfiles
│ │ └── profiles_settings.xml
│ ├── misc.xml
│ ├── modules.xml
│ ├── sdcs_grpc.iml
│ └── vcs.xml
├── README.md
├── app.py
├── grpc_request.paw
├── rpc_example
│ ├── example.proto
│ ├── example_pb2.py
│ ├── example_pb2_grpc.py
│ ├── node_client.py
│ └── node_server.py
├── rpc_hello
│ ├── hello.proto
│ ├── hello_client.py
│ ├── hello_pb2.py
│ ├── hello_pb2_grpc.py
│ └── hello_server.py
├── sdcs-testsuit
│ ├── README.md
│ └── sdcs-test.sh
├── sdcs_final
│ ├── README.md
│ ├── docker-compose.yaml
│ └── sdcs
│ │ ├── Dockerfile
│ │ ├── cache_node
│ │ ├── cache_node.py
│ │ ├── sdcs.proto
│ │ ├── sdcs_pb2.py
│ │ └── sdcs_pb2_grpc.py
│ │ ├── requirements.txt
│ │ └── sources.list
├── sdcs_local_test
│ ├── cache_node_local.py
│ ├── sdcs.proto
│ ├── sdcs_pb2.py
│ └── sdcs_pb2_grpc.py
└── simple_dcs
│ ├── README.md
│ ├── docker-compose.yaml
│ └── sdcs
│ ├── Dockerfile
│ ├── cache_node
│ ├── cache_node.py
│ ├── sdcs.proto
│ ├── sdcs_pb2.py
│ └── sdcs_pb2_grpc.py
│ ├── requirements.txt
│ └── sources.list
└── sdcs_http
├── .idea
├── .gitignore
├── .name
├── SDCS.iml
├── inspectionProfiles
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
├── sdcs_http.iml
└── vcs.xml
├── app.py
├── sdcs
├── dir
│ ├── Dockerfile
│ ├── app.py
│ └── requirements.txt
└── docker-compose.yaml
└── test-shell
├── sdcs-testsuit
├── README.md
└── sdcs-test.sh
├── test.sh
└── test2.sh
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Simple Distributed Cache System 简易分布式缓存系统
2 |
3 | 2023.12
4 |
5 | UESTC 分布式系统课程项目
6 |
7 |
8 |
9 | ### 项目说明
10 |
11 | 1. 系统功能要求详见`SDCS_ Simple Distributed Cache System.pdf`。
12 | 2. `sdcs_http`目录中为基于HTTP服务的简易分布式缓存系统。该系统能够实现项目要求的基本功能,通过了老师提供的第一版测试脚本的验证。但由于随后老师进一步提出要求希望我们了解、练习远程过程调用方法的使用,故该方案被淘汰,且未进行最终版的测试脚本验证。
13 | 3. `sdcs_grpc`目录中为基于GRPC服务的简易分布式缓存系统。该系统通过了终期测试脚本的验证,最终提交作业使用此方案的工程代码。详细说明见该工程目录下的`README.md`文件。
14 |
15 | - [老师提供的测试脚本代码](https://github.com/ruini-classes/sdcs-testsuit)
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/SDCS_ Simple Distributed Cache System.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Skyink/simple-distributed-cache-system/cc74ae98bd00138a92d1d8145a468855306193b7/SDCS_ Simple Distributed Cache System.pdf
--------------------------------------------------------------------------------
/sdcs_grpc/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/sdcs_grpc/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/sdcs_grpc/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/sdcs_grpc/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/sdcs_grpc/.idea/sdcs_grpc.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
--------------------------------------------------------------------------------
/sdcs_grpc/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/sdcs_grpc/README.md:
--------------------------------------------------------------------------------
1 | ## SDCS based on grpc
2 |
3 | 基于grpc的简易分布式缓存系统
4 |
5 | ### 文件说明
6 |
7 | 1. `rpc_example`和`rpc_hello`目录下的代码为测试grpc原理使用的demo程序。
8 | 2. `sdcs_local_test`目录下的代码为不使用docker的本地测试项目程序。(在mac上运行)
9 | 3. `simple_dcs`目录下的代码为使用docker的最终项目程序。(在M1pro芯片的mac上运行,使用的是ARM架构相关的指令和依赖包)
10 | 4. `sdcs_final`目录下的代码为提交作业的最终项目程序。(老师验证代码使用x86架构的计算机,故调整为相关的指令和依赖包)
11 | 5. `sdcs-testsuit`目录下的代码为老师提供的验证项目可行性的脚本文件。
12 | 6. 当前目录下的`app.py`文件,仅用于测试web服务相关代码。
13 | 7. `grpc_request.paw`文件为接口调试工具Paw的测试文件。
14 |
--------------------------------------------------------------------------------
/sdcs_grpc/app.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | from flask import Flask, request
4 |
5 | app = Flask(__name__)
6 |
7 | cache = dict()
8 | update_cnt = 0
9 | total_cnt = 0
10 |
11 | # kv_string = "{\"key-16\": \"value 16\"}"
12 | # kv_map = json.loads(kv_string)
13 | # for key, value in kv_map.items():
14 | # is_exist = cache.get(key)
15 | # if is_exist is None:
16 | # # 校验本地无重复key值,则存储键值对
17 | # cache.update({key: value})
18 | # update_cnt += 1
19 | # total_cnt += 1
20 | # print("update!")
21 | # else:
22 | # print("fail")
23 | # print(f"update cnt = {update_cnt}, total cnt = {total_cnt}")
24 |
25 |
26 | kv_to_update_list = []
27 | for server_i in range(0, 3):
28 | kv_list = {}
29 | kv_to_update_list.append(kv_list)
30 |
31 | kv_to_update_list[0].update({"key1": "value1"})
32 | kv_to_update_list[0].update({"key4": "value4"})
33 | kv_to_update_list[1].update({"key2": "value2"})
34 | kv_to_update_list[2].update({"key3": "value3"})
35 |
36 | print(kv_to_update_list[0])
37 | print(kv_to_update_list[1])
38 | print(kv_to_update_list[2])
39 |
40 |
41 |
42 |
43 | @app.route('/hello')
44 | def hello_world(): # put application's code here
45 | return 'Hello World!'
46 |
47 |
48 | @app.route("/", methods=['POST'])
49 | def print_request():
50 | # 解析请求参数
51 | request_data = request.json
52 | data = request.data
53 | print("request param: {}".format(request_data))
54 | print(data)
55 | return request_data
56 |
57 |
58 | if __name__ == '__main__':
59 | app.run()
60 |
--------------------------------------------------------------------------------
/sdcs_grpc/grpc_request.paw:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Skyink/simple-distributed-cache-system/cc74ae98bd00138a92d1d8145a468855306193b7/sdcs_grpc/grpc_request.paw
--------------------------------------------------------------------------------
/sdcs_grpc/rpc_example/example.proto:
--------------------------------------------------------------------------------
1 |
2 | syntax = "proto3";
3 |
4 | package helloworld;
5 |
6 | service CacheNode {
7 | // 添加kv
8 | rpc UpdateKeyValue (UpdateKeyValueRequest) returns (UpdateKeyValueResponse) {}
9 | // 查询kv
10 | rpc SearchKeyValue (SearchKeyValueRequest) returns (SearchKeyValueResponse) {}
11 | }
12 |
13 | // 更新请求
14 | message UpdateKeyValueRequest {
15 | map kv_map = 1; // 键值对集合,key-string,value-jsonString
16 | }
17 |
18 | // 更新响应
19 | message UpdateKeyValueResponse {
20 | uint32 cnt = 1; // 返回更新个数
21 | }
22 |
23 | message SearchKeyValueRequest {
24 | string key = 1;
25 | }
26 |
27 | message SearchKeyValueResponse {
28 | string value = 1; // jsonString
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/sdcs_grpc/rpc_example/example_pb2.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by the protocol buffer compiler. DO NOT EDIT!
3 | # source: example.proto
4 | """Generated protocol buffer code."""
5 | from google.protobuf import descriptor as _descriptor
6 | from google.protobuf import descriptor_pool as _descriptor_pool
7 | from google.protobuf import symbol_database as _symbol_database
8 | from google.protobuf.internal import builder as _builder
9 | # @@protoc_insertion_point(imports)
10 |
11 | _sym_db = _symbol_database.Default()
12 |
13 |
14 |
15 |
16 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rexample.proto\x12\nhelloworld\"\x83\x01\n\x15UpdateKeyValueRequest\x12<\n\x06kv_map\x18\x01 \x03(\x0b\x32,.helloworld.UpdateKeyValueRequest.KvMapEntry\x1a,\n\nKvMapEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"%\n\x16UpdateKeyValueResponse\x12\x0b\n\x03\x63nt\x18\x01 \x01(\r\"$\n\x15SearchKeyValueRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\"\'\n\x16SearchKeyValueResponse\x12\r\n\x05value\x18\x01 \x01(\t2\xc1\x01\n\tCacheNode\x12Y\n\x0eUpdateKeyValue\x12!.helloworld.UpdateKeyValueRequest\x1a\".helloworld.UpdateKeyValueResponse\"\x00\x12Y\n\x0eSearchKeyValue\x12!.helloworld.SearchKeyValueRequest\x1a\".helloworld.SearchKeyValueResponse\"\x00\x62\x06proto3')
17 |
18 | _globals = globals()
19 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
20 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'example_pb2', _globals)
21 | if _descriptor._USE_C_DESCRIPTORS == False:
22 | DESCRIPTOR._options = None
23 | _UPDATEKEYVALUEREQUEST_KVMAPENTRY._options = None
24 | _UPDATEKEYVALUEREQUEST_KVMAPENTRY._serialized_options = b'8\001'
25 | _globals['_UPDATEKEYVALUEREQUEST']._serialized_start=30
26 | _globals['_UPDATEKEYVALUEREQUEST']._serialized_end=161
27 | _globals['_UPDATEKEYVALUEREQUEST_KVMAPENTRY']._serialized_start=117
28 | _globals['_UPDATEKEYVALUEREQUEST_KVMAPENTRY']._serialized_end=161
29 | _globals['_UPDATEKEYVALUERESPONSE']._serialized_start=163
30 | _globals['_UPDATEKEYVALUERESPONSE']._serialized_end=200
31 | _globals['_SEARCHKEYVALUEREQUEST']._serialized_start=202
32 | _globals['_SEARCHKEYVALUEREQUEST']._serialized_end=238
33 | _globals['_SEARCHKEYVALUERESPONSE']._serialized_start=240
34 | _globals['_SEARCHKEYVALUERESPONSE']._serialized_end=279
35 | _globals['_CACHENODE']._serialized_start=282
36 | _globals['_CACHENODE']._serialized_end=475
37 | # @@protoc_insertion_point(module_scope)
38 |
--------------------------------------------------------------------------------
/sdcs_grpc/rpc_example/example_pb2_grpc.py:
--------------------------------------------------------------------------------
1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
2 | """Client and server classes corresponding to protobuf-defined services."""
3 | import grpc
4 |
5 | import example_pb2 as example__pb2
6 |
7 |
8 | class CacheNodeStub(object):
9 | """Missing associated documentation comment in .proto file."""
10 |
11 | def __init__(self, channel):
12 | """Constructor.
13 |
14 | Args:
15 | channel: A grpc.Channel.
16 | """
17 | self.UpdateKeyValue = channel.unary_unary(
18 | '/helloworld.CacheNode/UpdateKeyValue',
19 | request_serializer=example__pb2.UpdateKeyValueRequest.SerializeToString,
20 | response_deserializer=example__pb2.UpdateKeyValueResponse.FromString,
21 | )
22 | self.SearchKeyValue = channel.unary_unary(
23 | '/helloworld.CacheNode/SearchKeyValue',
24 | request_serializer=example__pb2.SearchKeyValueRequest.SerializeToString,
25 | response_deserializer=example__pb2.SearchKeyValueResponse.FromString,
26 | )
27 |
28 |
29 | class CacheNodeServicer(object):
30 | """Missing associated documentation comment in .proto file."""
31 |
32 | def UpdateKeyValue(self, request, context):
33 | """添加kv
34 | """
35 | context.set_code(grpc.StatusCode.UNIMPLEMENTED)
36 | context.set_details('Method not implemented!')
37 | raise NotImplementedError('Method not implemented!')
38 |
39 | def SearchKeyValue(self, request, context):
40 | """查询kv
41 | """
42 | context.set_code(grpc.StatusCode.UNIMPLEMENTED)
43 | context.set_details('Method not implemented!')
44 | raise NotImplementedError('Method not implemented!')
45 |
46 |
47 | def add_CacheNodeServicer_to_server(servicer, server):
48 | rpc_method_handlers = {
49 | 'UpdateKeyValue': grpc.unary_unary_rpc_method_handler(
50 | servicer.UpdateKeyValue,
51 | request_deserializer=example__pb2.UpdateKeyValueRequest.FromString,
52 | response_serializer=example__pb2.UpdateKeyValueResponse.SerializeToString,
53 | ),
54 | 'SearchKeyValue': grpc.unary_unary_rpc_method_handler(
55 | servicer.SearchKeyValue,
56 | request_deserializer=example__pb2.SearchKeyValueRequest.FromString,
57 | response_serializer=example__pb2.SearchKeyValueResponse.SerializeToString,
58 | ),
59 | }
60 | generic_handler = grpc.method_handlers_generic_handler(
61 | 'helloworld.CacheNode', rpc_method_handlers)
62 | server.add_generic_rpc_handlers((generic_handler,))
63 |
64 |
65 | # This class is part of an EXPERIMENTAL API.
66 | class CacheNode(object):
67 | """Missing associated documentation comment in .proto file."""
68 |
69 | @staticmethod
70 | def UpdateKeyValue(request,
71 | target,
72 | options=(),
73 | channel_credentials=None,
74 | call_credentials=None,
75 | insecure=False,
76 | compression=None,
77 | wait_for_ready=None,
78 | timeout=None,
79 | metadata=None):
80 | return grpc.experimental.unary_unary(request, target, '/helloworld.CacheNode/UpdateKeyValue',
81 | example__pb2.UpdateKeyValueRequest.SerializeToString,
82 | example__pb2.UpdateKeyValueResponse.FromString,
83 | options, channel_credentials,
84 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
85 |
86 | @staticmethod
87 | def SearchKeyValue(request,
88 | target,
89 | options=(),
90 | channel_credentials=None,
91 | call_credentials=None,
92 | insecure=False,
93 | compression=None,
94 | wait_for_ready=None,
95 | timeout=None,
96 | metadata=None):
97 | return grpc.experimental.unary_unary(request, target, '/helloworld.CacheNode/SearchKeyValue',
98 | example__pb2.SearchKeyValueRequest.SerializeToString,
99 | example__pb2.SearchKeyValueResponse.FromString,
100 | options, channel_credentials,
101 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
102 |
--------------------------------------------------------------------------------
/sdcs_grpc/rpc_example/node_client.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import flask
4 | import grpc
5 | import example_pb2, example_pb2_grpc
6 | import threading
7 | from flask import request
8 | from concurrent import futures
9 |
10 | app = flask.Flask(__name__)
11 |
12 | cache = {}
13 | total_cnt = 0
14 |
15 |
16 | class Node(example_pb2_grpc.CacheNodeServicer):
17 | def UpdateKeyValue(self, request, context):
18 | global total_cnt
19 | update_cnt = 0
20 | kv_map = request.kv_map
21 |
22 | for key, value in kv_map.items():
23 | is_exist = cache[key]
24 | if is_exist is None:
25 | cache.update({key: value})
26 | update_cnt += 1
27 | total_cnt += 1
28 |
29 | return example_pb2.UpdateKeyValueResponse(cnt=update_cnt)
30 |
31 |
32 | # grpc客户端
33 | def grpc_update_client(map={}):
34 | conn = grpc.insecure_channel("node1:8001")
35 | client = example_pb2_grpc.CacheNodeStub(channel=conn)
36 | request = example_pb2.UpdateKeyValueRequest(kv_map=map)
37 | rsp = client.UpdateKeyValue(request)
38 | cntStr = str(rsp.cnt)
39 | print(cntStr)
40 | return cntStr
41 |
42 |
43 | @app.route("/test", methods=["POST"])
44 | def update_key_value():
45 | k1 = "k1"
46 | v1 = "v1"
47 | k2 = "k2"
48 | v2 = "v2"
49 | kv_map = {k1: v1}
50 | kv_map.update({k2: v2})
51 | return grpc_update_client(kv_map)
52 |
53 |
54 | @app.route("/get", methods=["POST"])
55 | def search_local():
56 | print(cache)
57 | reply = json.dumps(cache)
58 | return reply
59 |
60 |
61 | if __name__ == '__main__':
62 | app.run('0.0.0.0', port=5002)
63 |
--------------------------------------------------------------------------------
/sdcs_grpc/rpc_example/node_server.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import flask
4 | import grpc
5 | import example_pb2, example_pb2_grpc
6 | import threading
7 | from concurrent import futures
8 | import time
9 | import os
10 |
11 | _ONE_DAY_IN_SECONDS = 60 * 60 * 24
12 |
13 | app = flask.Flask(__name__)
14 |
15 | server_url = ["http://node1", "http://node2", "http://node3"]
16 | server_cnt = 3
17 | server_index = os.environ.get('SERVER_INDEX', 0)
18 | print("Server " + str(server_index) + " is starting..")
19 |
20 | cache = {}
21 | total_cnt = 0
22 |
23 |
24 | class Node(example_pb2_grpc.CacheNodeServicer):
25 | def UpdateKeyValue(self, request, context):
26 | global total_cnt
27 | update_cnt = 0
28 | kv_map = request.kv_map
29 |
30 | for key, value in kv_map.items():
31 | is_exist = cache.get(key)
32 | if is_exist is None:
33 | cache.update({key: value})
34 | update_cnt += 1
35 | total_cnt += 1
36 |
37 | print("Successfully updated k-v cnt = " + str(update_cnt))
38 | return example_pb2.UpdateKeyValueResponse(cnt=update_cnt)
39 |
40 |
41 | # grpc服务器
42 | def run_grpc_server():
43 | grpc_server = grpc.server(futures.ThreadPoolExecutor(max_workers=4))
44 | example_pb2_grpc.add_CacheNodeServicer_to_server(Node(), grpc_server)
45 | grpc_server.add_insecure_port("127.0.0.1:8001")
46 | grpc_server.start()
47 | print("server start...")
48 |
49 | try:
50 | while True:
51 | print("server is running..")
52 | time.sleep(_ONE_DAY_IN_SECONDS)
53 | except KeyboardInterrupt:
54 | grpc_server.stop(0)
55 |
56 |
57 | # grpc客户端
58 | def grpc_update_client(map={}):
59 | conn = grpc.insecure_channel("127.0.0.1:8001")
60 | client = example_pb2_grpc.CacheNodeStub(channel=conn)
61 | request = example_pb2.UpdateKeyValueRequest(kv_map=map)
62 | rsp = client.UpdateKeyValue(request)
63 | print(rsp.result)
64 | return rsp.result
65 |
66 |
67 | # 启动grpc服务器
68 | grpc_thread = threading.Thread(target=run_grpc_server)
69 | grpc_thread.start()
70 |
71 |
72 | @app.route("/test", methods=["POST"])
73 | def update_key_value():
74 | k1 = "k1"
75 | v1 = "v1"
76 | k2 = "k2"
77 | v2 = "v2"
78 | kv_map = {k1: v1}
79 | kv_map.update({k2: v2})
80 | return grpc_update_client(kv_map)
81 |
82 |
83 | @app.route("/get", methods=["POST"])
84 | def search_local():
85 | print(cache)
86 | reply = json.dumps(cache)
87 | return reply
88 |
89 |
90 | if __name__ == '__main__':
91 | app.run('0.0.0.0', port=5001)
92 |
--------------------------------------------------------------------------------
/sdcs_grpc/rpc_hello/hello.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | service MsgService {
4 | rpc GetMsg (MsgRequest) returns (MsgResponse){}
5 | }
6 |
7 | message MsgRequest {
8 | string name = 1;
9 | }
10 |
11 | message MsgResponse {
12 | string msg = 1;
13 | }
--------------------------------------------------------------------------------
/sdcs_grpc/rpc_hello/hello_client.py:
--------------------------------------------------------------------------------
1 | import grpc
2 |
3 | import hello_pb2
4 | import hello_pb2_grpc
5 |
6 |
7 | def run():
8 | # NOTE(gRPC Python Team): .close() is possible on a channel and should be
9 | # used in circumstances in which the with statement does not fit the needs
10 | # of the code.
11 | with grpc.insecure_channel('localhost:50051') as channel:
12 | stub = hello_pb2_grpc.MsgServiceStub(channel)
13 | response = stub.GetMsg(hello_pb2.MsgRequest(name='world'))
14 | print("Client received: " + response.msg)
15 |
16 |
17 | if __name__ == '__main__':
18 | run()
--------------------------------------------------------------------------------
/sdcs_grpc/rpc_hello/hello_pb2.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by the protocol buffer compiler. DO NOT EDIT!
3 | # source: hello.proto
4 | """Generated protocol buffer code."""
5 | from google.protobuf import descriptor as _descriptor
6 | from google.protobuf import descriptor_pool as _descriptor_pool
7 | from google.protobuf import symbol_database as _symbol_database
8 | from google.protobuf.internal import builder as _builder
9 | # @@protoc_insertion_point(imports)
10 |
11 | _sym_db = _symbol_database.Default()
12 |
13 |
14 |
15 |
16 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0bhello.proto\"\x1a\n\nMsgRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x1a\n\x0bMsgResponse\x12\x0b\n\x03msg\x18\x01 \x01(\t23\n\nMsgService\x12%\n\x06GetMsg\x12\x0b.MsgRequest\x1a\x0c.MsgResponse\"\x00\x62\x06proto3')
17 |
18 | _globals = globals()
19 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
20 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'hello_pb2', _globals)
21 | if _descriptor._USE_C_DESCRIPTORS == False:
22 | DESCRIPTOR._options = None
23 | _globals['_MSGREQUEST']._serialized_start=15
24 | _globals['_MSGREQUEST']._serialized_end=41
25 | _globals['_MSGRESPONSE']._serialized_start=43
26 | _globals['_MSGRESPONSE']._serialized_end=69
27 | _globals['_MSGSERVICE']._serialized_start=71
28 | _globals['_MSGSERVICE']._serialized_end=122
29 | # @@protoc_insertion_point(module_scope)
30 |
--------------------------------------------------------------------------------
/sdcs_grpc/rpc_hello/hello_pb2_grpc.py:
--------------------------------------------------------------------------------
1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
2 | """Client and server classes corresponding to protobuf-defined services."""
3 | import grpc
4 |
5 | import hello_pb2 as hello__pb2
6 |
7 |
8 | class MsgServiceStub(object):
9 | """Missing associated documentation comment in .proto file."""
10 |
11 | def __init__(self, channel):
12 | """Constructor.
13 |
14 | Args:
15 | channel: A grpc.Channel.
16 | """
17 | self.GetMsg = channel.unary_unary(
18 | '/MsgService/GetMsg',
19 | request_serializer=hello__pb2.MsgRequest.SerializeToString,
20 | response_deserializer=hello__pb2.MsgResponse.FromString,
21 | )
22 |
23 |
24 | class MsgServiceServicer(object):
25 | """Missing associated documentation comment in .proto file."""
26 |
27 | def GetMsg(self, request, context):
28 | """Missing associated documentation comment in .proto file."""
29 | context.set_code(grpc.StatusCode.UNIMPLEMENTED)
30 | context.set_details('Method not implemented!')
31 | raise NotImplementedError('Method not implemented!')
32 |
33 |
34 | def add_MsgServiceServicer_to_server(servicer, server):
35 | rpc_method_handlers = {
36 | 'GetMsg': grpc.unary_unary_rpc_method_handler(
37 | servicer.GetMsg,
38 | request_deserializer=hello__pb2.MsgRequest.FromString,
39 | response_serializer=hello__pb2.MsgResponse.SerializeToString,
40 | ),
41 | }
42 | generic_handler = grpc.method_handlers_generic_handler(
43 | 'MsgService', rpc_method_handlers)
44 | server.add_generic_rpc_handlers((generic_handler,))
45 |
46 |
47 | # This class is part of an EXPERIMENTAL API.
48 | class MsgService(object):
49 | """Missing associated documentation comment in .proto file."""
50 |
51 | @staticmethod
52 | def GetMsg(request,
53 | target,
54 | options=(),
55 | channel_credentials=None,
56 | call_credentials=None,
57 | insecure=False,
58 | compression=None,
59 | wait_for_ready=None,
60 | timeout=None,
61 | metadata=None):
62 | return grpc.experimental.unary_unary(request, target, '/MsgService/GetMsg',
63 | hello__pb2.MsgRequest.SerializeToString,
64 | hello__pb2.MsgResponse.FromString,
65 | options, channel_credentials,
66 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
67 |
--------------------------------------------------------------------------------
/sdcs_grpc/rpc_hello/hello_server.py:
--------------------------------------------------------------------------------
1 | import grpc
2 | import hello_pb2
3 | import hello_pb2_grpc
4 | from concurrent import futures
5 | import time
6 |
7 | _ONE_DAY_IN_SECONDS = 60 * 60 * 24
8 |
9 |
10 | class MsgServicer(hello_pb2_grpc.MsgServiceServicer):
11 | def GetMsg(self, request, context):
12 | print("Received name: %s" % request.name)
13 | return hello_pb2.MsgResponse(msg='Hello, %s!' % request.name)
14 |
15 |
16 | def serve():
17 | server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
18 | hello_pb2_grpc.add_MsgServiceServicer_to_server(MsgServicer(), server)
19 | server.add_insecure_port('0.0.0.0:50051')
20 | server.start()
21 | print("server start..")
22 | try:
23 | while True:
24 | time.sleep(_ONE_DAY_IN_SECONDS)
25 | print("server is running..")
26 | except KeyboardInterrupt:
27 | server.stop(0)
28 |
29 |
30 | if __name__ == '__main__':
31 | serve()
32 |
--------------------------------------------------------------------------------
/sdcs_grpc/sdcs-testsuit/README.md:
--------------------------------------------------------------------------------
1 |
8 | # sdcs-testsuit
9 | Simple test for SDCS
10 |
11 | ```sh
12 | ./sdcs-test.sh {cache_server_number}
13 | ```
14 |
15 | ## Todo
16 | - [ ] Real performance test (by jmeter?)
17 | - [ ] Better command line argument processing and help.
18 |
19 | ## Q&A
20 | 1. Mac用户运行脚本时提示`shuf: command not found`,是因为系统缺少相关指令工具。
21 | - 解决方案:通过`brew install coreutils`安装相应的工具。
22 | 1. Mac用户运行脚本时提示`syntax error in expression(xxx)`或`declare: -A: invalid option`的错误,是bash版本过低导致。本测试脚本需要bash4及以上可以正常运行。Mac出厂自带的bash版本为3.2。
23 | - 解决方案:
24 | 1. 使用homebrew安装新版本的bash。使用`brew install bash`更新bash。
25 | 2. 安装完成后使用`which -a bash`指令查看bash路径。可以看到两个地址:`/bin/bash`是Mac出厂自带的bash,另一个是新安装的bash。请使用自己安装的bash进行操作。
26 | ```sh
27 | /usr/local/bin/bash # 新安装的bash,可能是其他路径如 /opt/homebrew/bin/bash
28 | /bin/bash # Mac出厂自带的bash
29 | ```
30 | 3. 可以通过`/usr/local/bin/bash --version`确认新bash的版本。
31 | 4. 使用`/usr/local/bin/bash ./sdcs-test.sh {cache_server_number}`运行测试脚本。
32 |
33 |
34 |
--------------------------------------------------------------------------------
/sdcs_grpc/sdcs-testsuit/sdcs-test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [[ $# -ne 1 ]]; then
4 | echo "Usage:"
5 | echo "$0 {cache server number}"
6 | exit 1
7 | fi
8 |
9 | cs_num=$1
10 |
11 | # TODO: should also set upperbound next year.
12 | [[ $cs_num -le 2 ]] && {
13 | echo "Error: cache server should be more than 3 ($cs_num provided)"
14 | exit 2
15 | }
16 |
17 | which jq >/dev/null 2>&1 || {
18 | echo "Error: please install jq first."
19 | exit 3
20 | }
21 |
22 | PORT_BASE=9526
23 | HOST_BASE=127.0.0.1
24 | MAX_ITER=500
25 | DELETED_KEYS=()
26 | _DELETED_KEYS_GENERATED=0
27 |
28 | PASS_PROMPT="\e[1;32mPASS\e[0m"
29 | FAIL_PROMPT="\e[1;31mFAIL\e[0m"
30 |
31 | function get_cs() {
32 | port=$(($PORT_BASE + $(shuf -i 1-$cs_num -n 1)))
33 | echo http://$HOST_BASE:$port
34 | }
35 |
36 | function get_key() {
37 | echo "key-$(shuf -i 1-$MAX_ITER -n 1)"
38 | }
39 |
40 | function gen_deleted_keys() {
41 | [[ $_DELETED_KEYS_GENERATED == 1 ]] && return 0
42 |
43 | local count=$((MAX_ITER / 10 * 3))
44 |
45 | while [[ ${#DELETED_KEYS[@]} -lt $count ]]; do
46 | local key="key-$(shuf -i 1-$MAX_ITER -n 1)"
47 | if ! [[ " ${DELETED_KEYS[@]} " =~ " ${key} " ]]; then
48 | DELETED_KEYS+=("$key")
49 | fi
50 | done
51 |
52 | _DELETED_KEYS_GENERATED=1
53 | }
54 |
55 | function gen_json_with_idx() {
56 | local idx=$1
57 |
58 | jq -n --arg key "key-$idx" --arg value "value $idx" '{($key): ($value)}'
59 | }
60 |
61 | function gen_json_with_key() {
62 | local idx=$(echo $key | cut -d- -f2)
63 |
64 | gen_json_with_idx $idx
65 | }
66 |
67 | function compare_json_for_key() {
68 | local key=$1
69 | local result=$2
70 | local expect=$3
71 |
72 | local value1=$(echo "$result" | jq -r ".\"$key\"" 2>/dev/null)
73 | local value2=$(echo "$expect" | jq -r ".\"$key\"" 2>/dev/null)
74 |
75 | [[ "$value1" = "$value2" ]]
76 | }
77 |
78 | function query_key() {
79 | local key=$1
80 | local exist=$2
81 | local response=$(curl -s -w "\n%{http_code}" $(get_cs)/$key)
82 | # everything but the last line. `head -n -1` breaks in macos, let's turn to sed trick.
83 | local result=$(echo "$response" | sed '$d')
84 | local status_code=$(echo "$response" | tail -n 1)
85 |
86 | if [[ $exist == 1 ]]; then
87 | local expect=$(gen_json_with_key $key)
88 | if [[ $status_code -ne 200 ]] || ! compare_json_for_key "$key" "$result" "$expect"; then
89 | echo -e "Error:\tInvalid response"
90 | echo -e "\texpect: 200 $expect"
91 | echo -e "\tgot: $status_code $result"
92 | return 1
93 | fi
94 | else
95 | if [[ $status_code -ne 404 ]]; then
96 | echo "Error: expect status code 404 but got $status_code"
97 | return 1
98 | fi
99 | fi
100 | }
101 |
102 | function test_set() {
103 | local i=1
104 |
105 | while [[ $i -le $MAX_ITER ]]; do
106 | status_code=$(curl -s -o /dev/null -w "%{http_code}" -XPOST -H "Content-type: application/json" -d "$(gen_json_with_idx $i)" $(get_cs))
107 | if [[ $status_code -ne 200 ]]; then
108 | echo "Error: expect status code 200 but got $status_code"
109 | return 1
110 | fi
111 | ((i++))
112 | done
113 | }
114 |
115 | function test_get() {
116 | local count=$((MAX_ITER / 10 * 3))
117 | local i=0
118 |
119 | while [[ $i -lt $count ]]; do
120 | query_key $(get_key) 1 || return 1
121 | ((i++))
122 | done
123 | }
124 |
125 | function test_delete() {
126 | gen_deleted_keys
127 | for key in "${DELETED_KEYS[@]}"; do
128 | local response=$(curl -XDELETE -s -w "\n%{http_code}" $(get_cs)/$key)
129 | # `head -n 1` works for delete actually. let's use sed for consistency.
130 | local result=$(echo "$response" | sed '$d')
131 | local status_code=$(echo "$response" | tail -n 1)
132 | local expect=1
133 | if [[ $status_code -ne 200 ]] || [[ "$result" != "$expect" ]]; then
134 | echo -e "Error:\tInvalid response"
135 | echo -e "\texpect: $status_code $expect"
136 | echo -e "\tgot: $status_code $result"
137 | return 1
138 | fi
139 | done
140 | }
141 |
142 | # need to check all keys to guarantee only appointed keys are removed.
143 | function test_get_after_delete() {
144 | local key
145 | local exist
146 | local i=1
147 | while [[ $i -le $MAX_ITER ]]; do
148 | key=$(get_key)
149 | [[ " ${DELETED_KEYS[@]} " =~ " ${key} " ]] && exist=0 || exist=1
150 |
151 | query_key $key $exist || return 1
152 |
153 | ((i++))
154 | done
155 | }
156 |
157 | function test_delete_after_delete() {
158 | for key in "${DELETED_KEYS[@]}"; do
159 | local response=$(curl -XDELETE -s -w "\n%{http_code}" $(get_cs)/$key)
160 | local result=$(echo "$response" | sed '$d')
161 | local status_code=$(echo "$response" | tail -n 1)
162 | if [[ $status_code -ne 200 ]] || [[ "$result" != "0" ]]; then
163 | echo -e "Error:\tInvalid response"
164 | echo -e "\texpect: 200 0"
165 | echo -e "\tgot: $status_code $result"
166 | return 1
167 | fi
168 | done
169 | }
170 |
171 | function run_test() {
172 | local test_function=$1
173 | local test_name=$2
174 |
175 | # echo "starting $test_name..."
176 | if $test_function; then
177 | echo -e "$test_name ...... ${PASS_PROMPT}"
178 | return 0
179 | else
180 | echo -e "$test_name ...... ${FAIL_PROMPT}"
181 | return 1
182 | fi
183 | }
184 |
185 | declare -a test_order=(
186 | "test_set"
187 | "test_get"
188 | "test_set again"
189 | "test_delete"
190 | "test_get_after_delete"
191 | "test_delete_after_delete"
192 | )
193 |
194 | declare -A test_func=(
195 | ["test_set"]="test_set"
196 | ["test_get"]="test_get"
197 | ["test_set again"]="test_set"
198 | ["test_delete"]="test_delete"
199 | ["test_get_after_delete"]="test_get_after_delete"
200 | ["test_delete_after_delete"]="test_delete_after_delete"
201 | )
202 |
203 | pass_count=0
204 | fail_count=0
205 |
206 | # NOTE: macos date does not support `date +%s%N`. Let's use the weird $TIMEFORMAT.
207 | TIMEFORMAT="======================================
208 | Run ${#test_order[@]} tests in %R seconds."
209 |
210 | time {
211 | for testname in "${test_order[@]}"; do
212 | if run_test "${test_func[$testname]}" "$testname"; then
213 | ((pass_count++))
214 | else
215 | ((fail_count++))
216 | fi
217 | done
218 | }
219 |
220 | echo -e "\e[1;32m$pass_count\e[0m passed, \e[1;31m$fail_count\e[0m failed."
221 |
--------------------------------------------------------------------------------
/sdcs_grpc/sdcs_final/README.md:
--------------------------------------------------------------------------------
1 | ### 启动方法一:直接启动
2 | 直接在当前目录下运行指令,完成打包镜像+启动容器。
3 | ```shell
4 | docker-compose up
5 | ```
6 |
7 | ### 启动方法二:分步启动
8 | 1. 在当前目录下执行指令打包镜像。
9 | ```shell
10 | docker build -t cache_node ./sdcs
11 | ```
12 |
13 | 2. 需要修改`docker-compose.yaml`文件中的`build`参数,将每个节点中的`build`参数替换为`images`。
14 |
15 | ```shell
16 | services:
17 | node0:
18 | image: "cache_node"
19 | # build:
20 | # context: ./sdcs
21 | # dockerfile: Dockerfile
22 | ports:
23 | - "9527:5000"
24 | tty: true
25 | ```
26 |
27 | 3. 在当前目录下执行指令启动容器。
28 | ```shell
29 | docker-compose up
30 | ```
31 |
32 |
33 | ### 其他说明
34 | - 当前sources.list中使用的镜像源为x86架构的镜像地址,如需使用arm架构的镜像地址需要到文件中修改。
35 | - 分步打包镜像+启动容器时,需要修改docker-compose中的指令,指定镜像名称。
36 | - 如果遇到权限问题,在指令前添加`sudo`。
--------------------------------------------------------------------------------
/sdcs_grpc/sdcs_final/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | node0:
5 | # image: "cache_node"
6 | build:
7 | context: ./sdcs
8 | dockerfile: Dockerfile
9 | ports:
10 | - "9527:5000"
11 | tty: true
12 |
13 | node1:
14 | # image: "cache_node"
15 | build:
16 | context: ./sdcs
17 | dockerfile: Dockerfile
18 | ports:
19 | - "9528:5000"
20 | tty: true
21 |
22 | node2:
23 | # image: "cache_node"
24 | build:
25 | context: ./sdcs
26 | dockerfile: Dockerfile
27 | ports:
28 | - "9529:5000"
29 | tty: true
--------------------------------------------------------------------------------
/sdcs_grpc/sdcs_final/sdcs/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:20.04
2 | WORKDIR /
3 |
4 | # 更换apt-get镜像源
5 | COPY ./sources.list /etc/apt/
6 | # 拷贝 requirements.txt
7 | COPY ./requirements.txt .
8 |
9 | # 更新apt-get并安装Python和pip环境
10 | RUN apt-get update -y \
11 | && apt-get install -y python3.9 \
12 | && apt-get install -y python3-pip \
13 | # 清理临时文件
14 | && apt-get clean \
15 | && rm -rf /var/lib/apt/lists/* \
16 | # 更换pip镜像源
17 | && mkdir ~/.pip && \
18 | echo '[global]' > ~/.pip/pip.conf && \
19 | echo 'index-url = https://pypi.tuna.tsinghua.edu.cn/simple/' >> ~/.pip/pip.conf \
20 | # 安装项目依赖的包
21 | && pip3 install --upgrade setuptools \
22 | && pip3 install -r requirements.txt
23 |
24 | # 拷贝主文件
25 | COPY . .
26 |
27 | # 暴露端口给处于同一网络的容器,不暴露给宿主机
28 | EXPOSE 8000
29 |
30 | CMD flask --app ./cache_node/cache_node.py run --host=0.0.0.0 --port=5000
--------------------------------------------------------------------------------
/sdcs_grpc/sdcs_final/sdcs/cache_node/cache_node.py:
--------------------------------------------------------------------------------
1 | import hashlib
2 | import json
3 | import threading
4 | import time
5 | from concurrent import futures
6 |
7 | import flask
8 | import grpc
9 | from flask import request
10 |
11 | import sdcs_pb2
12 | import sdcs_pb2_grpc
13 |
14 |
15 | # 用于异步线程定时
16 | _ONE_DAY_IN_SECONDS = 60 * 60 * 24
17 |
18 | app = flask.Flask(__name__)
19 |
20 | # 静态配置节点信息
21 | server_rpc_url = ["node0:8000", "node1:8000", "node2:8000"]
22 | server_cnt = 3
23 |
24 | # 节点的本地缓存
25 | cache = dict()
26 | # 节点中已存储的键值对总数
27 | total_cnt = 0
28 |
29 |
30 | # rpc节点
31 | class Node(sdcs_pb2_grpc.CacheNodeServicer):
32 | # rpc更新kv方法
33 | def UpdateKeyValue(self, request, context):
34 | global total_cnt
35 | update_cnt = 0
36 | # 入参
37 | kv_string = request.kv_string
38 | print("[grpc server] update_request param: {}".format(kv_string))
39 |
40 | # 更新kv
41 | kv_map = json.loads(kv_string)
42 | for key, value in kv_map.items():
43 | # 校验本地无重复key值,则total_cnt+1
44 | is_exist = cache.get(key)
45 | if is_exist is None:
46 | total_cnt += 1
47 | # 更新键值对
48 | cache.update({key: value})
49 | update_cnt += 1
50 |
51 | print("[grpc server] Successfully update k-v cnt = {} and total_cnt = {}".format(update_cnt, total_cnt))
52 | return sdcs_pb2.UpdateKeyValueResponse(update_cnt=update_cnt)
53 |
54 | # rpc查询kv方法
55 | def SearchKeyValue(self, request, context):
56 | # 入参
57 | key = request.key
58 | print("[grpc server] search_request param: {}".format(key))
59 |
60 | # 查询kv
61 | value = cache.get(key)
62 | resp_data = json.dumps({key: value})
63 | if value:
64 | print("[grpc server] Successfully get k-v: {}".format({key: value}))
65 | else:
66 | print("[grpc server] Get none.. The key-value is not found!")
67 | return sdcs_pb2.SearchKeyValueResponse(kv_string=resp_data)
68 |
69 | # rpc删除kv方法
70 | def DeleteKeyValue(self, request, context):
71 | delete_cnt = 0
72 | # 入参
73 | key = request.key
74 | print("[grpc server] delete_request param: {}".format(key))
75 |
76 | # 删除kv
77 | value = cache.pop(key, None)
78 | if value:
79 | delete_cnt += 1
80 | print("[grpc server] Successfully delete k-v: {}".format({key: value}))
81 | else:
82 | print("[grpc server] Delete none.. The key-value is not found!")
83 | return sdcs_pb2.DeleteKeyValueResponse(delete_cnt=delete_cnt)
84 |
85 |
86 | # grpc服务端
87 | def run_grpc_server():
88 | grpc_server = grpc.server(futures.ThreadPoolExecutor(max_workers=4))
89 | sdcs_pb2_grpc.add_CacheNodeServicer_to_server(Node(), grpc_server)
90 | grpc_server.add_insecure_port("0.0.0.0:8000")
91 | grpc_server.start()
92 | print("grpc_server starts..")
93 |
94 | try:
95 | while True:
96 | print("grpc_server is running..")
97 | time.sleep(_ONE_DAY_IN_SECONDS)
98 | except KeyboardInterrupt:
99 | grpc_server.stop(0)
100 |
101 |
102 | # 启动grpc服务器
103 | grpc_thread = threading.Thread(target=run_grpc_server)
104 | grpc_thread.start()
105 |
106 |
107 | # grpc客户端 更新请求
108 | def grpc_update_client(kv_map={}, server_id=0):
109 | conn = grpc.insecure_channel(server_rpc_url[server_id])
110 | client = sdcs_pb2_grpc.CacheNodeStub(channel=conn)
111 | kv_string = json.dumps(kv_map)
112 | rpc_request = sdcs_pb2.UpdateKeyValueRequest(kv_string=kv_string)
113 | rsp = client.UpdateKeyValue(rpc_request)
114 | print("[grpc client] update response: {}".format(rsp))
115 | update_cnt = rsp.update_cnt
116 | return update_cnt
117 |
118 |
119 | # grpc客户端 查询请求
120 | def grpc_search_client(key=None, server_id=0):
121 | conn = grpc.insecure_channel(server_rpc_url[server_id])
122 | client = sdcs_pb2_grpc.CacheNodeStub(channel=conn)
123 | rpc_request = sdcs_pb2.SearchKeyValueRequest(key=key)
124 | rsp = client.SearchKeyValue(rpc_request)
125 | print("[grpc client] search response: {}".format(rsp))
126 | kv_string = rsp.kv_string
127 | return kv_string
128 |
129 |
130 | # grpc客户端 删除请求
131 | def grpc_delete_client(key=None, server_id=0):
132 | conn = grpc.insecure_channel(server_rpc_url[server_id])
133 | client = sdcs_pb2_grpc.CacheNodeStub(channel=conn)
134 | rpc_request = sdcs_pb2.DeleteKeyValueRequest(key=key)
135 | rsp = client.DeleteKeyValue(rpc_request)
136 | print("[grpc client] delete response: {}".format(rsp))
137 | delete_cnt = rsp.delete_cnt
138 | return delete_cnt
139 |
140 |
141 | # 计算哈希值
142 | def get_hash_value(key):
143 | # 计算哈希值
144 | md5 = hashlib.md5() # 创建MD5哈希对象
145 | md5.update(str(key).encode()) # 传入要计算哈希值的数据
146 | hash_code = md5.hexdigest() # 获得哈希值
147 | # 累加hash_code各位的ASCII码值
148 | hash_value = 0
149 | for c in hash_code:
150 | hash_value = hash_value + ord(c)
151 | return hash_value
152 |
153 |
154 | # 写入/更新缓存请求
155 | @app.route('/', methods=['POST'])
156 | def update_cache():
157 | # 解析请求参数
158 | request_data = request.json
159 | print("[update request] param: {}".format(request_data))
160 |
161 | # 构建存放列表,每个列表对应一个服务器节点
162 | kv_to_update_list = []
163 | for server_i in range(0, server_cnt):
164 | kv_list = dict()
165 | kv_to_update_list.append(kv_list)
166 |
167 | # 遍历键值对,计算key的哈希值
168 | for key, value in request_data.items():
169 | print({key: value})
170 | # 计算哈希值
171 | hash_value = get_hash_value(key)
172 | # 计算分配的节点位置
173 | index = hash_value % server_cnt
174 | print("[update request] param_key {} : index = {}".format(key, index))
175 | # 将键值对添加到对应服务器节点的列表中
176 | kv_to_update_list[index].update({key: value})
177 |
178 | # 写入/更新至节点
179 | result_cnt = 0 # 累计写入/更新的键值对个数
180 | for server_i in range(0, server_cnt):
181 | if len(kv_to_update_list[server_i]) == 0:
182 | print("server list{} is none, skip!".format(server_i))
183 | continue
184 | # 发送rpc请求
185 | print("server list{} send grpc requset..".format(server_i))
186 | result = grpc_update_client(kv_to_update_list[server_i], server_i)
187 | print("[update rpc-request] grpc result: {}".format(result))
188 | # 解析rpc响应
189 | result_cnt += result
190 | print("[update request] update {} key-value successfully!".format(result_cnt))
191 |
192 | return "update successfully!"
193 |
194 |
195 | # 读取缓存请求
196 | @app.route('/', methods=['GET'])
197 | def get_cache(key):
198 | # 解析请求参数
199 | print("[search request] param: {}".format(key))
200 |
201 | # 计算哈希值
202 | hash_value = get_hash_value(key)
203 | # 计算分配的节点位置
204 | index = hash_value % server_cnt
205 |
206 | # 发送rpc请求
207 | result = grpc_search_client(key, index)
208 | print("[search rpc-request] grpc result: {}".format(result))
209 | # 解析rpc响应
210 | kv = json.loads(result)
211 |
212 | # 返回HTTP响应
213 | value = kv.get(key)
214 | if value is None:
215 | print("[search request] Target key not found!")
216 | return "", 404
217 | else:
218 | print("[search request] Target key-value: {}".format(kv))
219 | return result
220 |
221 |
222 | # 删除缓存请求
223 | @app.route('/', methods=['DELETE'])
224 | def delete_cache(key):
225 | # 解析请求参数
226 | print("[delete request] param: {}".format(key))
227 |
228 | # 计算哈希值
229 | hash_value = get_hash_value(key)
230 | # 计算分配的节点位置
231 | index = hash_value % server_cnt
232 |
233 | # 发送rpc请求
234 | result = grpc_delete_client(key, index)
235 | print("[delete rpc-request] grpc result: {}".format(result))
236 | # 解析rpc响应
237 | delete_cnt = 0
238 | delete_cnt += result
239 |
240 | # 返回HTTP响应
241 | print("[delete request] delete {} key-value successfully!".format(delete_cnt))
242 | return str(delete_cnt)
243 |
244 |
245 | # 查询当前节点缓存中全部的键值对
246 | @app.route("/getAll", methods=["POST"])
247 | def get_local_cache():
248 | print(cache)
249 | return json.dumps(cache)
250 |
251 |
252 | if __name__ == '__main__':
253 | app.run('0.0.0.0', port=5000)
254 |
--------------------------------------------------------------------------------
/sdcs_grpc/sdcs_final/sdcs/cache_node/sdcs.proto:
--------------------------------------------------------------------------------
1 |
2 | syntax = "proto3";
3 |
4 | package sdcs;
5 |
6 | service CacheNode {
7 | // 更新kv
8 | rpc UpdateKeyValue (UpdateKeyValueRequest) returns (UpdateKeyValueResponse) {}
9 | // 查询kv
10 | rpc SearchKeyValue (SearchKeyValueRequest) returns (SearchKeyValueResponse) {}
11 | // 删除kv
12 | rpc DeleteKeyValue (DeleteKeyValueRequest) returns (DeleteKeyValueResponse) {}
13 | }
14 |
15 | // 更新kv请求消息格式
16 | message UpdateKeyValueRequest {
17 | // 要更新的键值对,kv_string-JSON字符串类型
18 | string kv_string = 1;
19 | }
20 |
21 | // 更新kv响应消息格式
22 | message UpdateKeyValueResponse {
23 | // 更新个数,cnt-整型
24 | uint32 update_cnt = 1;
25 | }
26 |
27 | // 查询kv请求消息格式
28 | message SearchKeyValueRequest {
29 | // 键值对的关键字,key-字符串类型
30 | string key = 1;
31 | }
32 |
33 | // 查询kv响应消息格式
34 | message SearchKeyValueResponse {
35 | // 查询到的键值对,kv_string-JSON字符串类型
36 | string kv_string = 1;
37 | }
38 |
39 | // 删除kv请求消息格式
40 | message DeleteKeyValueRequest {
41 | // 键值对的关键字,key-字符串类型
42 | string key = 1;
43 | }
44 |
45 | // 删除kv响应消息格式
46 | message DeleteKeyValueResponse {
47 | // 删除个数,cnt-整型
48 | uint32 delete_cnt = 1;
49 | }
50 |
--------------------------------------------------------------------------------
/sdcs_grpc/sdcs_final/sdcs/cache_node/sdcs_pb2.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by the protocol buffer compiler. DO NOT EDIT!
3 | # source: sdcs.proto
4 | """Generated protocol buffer code."""
5 | from google.protobuf import descriptor as _descriptor
6 | from google.protobuf import descriptor_pool as _descriptor_pool
7 | from google.protobuf import symbol_database as _symbol_database
8 | from google.protobuf.internal import builder as _builder
9 | # @@protoc_insertion_point(imports)
10 |
11 | _sym_db = _symbol_database.Default()
12 |
13 |
14 |
15 |
16 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\nsdcs.proto\x12\x04sdcs\"*\n\x15UpdateKeyValueRequest\x12\x11\n\tkv_string\x18\x01 \x01(\t\",\n\x16UpdateKeyValueResponse\x12\x12\n\nupdate_cnt\x18\x01 \x01(\r\"$\n\x15SearchKeyValueRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\"+\n\x16SearchKeyValueResponse\x12\x11\n\tkv_string\x18\x01 \x01(\t\"$\n\x15\x44\x65leteKeyValueRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\",\n\x16\x44\x65leteKeyValueResponse\x12\x12\n\ndelete_cnt\x18\x01 \x01(\r2\xf8\x01\n\tCacheNode\x12M\n\x0eUpdateKeyValue\x12\x1b.sdcs.UpdateKeyValueRequest\x1a\x1c.sdcs.UpdateKeyValueResponse\"\x00\x12M\n\x0eSearchKeyValue\x12\x1b.sdcs.SearchKeyValueRequest\x1a\x1c.sdcs.SearchKeyValueResponse\"\x00\x12M\n\x0e\x44\x65leteKeyValue\x12\x1b.sdcs.DeleteKeyValueRequest\x1a\x1c.sdcs.DeleteKeyValueResponse\"\x00\x62\x06proto3')
17 |
18 | _globals = globals()
19 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
20 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'sdcs_pb2', _globals)
21 | if _descriptor._USE_C_DESCRIPTORS == False:
22 | DESCRIPTOR._options = None
23 | _globals['_UPDATEKEYVALUEREQUEST']._serialized_start=20
24 | _globals['_UPDATEKEYVALUEREQUEST']._serialized_end=62
25 | _globals['_UPDATEKEYVALUERESPONSE']._serialized_start=64
26 | _globals['_UPDATEKEYVALUERESPONSE']._serialized_end=108
27 | _globals['_SEARCHKEYVALUEREQUEST']._serialized_start=110
28 | _globals['_SEARCHKEYVALUEREQUEST']._serialized_end=146
29 | _globals['_SEARCHKEYVALUERESPONSE']._serialized_start=148
30 | _globals['_SEARCHKEYVALUERESPONSE']._serialized_end=191
31 | _globals['_DELETEKEYVALUEREQUEST']._serialized_start=193
32 | _globals['_DELETEKEYVALUEREQUEST']._serialized_end=229
33 | _globals['_DELETEKEYVALUERESPONSE']._serialized_start=231
34 | _globals['_DELETEKEYVALUERESPONSE']._serialized_end=275
35 | _globals['_CACHENODE']._serialized_start=278
36 | _globals['_CACHENODE']._serialized_end=526
37 | # @@protoc_insertion_point(module_scope)
38 |
--------------------------------------------------------------------------------
/sdcs_grpc/sdcs_final/sdcs/cache_node/sdcs_pb2_grpc.py:
--------------------------------------------------------------------------------
1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
2 | """Client and server classes corresponding to protobuf-defined services."""
3 | import grpc
4 |
5 | import sdcs_pb2 as sdcs__pb2
6 |
7 |
8 | class CacheNodeStub(object):
9 | """Missing associated documentation comment in .proto file."""
10 |
11 | def __init__(self, channel):
12 | """Constructor.
13 |
14 | Args:
15 | channel: A grpc.Channel.
16 | """
17 | self.UpdateKeyValue = channel.unary_unary(
18 | '/sdcs.CacheNode/UpdateKeyValue',
19 | request_serializer=sdcs__pb2.UpdateKeyValueRequest.SerializeToString,
20 | response_deserializer=sdcs__pb2.UpdateKeyValueResponse.FromString,
21 | )
22 | self.SearchKeyValue = channel.unary_unary(
23 | '/sdcs.CacheNode/SearchKeyValue',
24 | request_serializer=sdcs__pb2.SearchKeyValueRequest.SerializeToString,
25 | response_deserializer=sdcs__pb2.SearchKeyValueResponse.FromString,
26 | )
27 | self.DeleteKeyValue = channel.unary_unary(
28 | '/sdcs.CacheNode/DeleteKeyValue',
29 | request_serializer=sdcs__pb2.DeleteKeyValueRequest.SerializeToString,
30 | response_deserializer=sdcs__pb2.DeleteKeyValueResponse.FromString,
31 | )
32 |
33 |
34 | class CacheNodeServicer(object):
35 | """Missing associated documentation comment in .proto file."""
36 |
37 | def UpdateKeyValue(self, request, context):
38 | """更新kv
39 | """
40 | context.set_code(grpc.StatusCode.UNIMPLEMENTED)
41 | context.set_details('Method not implemented!')
42 | raise NotImplementedError('Method not implemented!')
43 |
44 | def SearchKeyValue(self, request, context):
45 | """查询kv
46 | """
47 | context.set_code(grpc.StatusCode.UNIMPLEMENTED)
48 | context.set_details('Method not implemented!')
49 | raise NotImplementedError('Method not implemented!')
50 |
51 | def DeleteKeyValue(self, request, context):
52 | """删除kv
53 | """
54 | context.set_code(grpc.StatusCode.UNIMPLEMENTED)
55 | context.set_details('Method not implemented!')
56 | raise NotImplementedError('Method not implemented!')
57 |
58 |
59 | def add_CacheNodeServicer_to_server(servicer, server):
60 | rpc_method_handlers = {
61 | 'UpdateKeyValue': grpc.unary_unary_rpc_method_handler(
62 | servicer.UpdateKeyValue,
63 | request_deserializer=sdcs__pb2.UpdateKeyValueRequest.FromString,
64 | response_serializer=sdcs__pb2.UpdateKeyValueResponse.SerializeToString,
65 | ),
66 | 'SearchKeyValue': grpc.unary_unary_rpc_method_handler(
67 | servicer.SearchKeyValue,
68 | request_deserializer=sdcs__pb2.SearchKeyValueRequest.FromString,
69 | response_serializer=sdcs__pb2.SearchKeyValueResponse.SerializeToString,
70 | ),
71 | 'DeleteKeyValue': grpc.unary_unary_rpc_method_handler(
72 | servicer.DeleteKeyValue,
73 | request_deserializer=sdcs__pb2.DeleteKeyValueRequest.FromString,
74 | response_serializer=sdcs__pb2.DeleteKeyValueResponse.SerializeToString,
75 | ),
76 | }
77 | generic_handler = grpc.method_handlers_generic_handler(
78 | 'sdcs.CacheNode', rpc_method_handlers)
79 | server.add_generic_rpc_handlers((generic_handler,))
80 |
81 |
82 | # This class is part of an EXPERIMENTAL API.
83 | class CacheNode(object):
84 | """Missing associated documentation comment in .proto file."""
85 |
86 | @staticmethod
87 | def UpdateKeyValue(request,
88 | target,
89 | options=(),
90 | channel_credentials=None,
91 | call_credentials=None,
92 | insecure=False,
93 | compression=None,
94 | wait_for_ready=None,
95 | timeout=None,
96 | metadata=None):
97 | return grpc.experimental.unary_unary(request, target, '/sdcs.CacheNode/UpdateKeyValue',
98 | sdcs__pb2.UpdateKeyValueRequest.SerializeToString,
99 | sdcs__pb2.UpdateKeyValueResponse.FromString,
100 | options, channel_credentials,
101 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
102 |
103 | @staticmethod
104 | def SearchKeyValue(request,
105 | target,
106 | options=(),
107 | channel_credentials=None,
108 | call_credentials=None,
109 | insecure=False,
110 | compression=None,
111 | wait_for_ready=None,
112 | timeout=None,
113 | metadata=None):
114 | return grpc.experimental.unary_unary(request, target, '/sdcs.CacheNode/SearchKeyValue',
115 | sdcs__pb2.SearchKeyValueRequest.SerializeToString,
116 | sdcs__pb2.SearchKeyValueResponse.FromString,
117 | options, channel_credentials,
118 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
119 |
120 | @staticmethod
121 | def DeleteKeyValue(request,
122 | target,
123 | options=(),
124 | channel_credentials=None,
125 | call_credentials=None,
126 | insecure=False,
127 | compression=None,
128 | wait_for_ready=None,
129 | timeout=None,
130 | metadata=None):
131 | return grpc.experimental.unary_unary(request, target, '/sdcs.CacheNode/DeleteKeyValue',
132 | sdcs__pb2.DeleteKeyValueRequest.SerializeToString,
133 | sdcs__pb2.DeleteKeyValueResponse.FromString,
134 | options, channel_credentials,
135 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
136 |
--------------------------------------------------------------------------------
/sdcs_grpc/sdcs_final/sdcs/requirements.txt:
--------------------------------------------------------------------------------
1 | Flask==3.0.0
2 | grpcio==1.59.0
3 | grpcio-tools==1.59.0
4 | protobuf==4.24.4
5 |
6 |
--------------------------------------------------------------------------------
/sdcs_grpc/sdcs_final/sdcs/sources.list:
--------------------------------------------------------------------------------
1 | # 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释
2 |
3 | # ARM架构镜像源地址
4 | #deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ focal main restricted universe multiverse
5 | #deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ focal-updates main restricted universe multiverse
6 | #deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ focal-backports main restricted universe multiverse
7 | #deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ focal-security main restricted universe multiverse
8 |
9 | # x86架构镜像源地址
10 | deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal main restricted universe multiverse
11 | deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-updates main restricted universe multiverse
12 | deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-backports main restricted universe multiverse
13 | deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-security main restricted universe multiverse
--------------------------------------------------------------------------------
/sdcs_grpc/sdcs_local_test/cache_node_local.py:
--------------------------------------------------------------------------------
1 | import hashlib
2 | import json
3 | import os
4 | import threading
5 | import time
6 | from concurrent import futures
7 |
8 | import flask
9 | import grpc
10 | from flask import request
11 | from google.protobuf import json_format
12 |
13 | import sdcs_pb2
14 | import sdcs_pb2_grpc
15 |
16 |
17 | # 用于异步线程定时
18 | _ONE_DAY_IN_SECONDS = 60 * 60 * 24
19 |
20 | app = flask.Flask(__name__)
21 |
22 | # 静态配置节点信息
23 | # server_url = ["http://node1", "http://node2", "http://node3"]
24 | server_rpc_url = ["http://127.0.0.1:8000", "127.0.0.1:8001", "127.0.0.1:8002"] # 本地测试
25 | server_cnt = 1
26 | server_index = os.environ.get('SERVER_INDEX', 1) # 从环境变量中获取节点id
27 | print("Server " + str(server_index) + " is starting..")
28 |
29 | # 节点的本地缓存
30 | cache = {}
31 | # 节点中已存储的键值对总数
32 | total_cnt = 0
33 |
34 |
35 | class Node(sdcs_pb2_grpc.CacheNodeServicer):
36 | # rpc更新kv方法
37 | def UpdateKeyValue(self, request, context):
38 | global total_cnt
39 | update_cnt = 0
40 | # 入参
41 | kv_string = request.kv_string
42 |
43 | # 更新kv
44 | kv_map = json.loads(kv_string)
45 | for key, value in kv_map.items():
46 | is_exist = cache.get(key)
47 | if is_exist is None:
48 | # 校验本地无重复key值,则存储键值对
49 | cache.update({key: value})
50 | update_cnt += 1
51 | total_cnt += 1
52 |
53 | print("[grpc server{}] Successfully update k-v cnt = {} and total_cnt = {}".format(server_index, update_cnt, total_cnt))
54 | return sdcs_pb2.UpdateKeyValueResponse(update_cnt=update_cnt)
55 |
56 | # rpc查询kv方法
57 | def SearchKeyValue(self, request, context):
58 | # 入参
59 | key = request.key
60 |
61 | # 查询kv
62 | value = cache.get(key)
63 | resp_data = None
64 | if value:
65 | print("[grpc server{}] Successfully get k-v: {}".format(server_index, {key: value}))
66 | resp_data = json.dumps({key: value})
67 | else:
68 | print("[grpc server{}] Get none.. The key-value is not found!".format(server_index))
69 | return sdcs_pb2.SearchKeyValueResponse(kv_string=resp_data)
70 |
71 | # rpc删除kv方法
72 | def DeleteKeyValue(self, request, context):
73 | delete_cnt = 0
74 | # 入参
75 | key = request.key
76 |
77 | # 删除kv
78 | value = cache.pop(key, default=None)
79 | if value:
80 | delete_cnt += 1
81 | print("[grpc server{}] Successfully delete k-v: {}".format(server_index, {key: value}))
82 | else:
83 | print("[grpc server{}] Delete none.. The key-value is not found!".format(server_index))
84 | return sdcs_pb2.DeleteKeyValueResponse(delete_cnt=delete_cnt)
85 |
86 |
87 | # grpc服务端
88 | def run_grpc_server():
89 | grpc_server = grpc.server(futures.ThreadPoolExecutor(max_workers=4))
90 | sdcs_pb2_grpc.add_CacheNodeServicer_to_server(Node(), grpc_server)
91 | grpc_server.add_insecure_port(server_rpc_url[server_index])
92 | grpc_server.start()
93 | print("grpc_server of Node{} starts..".format(server_index))
94 |
95 | try:
96 | while True:
97 | print("grpc_server {} is running..".format(server_index))
98 | time.sleep(_ONE_DAY_IN_SECONDS)
99 | except KeyboardInterrupt:
100 | grpc_server.stop(0)
101 |
102 |
103 | # 启动grpc服务器
104 | grpc_thread = threading.Thread(target=run_grpc_server)
105 | grpc_thread.start()
106 |
107 |
108 | # grpc客户端 更新请求
109 | def grpc_update_client(kv_map={}, server_id=0):
110 | conn = grpc.insecure_channel(server_rpc_url[server_id])
111 | client = sdcs_pb2_grpc.CacheNodeStub(channel=conn)
112 | kv_string = json.dumps(kv_map)
113 | rpc_request = sdcs_pb2.UpdateKeyValueRequest(kv_string=kv_string)
114 | rsp = client.UpdateKeyValue(rpc_request)
115 | print("[grpc client{}] update response: {}".format(server_index, rsp))
116 | update_cnt = rsp.update_cnt
117 | return update_cnt
118 |
119 |
120 | # grpc客户端 查询请求
121 | def grpc_search_client(key=None, server_id=0):
122 | conn = grpc.insecure_channel(server_rpc_url[server_id])
123 | client = sdcs_pb2_grpc.CacheNodeStub(channel=conn)
124 | rpc_request = sdcs_pb2.SearchKeyValueRequest(key=key)
125 | rsp = client.SearchKeyValue(rpc_request)
126 | print("[grpc client{}] search response: {}".format(server_index, rsp))
127 | kv_string = rsp.kv_string
128 | return kv_string
129 |
130 |
131 | # grpc客户端 删除请求
132 | def grpc_delete_client(key=None, server_id=0):
133 | conn = grpc.insecure_channel(server_rpc_url[server_id])
134 | client = sdcs_pb2_grpc.CacheNodeStub(channel=conn)
135 | rpc_request = sdcs_pb2.DeleteKeyValueRequest(key=key)
136 | rsp = client.DeleteKeyValue(rpc_request)
137 | print("[grpc client{}] delete response: {}".format(server_index, rsp))
138 | delete_cnt = rsp.delete_cnt
139 | return delete_cnt
140 |
141 |
142 | # 计算哈希值
143 | def get_hash_value(key):
144 | # 计算哈希值
145 | md5 = hashlib.md5() # 创建MD5哈希对象
146 | md5.update(str(key).encode()) # 传入要计算哈希值的数据
147 | hash_code = md5.hexdigest() # 获得哈希值
148 | # 累加hash_code各位的ASCII码值
149 | hash_value = 0
150 | for c in hash_code:
151 | hash_value = hash_value + ord(c)
152 | return hash_value
153 |
154 |
155 | # 写入/更新缓存请求
156 | @app.route('/', methods=['POST'])
157 | def update_cache(): # put application's code here
158 | request_data = request.json
159 | print("[node{} update request] param: {}".format(server_index, request_data)) # 打印请求参数≤
160 |
161 | # 构建存放空间
162 | kv_to_update_list = []
163 | for i in range(0, server_cnt):
164 | kv_list = {}
165 | kv_to_update_list.append(kv_list)
166 |
167 | # 遍历键值对,计算哈希值
168 | for key, value in request_data.items():
169 | index = 0 # index重置
170 | print({key: value})
171 | # 计算哈希值
172 | hash_value = get_hash_value(key)
173 | # 计算对应的节点位置
174 | index = hash_value % server_cnt
175 | print("[node{} update request] param_key {} : index = {}".format(server_index, key, index))
176 | kv_to_update_list[index].update({key: value})
177 |
178 | # 写入/更新至节点
179 | result_cnt = 0
180 | for i in range(0, server_cnt):
181 | if len(kv_to_update_list[i]) == 0:
182 | break
183 | # 发送rpc请求
184 | result = grpc_update_client(kv_to_update_list[i], i)
185 | print("[node{} update rpc-request] grpc result: {}".format(server_index, result))
186 | # 解析rpc响应
187 | result_cnt += result
188 | print("[node{} update request] update {} key-value successfully!".format(server_index, result_cnt))
189 |
190 | return "update successfully! "
191 |
192 |
193 | # 读取缓存请求
194 | @app.route('/', methods=['GET'])
195 | def get_cache(key):
196 | print("[node{} search request] param: {}".format(server_index, key))
197 |
198 | # 计算哈希值
199 | hash_value = get_hash_value(key)
200 | # 计算对应的节点位置
201 | index = hash_value % server_cnt
202 |
203 | # 发送rpc请求
204 | result = grpc_search_client(key, index)
205 | print("[node{} search rpc-request] grpc result: {}".format(server_index, result))
206 | # 解析rpc响应
207 | kv = json.loads(result)
208 | value = kv.get(key)
209 | if value is None:
210 | print("[node{} search request] Target key not found!".format(server_index))
211 | return "Target key not found!", 404
212 | else:
213 | print("[node{} search request] Target key-value: {}".format(server_index, result))
214 | return result
215 |
216 |
217 | # 删除缓存请求
218 | @app.route('/', methods=['DELETE'])
219 | def delete_cache(key):
220 | print("[node{} delete request] param: {}".format(server_index, key))
221 |
222 | # 计算哈希值
223 | hash_value = get_hash_value(key)
224 | # 计算对应的节点位置
225 | index = hash_value % server_cnt
226 |
227 | # 发送rpc请求
228 | result = grpc_delete_client(key, index)
229 | print("[node{} delete rpc-request] grpc result: {}".format(server_index, result))
230 | # 解析rpc响应
231 | delete_cnt = 0
232 | delete_cnt += result
233 | print("[node{} delete request] delete {} key-value successfully!".format(server_index, delete_cnt))
234 | return delete_cnt
235 |
236 |
237 | @app.route("/test", methods=["POST"])
238 | def get_local_cache():
239 | print(cache)
240 | return json.dumps(cache)
241 |
242 |
243 | if __name__ == '__main__':
244 | app.run('0.0.0.0', port=5000)
245 |
--------------------------------------------------------------------------------
/sdcs_grpc/sdcs_local_test/sdcs.proto:
--------------------------------------------------------------------------------
1 |
2 | syntax = "proto3";
3 |
4 | package sdcs;
5 |
6 | service CacheNode {
7 | // 更新kv
8 | rpc UpdateKeyValue (UpdateKeyValueRequest) returns (UpdateKeyValueResponse) {}
9 | // 查询kv
10 | rpc SearchKeyValue (SearchKeyValueRequest) returns (SearchKeyValueResponse) {}
11 | // 删除kv
12 | rpc DeleteKeyValue (DeleteKeyValueRequest) returns (DeleteKeyValueResponse) {}
13 | }
14 |
15 | // 更新kv请求消息格式
16 | message UpdateKeyValueRequest {
17 | // 要更新的键值对,kv_string-JSON字符串类型
18 | string kv_string = 1;
19 | }
20 |
21 | // 更新kv响应消息格式
22 | message UpdateKeyValueResponse {
23 | // 更新个数,cnt-整型
24 | uint32 update_cnt = 1;
25 | }
26 |
27 | // 查询kv请求消息格式
28 | message SearchKeyValueRequest {
29 | // 键值对的关键字,key-字符串类型
30 | string key = 1;
31 | }
32 |
33 | // 查询kv响应消息格式
34 | message SearchKeyValueResponse {
35 | // 查询到的键值对,kv_string-JSON字符串类型
36 | string kv_string = 1;
37 | }
38 |
39 | // 删除kv请求消息格式
40 | message DeleteKeyValueRequest {
41 | // 键值对的关键字,key-字符串类型
42 | string key = 1;
43 | }
44 |
45 | // 删除kv响应消息格式
46 | message DeleteKeyValueResponse {
47 | // 删除个数,cnt-整型
48 | uint32 delete_cnt = 1;
49 | }
50 |
--------------------------------------------------------------------------------
/sdcs_grpc/sdcs_local_test/sdcs_pb2.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by the protocol buffer compiler. DO NOT EDIT!
3 | # source: sdcs.proto
4 | """Generated protocol buffer code."""
5 | from google.protobuf import descriptor as _descriptor
6 | from google.protobuf import descriptor_pool as _descriptor_pool
7 | from google.protobuf import symbol_database as _symbol_database
8 | from google.protobuf.internal import builder as _builder
9 | # @@protoc_insertion_point(imports)
10 |
11 | _sym_db = _symbol_database.Default()
12 |
13 |
14 |
15 |
16 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\nsdcs.proto\x12\x04sdcs\"*\n\x15UpdateKeyValueRequest\x12\x11\n\tkv_string\x18\x01 \x01(\t\",\n\x16UpdateKeyValueResponse\x12\x12\n\nupdate_cnt\x18\x01 \x01(\r\"$\n\x15SearchKeyValueRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\"+\n\x16SearchKeyValueResponse\x12\x11\n\tkv_string\x18\x01 \x01(\t\"$\n\x15\x44\x65leteKeyValueRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\",\n\x16\x44\x65leteKeyValueResponse\x12\x12\n\ndelete_cnt\x18\x01 \x01(\r2\xf8\x01\n\tCacheNode\x12M\n\x0eUpdateKeyValue\x12\x1b.sdcs.UpdateKeyValueRequest\x1a\x1c.sdcs.UpdateKeyValueResponse\"\x00\x12M\n\x0eSearchKeyValue\x12\x1b.sdcs.SearchKeyValueRequest\x1a\x1c.sdcs.SearchKeyValueResponse\"\x00\x12M\n\x0e\x44\x65leteKeyValue\x12\x1b.sdcs.DeleteKeyValueRequest\x1a\x1c.sdcs.DeleteKeyValueResponse\"\x00\x62\x06proto3')
17 |
18 | _globals = globals()
19 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
20 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'sdcs_pb2', _globals)
21 | if _descriptor._USE_C_DESCRIPTORS == False:
22 | DESCRIPTOR._options = None
23 | _globals['_UPDATEKEYVALUEREQUEST']._serialized_start=20
24 | _globals['_UPDATEKEYVALUEREQUEST']._serialized_end=62
25 | _globals['_UPDATEKEYVALUERESPONSE']._serialized_start=64
26 | _globals['_UPDATEKEYVALUERESPONSE']._serialized_end=108
27 | _globals['_SEARCHKEYVALUEREQUEST']._serialized_start=110
28 | _globals['_SEARCHKEYVALUEREQUEST']._serialized_end=146
29 | _globals['_SEARCHKEYVALUERESPONSE']._serialized_start=148
30 | _globals['_SEARCHKEYVALUERESPONSE']._serialized_end=191
31 | _globals['_DELETEKEYVALUEREQUEST']._serialized_start=193
32 | _globals['_DELETEKEYVALUEREQUEST']._serialized_end=229
33 | _globals['_DELETEKEYVALUERESPONSE']._serialized_start=231
34 | _globals['_DELETEKEYVALUERESPONSE']._serialized_end=275
35 | _globals['_CACHENODE']._serialized_start=278
36 | _globals['_CACHENODE']._serialized_end=526
37 | # @@protoc_insertion_point(module_scope)
38 |
--------------------------------------------------------------------------------
/sdcs_grpc/sdcs_local_test/sdcs_pb2_grpc.py:
--------------------------------------------------------------------------------
1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
2 | """Client and server classes corresponding to protobuf-defined services."""
3 | import grpc
4 |
5 | import sdcs_pb2 as sdcs__pb2
6 |
7 |
8 | class CacheNodeStub(object):
9 | """Missing associated documentation comment in .proto file."""
10 |
11 | def __init__(self, channel):
12 | """Constructor.
13 |
14 | Args:
15 | channel: A grpc.Channel.
16 | """
17 | self.UpdateKeyValue = channel.unary_unary(
18 | '/sdcs.CacheNode/UpdateKeyValue',
19 | request_serializer=sdcs__pb2.UpdateKeyValueRequest.SerializeToString,
20 | response_deserializer=sdcs__pb2.UpdateKeyValueResponse.FromString,
21 | )
22 | self.SearchKeyValue = channel.unary_unary(
23 | '/sdcs.CacheNode/SearchKeyValue',
24 | request_serializer=sdcs__pb2.SearchKeyValueRequest.SerializeToString,
25 | response_deserializer=sdcs__pb2.SearchKeyValueResponse.FromString,
26 | )
27 | self.DeleteKeyValue = channel.unary_unary(
28 | '/sdcs.CacheNode/DeleteKeyValue',
29 | request_serializer=sdcs__pb2.DeleteKeyValueRequest.SerializeToString,
30 | response_deserializer=sdcs__pb2.DeleteKeyValueResponse.FromString,
31 | )
32 |
33 |
34 | class CacheNodeServicer(object):
35 | """Missing associated documentation comment in .proto file."""
36 |
37 | def UpdateKeyValue(self, request, context):
38 | """更新kv
39 | """
40 | context.set_code(grpc.StatusCode.UNIMPLEMENTED)
41 | context.set_details('Method not implemented!')
42 | raise NotImplementedError('Method not implemented!')
43 |
44 | def SearchKeyValue(self, request, context):
45 | """查询kv
46 | """
47 | context.set_code(grpc.StatusCode.UNIMPLEMENTED)
48 | context.set_details('Method not implemented!')
49 | raise NotImplementedError('Method not implemented!')
50 |
51 | def DeleteKeyValue(self, request, context):
52 | """删除kv
53 | """
54 | context.set_code(grpc.StatusCode.UNIMPLEMENTED)
55 | context.set_details('Method not implemented!')
56 | raise NotImplementedError('Method not implemented!')
57 |
58 |
59 | def add_CacheNodeServicer_to_server(servicer, server):
60 | rpc_method_handlers = {
61 | 'UpdateKeyValue': grpc.unary_unary_rpc_method_handler(
62 | servicer.UpdateKeyValue,
63 | request_deserializer=sdcs__pb2.UpdateKeyValueRequest.FromString,
64 | response_serializer=sdcs__pb2.UpdateKeyValueResponse.SerializeToString,
65 | ),
66 | 'SearchKeyValue': grpc.unary_unary_rpc_method_handler(
67 | servicer.SearchKeyValue,
68 | request_deserializer=sdcs__pb2.SearchKeyValueRequest.FromString,
69 | response_serializer=sdcs__pb2.SearchKeyValueResponse.SerializeToString,
70 | ),
71 | 'DeleteKeyValue': grpc.unary_unary_rpc_method_handler(
72 | servicer.DeleteKeyValue,
73 | request_deserializer=sdcs__pb2.DeleteKeyValueRequest.FromString,
74 | response_serializer=sdcs__pb2.DeleteKeyValueResponse.SerializeToString,
75 | ),
76 | }
77 | generic_handler = grpc.method_handlers_generic_handler(
78 | 'sdcs.CacheNode', rpc_method_handlers)
79 | server.add_generic_rpc_handlers((generic_handler,))
80 |
81 |
82 | # This class is part of an EXPERIMENTAL API.
83 | class CacheNode(object):
84 | """Missing associated documentation comment in .proto file."""
85 |
86 | @staticmethod
87 | def UpdateKeyValue(request,
88 | target,
89 | options=(),
90 | channel_credentials=None,
91 | call_credentials=None,
92 | insecure=False,
93 | compression=None,
94 | wait_for_ready=None,
95 | timeout=None,
96 | metadata=None):
97 | return grpc.experimental.unary_unary(request, target, '/sdcs.CacheNode/UpdateKeyValue',
98 | sdcs__pb2.UpdateKeyValueRequest.SerializeToString,
99 | sdcs__pb2.UpdateKeyValueResponse.FromString,
100 | options, channel_credentials,
101 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
102 |
103 | @staticmethod
104 | def SearchKeyValue(request,
105 | target,
106 | options=(),
107 | channel_credentials=None,
108 | call_credentials=None,
109 | insecure=False,
110 | compression=None,
111 | wait_for_ready=None,
112 | timeout=None,
113 | metadata=None):
114 | return grpc.experimental.unary_unary(request, target, '/sdcs.CacheNode/SearchKeyValue',
115 | sdcs__pb2.SearchKeyValueRequest.SerializeToString,
116 | sdcs__pb2.SearchKeyValueResponse.FromString,
117 | options, channel_credentials,
118 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
119 |
120 | @staticmethod
121 | def DeleteKeyValue(request,
122 | target,
123 | options=(),
124 | channel_credentials=None,
125 | call_credentials=None,
126 | insecure=False,
127 | compression=None,
128 | wait_for_ready=None,
129 | timeout=None,
130 | metadata=None):
131 | return grpc.experimental.unary_unary(request, target, '/sdcs.CacheNode/DeleteKeyValue',
132 | sdcs__pb2.DeleteKeyValueRequest.SerializeToString,
133 | sdcs__pb2.DeleteKeyValueResponse.FromString,
134 | options, channel_credentials,
135 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
136 |
--------------------------------------------------------------------------------
/sdcs_grpc/simple_dcs/README.md:
--------------------------------------------------------------------------------
1 | # Simple Distributed Cache System
2 |
3 | ---
4 | ### 基本原理
5 | TODO: 待补充
6 |
7 | ### 操作步骤
8 |
9 | 1. 节点逻辑功能的代码实现
10 | 1. 编写`sdcs.proto`文件,定义gRPC的消息传递对象和格式。
11 | 2. 在Terminal中进入proto文件所在目录,执行指令生成`xxx_pb2.py`和`xxx_pb2_grpc.py`文件。
12 | ```shell
13 | python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. sdcs.proto
14 | ```
15 | 3. 在`cache_node.py`文件中,编辑每个节点对应的处理逻辑
16 | - 定义grpc对象的具体方法实现
17 | ```python
18 | class Node:
19 | def method1(self, request, context):
20 | # method1 implement
21 | def method2(self, request, context):
22 | # method2 implement
23 | def method3(self, request, context):
24 | # method3 implement
25 | ```
26 | - 定义grpc服务端并指定监听的端口,以多线程模式启动该服务,使之在后台持续监听
27 | ```python
28 | def run_grpc_server():
29 | # run_grpc_server implement
30 | ```
31 | - 定义grpc客户端(对应不同的rpc方法)
32 | ```python
33 | def grpc_xxx_client():
34 | # grpc_xxx_client implement
35 | ```
36 | - 定义外部http请求的各功能接口,配置路由
37 | ```python
38 | @app.route("/xxx")
39 | def xxx_api():
40 | # api implement
41 | ```
42 | - 写入/更新接口:
43 | ```python
44 | # 写入/更新缓存请求
45 | @app.route('/', methods=['POST'])
46 | def update_cache():
47 | # 解析请求参数
48 | # 构建存放列表,每个列表对应一个服务器节点
49 | # 遍历参数中的键值对
50 | # 计算key的哈希值,将其添加到对应节点的存放列表
51 | # 通过rpc请求,将存放列表分配到对应节点
52 | # 在各自节点内完成写入/更新操作
53 | # 解析rpc的响应,放回HTTP的响应
54 | return "update_response"
55 | ```
56 | - 查询接口
57 | ```python
58 | # 查询缓存请求
59 | @app.route('/', methods=['GET'])
60 | def get_cache(key):
61 | # 解析请求参数key
62 | # 计算key的哈希值,根据key获得对应的节点号
63 | # 通过rpc请求对应节点,在节点内完成查询操作
64 | # 解析rpc的响应,返回HTTP的响应
65 | return "get_response"
66 | ```
67 | - 删除接口
68 | ```python
69 | # 删除缓存请求
70 | @app.route('/', methods=['DELETE'])
71 | def delete_cache(key):
72 | # 解析请求参数key
73 | # 计算key的哈希值,根据key获得对应的节点号
74 | # 通过rpc请求对应节点,在节点内完成删除操作
75 | # 解析rpc的响应,返回HTTP的响应
76 | return "delete_response"
77 | ```
78 |
79 | - 在http接口中调用grpc客户端方法,实现节点间的rpc通信
80 |
81 | 2. 打包部署docker
82 | 1. 将项目依赖包导入`requirements.txt`文件。
83 | 2. 编写`Dockerfile`文件打包项目的镜像(大致如下,可以考虑压缩镜像大小进行优化)。
84 | ```Dockerfile
85 | # 指定基础镜像
86 | FROM ubuntu:20.04
87 | # 指定工作目录
88 | WORKDIR /
89 |
90 | # 更换apt-get镜像源
91 |
92 | # 更新apt-get并安装Python和pip环境
93 | RUN apt-get update -y \
94 | && apt-get install -y python3.9 \
95 | && apt-get install -y python3-pip \
96 | # 清理临时文件
97 | && apt-get clean \
98 | && rm -rf /var/lib/apt/lists/*
99 |
100 | # 更换pip镜像源
101 |
102 | # 安装项目依赖的包
103 | RUN pip3 install -r requirements.txt
104 |
105 | # 暴露端口给处于同一网络的容器,不暴露给宿主机
106 | EXPOSE 8000
107 |
108 | # 启动flask的指令
109 | CMD flask --app cache_node.py run --host=0.0.0.0 --port=5000
110 | ```
111 | 3. 先测试手动打包镜像,及启动容器。
112 | *!!打包前确认一下系统架构,arm架构使用的镜像源,x86系统使用x86架构的镜像源!!*
113 | - 在Terminal中进入`Dockerfile`所在目录,执行`docker build`指令生成镜像。
114 | 其中,`image_name`为自定义的镜像名称,
115 | `ENV_CONFIG`是需要设置的环境变量名称,
116 | `MY_CONFIG_VALUE`是自定义的环境变量的值。
117 | ```shell
118 | docker build -t image_name .
119 | ```
120 | - 使用`docker run`指令启动容器。
121 | 其中,`host_port`表示要指定的宿主机端口,
122 | `docker_name`表示要启动的容器的名称,
123 | `docker_port`表示容器中的端口,
124 | `image_name`是之前打包的镜像名称。
125 | ```shell
126 | docker run --name=docker_name -t -p host_port:docker_port image_name
127 | ```
128 | 4. 使用`docker-compose.yaml`文件,在docker部署节点。
129 | 执行以下指令运行`docker-compose.yaml`文件。
130 | ```shell
131 | docker-compose up
132 | ```
133 | 通过`docker-compose`启动的容器之间可以相互解析容器名的DNS地址。
134 |
135 |
--------------------------------------------------------------------------------
/sdcs_grpc/simple_dcs/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | node0:
5 | # image: "cache_node"
6 | build:
7 | context: ./sdcs
8 | dockerfile: Dockerfile
9 | ports:
10 | - "9527:5000"
11 | tty: true
12 |
13 | node1:
14 | # image: "cache_node"
15 | build:
16 | context: ./sdcs
17 | dockerfile: Dockerfile
18 | ports:
19 | - "9528:5000"
20 | tty: true
21 |
22 | node2:
23 | # image: "cache_node"
24 | build:
25 | context: ./sdcs
26 | dockerfile: Dockerfile
27 | ports:
28 | - "9529:5000"
29 | tty: true
--------------------------------------------------------------------------------
/sdcs_grpc/simple_dcs/sdcs/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:20.04
2 | WORKDIR /
3 |
4 | # 更换apt-get镜像源
5 | COPY ./sources.list /etc/apt/
6 | # 拷贝 requirements.txt
7 | COPY ./requirements.txt .
8 |
9 | # 更新apt-get并安装Python和pip环境
10 | RUN apt-get update -y \
11 | && apt-get install -y python3.9 \
12 | && apt-get install -y python3-pip \
13 | # 清理临时文件
14 | && apt-get clean \
15 | && rm -rf /var/lib/apt/lists/* \
16 | # 更换pip镜像源
17 | && mkdir ~/.pip && \
18 | echo '[global]' > ~/.pip/pip.conf && \
19 | echo 'index-url = https://pypi.tuna.tsinghua.edu.cn/simple/' >> ~/.pip/pip.conf \
20 | # 安装项目依赖的包
21 | && pip3 install --upgrade setuptools \
22 | && pip3 install -r requirements.txt
23 |
24 | # 拷贝主文件
25 | COPY . .
26 |
27 | # 暴露端口给处于同一网络的容器,不暴露给宿主机
28 | EXPOSE 8000
29 |
30 | CMD flask --app ./cache_node/cache_node.py run --host=0.0.0.0 --port=5000
--------------------------------------------------------------------------------
/sdcs_grpc/simple_dcs/sdcs/cache_node/cache_node.py:
--------------------------------------------------------------------------------
1 | import hashlib
2 | import json
3 | import threading
4 | import time
5 | from concurrent import futures
6 |
7 | import flask
8 | import grpc
9 | from flask import request
10 |
11 | import sdcs_pb2
12 | import sdcs_pb2_grpc
13 |
14 |
15 | # 用于异步线程定时
16 | _ONE_DAY_IN_SECONDS = 60 * 60 * 24
17 |
18 | app = flask.Flask(__name__)
19 |
20 | # 静态配置节点信息
21 | server_rpc_url = ["node0:8000", "node1:8000", "node2:8000"]
22 | server_cnt = 3
23 |
24 | # 节点的本地缓存
25 | cache = dict()
26 | # 节点中已存储的键值对总数
27 | total_cnt = 0
28 |
29 |
30 | class Node(sdcs_pb2_grpc.CacheNodeServicer):
31 | # rpc更新kv方法
32 | def UpdateKeyValue(self, request, context):
33 | global total_cnt
34 | update_cnt = 0
35 | # 入参
36 | kv_string = request.kv_string
37 | print("[grpc server] update_request param: {}".format(kv_string))
38 |
39 | # 更新kv
40 | kv_map = json.loads(kv_string)
41 | for key, value in kv_map.items():
42 | # 校验本地无重复key值,则total_cnt+1
43 | is_exist = cache.get(key)
44 | if is_exist is None:
45 | total_cnt += 1
46 | # 更新键值对
47 | cache.update({key: value})
48 | update_cnt += 1
49 |
50 | print("[grpc server] Successfully update k-v cnt = {} and total_cnt = {}".format(update_cnt, total_cnt))
51 | return sdcs_pb2.UpdateKeyValueResponse(update_cnt=update_cnt)
52 |
53 | # rpc查询kv方法
54 | def SearchKeyValue(self, request, context):
55 | # 入参
56 | key = request.key
57 | print("[grpc server] search_request param: {}".format(key))
58 |
59 | # 查询kv
60 | value = cache.get(key)
61 | resp_data = json.dumps({key: value})
62 | if value:
63 | print("[grpc server] Successfully get k-v: {}".format({key: value}))
64 | else:
65 | print("[grpc server] Get none.. The key-value is not found!")
66 | return sdcs_pb2.SearchKeyValueResponse(kv_string=resp_data)
67 |
68 | # rpc删除kv方法
69 | def DeleteKeyValue(self, request, context):
70 | delete_cnt = 0
71 | # 入参
72 | key = request.key
73 | print("[grpc server] delete_request param: {}".format(key))
74 |
75 | # 删除kv
76 | value = cache.pop(key, None)
77 | if value:
78 | delete_cnt += 1
79 | print("[grpc server] Successfully delete k-v: {}".format({key: value}))
80 | else:
81 | print("[grpc server] Delete none.. The key-value is not found!")
82 | return sdcs_pb2.DeleteKeyValueResponse(delete_cnt=delete_cnt)
83 |
84 |
85 | # grpc服务端
86 | def run_grpc_server():
87 | grpc_server = grpc.server(futures.ThreadPoolExecutor(max_workers=4))
88 | sdcs_pb2_grpc.add_CacheNodeServicer_to_server(Node(), grpc_server)
89 | grpc_server.add_insecure_port("0.0.0.0:8000")
90 | grpc_server.start()
91 | print("grpc_server starts..")
92 |
93 | try:
94 | while True:
95 | print("grpc_server is running..")
96 | time.sleep(_ONE_DAY_IN_SECONDS)
97 | except KeyboardInterrupt:
98 | grpc_server.stop(0)
99 |
100 |
101 | # 启动grpc服务器
102 | grpc_thread = threading.Thread(target=run_grpc_server)
103 | grpc_thread.start()
104 |
105 |
106 | # grpc客户端 更新请求
107 | def grpc_update_client(kv_map={}, server_id=0):
108 | conn = grpc.insecure_channel(server_rpc_url[server_id])
109 | client = sdcs_pb2_grpc.CacheNodeStub(channel=conn)
110 | kv_string = json.dumps(kv_map)
111 | rpc_request = sdcs_pb2.UpdateKeyValueRequest(kv_string=kv_string)
112 | rsp = client.UpdateKeyValue(rpc_request)
113 | print("[grpc client] update response: {}".format(rsp))
114 | update_cnt = rsp.update_cnt
115 | return update_cnt
116 |
117 |
118 | # grpc客户端 查询请求
119 | def grpc_search_client(key=None, server_id=0):
120 | conn = grpc.insecure_channel(server_rpc_url[server_id])
121 | client = sdcs_pb2_grpc.CacheNodeStub(channel=conn)
122 | rpc_request = sdcs_pb2.SearchKeyValueRequest(key=key)
123 | rsp = client.SearchKeyValue(rpc_request)
124 | print("[grpc client] search response: {}".format(rsp))
125 | kv_string = rsp.kv_string
126 | return kv_string
127 |
128 |
129 | # grpc客户端 删除请求
130 | def grpc_delete_client(key=None, server_id=0):
131 | conn = grpc.insecure_channel(server_rpc_url[server_id])
132 | client = sdcs_pb2_grpc.CacheNodeStub(channel=conn)
133 | rpc_request = sdcs_pb2.DeleteKeyValueRequest(key=key)
134 | rsp = client.DeleteKeyValue(rpc_request)
135 | print("[grpc client] delete response: {}".format(rsp))
136 | delete_cnt = rsp.delete_cnt
137 | return delete_cnt
138 |
139 |
140 | # 计算哈希值
141 | def get_hash_value(key):
142 | # 计算哈希值
143 | md5 = hashlib.md5() # 创建MD5哈希对象
144 | md5.update(str(key).encode()) # 传入要计算哈希值的数据
145 | hash_code = md5.hexdigest() # 获得哈希值
146 | # 累加hash_code各位的ASCII码值
147 | hash_value = 0
148 | for c in hash_code:
149 | hash_value = hash_value + ord(c)
150 | return hash_value
151 |
152 |
153 | # 写入/更新缓存请求
154 | @app.route('/', methods=['POST'])
155 | def update_cache():
156 | # 解析请求参数
157 | request_data = request.json
158 | print("[update request] param: {}".format(request_data))
159 |
160 | # 构建存放列表,每个列表对应一个服务器节点
161 | kv_to_update_list = []
162 | for server_i in range(0, server_cnt):
163 | kv_list = dict()
164 | kv_to_update_list.append(kv_list)
165 |
166 | # 遍历键值对,计算key的哈希值
167 | for key, value in request_data.items():
168 | print({key: value})
169 | # 计算哈希值
170 | hash_value = get_hash_value(key)
171 | # 计算分配的节点位置
172 | index = hash_value % server_cnt
173 | print("[update request] param_key {} : index = {}".format(key, index))
174 | # 将键值对添加到对应服务器节点的列表中
175 | kv_to_update_list[index].update({key: value})
176 |
177 | # 写入/更新至节点
178 | result_cnt = 0 # 累计写入/更新的键值对个数
179 | for server_i in range(0, server_cnt):
180 | if len(kv_to_update_list[server_i]) == 0:
181 | print("server list{} is none, skip!".format(server_i))
182 | continue
183 | # 发送rpc请求
184 | print("server list{} send grpc requset..".format(server_i))
185 | result = grpc_update_client(kv_to_update_list[server_i], server_i)
186 | print("[update rpc-request] grpc result: {}".format(result))
187 | # 解析rpc响应
188 | result_cnt += result
189 | print("[update request] update {} key-value successfully!".format(result_cnt))
190 |
191 | return "update successfully!"
192 |
193 |
194 | # 读取缓存请求
195 | @app.route('/', methods=['GET'])
196 | def get_cache(key):
197 | # 解析请求参数
198 | print("[search request] param: {}".format(key))
199 |
200 | # 计算哈希值
201 | hash_value = get_hash_value(key)
202 | # 计算分配的节点位置
203 | index = hash_value % server_cnt
204 |
205 | # 发送rpc请求
206 | result = grpc_search_client(key, index)
207 | print("[search rpc-request] grpc result: {}".format(result))
208 | # 解析rpc响应
209 | kv = json.loads(result)
210 |
211 | # 返回HTTP响应
212 | value = kv.get(key)
213 | if value is None:
214 | print("[search request] Target key not found!")
215 | return "", 404
216 | else:
217 | print("[search request] Target key-value: {}".format(kv))
218 | return result
219 |
220 |
221 | # 删除缓存请求
222 | @app.route('/', methods=['DELETE'])
223 | def delete_cache(key):
224 | # 解析请求参数
225 | print("[delete request] param: {}".format(key))
226 |
227 | # 计算哈希值
228 | hash_value = get_hash_value(key)
229 | # 计算分配的节点位置
230 | index = hash_value % server_cnt
231 |
232 | # 发送rpc请求
233 | result = grpc_delete_client(key, index)
234 | print("[delete rpc-request] grpc result: {}".format(result))
235 | # 解析rpc响应
236 | delete_cnt = 0
237 | delete_cnt += result
238 |
239 | # 返回HTTP响应
240 | print("[delete request] delete {} key-value successfully!".format(delete_cnt))
241 | return str(delete_cnt)
242 |
243 |
244 | # 查询当前节点缓存中全部的键值对
245 | @app.route("/getAll", methods=["POST"])
246 | def get_local_cache():
247 | print(cache)
248 | return json.dumps(cache)
249 |
250 |
251 | # 查询当前节点缓存中全部的键值对
252 | @app.route("/checkHashCode/", methods=["POST"])
253 | def check_hash_code(key):
254 | print("[check hash key] param: {}".format(key))
255 | # 计算哈希值
256 | md5 = hashlib.md5() # 创建MD5哈希对象
257 | md5.update(str(key).encode()) # 传入要计算哈希值的数据
258 | hash_code = md5.hexdigest() # 获得哈希值
259 | # 累加hash_code各位的ASCII码值
260 | hash_value = 0
261 | for c in hash_code:
262 | hash_value = hash_value + ord(c)
263 |
264 | result = "{}-{}-{}".format(key, hash_code, hash_value)
265 | return result
266 |
267 |
268 | if __name__ == '__main__':
269 | app.run('0.0.0.0', port=5000)
270 |
--------------------------------------------------------------------------------
/sdcs_grpc/simple_dcs/sdcs/cache_node/sdcs.proto:
--------------------------------------------------------------------------------
1 |
2 | syntax = "proto3";
3 |
4 | package sdcs;
5 |
6 | service CacheNode {
7 | // 更新kv
8 | rpc UpdateKeyValue (UpdateKeyValueRequest) returns (UpdateKeyValueResponse) {}
9 | // 查询kv
10 | rpc SearchKeyValue (SearchKeyValueRequest) returns (SearchKeyValueResponse) {}
11 | // 删除kv
12 | rpc DeleteKeyValue (DeleteKeyValueRequest) returns (DeleteKeyValueResponse) {}
13 | }
14 |
15 | // 更新kv请求消息格式
16 | message UpdateKeyValueRequest {
17 | // 要更新的键值对,kv_string-JSON字符串类型
18 | string kv_string = 1;
19 | }
20 |
21 | // 更新kv响应消息格式
22 | message UpdateKeyValueResponse {
23 | // 更新个数,cnt-整型
24 | uint32 update_cnt = 1;
25 | }
26 |
27 | // 查询kv请求消息格式
28 | message SearchKeyValueRequest {
29 | // 键值对的关键字,key-字符串类型
30 | string key = 1;
31 | }
32 |
33 | // 查询kv响应消息格式
34 | message SearchKeyValueResponse {
35 | // 查询到的键值对,kv_string-JSON字符串类型
36 | string kv_string = 1;
37 | }
38 |
39 | // 删除kv请求消息格式
40 | message DeleteKeyValueRequest {
41 | // 键值对的关键字,key-字符串类型
42 | string key = 1;
43 | }
44 |
45 | // 删除kv响应消息格式
46 | message DeleteKeyValueResponse {
47 | // 删除个数,cnt-整型
48 | uint32 delete_cnt = 1;
49 | }
50 |
--------------------------------------------------------------------------------
/sdcs_grpc/simple_dcs/sdcs/cache_node/sdcs_pb2.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by the protocol buffer compiler. DO NOT EDIT!
3 | # source: sdcs.proto
4 | """Generated protocol buffer code."""
5 | from google.protobuf import descriptor as _descriptor
6 | from google.protobuf import descriptor_pool as _descriptor_pool
7 | from google.protobuf import symbol_database as _symbol_database
8 | from google.protobuf.internal import builder as _builder
9 | # @@protoc_insertion_point(imports)
10 |
11 | _sym_db = _symbol_database.Default()
12 |
13 |
14 |
15 |
16 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\nsdcs.proto\x12\x04sdcs\"*\n\x15UpdateKeyValueRequest\x12\x11\n\tkv_string\x18\x01 \x01(\t\",\n\x16UpdateKeyValueResponse\x12\x12\n\nupdate_cnt\x18\x01 \x01(\r\"$\n\x15SearchKeyValueRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\"+\n\x16SearchKeyValueResponse\x12\x11\n\tkv_string\x18\x01 \x01(\t\"$\n\x15\x44\x65leteKeyValueRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\",\n\x16\x44\x65leteKeyValueResponse\x12\x12\n\ndelete_cnt\x18\x01 \x01(\r2\xf8\x01\n\tCacheNode\x12M\n\x0eUpdateKeyValue\x12\x1b.sdcs.UpdateKeyValueRequest\x1a\x1c.sdcs.UpdateKeyValueResponse\"\x00\x12M\n\x0eSearchKeyValue\x12\x1b.sdcs.SearchKeyValueRequest\x1a\x1c.sdcs.SearchKeyValueResponse\"\x00\x12M\n\x0e\x44\x65leteKeyValue\x12\x1b.sdcs.DeleteKeyValueRequest\x1a\x1c.sdcs.DeleteKeyValueResponse\"\x00\x62\x06proto3')
17 |
18 | _globals = globals()
19 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
20 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'sdcs_pb2', _globals)
21 | if _descriptor._USE_C_DESCRIPTORS == False:
22 | DESCRIPTOR._options = None
23 | _globals['_UPDATEKEYVALUEREQUEST']._serialized_start=20
24 | _globals['_UPDATEKEYVALUEREQUEST']._serialized_end=62
25 | _globals['_UPDATEKEYVALUERESPONSE']._serialized_start=64
26 | _globals['_UPDATEKEYVALUERESPONSE']._serialized_end=108
27 | _globals['_SEARCHKEYVALUEREQUEST']._serialized_start=110
28 | _globals['_SEARCHKEYVALUEREQUEST']._serialized_end=146
29 | _globals['_SEARCHKEYVALUERESPONSE']._serialized_start=148
30 | _globals['_SEARCHKEYVALUERESPONSE']._serialized_end=191
31 | _globals['_DELETEKEYVALUEREQUEST']._serialized_start=193
32 | _globals['_DELETEKEYVALUEREQUEST']._serialized_end=229
33 | _globals['_DELETEKEYVALUERESPONSE']._serialized_start=231
34 | _globals['_DELETEKEYVALUERESPONSE']._serialized_end=275
35 | _globals['_CACHENODE']._serialized_start=278
36 | _globals['_CACHENODE']._serialized_end=526
37 | # @@protoc_insertion_point(module_scope)
38 |
--------------------------------------------------------------------------------
/sdcs_grpc/simple_dcs/sdcs/cache_node/sdcs_pb2_grpc.py:
--------------------------------------------------------------------------------
1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
2 | """Client and server classes corresponding to protobuf-defined services."""
3 | import grpc
4 |
5 | import sdcs_pb2 as sdcs__pb2
6 |
7 |
8 | class CacheNodeStub(object):
9 | """Missing associated documentation comment in .proto file."""
10 |
11 | def __init__(self, channel):
12 | """Constructor.
13 |
14 | Args:
15 | channel: A grpc.Channel.
16 | """
17 | self.UpdateKeyValue = channel.unary_unary(
18 | '/sdcs.CacheNode/UpdateKeyValue',
19 | request_serializer=sdcs__pb2.UpdateKeyValueRequest.SerializeToString,
20 | response_deserializer=sdcs__pb2.UpdateKeyValueResponse.FromString,
21 | )
22 | self.SearchKeyValue = channel.unary_unary(
23 | '/sdcs.CacheNode/SearchKeyValue',
24 | request_serializer=sdcs__pb2.SearchKeyValueRequest.SerializeToString,
25 | response_deserializer=sdcs__pb2.SearchKeyValueResponse.FromString,
26 | )
27 | self.DeleteKeyValue = channel.unary_unary(
28 | '/sdcs.CacheNode/DeleteKeyValue',
29 | request_serializer=sdcs__pb2.DeleteKeyValueRequest.SerializeToString,
30 | response_deserializer=sdcs__pb2.DeleteKeyValueResponse.FromString,
31 | )
32 |
33 |
34 | class CacheNodeServicer(object):
35 | """Missing associated documentation comment in .proto file."""
36 |
37 | def UpdateKeyValue(self, request, context):
38 | """更新kv
39 | """
40 | context.set_code(grpc.StatusCode.UNIMPLEMENTED)
41 | context.set_details('Method not implemented!')
42 | raise NotImplementedError('Method not implemented!')
43 |
44 | def SearchKeyValue(self, request, context):
45 | """查询kv
46 | """
47 | context.set_code(grpc.StatusCode.UNIMPLEMENTED)
48 | context.set_details('Method not implemented!')
49 | raise NotImplementedError('Method not implemented!')
50 |
51 | def DeleteKeyValue(self, request, context):
52 | """删除kv
53 | """
54 | context.set_code(grpc.StatusCode.UNIMPLEMENTED)
55 | context.set_details('Method not implemented!')
56 | raise NotImplementedError('Method not implemented!')
57 |
58 |
59 | def add_CacheNodeServicer_to_server(servicer, server):
60 | rpc_method_handlers = {
61 | 'UpdateKeyValue': grpc.unary_unary_rpc_method_handler(
62 | servicer.UpdateKeyValue,
63 | request_deserializer=sdcs__pb2.UpdateKeyValueRequest.FromString,
64 | response_serializer=sdcs__pb2.UpdateKeyValueResponse.SerializeToString,
65 | ),
66 | 'SearchKeyValue': grpc.unary_unary_rpc_method_handler(
67 | servicer.SearchKeyValue,
68 | request_deserializer=sdcs__pb2.SearchKeyValueRequest.FromString,
69 | response_serializer=sdcs__pb2.SearchKeyValueResponse.SerializeToString,
70 | ),
71 | 'DeleteKeyValue': grpc.unary_unary_rpc_method_handler(
72 | servicer.DeleteKeyValue,
73 | request_deserializer=sdcs__pb2.DeleteKeyValueRequest.FromString,
74 | response_serializer=sdcs__pb2.DeleteKeyValueResponse.SerializeToString,
75 | ),
76 | }
77 | generic_handler = grpc.method_handlers_generic_handler(
78 | 'sdcs.CacheNode', rpc_method_handlers)
79 | server.add_generic_rpc_handlers((generic_handler,))
80 |
81 |
82 | # This class is part of an EXPERIMENTAL API.
83 | class CacheNode(object):
84 | """Missing associated documentation comment in .proto file."""
85 |
86 | @staticmethod
87 | def UpdateKeyValue(request,
88 | target,
89 | options=(),
90 | channel_credentials=None,
91 | call_credentials=None,
92 | insecure=False,
93 | compression=None,
94 | wait_for_ready=None,
95 | timeout=None,
96 | metadata=None):
97 | return grpc.experimental.unary_unary(request, target, '/sdcs.CacheNode/UpdateKeyValue',
98 | sdcs__pb2.UpdateKeyValueRequest.SerializeToString,
99 | sdcs__pb2.UpdateKeyValueResponse.FromString,
100 | options, channel_credentials,
101 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
102 |
103 | @staticmethod
104 | def SearchKeyValue(request,
105 | target,
106 | options=(),
107 | channel_credentials=None,
108 | call_credentials=None,
109 | insecure=False,
110 | compression=None,
111 | wait_for_ready=None,
112 | timeout=None,
113 | metadata=None):
114 | return grpc.experimental.unary_unary(request, target, '/sdcs.CacheNode/SearchKeyValue',
115 | sdcs__pb2.SearchKeyValueRequest.SerializeToString,
116 | sdcs__pb2.SearchKeyValueResponse.FromString,
117 | options, channel_credentials,
118 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
119 |
120 | @staticmethod
121 | def DeleteKeyValue(request,
122 | target,
123 | options=(),
124 | channel_credentials=None,
125 | call_credentials=None,
126 | insecure=False,
127 | compression=None,
128 | wait_for_ready=None,
129 | timeout=None,
130 | metadata=None):
131 | return grpc.experimental.unary_unary(request, target, '/sdcs.CacheNode/DeleteKeyValue',
132 | sdcs__pb2.DeleteKeyValueRequest.SerializeToString,
133 | sdcs__pb2.DeleteKeyValueResponse.FromString,
134 | options, channel_credentials,
135 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
136 |
--------------------------------------------------------------------------------
/sdcs_grpc/simple_dcs/sdcs/requirements.txt:
--------------------------------------------------------------------------------
1 | Flask==3.0.0
2 | grpcio==1.59.0
3 | grpcio-tools==1.59.0
4 | protobuf==4.24.4
5 |
6 |
--------------------------------------------------------------------------------
/sdcs_grpc/simple_dcs/sdcs/sources.list:
--------------------------------------------------------------------------------
1 | # 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释
2 |
3 | # ARM架构镜像源地址
4 | deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ focal main restricted universe multiverse
5 | deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ focal-updates main restricted universe multiverse
6 | deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ focal-backports main restricted universe multiverse
7 | deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ focal-security main restricted universe multiverse
8 |
9 | # x86架构镜像源地址
10 | #deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal main restricted universe multiverse
11 | #deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-updates main restricted universe multiverse
12 | #deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-backports main restricted universe multiverse
13 | #deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-security main restricted universe multiverse
--------------------------------------------------------------------------------
/sdcs_http/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/sdcs_http/.idea/.name:
--------------------------------------------------------------------------------
1 | sdcs_http
--------------------------------------------------------------------------------
/sdcs_http/.idea/SDCS.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/sdcs_http/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/sdcs_http/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/sdcs_http/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/sdcs_http/.idea/sdcs_http.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/sdcs_http/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/sdcs_http/app.py:
--------------------------------------------------------------------------------
1 | from flask import Flask
2 | from flask import request
3 | import json, requests, hashlib
4 |
5 | app = Flask(__name__)
6 |
7 | # 键值对存储缓存空间
8 | cache_dict = {}
9 | # 服务器节点地址
10 | server_url = ["http://node1:5000", "http://node2:5000", "http://node3:5000"]
11 | # 节点个数
12 | server_cnt = 3
13 | # 节点序号
14 | # server_id = 0
15 |
16 |
17 | # 写入/更新缓存请求
18 | @app.route('/', methods=['POST'])
19 | def update_cache(): # put application's code here
20 | request_data = request.json
21 | print("[update request] param: " + json.dumps(request_data)) # 打印请求参数
22 |
23 | # 构建存放空间
24 | dict_kv_list_to_update = []
25 | for i in range(0, server_cnt):
26 | dict_kv_list_to_update_index = {}
27 | dict_kv_list_to_update.append(dict_kv_list_to_update_index)
28 |
29 | # 遍历键值对,计算哈希值
30 | dict_kv_list = []
31 | index = 0
32 | for kv in request_data.items():
33 | dict_kv_list.append(kv)
34 | for i in range(len(dict_kv_list)):
35 | key = dict_kv_list[i][0]
36 | value = dict_kv_list[i][1]
37 | kv = {key: value}
38 | print(kv)
39 | # 计算哈希值
40 | md5 = hashlib.md5() # 创建MD5哈希对象
41 | md5.update(str(key).encode()) # 传入要计算哈希值的数据
42 | hash_code = md5.hexdigest() # 计算出哈希值
43 | hash_value = 0
44 | for c in hash_code:
45 | hash_value = hash_value + ord(c)
46 | index = hash_value % server_cnt
47 | print(key + ": hashCode = " + hash_code + ", index = " + str(index))
48 |
49 | dict_kv_list_to_update[index][key] = value
50 | index = 0
51 |
52 | # 写入/更新至节点
53 | for index in range(0, server_cnt):
54 | if len(dict_kv_list_to_update[index]) == 0:
55 | break
56 | # 构建请求地址
57 | request_url = server_url[index] + "/save_to_cache"
58 | print("request to " + request_url)
59 | # 构建请求参数
60 | request_json = json.dumps(dict_kv_list_to_update[index])
61 | # 解析响应
62 | headers = {"Content-Type": "application/json", "Accept-Charset": "UTF-8"}
63 | response = requests.post(request_url, request_json, headers=headers)
64 | if response.status_code != 200:
65 | update_result = "server id = " + str(index) + " error!"
66 | print(update_result)
67 | return update_result, 400
68 | print("server id = " + str(index) + " response: \n" + response.text)
69 |
70 | return "update success!"
71 |
72 |
73 | # 写入/更新至缓存
74 | @app.route('/save_to_cache', methods=['POST'])
75 | def save_to_cache():
76 | request_data = request.json
77 | print("[update to cache request] param: " + json.dumps(request_data)) # 打印请求参数
78 |
79 | dict_kv_list = []
80 | for kv in request_data.items():
81 | dict_kv_list.append(kv)
82 | for i in range(len(dict_kv_list)):
83 | key = dict_kv_list[i][0]
84 | value = dict_kv_list[i][1]
85 | kv = {key: value}
86 | cache_dict.update(kv)
87 | print(cache_dict)
88 |
89 | return "[update response] Total length = " + str(len(cache_dict))
90 |
91 |
92 | # 读取缓存请求
93 | @app.route('/', methods=['GET'])
94 | def get_cache(key):
95 | print("[get request] param: " + key)
96 |
97 | md5 = hashlib.md5() # 创建md5对象
98 | md5.update(str(key).encode()) # 传入要计算哈希值的数据
99 | hash_code = md5.hexdigest()
100 | # 累加hash_code各位的ASCII码值
101 | hash_value = 0
102 | for c in hash_code:
103 | hash_value = hash_value + ord(c)
104 | # 计算查询节点位置
105 | index = hash_value % server_cnt
106 | index = 0
107 |
108 | # 构建请求地址
109 | request_url = server_url[index] + "/search_from_cache"
110 | print("request to " + request_url)
111 | # 构建请求参数
112 | request_data = {"key": key}
113 | request_json = json.dumps(request_data)
114 | # 解析响应
115 | headers = {"Content-Type": "application/json", "Accept-Charset": "UTF-8"}
116 | response = requests.post(request_url, request_json, headers=headers)
117 | if response.status_code == 200:
118 | print("server id = " + str(index) + " response: \n" + response.text)
119 | response_dict = json.loads(response.text)
120 | if response_dict:
121 | return response_dict
122 | else:
123 | print("[get request] get None!")
124 | return "", 404
125 | else:
126 | print("search server id = " + str(index) + " error!")
127 | return "", 404
128 |
129 |
130 | # 从缓存中读取
131 | @app.route('/search_from_cache', methods=['POST'])
132 | def search_from_cache():
133 | request_data = request.json
134 | print("[search from cache request] param: " + json.dumps(request_data)) # 打印请求参数
135 |
136 | key = request_data.get("key")
137 | # 查询本地缓存
138 | key_value = search_kv(key)
139 | if key_value:
140 | print("[get request] get k-v = " + key_value)
141 | return key_value
142 | else:
143 | return {}
144 |
145 |
146 | # 查询本地缓存中的键值对
147 | def search_kv(key):
148 | if key in cache_dict.keys():
149 | value = cache_dict.get(key)
150 | get_dict = {key: value}
151 | get_kv = json.dumps(get_dict)
152 | print("[search local key-value] get k-v = " + get_kv)
153 | return get_kv
154 | else:
155 | print("[search local key-value] get None!")
156 | return None
157 |
158 |
159 | # 删除缓存请求
160 | @app.route('/', methods=['DELETE'])
161 | def delete_cache(key):
162 | print("[delete request] param: " + key)
163 |
164 | md5 = hashlib.md5() # 创建md5对象
165 | md5.update(str(key).encode()) # 传入要计算哈希值的数据
166 | hash_code = md5.hexdigest()
167 | # 累加hash_code各位的ASCII码值
168 | hash_value = 0
169 | for c in hash_code:
170 | hash_value = hash_value + ord(c)
171 | # 计算查询节点位置
172 | index = hash_value % server_cnt
173 | index = 0
174 |
175 | # 构建请求地址
176 | request_url = server_url[index] + "/delete_from_cache"
177 | print("request to " + request_url)
178 | # 构建请求参数
179 | request_data = {"key": key}
180 | request_json = json.dumps(request_data)
181 | # 解析响应
182 | headers = {"Content-Type": "application/json", "Accept-Charset": "UTF-8"}
183 | response = requests.post(request_url, request_json, headers=headers)
184 | if response.status_code == 200:
185 | print("server id = " + str(index) + " response: \n" + response.text)
186 | delete_cnt = response.text
187 | return delete_cnt
188 | else:
189 | print("delete server id = " + str(index) + " error!")
190 | return "", 404
191 |
192 |
193 | @app.route('/delete_from_cache', methods=['POST'])
194 | def delete_from_cache():
195 | request_data = request.json
196 | print("[delete from cache request] param: " + json.dumps(request_data)) # 打印请求参数
197 |
198 | key = request_data.get("key")
199 | delete_cnt = 0 # 删除计数器
200 | value = None
201 | if key in cache_dict.keys():
202 | value = cache_dict.pop(key)
203 | delete_cnt = delete_cnt + 1
204 |
205 | # 打印删除的键值对
206 | delete_dict = {key: value}
207 | delete_kv = json.dumps(delete_dict)
208 | print("[delete response] delete k-v: " + delete_kv + ", delete_cnt = " + str(delete_cnt))
209 | return str(delete_cnt)
210 |
211 |
212 | if __name__ == '__main__':
213 | app.run()
214 | # docker中的启动设置
215 | # app.run(host='0.0.0.0', port=5000)
216 |
--------------------------------------------------------------------------------
/sdcs_http/sdcs/dir/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:20.04
2 | WORKDIR /
3 |
4 | COPY ./requirements.txt /requirements.txt
5 |
6 | RUN sed -i s@/archive.ubuntu.com/@/mirrors.ustc.edu.cn/@g /etc/apt/sources.list \
7 | && apt-get update -y \
8 | && apt-get install -y python3-pip python3-dev \
9 | && pip3 install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
10 |
11 | COPY . .
12 |
13 | CMD flask --app app.py run --host=0.0.0.0 --port=5000
14 |
--------------------------------------------------------------------------------
/sdcs_http/sdcs/dir/app.py:
--------------------------------------------------------------------------------
1 | from flask import Flask
2 | from flask import request
3 | import json, requests, hashlib
4 |
5 | app = Flask(__name__)
6 |
7 | # 键值对存储缓存空间
8 | cache_dict = {}
9 | # 服务器节点地址
10 | server_url = ["http://node1:5000", "http://node2:5000", "http://node3:5000"]
11 | # 节点个数
12 | server_cnt = 3
13 | # 节点序号
14 | # server_id = 0
15 |
16 |
17 | # 写入/更新缓存请求
18 | @app.route('/', methods=['POST'])
19 | def update_cache(): # put application's code here
20 | request_data = request.json
21 | print("[update request] param: " + json.dumps(request_data)) # 打印请求参数≤
22 |
23 | # 构建存放空间
24 | dict_kv_list_to_update = []
25 | for i in range(0, server_cnt):
26 | dict_kv_list_to_update_index = {}
27 | dict_kv_list_to_update.append(dict_kv_list_to_update_index)
28 |
29 | # 遍历键值对,计算哈希值
30 | dict_kv_list = []
31 | index = 0
32 | for kv in request_data.items():
33 | dict_kv_list.append(kv)
34 | for i in range(len(dict_kv_list)):
35 | key = dict_kv_list[i][0]
36 | value = dict_kv_list[i][1]
37 | kv = {key: value}
38 | print(kv)
39 | # 计算哈希值
40 | md5 = hashlib.md5() # 创建MD5哈希对象
41 | md5.update(str(key).encode()) # 传入要计算哈希值的数据
42 | hash_code = md5.hexdigest() # 计算出哈希值
43 | hash_value = 0
44 | for c in hash_code:
45 | hash_value = hash_value + ord(c)
46 | index = hash_value % server_cnt
47 | print(key + ": hashCode = " + hash_code + ", index = " + str(index))
48 |
49 | dict_kv_list_to_update[index][key] = value
50 | index = 0 # index重置
51 |
52 | # 写入/更新至节点
53 | for j in range(0, server_cnt):
54 | if len(dict_kv_list_to_update[j]) == 0:
55 | break
56 | # 构建请求地址
57 | request_url = server_url[j] + "/save_to_cache"
58 | print("request to " + request_url)
59 | # 构建请求参数
60 | request_json = json.dumps(dict_kv_list_to_update[j])
61 | # 解析响应
62 | headers = {"Content-Type": "application/json", "Accept-Charset": "UTF-8"}
63 | response = requests.post(request_url, request_json, headers=headers)
64 | if response.status_code != 200:
65 | update_result = "server id = " + str(j) + " error!"
66 | print(update_result)
67 | return update_result, 400
68 | print("server id = " + str(j) + " response: \n" + response.text)
69 |
70 | return "update success!"
71 |
72 |
73 | # 写入/更新至缓存
74 | @app.route('/save_to_cache', methods=['POST'])
75 | def save_to_cache():
76 | request_data = request.json
77 | print("[update to cache request] param: " + json.dumps(request_data)) # 打印请求参数
78 |
79 | dict_kv_list = []
80 | for kv in request_data.items():
81 | dict_kv_list.append(kv)
82 | for i in range(len(dict_kv_list)):
83 | key = dict_kv_list[i][0]
84 | value = dict_kv_list[i][1]
85 | kv = {key: value}
86 | cache_dict.update(kv)
87 | print(cache_dict)
88 |
89 | return "[update response] Total length = " + str(len(cache_dict))
90 |
91 |
92 | # 读取缓存请求
93 | @app.route('/', methods=['GET'])
94 | def get_cache(key):
95 | print("[get request] param: " + key)
96 |
97 | md5 = hashlib.md5() # 创建md5对象
98 | md5.update(str(key).encode()) # 传入要计算哈希值的数据
99 | hash_code = md5.hexdigest()
100 | # 累加hash_code各位的ASCII码值
101 | hash_value = 0
102 | for c in hash_code:
103 | hash_value = hash_value + ord(c)
104 | # 计算查询节点位置
105 | index = hash_value % server_cnt
106 |
107 | # 构建请求地址
108 | request_url = server_url[index] + "/search_from_cache"
109 | print("request to " + request_url)
110 | # 构建请求参数
111 | request_data = {"key": key}
112 | request_json = json.dumps(request_data)
113 | # 解析响应
114 | headers = {"Content-Type": "application/json", "Accept-Charset": "UTF-8"}
115 | response = requests.post(request_url, request_json, headers=headers)
116 | if response.status_code == 200:
117 | print("server id = " + str(index) + " response: \n" + response.text)
118 | response_dict = json.loads(response.text)
119 | if response_dict:
120 | return response_dict
121 | else:
122 | print("[get request] get None!")
123 | return "", 404
124 | else:
125 | print("search server id = " + str(index) + " error!")
126 | return "", 404
127 |
128 |
129 | # 从缓存中读取
130 | @app.route('/search_from_cache', methods=['POST'])
131 | def search_from_cache():
132 | request_data = request.json
133 | print("[search from cache request] param: " + json.dumps(request_data)) # 打印请求参数
134 |
135 | key = request_data.get("key")
136 | # 查询本地缓存
137 | key_value = search_kv(key)
138 | if key_value:
139 | print("[get request] get k-v = " + key_value)
140 | return key_value
141 | else:
142 | return {}
143 |
144 |
145 | # 查询本地缓存中的键值对
146 | def search_kv(key):
147 | if key in cache_dict.keys():
148 | value = cache_dict.get(key)
149 | get_dict = {key: value}
150 | get_kv = json.dumps(get_dict)
151 | print("[search local key-value] get k-v = " + get_kv)
152 | return get_kv
153 | else:
154 | print("[search local key-value] get None!")
155 | return None
156 |
157 |
158 | # 删除缓存请求
159 | @app.route('/', methods=['DELETE'])
160 | def delete_cache(key):
161 | print("[delete request] param: " + key)
162 |
163 | md5 = hashlib.md5() # 创建md5对象
164 | md5.update(str(key).encode()) # 传入要计算哈希值的数据
165 | hash_code = md5.hexdigest()
166 | # 累加hash_code各位的ASCII码值
167 | hash_value = 0
168 | for c in hash_code:
169 | hash_value = hash_value + ord(c)
170 | # 计算查询节点位置
171 | index = hash_value % server_cnt
172 |
173 | # 构建请求地址
174 | request_url = server_url[index] + "/delete_from_cache"
175 | print("request to " + request_url)
176 | # 构建请求参数
177 | request_data = {"key": key}
178 | request_json = json.dumps(request_data)
179 | # 解析响应
180 | headers = {"Content-Type": "application/json", "Accept-Charset": "UTF-8"}
181 | response = requests.post(request_url, request_json, headers=headers)
182 | if response.status_code == 200:
183 | print("server id = " + str(index) + " response: \n" + response.text)
184 | delete_cnt = response.text
185 | return delete_cnt
186 | else:
187 | print("delete server id = " + str(index) + " error!")
188 | return "", 404
189 |
190 |
191 | @app.route('/delete_from_cache', methods=['POST'])
192 | def delete_from_cache():
193 | request_data = request.json
194 | print("[delete from cache request] param: " + json.dumps(request_data)) # 打印请求参数
195 |
196 | key = request_data.get("key")
197 | delete_cnt = 0 # 删除计数器
198 | value = None
199 | if key in cache_dict.keys():
200 | value = cache_dict.pop(key)
201 | delete_cnt = delete_cnt + 1
202 |
203 | # 打印删除的键值对
204 | delete_dict = {key: value}
205 | delete_kv = json.dumps(delete_dict)
206 | print("[delete response] delete k-v: " + delete_kv + ", delete_cnt = " + str(delete_cnt))
207 | return str(delete_cnt)
208 |
209 |
210 | if __name__ == '__main__':
211 | # docker中的启动设置
212 | app.run()
213 |
--------------------------------------------------------------------------------
/sdcs_http/sdcs/dir/requirements.txt:
--------------------------------------------------------------------------------
1 | Flask==2.3.3
2 | requests==2.31.0
--------------------------------------------------------------------------------
/sdcs_http/sdcs/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | node1:
5 | build:
6 | context: ./dir
7 | dockerfile: Dockerfile
8 | ports:
9 | - "9527:5000"
10 | tty: true
11 |
12 | node2:
13 | build:
14 | context: ./dir
15 | dockerfile: Dockerfile
16 | ports:
17 | - "9528:5000"
18 | tty: true
19 |
20 | node3:
21 | build:
22 | context: ./dir
23 | dockerfile: Dockerfile
24 | ports:
25 | - "9529:5000"
26 | tty: true
--------------------------------------------------------------------------------
/sdcs_http/test-shell/sdcs-testsuit/README.md:
--------------------------------------------------------------------------------
1 | # sdcs-testsuit
2 | Simple test for SDCS
3 |
4 | ```sh
5 | ./sdcs_http-test.sh {cache_server_number}
6 | ```
7 |
8 | ## Todo
9 | - [ ] More reasonable correctness tests (e.g., get those deleted keys explicitely.)
10 | - [ ] Evaluate return value.
11 | - [ ] Real performance test (by jmeter?)
12 | - [ ] Better command line argument processing and help.
13 |
--------------------------------------------------------------------------------
/sdcs_http/test-shell/sdcs-testsuit/sdcs-test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [[ $# -ne 1 ]]; then
4 | echo "Usage:"
5 | echo "$0 {cache server number}"
6 | exit 1
7 | fi
8 |
9 | cs_num=$1
10 |
11 | # TODO: should also set upperbound next year.
12 | [[ $cs_num -le 2 ]] && {
13 | echo "Error: cache server should be more than 3 ($cs_num provided)"
14 | exit 2
15 | }
16 |
17 | PORT_BASE=9526
18 | HOST_BASE=127.0.0.1
19 | MAX_ITER=500
20 |
21 | function get_cs() {
22 | port=$(( $PORT_BASE + $(shuf -i 1-$cs_num -n 1) ))
23 | echo http://$HOST_BASE:$port
24 | }
25 |
26 | function get_key() {
27 | echo "key-$(shuf -i 1-$MAX_ITER -n 1)"
28 | }
29 |
30 | function test_set() {
31 | local i=1
32 | while [[ $i -le $MAX_ITER ]]; do
33 | curl -XPOST -H "Content-type: application/json" -d "{\"key-$i\": \"value $i\"}" $(get_cs)
34 | ((i++))
35 | done
36 | }
37 |
38 | function test_get() {
39 | local count=$(( MAX_ITER / 10 ))
40 | local i=0
41 | while [[ $i -lt $count ]]; do
42 | curl $(get_cs)/$(get_key)
43 | ((i++))
44 | done
45 | }
46 |
47 | function test_delete() {
48 | local count=$(( MAX_ITER / 10 * 9 ))
49 | local i=0
50 | while [[ $i -lt $count ]]; do
51 | curl -XDELETE $(get_cs)/$(get_key)
52 | ((i++))
53 | done
54 | }
55 |
56 | test_set
57 | test_get
58 | test_set
59 | test_delete
60 | test_get
61 |
--------------------------------------------------------------------------------
/sdcs_http/test-shell/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cs_num=$1
4 | HOST_BASE=127.0.0.1
5 | PORT_BASE=9526
6 |
7 |
8 | port=$(( $PORT_BASE + $(shuf -i 1-$cs_num -n 1) ))
9 | echo http://$HOST_BASE:$port
--------------------------------------------------------------------------------
/sdcs_http/test-shell/test2.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cs_num=$1
4 | HOST_BASE=127.0.0.1
5 | PORT_BASE=9526
6 |
7 | port=$(( $PORT_BASE + $(shuf -i 1-cs_num -n 1) ))
8 | echo http://$HOST_BASE:$port
--------------------------------------------------------------------------------