美文网首页
小结UID替换器的实现

小结UID替换器的实现

作者: 承羿 | 来源:发表于2017-03-11 12:28 被阅读45次

在推荐系统中,我们常需要根据用户的标示(UID)和访问行为给用户生成推荐。但某些情况下,我们需要替换用户请求中的UID为特殊的UID,以测试推荐结果。本周基于这个需求,实现了UID替换器。

功能

首先我们需要做一个管理后台,对我们要替换的UID或者其他ID进行管理,有如下需求:

1 增加,修改,删除UID替换规则
2 UID规则启用/停用
3 UID替换规则在线上生效

以上第三个需求相对难以实现一些。UID替换规则在线上生效,其潜台词是每次修改了替换规则之后,需要其立即在线上生效。对于UID替换规则对象,我们可以用关系数据库,如MySQL存储。

一般的想法是在线上请求过来的时候,依据请求中的UID查询数据库,看是否有相应的规则,如果有则取出替换UID进行替换,如果没有,则略过。这个想法在请求量不大的时候没有问题,但如果是每秒成千上万次请求,MySQL数据库难以抗住,就会出现问题。

这时候我们想到的是把规则数据加载到内存中,线上请求直接从内存中查找,就不需要每个请求都要查找数据库。在管理系统中,如果对规则修改了之后,“UID替换规则在线上生效”就是把修改后的数据重新加载到内存。

这就是“UID替换规则在线上生效”这个需求的真实含义。

其次,我们的线上系统要提供两方面的功能:

1 提供加载MySQL数据到线上内存的接口,方便管理系统调用
2 在请求过来的时候,根据请求中的UID和内存中的规则,实现UID替换

第一个功能,由线上系统暴露一个API,管理系统需要使线上生效时,就调用该API,重新由MySQL对应的规则表加载数据到内存对象中。线上API可以直接访问MySQL数据,也可以由管理系统暴露一个服务,然后线上API调用该服务获取生效规则数据。

第二个功能的实现要求从MySQL中加载出来的数据保存在一个全局的对象中。当然,也可以直接存储在Tair/Redis/Memcache这样的内存数据库中,甚至可以做成一个服务,供其他服务调用。不过此处为了简略,我们实现的版本就是直接放在一个全局的Java的HashMap对象中。然后对线上的Servlet做一个Filter,在Filter中使用全局对象提供的规则数据实现替换逻辑。

在本处我们使用了HashMap作为规则存储对象,不用担心线程安全的问题。HashMap并发读没有问题,并发写会出现问题。但在我们的场景中,写数据场景有二:线上服务启动时,要加载数据到HashMap;我们刷新时,要重新加载数据到HashMap。这两个场景都是单线程去写,所以HashMap堪堪够用,如果实在不放心,可以改用concurrentHashMap。

设计

设计主要有两个方面,一个是数据库表的设计,另一个是HashMap的设计。前者可以这么做:

---- table idmapper
id int 规则ID 自增
name varchar 规则名
source_id varchar 源ID
direct_id varchar 目的ID
enable int 是否启用 0:不启用,1:启用

除了常见的对字段的增删改查功能之外,还需要向外暴露已经启用的规则,查询语句如下:

select * from idmapper where enable=1

可在Web框架中,如Spring MVC,把这个查询的结果以API的方式暴露,访问API可以获得对应的Json数据。

第二个是HashMap的设计,因为在文章里面描述的场景相对简单,而实际上我们有更复杂的需求,如不止替换UID这样的参数,还可以替换别的参数,还有一些场景过滤,如只在特定场景下并且有对应规则才实现替换。这些功能使得HashMap的设计相对复杂,此处只需要将sourceID为key,directID为value。

第三个是要设计一个单例。单例中包含一个静态的HashMap对象,以及加载数据到HashMap对象的函数和根据UID查找替换UID的函数。实际上我们的系统相对复杂很多,提供有专门的数据服务接口,需要按照该接口进行开发。此处为了方便,我们简单就设计一个单例就好。伪代码大概如下:


public class MapRuler {

    private static HashMap ruler = new HashMap();
    // 私有构造函数
    private MapRuler() {}
    
    // 此处使用饿汉式即可
    private static MapRuler mapRuler = new MapRuler();

    public void loadEnableRuler() {
            ruler = ... // 调用管理系统提供的API,将Json数据解析成HashMap
    }

    public String getDirectId(String sourceId) {

            if(ruler.containsKey(sourceId)) {
                    return ruler.get(sourceId);
            }
            // 没有匹配的规则返回null
            return null;
   }
 
 public static MapRuler getInstance() {
            return mapRuler;
    }

}

单例使用饿汉式就可以,因为规则数据在线上应用启动时就要加载到内存对象中,这时候就要存在MapRuler对象。这个场景并不需要延迟加载这种功能。

实现

在实现过程中,我遇到不少问题,踩了不少坑。一方面是我很久没写前端了,目前的管理系统是之前留下来的,数据都用ajax请求后端API,然后前端写js把数据组装呈现。整个前端代码中JS/CSS/HTML混杂在一起,让人感觉很不好。如果要让我开发这个管理系统,我肯定不会这么做。

  1. css/js/html要尽可能做到分离开来,css放在专用的文件中,js的函数尽可能做到复用,不能每个页面都写一套js函数(这个项目几乎是的)。
  1. 现有的一些前端框架如vue/anglar/react都能帮助省很多功夫。这个系统中只使用了angular的路由功能,其他是jQuery和原生js拼接。
  2. 不会考虑只用cookie标记用户身份,居然还是明文的用户名,我也是醉了。其实内部伪造一下这个cookie,管理系统的权限如同虚设。像这种后端只负责提供API,并由前端呈现的应用,有专门的权限方案,例如授权机制,或者token令牌等。
  3. 代码里面不使用eval函数执行js函数。eval本身就不够安全,很多语言中都不怎么建议使用这个函数,js也是一样。

这种前后端分离的做法优点很多,其一后端减轻了重担,只需要提供API接口。而前端相应的任务就重了些,不过功能实现会更加灵活。我们的后端是用Spring MVC写的,实现对规则的增删改查的API也很简单。但是在前端实现上踩了不少坑。

例如用jQuery取id的数据时,如果前面忘了加#,然后就取不到数据。另外,原有的管理系统的代码有一个比较坑爹的BUG。在实现修改功能的时候,onclick对应的方法传进了很多参数,那些参数是通过js用字符串拼接的,然后用eval执行。这造成某个参数是json字符串,或者里面含有单引号的时候,该修改按钮点击无效。我尝试了好几个办法,都没有效果。但后来想到其实可以把带有单引号的字符串先用Base64加密,去掉单引号,然后作为参数传入onclick对应的方法,在onclick对应的方法里面用Base64解密,这样问题才解决。其实一个比较好的办法是,在onclick对应的函数参数中,只传入id,函数实现中用ajax从后端获取数据。

有一个比较好的工具可能帮助省了很多前端问题。chrome的开发者工具,真是神器,用好了这个东西,前端问题基本搞定了一半。不会的,不确定的代码都可以拿到控制台运行一下,看看结果,再写到生产环境的代码中。

chrome控制台

另一方面是我对线上API还不够熟悉,也跟不熟悉aone发布系统有关,毕竟这个东西也是刚用起来。推荐API中会对请求用一个Filter进行包装,包装成我们所需要的用于推荐的Request。我最开始走了弯路,把UID替换放在了该Filter之前,那时候我们只能通过getParameter()取得UID参数,但修改了后是没法用setParameter()函数改回去的,因为没有这个函数。正确的做法是把替换的Filter放在包装Filter之后,根据我们自己定义的Request对象替换掉里面的UID。还值得一说的是,替换Filter的配置要在包装Filter之后,这样获得的Request对象才正常而不是一个null,毕竟只有先把原request转换成推荐Request,我们才能用上推荐Request是不是?

总结

说起来这并不是一个很难实现的东西,但借助这次的实现,重新写了一把js,学了点Spring MVC,以及了解了线上API的东西。当我在预发机器上测试并打印出替换成功的结果时,感觉还是很开心的。但是我也知道,要学的东西还有很多。接下来可能找时间评估一下把后台管理系统重写的任务量,前端部分考虑用vue或者其他框架,优化一些使用功能。

虽然目标是一个数据工程师,但怎么也得会Web应用开发吧。So,走起来。

    雄关漫道真如铁,而今漫步从头越。

相关文章

  • 小结UID替换器的实现

    在推荐系统中,我们常需要根据用户的标示(UID)和访问行为给用户生成推荐。但某些情况下,我们需要替换用户请求中的U...

  • JS混淆简单参考

    一、 基本原理 混淆主要涉及两种思路: a.通过正则替换实现的混淆器; b.通过语法树替换实现的混淆器。 第一种实...

  • 实现动态替换psd智能图层的实时渲染

    小伙伴有没有想过通过api或者浏览器插件实现动态替换psd的图层,从而实现图层的实时替换今天就给大家推荐一个网站 ...

  • 将Libcef打造为win32控件之三:资源拦截替换、JS调用C

    实现资源拦截器 cefclient 示例代码自带一些资源拦截替换的实现,名为某某Provider。比如“查看网页源...

  • Python 18(2)  miniweb项目

    MiniWEB项目、模版、使用正则进行替换 {\%content\%} 为内容、路由、装饰器实现路由、AOP面向切...

  • Android OpenGL ES(八) - 简单实现绿幕抠图

    实现绿幕抠图,其实想法很简单。这里简单粗暴的使用着色器替换。 OES Filter 直接实现在相机预览上的Shad...

  • iOS UIViewControllerAnimatedTran

    控制器A push to 控制器B 我们可以在A中拦截Push然后替换成自定义的转场并返回。 具体实现步骤: 1....

  • React系列学习笔记:2.Express配置

    前言 本节主要利用express搭配webpack作为开发服务器,并利用babel的插件,实现热替换功能,本节使用...

  • HTML标签

    替换元素和非替换元素 替换元素:替换元素是浏览器根据其标签的元素与属性来判断显示具体的内容。 例如浏览器会根据 标...

  • CAS

    前言 CAS(Compare and Swap),即比较并替换,实现并发算法时常用到的一种技术,java同步器中大...

网友评论

      本文标题:小结UID替换器的实现

      本文链接:https://www.haomeiwen.com/subject/tgpagttx.html