2

servlet.Filter(IPアドレスに基づいて)データベーステーブルでクライアントのユーザーIDを検索する実装があり、このデータをHttpSession属性に添付します。フィルタは、が定義されていないクライアントから要求を受信するたびにこれを実行しますHttpSession

つまり、リクエストに関連付けられたセッションがない場合、フィルターは次のようになります。

  • クライアントのセッションを作成します
  • ユーザーIDのデータベース検索を実行します
  • ユーザーIDをセッション属性として添付します

「セッションのない」クライアントからの要求の間に時間があれば、これはすべて正常に機能します。

しかし、「セッションのない」クライアントが互いにミリ秒以内に10個の要求を送信すると、10個のセッションと10個のデータベースクエリが発生します。それでも「機能」しますが、リソース上の理由から、これらのセッションとクエリのすべてが好きではありません。

これは、リクエストが非常に接近しているためだと思います。「セッションレス」クライアントがリクエストを送信し、別のリクエストが送信される前にレスポンスを取得する場合、この問題は発生しません。

私のフィルターの関連部分は次のとおりです。

// some other imports

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.MapHandler;

public class QueryFilter implements Filter {

    private QueryRunner myQueryRunner;  
    private String myStoredProcedure;
    private String myPermissionQuery;
    private MapHandler myMapHandler;

    @Override
    public void init(final FilterConfig filterConfig) throws ServletException {
        Config config = Config.getInstance(filterConfig.getServletContext());
        myQueryRunner = config.getQueryRunner();
        myStoredProcedure = config.getStoredProcedure();
        myUserQuery = filterConfig.getInitParameter("user.query");
        myMapHandler = new MapHandler();
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws ServletException {

        HttpServletRequest myHttpRequest = (HttpServletRequest) request;
        HttpServletResponse myHttpResponse = (HttpServletResponse) response;
        HttpSession myHttpSession = myHttpRequest.getSession(false);
        String remoteAddress = request.getRemoteAddr();

        // if there is not already a session
        if (null == myHttpSession) {

            // create a session
            myHttpSession = myHttpRequest.getSession();

            // build a query parameter object to request the user data
            Object[] queryParams = new Object[] { 
                myUserQuery, 
                remoteAddress
            };

            // query the database for user data
            try {
                Map<String, Object> userData = myQueryRunner.query(myStoredProcedure, myMapHandler, queryParams);

                // attach the user data to session attributes
                for (Entry<String, Object> userDatum : userData.entrySet()) {
                    myHttpSession.setAttribute(userDatum.getKey(), userDatum.getValue());
                }

            } catch (SQLException e) {
                throw new ServletException(e);
            }

            // see below for the results of this logging
            System.out.println(myHttpSession.getCreationTime());
        }

        // ... some other filtering actions based on session
    }
}

1つのクライアントからのロギングmyHttpSession.getCreationTime()(タイムスタンプ)の結果は次のとおりです。

1343944955586
1343944955602
1343944955617
1343944955633
1343944955664
1343944955680
1343944955804
1343944955836
1343944955867
1343944955898
1343944955945
1343944955945
1343944956007
1343944956054

ご覧のとおり、ほとんどすべてのセッションが異なります。これらのタイムスタンプは、リクエストの間隔がどれだけ近いか(20ミリ秒から50ミリ秒)の良いアイデアにもなります。

すべてのクライアント側アプリケーションを再設計して、最初に別の要求を送信する前に少なくとも1つの応答を受け取るようにすることはできないので、フィルターでそれを実行したいと思います。

また、後続のリクエストを単に失敗させたくはありません。それらを処理する方法を見つけたいと思います。

質問

  • 最初のリクエストからセッションが確立されるまで、同じクライアント(IPアドレス)からの後続のリクエストを「リンボ」に入れる方法はありますか?

  • HttpSessionそして、それを管理した場合、後で電話をかけたときに正しいもの(ユーザーデータを添付したもの)を取得するにはどうすればよいaSubsequentRequest.getSession()ですか?リクエストにセッションを割り当てることはできないと思いますが、間違っている可能性があります。

たぶん、これを完全に回避するためのより良い方法がいくつかあります。基本的に、このフィルターが2秒以内にルックアップクエリを不必要に10〜20回実行しないようにします。

4

6 に答える 6

1

データベースルックアップをキャッシュし、データベースが変更されたときにキャッシュを無効にする方法、またはキャッシュでタイムアウトを使用する方法を見つけます。たとえば、GoogleのGauvaには、指定された時間が経過するとエントリを無効にするキャッシュがあります。ここにいくつかの基本的なコードがあります。同じ値でセッションに属性を設定することは問題ないはずです。また、HttpSessionListenerを使用して、セッションが破棄されたときに「userID」を含む特定のキャッシュエントリを無効にすることもできます。

static LoadingCache<String, String> ipAddressToUserLookupCache = CacheBuilder.newBuilder()
        .maximumSize(10000)
        .expireAfterWrite(10, TimeUnit.MINUTES)
        .build(
            new CacheLoader<String, String>() {
              public String load(String ipAddress) throws Exception {
                // find the user ID
                return "<user id>";
              }
            });

@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain fc) throws IOException,
        ServletException {
    final String ipAddress = req.getRemoteAddr();
    final String userName = ipAddressToUserLookupCache.get(ipAddress);
    ((HttpServletRequest)req).getSession(true).setAttribute("username", userName);
}
于 2012-08-01T00:49:37.270 に答える
1

あなたは雷鳴の群れの問題を扱っています。これを解決する最善の方法は、この問題に対処するキャッシング実装を使用することです。ここにそれを解決する1つの方法があります。

  1. フィルタでは、Google Guava読み込みキャッシュを使用し、SessionIdを使用して必要な情報を検索します。Google guavaは、キーがキャッシュになく、スレッドが同時にオブジェクトを探してキャッシュにヒットした場合、アイテムがキャッシュに取り込まれる間、1つのスレッドのみがloadメソッドを呼び出し、他のスレッドはブロックするように設計されています。キャッシュのサイズはhttpセッションの数と同じになるため、このguavaキャッシュに上限を設定しないでください。セッションにアイテムを保存したい場合は、同時に到着するリクエストのコンテナによって複数のhttpSessionが作成されていることが問題である場合は、サンプルコードのqueryParamsのユーザーIDや一部のフィールドなどを変更しないリクエスト内の何かに基づいてキャッシュします。

  2. HttpSessionListenerを作成します。これは、セッションが期限切れになるか、HtttpSessionListenerで無効になると、サーブレットコンテナによって自動的に呼び出されます。その後、Google guavaキャッシュでinvalidateメソッドを呼び出すと、最初のリクエストでアイテムがキャッシュに追加され、セッションの有効期限が切れると、キャッシュから追い出されました。

  3. また、Webコンテナによってセッションがディスクにパッシベーションされたときに通知するHttpSessionActivationListenerを実装することもできます。これは、メモリ不足、クライアントがしばらくの間要求を送信していないがセッションがまだ期限切れになっていないなど、さまざまな理由で発生する可能性があります。だからそれは不動態化されます。パッシベーションイベントではアイテムをキャッシュから削除し、アクティベーションイベントではアイテムをキャッシュに戻すのが理にかなっています。

  4. キャッシュに入れるアイテムがスレッドセーフであることを確認する必要があります。安全なオブジェクト構築手法で構築された不変オブジェクトをお勧めします。

Springをベースにしたアプリケーションで上記の手法を使用しているので、少し変更を加えて上記の手法を実行しています。

  1. 私はSpringApplicationContextイベントを使用して、キャッシュを無効にする可能性のある何かが発生したときにイベントを発生させています。これにより、キャッシュはSpringアプリケーションコンテキストでイベントをリッスンし、その状態を無効にすることができます。セッションのアクティブ化/パッシベーションおよび作成/破棄のファイアイベントが発生すると、複数のキャッシュが反応する可能性があります。

  2. 私はフィルターを使用せず、自然キーを使用します。たとえば、使用プロファイルキャッシュはユーザーIDにキー設定され、誰かがユーザーID12304のユーザープロファイルを要求するまで入力されません。

  3. 私はスレッドセーフに忠実であり、すべてのキャッシュで不変のオブジェクトを使用するようにしています。これは、リストやマップなどの不変のデータ構造が必要であることを意味します。これは、Google Guavaが素晴らしいもう1つの領域であり、多くの有用なデータ構造を取得できます。不変のリスト、マップ、セット、マルチマップ...など。

コードサンプルが必要な場合はお知らせください。

もう1つの可能性は、フィルターで同期を使用できることです。これにより、パフォーマンスは低下しますが、シリアル化されます。

于 2012-08-07T18:41:46.043 に答える
1

私はあなたがする必要があるのはあなたのクライアントが最初に(成功して)認証し、次に追加の要求をすることを要求することだと思います。そうしないと、複数のセッションを生成するリスクがあります(そしてそれらを別々に維持する必要があります)。これは、IMOの要件としてはそれほど悪くありません。

NTLMクレデンシャルに依存できる場合は、ユーザー->トークンのマップを設定して、最初の接続時にトークンをマップに配置し、いずれかの要求が正常に完了するまですべての要求をブロック(または失敗)することができます。トークンが削除される(または優先セッションIDを使用できるように更新される)認証ステップ。

于 2012-08-07T22:01:27.807 に答える
0

最初にチェックを行うことで(リクエストにセッションがあるかどうかを確認するため)、競合状態になります。

代わりに以下を使用する必要があります。

request.getSession()

javadocでHttpServletRequestを確認すると、次のように表示されます。

このリクエストに関連付けられている現在のセッションを返します。リクエストにセッションがない場合は、セッションを作成します。

そのメソッドを使用する場合、両方の呼び出しが同じセッションを返す必要がある場合は、userID属性を設定する前に、属性の存在を確認できます。

于 2012-08-01T00:35:40.060 に答える
0
  1. 質問したいのですが、リアルタイムの世界でこのようなシナリオを実際にどのように実現できますか?複数のリクエスト(2〜3以上)が同じIPまたは同じクライアントからわずか20ミリ秒の差で送信されますか?私が作業しているアプリで、送信ボタンをもう一度クリックしようとすると、ページが再度送信されることはなく、インテリジェントに動作します。

  2. 基本的に、私たちは通常、アプリケーションが二重提出証明であることを確認します。詳細については、このリンクを参照してください。 二重提出問題の解決

同じクライアントからの二重送信や複数送信のようなシナリオを回避できれば、問題は発生しないと思います。

于 2012-08-03T18:25:47.027 に答える
0

本当に最も簡単な解決策は、自己入力戦略を提供するいくつかのキャッシングフレームワークの1つを使用することです。

基本的に、これが意味するのは、特定のキーのキャッシュに移動するときに、キーが存在しない場合に、そのキーのデータを作成する関数を提供したということです。

その機能の実行中、同じキーブロックへの他のアクセス。

したがって、特定のIPのキャッシュをヒットしようとすると、キャッシュはそのIPのエントリがないことを確認します。次に、ルーチンを呼び出してデータベースからロードします。それがロードされている間、同じIPを試行する他のすべての人は、ルーチンが完了するまで待機し、その後、すべて同じ値を返します。

ehcacheはこれをサポートするフレームワークですが、確かに他にもあります。

ロックや競合などを管理するための苦労をすべて経験しているため、このためのフレームワークを使用する必要があります。

于 2012-08-04T04:23:56.570 に答える