Redis进行职位搜索

本文可作为redis in action第七章的读书笔记

需求背景

职位数据库里有这样的数据
job1:skill1,skill2,skill4
job2:skill3,skill2


而每一个应聘者,都有若干个技能
给定一个应聘者,从库里筛选出他能胜任(职位要求的仅能这个人必须都具备,当然他有更多的职位并没有要求的技能也是OK的)的职位


方案一

这也是最符合我们第一思维的逻辑
有一个set域 job:jobid
里面存放了这个职位需要的技能
在检查某个人是否能胜任某个职位的时候
用job:jobid与它本人的技能求差集
如果差集的size等于0说明职位要求的技能,这个人都具备,他可以胜任这个工作
如果差集的size大于0说明职位要求的技能,这个人并不都具备,他不能胜任这个工作
    public void addJob(Jedis conn, String jobId, String... requiredSkills) {
        conn.sadd("job:" + jobId, requiredSkills);
    }


    @SuppressWarnings("unchecked")
    public boolean isQualified(Jedis conn, String jobId, String... candidateSkills) {
        String temp = UUID.randomUUID().toString();
        Transaction trans = conn.multi();
        for(String skill : candidateSkills) {
            trans.sadd(temp, skill);
        }
        trans.expire(temp, 5);
        trans.sdiff("job:" + jobId, temp);


        List<Object> response = trans.exec();
        Set<String> diff = (Set<String>)response.get(response.size() - 1);
        return diff.size() == 0;
    }
测试代码:
    public class Chapter07Test {
	static Jedis conn = null;
	static Chapter07 c=null;
	
	@BeforeClass
	public static void initConn(){
		System.out.println("test before");
		conn = new Jedis("10.150.0.80");
        conn.auth("dlf123123");
        
        c=new Chapter07();
	}
	
	@Test
    public void testIsQualifiedForJob() {
        System.out.println("\n----- testIsQualifiedForJob -----");
        c.addJob(conn, "test", "q1", "q2", "q3");
	//返回true
        System.out.println(c.isQualified(conn, "test", "q1", "q3", "q2","q4")); 
        //返回false
	System.out.println(c.isQualified(conn, "test", "q1", "q2")); 
    }
}
OK,上面的代码能正常运行 那么有问题么?
如果库里有10000个职位,那我就得判断10000次
这就是问题


方案二

还有第二种方式
假如有10000个职位,每个职位都有若干个所需技能
我先算出某个人对于这10000个职位里,每个职位都具备几个技能--这个我们称为set-a
然后我还得计算出,每个职位都需要几个技能--这个我们称为set-b
我用setb与seta相减,结果仍然是一个zset集合,member是职位,score等于0的话就说明这个人是能胜任这个工作的#


我们先设计下面几个域

idx:skill:sillId--set集合
这里面列出了,需要这个技能的所有职位信息

idx:jobs:req---zset集合

member是职位 score是这个职位需要的技能的数量


首先将职位与技能添加到库里

    public void indexJob(Jedis conn, String jobId, String... skills) {
        Transaction trans = conn.multi();
        Set<String> unique = new HashSet<String>();
        for (String skill : skills) {
            trans.sadd("idx:skill:" + skill, jobId);
            unique.add(skill);
        }
	         //   记录每个职位需要的技能数量
        trans.zadd("idx:jobs:req", unique.size(), jobId); 
        trans.exec();
    }

好了,我们现在先不看代码了,先说说我们得了解的redis的几个命令
Zunionstore 命令
redis 127.0.0.1:6379> ZRANGE programmer 0 -1 WITHSCORES
1) "peter"
2) "2000"
3) "jack"
4) "3500"
5) "tom"
6) "5000"


redis 127.0.0.1:6379> ZRANGE manager 0 -1 WITHSCORES
1) "herry"
2) "2000"
3) "mary"
4) "3500"
5) "bob"
6) "4000"
# 一共有2个待处理集合分别是programmer和manager
# 给他们的工资分别乘以1和3 我艹 为啥?
# 最后那两个新的工资单 相加
# 结果放到salary集合中
# 公司决定加薪。。。除了程序员。。。
redis 127.0.0.1:6379> ZUNIONSTORE salary 2 programmer manager WEIGHTS 1 3   
(integer) 6


redis 127.0.0.1:6379> ZRANGE salary 0 -1 WITHSCORES
1) "peter"
2) "2000"
3) "jack"
4) "3500"
5) "tom"
6) "5000"
7) "herry"
8) "6000"
9) "mary"
10) "10500"
11) "bob"
12) "12000"
另外对于Zunionstore命令来说,如果待操作的集合并不是有序集合(也就是说没有score域),那么系统就默认认为它的score是1
更详细的见: http://www.runoob.com/redis/sorted-sets-zunionstore.html


那么我们怎么求出某个用户在每个职位下能满足几个技能要求呢?
假如我有c++与db这两个技能
那我把idx:skill:c++和idx:skill:db这两个域都看做zset(score都是1)
然后相加 如果后台工程师这个职位本身既在c++这个技能要求里,也在db这个技能要求里
最后得出的数据域里后台工程师这个member对应的score就是2


那么下一个问题
我用setb与seta相减,结果仍然是一个zset集合,member是职位,score等于0的话就说明这个人是能胜任这个工作的#
怎么办?WEIGHTS设为-1不就OK了?
上面那个命令是求并集呀?
去看这个命令:ZINTERSTORE
http://doc.redisfans.com/sorted_set/zinterstore.html

OK,再加上下面的代码就搞定了
这么简单?
主要是redis提供了很多强大的命令
   
 public Set<String> findJobs(Jedis conn, String... candidateSkills) {
        String[] keys = new String[candidateSkills.length];
        double[] weights = new double[candidateSkills.length];
        for (int i = 0; i < candidateSkills.length; i++) {
            keys[i] = "skill:" + candidateSkills[i];
            weights[i] = 1.0;
        }


        Transaction trans = conn.multi();
        
        //这里的idx:jobScores
        //member是职位   score是我能满足这个职位的技能的数量
	//这里面是所有跟我还有点"关系"的职位
	//国务院总理这个职位所要求的技能 我一个都没有
	//那么 这个idx:jobScores里肯定就没有国务院总理这个职位
        String jobScores = zunion(
            trans, 100, new ZParams().weightsByDouble(weights), keys);
        System.out.println("jobscores:   zrange idx:"+jobScores+" 0 -1 withscores");
        
        
        // 用需要的技能数 减去我现在有的技能数
        String finalResult = zintersect(
            trans, 100, new ZParams().weightsByDouble(-1, 1), jobScores, "jobs:req");
        System.out.println("jobscores:  zrange idx:"+finalResult+" 0 -1 withscores");
        trans.exec();


        return conn.zrangeByScore("idx:" + finalResult, 0, 0);
    }


    public String zintersect(Transaction trans, int ttl,
    		ZParams params, String... sets){
        return zsetCommon(trans, "zinterstore", ttl, params, sets);
    }


    public String zunion(Transaction trans, int ttl, 
    		ZParams params, String... sets)    {
        return zsetCommon(trans, "zunionstore", ttl, params, sets);
    }


     //把zunionstore和zinterstore提取出来
     //使用反射技术 不懂的自己去看反射
     //这里产生的中间数据 都不必一直保存 所有有个ttl
     private String zsetCommon(Transaction trans, String method, int ttl, 
    		ZParams params, String... sets) {
        String[] keys = new String[sets.length];
        for (int i = 0; i < sets.length; i++) {
            keys[i] = "idx:" + sets[i];
        }


        String id = UUID.randomUUID().toString();
        try{
            Method m=trans.getClass().getDeclaredMethod(method, String.class, 
            		ZParams.class, String[].class);
            System.out.println("method: "+method);
            m.invoke(trans,"idx:" + id, params, keys);
        }catch(Exception e){
            throw new RuntimeException(e);
        }
        trans.expire("idx:" + id, ttl);
        return id;
    }

再看看测试代码:

	@Test
	  public void testIndexAndFindJobs() {
	        System.out.println("\n----- testIndexAndFindJobs -----");
	        c.indexJob(conn, "test1", "q1", "q2", "q3");
	        c.indexJob(conn, "test2", "q1", "q3", "q4");
	        c.indexJob(conn, "test3", "q1", "q3", "q5");

	     //   System.out.println(c.findJobs(conn, "q1").size());  

	        Iterator<String> result =null;
	      //  result=c.findJobs(conn, "q1", "q3", "q4").iterator();
	     //   printIterator(result);

	        result = c.findJobs(conn, "q1", "q3", "q5").iterator();
	        printIterator(result);

	        result = c.findJobs(conn, "q8", "q9", "q310", "q11", "q12").iterator();
	        printIterator(result);

	    }

测试结果:

test before



----- testIndexAndFindJobs -----
method: zunionstore
jobscores:   zrange idx:51926856-67fd-42ba-8620-fa90cae4f4d8 0 -1 withscores
method: zinterstore
jobscores:  zrange idx:b6df8812-f725-46fd-a121-a08c75248d5a 0 -1 withscores
test3
abc
method: zunionstore
jobscores:   zrange idx:784ce6b6-5218-4e4d-ac53-b235cf317374 0 -1 withscores
method: zinterstore
jobscores:  zrange idx:990004c6-2c9e-4c26-95b9-248dbf2a5864 0 -1 withscores

abc


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值