├── .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 | 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 | 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 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /sdcs_http/.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 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 --------------------------------------------------------------------------------