Using redis to achieve a shake similar to WeChat

     Shake it doesn't need to be introduced, we want to implement a similar function: multiple users shake their phones at about the same time, and then get a list of nearby people from the server every few seconds, which can be achieved with a relational database such as mysql, Moreover, it is very convenient to query and sort by distance, but each user will generate a record by shaking it once, and these records are valid within a few seconds, and they are useless after the time is exceeded, so it is necessary to clean up these records regularly. Data (of course it can not be cleaned, the data will be slow to a certain extent), we choose to use redis to achieve this function.
Principle
1, the client uploads the user id and latitude and longitude The
user shakes the phone, and the client uploads the user id and latitude and longitude To the server side, the server side calculates the latitude and longitude into a string (geohash, there are many such codes on the Internet, which are also provided below), for example, the calculated string is wkw69yv6, and then this string is concatenated with the user id and stored as a key in redis, and set the timeout time (such as 10 seconds), the server returns this string to the client (only the first 5 or 6 digits can be returned, the precision of the first 5 digits is about 2.4Km, and the precision of the first 6 digits is about 610m), assuming If we want to take users within 2.4km as nearby people, we can return to the top 5. If we want to take users within 610m as nearby people, then return to the top 6. Currently we use the first 6, the specific bit The number and precision are compared as follows:


2. Get the list of nearby
people . After waiting for n seconds, the client uploads the user id and the string (wkw69y) returned by the server last time to the server. The server only needs to use the redis keys wkw69y * You can get all the keys, and you can get the user ids of nearby people by intercepting the userid in the key.

3. If the client wants to display the distance of xx meters, how to sort by distance?
Cut out the geohash string from the key (the complete string is spliced ​​in the key, not the first 6 digits), and reverse it into latitude and longitude (there are many codes for calculation and reverse solution on the Internet, and I also provide a java implementation below) It can be returned to the client. The client can calculate the distance and sorting by getting the latitude and longitude. The server can also calculate the distance and sort it and give it to the client. The server can also sort it directly by geohash to the client. The client only needs to You can calculate the distance yourself.

4. What if you shake it to add a password? For
example, you enter a password of 1234 before shaking, and I also enter a password before shaking. Only if the distance between the two of us is within the allowable range and the passwords are equal, it will be considered a match. Success.
The password can also be spliced ​​into the key of redis, and it is at the front. The value returned to the client should be 1234-wkw69y,
your key is 1234-wkw69yv6-100000, my key is 1234-wkw69yvb-100001, In this way,
keys 1234-wkw69y* can obtain two people, and excluding yourself is the required data.

Question
1, why should the user id be spliced ​​into the key instead of being stored as a value?
Because the geohash calculated by the nearby latitude and longitude is the same , For example, if two people use the same wifi, then the geohash of the two people is the same. If the redis key does not add the user id, it will cause data coverage.


2. Why not use the geoadd, GEODIST and other location-related commands that come with redis?
These commands are indeed very useful. Convenience:
geoadd is to store data
geopos is to obtain its latitude and longitude according to the member, that is to reverse the geohash string to get the latitude and longitude
geodist is to calculate the distance between two members
georadius is based on the specified radius and latitude and longitude to obtain all members within the range
georadiusbymember is based on the origin of a member, all members within a radius
can be found at http://blog.csdn.net/opensure/article/details/51375961
Did you find that there is no command to delete members? Because geoadd uses sortedset for storage, so you can use zrem to delete it.

Because we need to set the expiration time for each person, and the bottom layer of geoadd is actually sortedset, which cannot be used for each individual. Individual (that is, each element in the sortedset) sets the expiration time.
Specifically:
first look at the geoadd command usage:
GEOADD key longitude latitude member [longitude latitude member ...]
For example:
GEOADD cities 113.2278442 23.1255978 Guangzhou 113.106308 23.0088312 Foshan;
Indicates that the latitude and longitude of Guangzhou and Fushan are stored in the collection corresponding to the city key. This collection is not a new data type, but a sortedset. You will definitely ask how an element of a sortedset can have three attributes (name, longitude and latitude), how is this stored? Redis calculates the latitude and longitude into a geohash string (geoadd automatically calculates, so geoadd is actually a combination of geohash+zset), and uses this string as the score of the sortedset, that is, Guangzhou as a member, Its latitude and longitude geohash is stored in sortedset as score. If you want to realize the shake function, you can store user id as member and latitude and longitude as geohash, that is, like this: GEOADD
yaoyiyao_users 113.2278442 23.1255978 100000 113.106308 23.0088312 100001;
The longitude and latitude of the two users of 100001 and 100001 are saved, but there is a problem. The shaking time is short, that is, the time each user stores in redis is short, and the expiration time should be set for each person, but We know that sortedset cannot set an expiration time for each member, so this command is good, but it can't meet the needs, so we thought that we should not set the expiration time. Can the client delete the person he pulls when he pulls the list? No? Yes, because only one person can always pull the list data, and no one else has the data (because the first person deleted the data after getting the data).

Shake interface
@RequestMapping(value = "/xx")
    @ResponseBody
    public String yaoYiYao(Integer userid, String pwd, Double longitude, Double latitude) {
        String hashval = GeoHash.encode (latitude, longitude);
        //Password-hash value-user id, spliced ​​together as the key of redis
        String key = (StringUtils.isNotBlank(pwd) ? "" : (pwd.trim() + "-")) + hashval + "-" + userid;
        redisCache.setWithExpire(key, "", 10);
        return (StringUtils.isNotBlank(pwd) ? "" : (pwd.trim() + "-")) + hashval.substring(0, 6);
    }


List interface
    @RequestMapping(value = "/xx")
    @ResponseBody
    public String yaoYiYaoList(Integer userid, String hashval) {
        //Remove all keys starting with hashval from redis
        Set<String> set = redisCache.keys(hashval + "*");
        if (CollectionUtils.isEmpty(set)) {
            return null;
        }
        Set<Integer> useridSet = new HashSet<>();

        // Traverse the key and extract the user id from the key
        for (String key : set) {
            String keyArr[] = key.split("-");
            useridSet.add(Integer.valueOf(keyArr[keyArr.length - 1]));//Get the last one
        }
        useridSet.remove(userid);//Exclude yourself

      // TODO has obtained the user id set, the next step is to query the data
     }



The geohash algorithm is implemented in java, the calculated geohash is 12 bits, and the precision is very high
import java.util.BitSet;
import java.util.HashMap;

public class GeoHash {
    private static int numbits = 6 * 5; // longitude and latitude encode the length separately  
    //32-bit encoding corresponding characters
    final static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
            'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
    //Define the encoding mapping relationship  
    final static HashMap<Character, Integer> lookup = new HashMap<Character, Integer>();
    //Initialize the content of the encoding map
    static {
        int i = 0;
        for (char c : digits)
            lookup.put(c, i++);
    }

    //decode the encoded string
    public double[] decode(String geohash) {
        StringBuilder buffer = new StringBuilder();
        for (char c : geohash.toCharArray()) {

            int i = lookup.get(c) + 32;
            buffer.append(Integer.toString(i, 2).substring(1));
        }

        BitSet lonset = new BitSet();
        BitSet latset = new BitSet();

        //even digits, longitude
        int j = 0;
        for (int i = 0; i < numbits * 2; i += 2) {
            boolean isSet = false;
            if (i < buffer.length())
                isSet = buffer.charAt(i) == '1';
            lonset.set(j++, isSet);
        }

        //odd digits, latitude
        j = 0;
        for (int i = 1; i < numbits * 2; i += 2) {
            boolean isSet = false;
            if (i < buffer.length())
                isSet = buffer.charAt(i) == '1';
            latset.set(j++, isSet);
        }

        double lon = decode(lonset, -180, 180);
        double lat = decode(latset, -90, 90);

        return new double[] { lat, lon };
    }

    // decode according to binary and range
    private double decode(BitSet bs, double floor, double ceiling) {
        double mid = 0;
        for (int i = 0; i < bs.length(); i++) {
            mid = (floor + ceiling) / 2;
            if (bs.get(i))
                floor = mid;
            else
                ceiling = mid;
        }
        return mid;
    }

    //encode the latitude and longitude
    public static String encode(double lat, double lon) {
        BitSet latbits = getBits(lat, -90, 90);
        BitSet lonbits = getBits(lon, -180, 180);
        StringBuilder buffer = new StringBuilder();
        for (int i = 0; i < numbits; i++) {
            buffer.append((lonbits.get(i)) ? '1' : '0');
            buffer.append((latbits.get(i)) ? '1' : '0');
        }
        return base32(Long.parseLong(buffer.toString(), 2));
    }

    //According to the latitude and longitude and range, get the corresponding binary
    private static BitSet getBits(double lat, double floor, double ceiling) {
        BitSet buffer = new BitSet(numbits);
        for (int i = 0; i < numbits; i++) {
            double mid = (floor + ceiling) / 2;
            if (lat >= mid) {
                buffer.set(i);
                floor = mid;
            } else {
                ceiling = mid;
            }
        }
        return buffer;
    }

    / / The specified 32-bit encoding of the combined latitude and longitude binary
    private static String base32(long i) {
        char[] buf = new char[65];
        int charPos = 64;
        boolean negative = (i < 0);
        if (!negative)
            i = -i;
        while (i <= -32) {
            buf[charPos--] = digits[(int) (-(i % 32))];
            i /= 32;
        }
        buf[charPos] = digits[(int) (-i)];

        if (negative)
            buf [- charPos] = '-';
        return new String (buf, charPos, (65 - charPos));
    }

    public static void main(String[] args) throws Exception {
        GeoHash geohash = new GeoHash ();
        String s = geohash.encode (25.770000, 110.125555);
        System.out.println(s);
        double[] geo = geohash.decode(s);
        System.out.println(geo[0] + " " + geo[1]);
    }

}




If you are interested in understanding the calculation principle of geohash, you can refer to http://blog.jobbole.com/80633/

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326250911&siteId=291194637