Redisによる職階検索


本文はredis in action第七章の読書ノートとすることができる

需要背景

職階データベースにはこのようなデータがあります
job1:skill1,skill2,skill4
job2:skill3,skill2
応募者ごとにいくつかのスキルがあります
応募者を特定し、ライブラリから適任者(職位が要求されるのはこの人だけが備えなければならない.もちろん、彼はより多くの職位を持っていて、要求されていないスキルもOK)の職位を選別する.

シナリオ1

これも私たちの第一の思考に最も合致する論理です.
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 response = trans.exec();
        Set diff = (Set)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("
----- 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回判断しなければなりません.
これが問題だ

シナリオ2

には2つ目の方法があります
10,000のポジションがある場合は、各ポジションに必要なスキルがいくつかあります.
私はまずある人がこの10000のポストの中で、すべてのポストにいくつかのスキルを持っていることを計算します.これはset-aと呼ばれています.
各ポジションにいくつかのスキルが必要だと計算しなければなりませんset-bと呼ばれています
私はsetbでsetaと減らして、結果は依然として1つのzsetの集合で、memberは職位で、scoreは0に等しいならばこの人がこの仕事に適任であることを説明します
まず次のドメインを設計します
idx:skill:sillId--set集合
このスキルが必要なすべてのポジション情報がリストされています
idx:jobs:req---zset集合
メンバーはポジションscoreこのポジションに必要なスキルの数です
まず、ポジションとスキルをライブラリに追加します.
    public void indexJob(Jedis conn, String jobId, String... skills) {
        Transaction trans = conn.multi();
        Set unique = new HashSet();
        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"
#programmerとmanagerの2つの処理対象セット
彼らの給料にそれぞれ1と3を乗じて、私はどうしてですか?
最後の2つの新しい給与計算書を加算
#結果を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の2つのスキルを持っていたら
ではidx:skill:c++とidx:skill:dbの2つのドメインをzsetと見なします(scoreはすべて1です)
そして、バックグラウンドエンジニアというポジション自体がc++というスキル要件にある場合も、dbというスキル要件にある場合も加算します.
最後に得られたデータドメインのバックグラウンドエンジニアというメンバーに対応するscoreは2です.
では次の質問です
私はsetbでsetaと減らして、結果は依然として1つのzsetの集合で、memberは職位で、scoreは0に等しいならばこの人がこの仕事に適任であることを説明します
どうしよう?WEIGHTSを-1にすればOKじゃないですか?
上の命令は合併を求めていますか?
このコマンドを見に行きます:ZINTERTORE
http://doc.redisfans.com/sorted_set/zinterstore.html
OK、それに次のコードを付ければ終わりです
こんなに簡単ですか.
主にredisは多くの強力なコマンドを提供しています
   
 public Set 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("
----- 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 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