Spring BootはRedisのzsetを使ってオンラインユーザー情報を統計します。


オンラインユーザーの数を統計すると、よく使う需要があります。正確な統計が必要ならば、ユーザーはオンラインでオフライン状態で、クライアントとサーバだけがTCPの長い接続を維持することで実現すると思います。アプリケーション自体がIMアプリケーションでない場合、この方法は非常にコストが高いです。
現在のアプリケーションは、ユーザーがオンラインであるかどうかを識別するために心拍カバンを使用する傾向があります。ユーザーがログインした後、しばらくの間、サーバーにメッセージを送り、現在のユーザーがオンラインであることを示します。サーバは、例えば5分以内にクライアントの心拍メッセージを受信し、オンラインユーザと見なす時間差を定義することができる。
オンラインユーザー統計の実現
データベースに基づく実装
一番簡単な方法は、ユーザーテーブルに最後の心拍カバンの日付フィールドラスボスを追加することです。activeサーバーが心拍数を受信したら、このフィールドは現在の最新の時間に更新されます。
最近5分間のアクティブなユーザー数を調べると、簡単にSQLで完成できます。

SELECT COUNT(1) AS `online_user_count` FROM `user` WHERE `last_active` BETWEEN  '2020-12-22 13:00:00' AND '020-12-22 13:05:00';
弊害も明らかで、検索効率を上げるためには、ラストガルにならざるを得ない。activeフィールドにインデックスを追加すると、心拍数の更新によりインデックスツリーの再維持が頻繁に行われ、効率が非常に低下します。
Redisに基づいて実現
これは理想的な一実施形態であり、Redisはメモリに基づいて読み書きを行い、性能は自然に関係型データベースよりずっと優れています。そして、提供されたZsetはオンラインユーザーの統計サービスを簡単に構築することができます。
RedisのZset
ここは多すぎるredisのものに関連しないで、簡単に以下のzsetを説明します。それは規則的なセットです。集合の各要素は2つのものからなります。
  • memberがセットである以上、それはセット内の要素であり、
  • を繰り返すことができない。
  • score 順序である以上、並べ替えのための重みフィールドです。
  • Zsetの部分操作
    要素を追加
    
    ZADD key score member [score member ...]
    一度に1つ以上の要素をセットに追加します。メンバーが既に存在している場合、現在のscoreを使って上書きします。
    すべての要素の数を統計します。
    
    ZCARD key
    score値のminとmaxの間の元素数を統計します。
    
    ZCOUNT key min max
    score値のminとmax間の要素を削除します。
    
    ZREMRANGEBYSCORE key min max
    一例
    私はzsetで心のプログラミング言語の採点順位を記憶するつもりです。このkeyはlangといいます。
    追加された要素の個数を返します。
    
    > zadd lang 999 php 10 java 9 go 8 python 7 javascript
    "5"
    追加の数を表示します。
    
    > zcard lang
    "5"
    評価が8-10の間にある要素の個数を表示します。3つあります。
    
    > zcount lang 8 10
    "3"
    評価された8-1000の要素を削除し、削除された個数を返します。
    
    > ZREMRANGEBYSCORE lang 8 1000
    "4"
    オンラインユーザーサービスの実現
    zsetを知ったら、オンラインユーザーの統計サービスが実現できます。
    構想を実現する
    クライアントは5分ごとにサーバーに心拍を送ります。サーバはセッションによってユーザIDを取得し、zsetのメンバーとして働きます。
    zsetを預け入れて、scoreは現在心拍を受信したタイムスタンプです。同じユーザが第二回心拍を送信すると、彼の対応するscore値が更新されます。更新はメモリにあるため、この速度はかなり速いです。
    
    zadd users 1608616915109 10000
    オンラインユーザーの数を統計する必要があります。本質的には統計が必要です。最近の5分間は心拍を送るユーザーがいます。zcountを通じて簡単に統計できます。プログラムで現在のタイムスタンプを取得し、maxScoreとしてタイムスタンプから5分引いた後、minScoreとします。
    
    zcount users 1608616615109 1608616915109 
    一部のユーザーは長い間ログインしていないかもしれないので、ZEREMREANGEBYSCOREで整理できます。プログラムで現在のタイムスタンプを取得し、5分を差し引いてからmaxScoreとして、0を使用して、minScoreとして、5分を超えて心拍パケットを送信したことがないすべてのユーザを整理するという意味です。
    
    ZREMRANGEBYSCORE users 0 1608616615109 
    コードを実現
    
    import java.time.Duration;
    import java.time.Instant;
    import java.time.LocalDateTime;
    import java.time.ZoneId;
    
    import javax.annotation.Resource;
    
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Component;
    
    /**
     * 
     * 
     *       
     * 
     * @author Administrator
     *
     */
    @Component
    public class OnlineUserStatsService {
        
        private static final String ONLINE_USERS = "onlie_users";
    
        @Resource
        private StringRedisTemplate stringRedisTemplate;
    
        /**
         *         
         * @param userId
         * @return 
         */
        public Boolean online(Integer userId) {
            return this.stringRedisTemplate.opsForZSet().add(ONLINE_USERS, userId.toString(), Instant.now().toEpochMilli());
        }
        
        /**
         *        ,       
         * @param duration
         * @return
         */
        public Long count(Duration duration) {
            LocalDateTime now = LocalDateTime.now();
            return this.stringRedisTemplate.opsForZSet().count(ONLINE_USERS, 
                                        now.minus(duration).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(), 
                                        now.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
        }
        
        /**
         *             ,    
         * @return
         */
        public Long count() {
            return this.stringRedisTemplate.opsForZSet().zCard(ONLINE_USERS);
        }
        
        /**
         *                 
         * @param duration
         * @return
         */
        public Long clear(Duration duration) {
            return this.stringRedisTemplate.opsForZSet().removeRangeByScore(ONLINE_USERS, 0, 
                    LocalDateTime.now().minus(duration).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
        }
    }
    使用例
    
    @Resource
    private OnlineUserStatsService onlineUserStatsService;
    
    @Test
    public void test() {
        
        // ID 1         
        boolean result = this.onlineUserStatsService.online(1);
        System.out.println("online=" + result);
        
        //   5   ,           ,          
        Long count = this.onlineUserStatsService.count(Duration.ofMinutes(5));
        System.out.println("oneline count=" + count);
        
        //                
        count = this.onlineUserStatsService.count();
        System.out.println("all count=" + count);
        
        //     1            
        Long clear = this.onlineUserStatsService.clear(Duration.ofDays(1));
        System.out.println("clear=" + clear);
    }
    メモリ消費分析
    http://www.redis.cn/redis_memory/予算でRedisのメモリ消費が可能です。
    私はRedisのメモリ割り当てに慣れていません。自分の考えでデータを記入しただけです。だからここで理解したことは間違いかもしれません。このような場面でZsetを使ってメモリに対する消費が極めて低いという事実を証明したいです。
    オリエを想定するusersは1億人のユーザの状態情報を記憶する必要があります。各要素scoreとmemberは10バイトの記憶が必要です。そうすると、全部で約20 Gのメモリが必要です。20 Gのメモリは現在のサーバーにとって大きな問題ではありません。

    最後に
  • 心拍プロトコルは必ずしもHTTPを必要としません。クライアントがサポートするなら、UDPが最適です。システムオーバーヘッドを節約できます。
  • zsetのkeyは、必ずしもStringを使用する必要はなく、プログレッシブ方式を変更することができ、固定されたバイト形式でユーザIDを記憶し、ユーザIDが大きすぎる場合、いくつかの記憶空間を節約することができる。
  • 
    String userId = "10010";
    System.out.println(userId.getBytes().length); //          =>   5   
    
    byte[] bin = ByteBuffer.allocate(4).putInt(Integer.valueOf(userId)).array();
    System.out.println(bin.length);                    //            =>   4   
    
    System.out.println(ByteBuffer.wrap(bin).getInt());    //      ID => 10010
    以上はSpringBootを使ってRedisのzsetを使ってオンラインのユーザー情報の詳しい内容を統計して、更にSpring Bootに関してオンラインのユーザー情報の資料を統計して私達のその他の関連している文章に注意して下さい!