推荐系统如何提高线上高并发推荐服务

本文详细介绍了推荐系统线上服务的复杂功能,包括与离线模型交互、数据库操作及业务逻辑处理。高并发服务通过负载均衡、缓存和降级机制来保证稳定性和效率。负载均衡利用Nginx分发任务,缓存减少计算压力,降级机制在故障时启用简单服务。推荐服务框架Jetty因其轻量级和高效成为Java环境的理想选择,文中还展示了使用Jetty搭建推荐API服务器的示例代码。
摘要由CSDN通过智能技术生成

推荐系统线上服务功能有什么

  • 推荐系统架构图
    在这里插入图片描述
  • 红色区域为线上服务,线上服务模块的功能非常繁杂,它不仅需要跟离线训练好的模型打交道,将离线模型上线,在线进行模型服务;还需要跟数据库打交道,把候选物品和离线处理好的特征载入到服务器上。
  • 线上服务器内部的逻辑层也复杂,不仅包含了一些经典的过程,比如召回层和排序层,还包括一些业务逻辑:推荐结果多样性,流行度等补充策略,甚至还有线上AB测试相关的测试代码。

高并发推荐服务的整体架构有什么

  • 高并发推荐服务的整体架构主要由三个重要机制支撑:负载均衡、缓存、推荐服务降级机制

负载均衡

  • 负载均衡是整个推荐服务能够实现高可用、可扩展的基础。当推荐服务支持的业务量达到一定规模的时候,单靠一台服务器肯定是不行的,无论这台服务器的性能有多强大,都不可能独立支撑起高 QPS(Queries Per Second,每秒查询次数)的需求。这时候,我们就需要增加服务器来分担单节点的压力。既然有多个节点在运行,这时我们就需要一个负载均衡服务器来分配任务,以达到按能力分配和高效率分配的目的。
  • 在实际工程中,负载均衡服务器往往采用非常高效的Nginx作为技术选型,工业级甚至会专门采用硬件级负载均衡(F5等)设备作为解决方案。

在这里插入图片描述

缓存

  • 基于深度学习的推荐过程往往是比较复杂的,当候选物品规模比较大的时候,产生推荐列表的过程其实非常消耗计算资源,服务器的计算量非常大。这个时候,我们就可以通过减少“硬算”推荐结果的次数来给推荐服务器减负,此时我们就需要用到缓存来解决此类现象。
  • 当同一个用户多次请求同样的推荐服务时,我们就可以在第一次请求时把 TA 的推荐结果缓存起来,在后续请求时直接返回缓存中的结果就可以了,不用再通过复杂的推荐逻辑重新算一遍。但对于新用户来说,因为他们几乎没有行为历史的记录,所以我们可以先按照一些规则预先缓存好几类新用户的推荐列表,等遇到新用户的时候就直接返回(冷启动)。
  • 合理的缓存策略甚至能够阻挡掉 90% 以上的推荐请求,大大减小推荐服务器的计算压力。

推荐服务降级机制

  • 不管再强大的服务集群,再有效的缓存方案,也都有可能遭遇特殊时刻的流量洪峰或者软硬件故障。在这种特殊情况下,为了防止推荐服务彻底熔断崩溃,甚至造成相关微服务依次崩溃的“雪崩效应”,我们就要在第一时间将问题控制在推荐服务内部,而应对的最好机制就是服务降级。
  • 服务降级就是抛弃原本的复杂逻辑,采用最保险、最简单、最不消耗资源的降级服务来渡过特殊时期。对于推荐服务来说,我们可以抛弃原本的复杂推荐模型,采用基于规则的推荐方法来生成推荐列表,甚至直接在缓存或者内存中提前准备好应对故障时的默认推荐列表,做到“0”计算产出服务结果,这些都是服务降级的可行策略。

总之,负载均衡提升服务能力,缓存降低服务压力,服务降级机制保证故障时刻的服务不崩溃,压力不传导。

推荐系统线上服务框架Jetty

  • 为什么要选用Jetty?

    • 相比于py服务器效率和c++维护难度,Java权衡利弊,扩展性好。
    • 相比于Tomcat,Jetty是嵌入式、轻量级的,可以专注于建立高效的API推荐服务。
    • 相比于Go,Node.js,技术成熟应用范围广。
  • Jetty的最大优势是除了Java环境外,不需配置任何其他环境,也不用安装额外的软件依赖,直接在Java程序中创建对外服务的HTTP API,之后在IDE中运行或者打Jar包运行就可以了。

  • Sparrow Recsys开源项目创建推荐服务器代码

public class RecSysServer {
    //主函数,创建推荐服务器并运行
    public static void main(String[] args) throws Exception {
        new RecSysServer().run();
    }
    //推荐服务器的默认服务端口6010
    private static final int DEFAULT_PORT = 6010;


    //运行推荐服务器的函数
    public void run() throws Exception{
        int port = DEFAULT_PORT;
        //绑定IP地址和端口,0.0.0.0代表本地运行
        InetSocketAddress inetAddress = new InetSocketAddress("0.0.0.0", port);
        //创建Jetty服务器
        Server server = new Server(inetAddress);
        //创建Jetty服务器的环境handler
        ServletContextHandler context = new ServletContextHandler();
        context.setContextPath("/");
        context.setWelcomeFiles(new String[] { "index.html" });


        //添加API,getMovie,获取电影相关数据
        context.addServlet(new ServletHolder(new MovieService()), "/getmovie");
        //添加API,getuser,获取用户相关数据
        context.addServlet(new ServletHolder(new UserService()), "/getuser");
        //添加API,getsimilarmovie,获取相似电影推荐
        context.addServlet(new ServletHolder(new SimilarMovieService()), "/getsimilarmovie");
        //添加API,getrecommendation,获取各类电影推荐
        context.addServlet(new ServletHolder(new RecommendationService()), "/getrecommendation");
        //设置Jetty的环境handler
        server.setHandler(context);


        //启动Jetty服务器
        server.start();
        server.join();
    }
  • Jetty中的Servlet服务相关代码

//MovieService需要继承Jetty的HttpServlet
public class MovieService extends HttpServlet {
    //实现servlet中的get method
    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response) throws IOException {
        try {
            //该接口返回json对象,所以设置json类型
            response.setContentType("application/json");
            response.setStatus(HttpServletResponse.SC_OK);
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Access-Control-Allow-Origin", "*");
            
            //获得请求中的id参数,转换为movie id
            String movieId = request.getParameter("id");
            //从数据库中获取该movie的数据对象
            Movie movie = DataManager.getInstance().getMovieById(Integer.parseInt(movieId));


            if (null != movie) {
                //使用fasterxml.jackson库把movie对象转换成json对象
                ObjectMapper mapper = new ObjectMapper();
                String jsonMovie = mapper.writeValueAsString(movie);
                //返回json对象
                response.getWriter().println(jsonMovie);
            }else {
                response.getWriter().println("");
            }


        } catch (Exception e) {
            e.printStackTrace();
            response.getWriter().println("");
        }
    }
  • 验证测试:在浏览器输入:http://localhost:6010/getmovie?id=1,就可以看到getMovie接口的返回对象了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值