zoukankan      html  css  js  c++  java
  • Using Eredis, Redis With Erlang

    http://no-fucking-idea.com/blog/2012/03/23/using-eredis-in-erlang/

    Using Eredis, Redis With Erlang

    MAR 23RD, 2012 | COMMENTS

    Recently i decided to move my blog from tumblr.com to octopress engine because it is just easier for me to maintain it and it looks nicer. The old blog is under http://no-fucking-idea.tumblr.com. My first post on new blog is dedicated to using redis with erlang.

    Eredis

    Wooga created a really nice (performance driven) redis driver for erlang. You can get it here https://github.com/wooga/eredis. It is really easy and nice.

    Initial sample

    On project page you can find simple examples how to use eredis. Examples there are all you need (but i need something for front page of my new blog so i will rewrite them and explain them :) ).

    First you need to start your eredis application

    initialization
    1
    
    {ok, Client} = eredis:start_link().
    

    Client is the “connection / state” we will be using with rest of queries.

    To query things with redis we will use q method from eredis module which takes “Connection / Client” state and list with params. This api is very simple here are two most basic examples of get and set. GET:

    get
    1
    
    {ok, <<"OK">>} = eredis:q(Client, ["SET", "name", "kuba"]).
    

    and SET:

    set
    1
    
    {ok, <<"kuba">>} = eredis:q(Client, ["GET", "name"]).
    

    From my point of view this is ideal candidate to be turned into gen_server behavior. We will pass “Connection / Client” as state and also we will build some “key” serialization methods around it to make it more durable and make our life easy if we will decide to refactor it later on.

    Free Api wrapper

    First thing i saw during development using Erlang is that you get free api if you follow simple patterns and encapsulate things into gen_server’s and applications.

    example_db.erl
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    
    -module(example_db).
    -behaviour(gen_server).
    
    -author("jakub.oboza@gmail.com").
    -define(Prefix, "example").
    
    -export([start_link/0]).
    -export([init/1, handle_call/3, handle_cast/2, terminate/2, handle_info/2, code_change/3, stop/1]).
    -export([get_script/2, save_script/3]).
    
    % public api
    
    start_link() ->
      gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
    
    init([]) ->
      {ok, Redis} = eredis:start_link(),
      {ok, Redis}.
    
    stop(_Pid) ->
      stop().
    
    stop() ->
        gen_server:cast(?MODULE, stop).
    
    %% public client api
    
    get_script(Api, Method) ->
      gen_server:call(?MODULE, {get_script, Api, Method}).
    
    save_script(Api, Method, Script) ->
      gen_server:call(?MODULE, {save_script, Api, Method, Script}).
    
    %% genserver handles
    
    handle_call({get_script, Api, Method}, _From, Redis) ->
      Response = eredis:q(Redis, [ "GET", get_key(Api, Method) ]),
      {reply, Response, Redis};
    
    handle_call({save_script, Api, Method, Script}, _From, Redis) ->
      Response = eredis:q(Redis, ["SET", get_key(Api, Method), Script]),
      {reply, Response, Redis};
    
    handle_call(_Message, _From, Redis) ->
      {reply, error, Redis}.
    
    handle_cast(_Message, Redis) -> {noreply, Redis}.
    handle_info(_Message, Redis) -> {noreply, Redis}.
    terminate(_Reason, _Redis) -> ok.
    code_change(_OldVersion, Redis, _Extra) -> {ok, Redis}.
    
    %% helper methods
    
    get_key(Api, Method) ->
      generate_key([Api, Method]).
    
    generate_key(KeysList) ->
      lists:foldl(fun(Key, Acc) -> Acc ++ ":" ++ Key end, ?Prefix, KeysList).
    
    % tests
    
    -ifdef(TEST).
    -include_lib("eunit/include/eunit.hrl").
    -endif.
    
    
    -ifdef(TEST).
    
    generate_key_test() ->
      Key = generate_key(["one", "two", "three"]),
      ?assertEqual("example:one:two:three", Key).
    
    server_test_() ->
      {setup, fun() -> example_db:start_link() end,
       fun(_Pid) -> example_db:stop(_Pid) end,
       fun generate_example_db_tests/1}.
    generate_example_db_tests(_Pid) ->
    [
    ?_assertEqual({ok,<<"OK">>}, example_db:save_script("jakub", "oboza", <<"yo dwang">>) ),
    ?_assertEqual({ok,<<"yo dwang">>}, example_db:get_script("jakub", "oboza") )
    ].
    -endif
    

    Public Api

    This code listing has two important parts first at top it starts at line 26. This is the public API which will be used by developer. This is this free api. Later on i will explain how to change redis to mongodb and probably to other db engines without doing changes in rest of our app. From my perspective this is awesome feature. In most cases when i had to make app scale problem of having code that was glues to one db engine was heavy.

    eunit tests

    At line 60. starts the declaration of tests, using rebar and eunit is very easy and it is always good to have test coverage in case of refactoring. I’m a fan of test driven development so i like to cover in tests first things that i will use or i think they might be error prone. Here is used “test generators” to just to show how to write tests for gen_server.

    Rebar

    Before i will explain more i need to say little about rebar. It is a command line tool that was developed by basho to help create app. it is by far the best thing i found learning erlang to help me do boring stuff and eliminate a lot of rage writing app.src files. To get rebar simply do (you can always go to https://github.com/basho/rebar to get most up to date informations about building it)

    1
    2
    3
    
    λ  git clone git://github.com/basho/rebar.git
    λ  cd rebar
    λ  ./bootstrap
    

    I use my own set of zsh scripts so all i did to add it to my path was to edit .furby file in my home dir. I strongly suggest also adding it to $PATH just to make your life easier.

    Back to example_db!

    To create app using rebar you just need to

    1
    2
    3
    4
    5
    6
    
    λ mkdir example_db
    λ rebar create-app appid=example_db
    ==> example_db (create-app)
    Writing src/example_db.app.src
    Writing src/example_db_app.erl
    Writing src/example_db_sup.erl
    

    This command created src folder with scaffold of application OTP pattern and supervisorthats almost all we need :). Now you can compile it using rebar compile and run tests using rebar compile eunit in out app currently we will see

    rebar compile eunit
    1
    2
    3
    4
    5
    6
    7
    8
    
    λ rebar compile eunit
    ==> example_db (compile)
    Compiled src/example_db_app.erl
    Compiled src/example_db_sup.erl
    ==> example_db (eunit)
    Compiled src/example_db_app.erl
    Compiled src/example_db_sup.erl
      There were no tests to run.
    

    Nothing to do because its empty. Lets add our db module. But before this we need to add dependencies for eredis module. Lets create rebar.config file and add it.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    λ emacs rebar.config
    λ cat rebar.config
    %%-*- mode: erlang -*-
    
    {erl_opts, []}.
    {cover_enabled, true}.
    
    {deps,
      [
        {eredis, ".*", {git, "https://github.com/wooga/eredis.git", "HEAD"}}
      ]
    }.
    

    Now just run rebar get-deps to get all dependencies downloaded. After adding our example_db.erl into src directory we can run rebar compile eunit to compile and run tests. We have added {cover_enabled, true} in rebar.conf so also test code coverage will be generated for us.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
    λ rebar compile eunit
    ==> eredis (compile)
    ==> example_db (compile)
    Compiled src/example_db.erl
    ==> eredis (eunit)
    
    =ERROR REPORT==== 28-Mar-2012::22:19:35 ===
    ** Generic server <0.263.0> terminating
    ** Last message in was {tcp,#Port<0.4516>,
                                <<"*3
    $7
    message
    $3
    foo
    $2
    12
    ">>}
    ** When Server state == {state,"127.0.0.1",6379,<<>>,100,#Port<0.4516>,
                                   {pstate,undefined,undefined},
                                   [<<"foo">>],
                                   {#Ref<0.0.0.4058>,<0.180.0>},
                                   {[{message,<<"foo">>,<<"11">>,<0.263.0>},
                                     {message,<<"foo">>,<<"10">>,<0.263.0>},
                                     {message,<<"foo">>,<<"9">>,<0.263.0>},
                                     {message,<<"foo">>,<<"8">>,<0.263.0>},
                                     {message,<<"foo">>,<<"7">>,<0.263.0>},
                                     {message,<<"foo">>,<<"6">>,<0.263.0>},
                                     {message,<<"foo">>,<<"5">>,<0.263.0>},
                                     {message,<<"foo">>,<<"4">>,<0.263.0>},
                                     {message,<<"foo">>,<<"3">>,<0.263.0>}],
                                    [{message,<<"foo">>,<<"2">>,<0.263.0>}]},
                                   10,exit,need_ack}
    ** Reason for termination ==
    ** max_queue_size
      All 53 tests passed.
    Cover analysis: /private/tmp/example_db/deps/eredis/.eunit/index.html
    ==> example_db (eunit)
    Compiled src/example_db.erl
      All 3 tests passed.
    Cover analysis: /private/tmp/example_db/.eunit/index.html
    

    All seems to be fine! lets create file called start.sh to test it out

    1
    2
    
    λ cat start.sh
    erl -pa ebin -pa deps/*/ebin
    

    and make it executable with chmod +x start.sh

    And lets rock ‘n’ roll

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
     λ ./start.sh
    Erlang R15B (erts-5.9) [source] [64-bit] [smp:4:4] [async-threads:0] [hipe] [kernel-poll:false]
    
    Eshell V5.9  (abort with ^G)
    1> example_db:start_link().
    {ok,<0.33.0>}
    2> example_db:save_script("example", "script", "puts '2+2'").
    {ok,<<"OK">>}
    3> example_db:get_script("example", "script").
    {ok,<<"puts '2+2'">>}
    

    Have fun :) Hope it was useful. You can download code for this blog post here https://github.com/JakubOboza/example_db-code

    Huh ^___^

    that was my first post on new blog ;)

  • 相关阅读:
    网站访问量和服务器带宽的选择
    PHP实现四种基本排序算法
    常用的PHP排序算法以及应用场景
    常见的mysql数据库sql语句的编写和运行结果
    MyBatis拦截器:给参数对象属性赋值
    《自律让你自由》摘要
    Java JDK1.5、1.6、1.7新特性整理(转)
    人人都能做产品经理吗?
    Windows下查询进程、端口
    一语收录(2016-09-18)
  • 原文地址:https://www.cnblogs.com/fvsfvs123/p/4269870.html
Copyright © 2011-2022 走看看