Flask首页新闻推荐与RPC

Flask首页新闻推荐与RPC

  1. RPC应用场景

    RPC更多的面向产品内部服务器间的通讯,目的时高效,不注重通用。

  2. 选用RPC原因
    推荐系统:
    	作用:根据不同的用户id,经过计算,返回推荐的文章id
    
    现在需要解决的问题是:
    	web应用程序如何跟推荐系统通讯。
    
    方案一:
    	把推荐系统封装成python语言能直接调用的类或者函数,直接调用。
    	缺点:耦合性高,维护困难
    方案二:
    	使用Flask或者其他web框架,把推荐系统编成web应用,通过接口调用。
    	优点:通用,http协议标准的协议
    	缺点:效率低,成本高
    方案三:
    	使用rpc调用
    	优点:效率高,采用二进制数据直接传递(相对于http),耦合低(相对于封装成函数或者类)
    	缺点:不通用,需要专门的客户端与服务器能够支持特定的二进制协议
    
  3. 使用Protobuf 定义的接口

    新建reco.proto文件

    syntax = "proto3";
    
    message UserRequest {
        string user_id=1;
        int32 channel_id=2;
        int32 article_num=3;
        int64 time_stamp=4;
    }
    
    message Track {
        string click=1;
        string collect=2;
        string share=3;
        string read=4;
    }
    
    message Article {
        int64 article_id=1;
        Track track=2;
    }
    
    message ArticleResponse {
        string exposure=1;
        int64 time_stamp=2;
        repeated Article recommends=3;
    }
    
    service UserRecommend {
        rpc user_recommend(UserRequest) returns(ArticleResponse) {}
    }
    
  4. 代码生成

    编译生成代码

    python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. reco.proto
    
    • -I表示搜索proto文件中被导入文件的目录
    • --python_out表示保存生成Python文件的目录,生成的文件中包含接口定义中的数据类型
    • --grpc_python_out表示保存生成Python文件的目录,生成的文件中包含接口定义中的服务类型

    在rpc目录下执行上述命令,会自动生成如下两个rpc调用辅助代码模块:

    • reco_pb2.py 保存根据接口定义文件中的数据类型生成的python类

    • reco_pb2_grpc.py 保存根据接口定义文件中的服务方法类型生成的python调用RPC方法

  5. 服务端
    import reco_pb2
    import reco_pb2_grpc
    import grpc
    from concurrent.futures import ThreadPoolExecutor
    import time
    
    # rpc接口定义中服务对应成Python的类
    class UserRecommendService(reco_pb2_grpc.UserRecommendServicer):
        # 在接口定义的同名方法中补全,被调用时应该执行的逻辑
        def user_recommend(self, request, context):
            # request是调用的请求数据对象
            user_id = request.user_id
            channel_id = request.channel_id
            article_num = request.article_num
            time_stamp = request.time_stamp
    
            response = reco_pb2.ArticleResponse()
            response.exposure = 'exposure param'
            response.time_stamp = round(time.time()*1000)
            recommends = []
            for i in range(article_num):
                article = reco_pb2.Article()
                article.track.click = 'click param {}'.format(i+1)
                article.track.collect = 'collect param {}'.format(i+1)
                article.track.share = 'share param {}'.format(i+1)
                article.track.read = 'read param {}'.format(i+1)
                article.article_id = i+1
                recommends.append(article)
            response.recommends.extend(recommends)
    
            # 最终要返回一个调用结果
            return response
    
    def serve():
        """
        rpc服务端启动方法
        """
        # 创建一个rpc服务器
        server = grpc.server(ThreadPoolExecutor(max_workers=10))
    
        # 向服务器中添加被调用的服务方法
        reco_pb2_grpc.add_UserRecommendServicer_to_server(UserRecommendService(), server)
    
        # 微服务器绑定ip地址和端口
        server.add_insecure_port('127.0.0.1:8888')
    
        # 启动rpc服务
        server.start()
    
        # start()不会阻塞,此处需要加上循环睡眠 防止程序退出
        while True:
            time.sleep(10)
    
    if __name__ == '__main__':
        serve()
    
  6. 客户端
    import grpc
    import reco_pb2
    import reco_pb2_grpc
    import time
    
    
    def feed_articles(stub):
        # 构建rpc调用的调用参数
        user_request = reco_pb2.UserRequest()
        user_request.user_id = '1'
        user_request.channel_id = 1
        user_request.article_num = 10
        user_request.time_stamp = round(time.time()*1000)
    
        # 通过stub进行方法调用,并接收调用返回值
        ret = stub.user_recommend(user_request)
        print('ret={}'.format(ret))
    
    def run():
        """
        rpc客户端调用的方法
        """
        # 使用with语句连接rpc服务器
        with grpc.insecure_channel('127.0.0.1:8888') as channel:
            # 创建调用rpc远端服务的辅助对象stub
            stub = reco_pb2_grpc.UserRecommendStub(channel)
            # 通过stub进行rpc调用
            feed_articles(stub)
    
    if __name__ == '__main__':
        run()
    
  7. 首页新闻推荐接口编写
    from rpc import reco_pb2, reco_pb2_grpc
    
    class ArticleListResource(Resource):
        """
        获取推荐文章列表数据
        """
        def _feed_articles(self, channel_id, timestamp, feed_count):
            """
            获取推荐文章
            :param channel_id: 频道id
            :param feed_count: 推荐数量
            :param timestamp: 时间戳
            :return: [{article_id, trace_params}, ...], timestamp
            """
            # 组装调用gRPC方法的参数
            user_request = reco_pb2.UserRequest()
            user_request.user_id = g.user_id or 'annoy'
            user_request.channel_id = channel_id
            user_request.article_num = feed_count
            user_request.time_stamp = round(time.time() * 1000)
    	   # 使用跟推荐系统通讯的通道,创建存根。
            stub = reco_pb2_grpc.UserRecommendStub(current_app.rpc_reco)
            ret = stub.user_recommend(user_request)
            return ret.recommends, ret.time_stamp
    
        def get(self):
            """
            获取文章列表
            """
            qs_parser = RequestParser()
            qs_parser.add_argument('channel_id', type=parser.channel_id, required=True, location='args')
            qs_parser.add_argument('timestamp', type=inputs.positive, required=True, location='args')
            args = qs_parser.parse_args()
            channel_id = args.channel_id
            timestamp = args.timestamp
            per_page = constants.DEFAULT_ARTICLE_PER_PAGE_MIN
            try:
                feed_time = time.strftime('%Y-%m-%dT%H:%M:%S', time.localtime(time.time()))
            except Exception:
                return {'message': 'timestamp param error'}, 400
    
            results = []
    
            # 获取推荐文章列表
            feeds, pre_timestamp = self._feed_articles(channel_id, timestamp, per_page)
    
            # 查询文章
            for feed in feeds:
                article = cache_article.ArticleInfoCache(feed.article_id).get()
                if article:
                    article['pubdate'] = feed_time
                    article['trace'] = {
                        'click': feed.track.click,
                        'collect': feed.track.collect,
                        'share': feed.track.share,
                        'read': feed.track.read
                    }
                    results.append(article)
    
            return {'pre_timestamp': pre_timestamp, 'results': results}
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值