標籤:
redis儲存方式有很多種,但是我個人覺得最好用的並非是String儲存類型,而是Hash儲存類型,如果在使用redis的時候單純的只使用到String儲存類型的話,我個人覺得完全體現不了redis的特性。
redis 是一個key-value資料庫,但在我看來他並不是單純的key-value資料庫,因為他相對於其他同類型的nosql資料來講,redis提供了更多資料類型儲存格式。比如如果需要使用nosql類型的資料庫作為應用的緩衝,我相信memcached比redis更適合,但是現實中往往很多人使用redis就僅僅只是使用String類型來做緩衝。
redis有hash類型的value儲存資料格式,不知道說到這裡,大家有沒有想到我們傳統的關係型資料的資料存放區格式。
其實我一直都想把redis做成一個應用與傳統關係型資料庫之間的媒介,從而實現盡量在高負載的環境下減少資料庫的IO,並實現資料庫持久化,且能夠更好的實現非同步與傳統資料庫的資料同步。
Hash
常用命令:
hget,hset,hgetall 等。
應用情境:
我們簡單舉個執行個體來描述下Hash的應用情境,比如我們要儲存一個使用者資訊對象資料,包含以下資訊:
使用者ID為尋找的key,儲存的value使用者物件包含姓名,年齡,生日等資訊,如果用普通的key/value結構來儲存,主要有以下2種儲存方式:
第一種方式將使用者ID作為尋找key,把其他資訊封裝成一個對象以序列化的方式儲存,這種方式的缺點是,增加了序列化/還原序列化的開銷,並且在需要修改其中一項資訊時,需要把整個對象取回,並且修改操作需要對並發進行保護,引入CAS等複雜問題。
第二種方法是這個使用者資訊對象有多少成員就存成多少個key-value對兒,用使用者ID+對應屬性的名稱作為唯一標識來取得對應屬性的值,雖然省去了序列化開銷和並發問題,但是使用者ID為重複儲存,如果存在大量這樣的資料,記憶體浪費還是非常可觀的。
那麼Redis提供的Hash很好的解決了這個問題,Redis的Hash實際是內部儲存的Value為一個HashMap,並提供了直接存取這個Map成員的介面,如:
也就是說,Key仍然是使用者ID, value是一個Map,這個Map的key是成員的屬性名稱,value是屬性值,這樣對資料的修改和存取都可以直接通過其內部Map的Key(Redis裡稱內部Map的key為field), 也就是通過 key(使用者ID) + field(屬性標籤) 就可以操作對應屬性資料了,既不需要重複儲存資料,也不會帶來序列化和並發修改控制的問題。很好的解決了問題。
hash操作對應資料庫的表資料存放區方式其實是一樣的,所以我們能夠很容易切入到redis中,並把傳統資料庫的表資料存入到redis的hash資料類型中。這裡我需要跟大家分享的是如果快速的插入對象到redis中。
網上很多資料上都有pojo存入到redis的hash中,但是大多操作都比較繁瑣,這裡說的繁瑣是開發人員需要做更多的事並不是代碼的繁瑣,我這裡跟大家分享一個更簡單方便的方法:
/** * * @Title: set* @Description: 儲存實體* @return void (region + ":H:" + setKey(region)) key標示。* @throws */@SuppressWarnings({ "unchecked", "rawtypes" })public void save(V v) {Map<K, V> map = new JacksonHashMapper(entityClass).toHash(v);redisTemplate.boundHashOps((K) (region + ":H:" + setKey(region))) .putAll(map);};
儲存之後格式是:
擷取資料:
/** * * @Title: get* @Description: 根據redis 中對象的key 擷取對象資訊 想請請看entries()方法,connection.hGetAll* @return E* @throws */@SuppressWarnings({ "unchecked", "rawtypes" })public E get(String key) {Map<K, V> map = (Map<K, V>) redisTemplate.boundHashOps((K) (region + ":H:" + key)).entries();return (E) new JacksonHashMapper(entityClass).fromHash(map);}
以上介紹的是單個pojo的資料存放區於擷取,但是如果是大批量的資料,我們會不會也只是多次執行同一個方法的方式去擷取對象集合的呢?答案是否定的,因為redis已經為我們提供了更好的處理方法Pipelined 管道操作,意指多個資料統一返回。
/** * * @Title: listConn* @Description: 管道批量擷取對象集合 傳入對象的key 官方提供方法* @return List<E>* @throws */@SuppressWarnings("unchecked")public List<E> listPipe(final List<String> keys) {List<Object> result = redisTemplate.executePipelined(new RedisCallback<List<Object>>() {@Overridepublic List<Object> doInRedis(RedisConnection connection)throws DataAccessException {for (int i = 0; i < keys.size(); i++) {byte[] key = redisTemplate.getStringSerializer().serialize((region + ":H:"+keys.get(i)));connection.hGetAll(key);}return null;}});return ((List<E>) result);}自己實現的方法:executeConn@SuppressWarnings("unchecked")public List<E> listConn(final List<String> keys) {HashMapper<E, K, V> jhm = (HashMapper<E, K, V>) new JacksonHashMapper<E>(entityClass);List<Object> result = redisTemplate.executeConn(new RedisCallback<Map<byte[], byte[]>>() {@Overridepublic Map<byte[], byte[]> doInRedis(RedisConnection connection)throws DataAccessException {Map<byte[], byte[]> map = new HashMap<byte[], byte[]>();for (int i = 0; i < keys.size(); i++) {byte[] key = redisTemplate.getStringSerializer().serialize((region + ":H:"+keys.get(i)));connection.hGetAll(key);}return null;}},jhm);return ((List<E>) result);}
上述方法會返回一個結果集,但是我發現返回的結果集中序列化的屬性並沒有還原序列化完全導致輸出出現亂碼,先看下springdataredis提供的還原序列化方法:
@SuppressWarnings({ "unchecked", "rawtypes" })private List<Object> deserializeMixedResults(List<Object> rawValues, RedisSerializer valueSerializer,RedisSerializer hashKeySerializer, RedisSerializer hashValueSerializer) {if (rawValues == null) {return null;}List<Object> values = new ArrayList<Object>();for (Object rawValue : rawValues) {if (rawValue instanceof byte[] && valueSerializer != null) {values.add(valueSerializer.deserialize((byte[]) rawValue));} else if (rawValue instanceof List) {// Lists are the only potential Collections of mixed values....values.add(deserializeMixedResults((List) rawValue, valueSerializer, hashKeySerializer, hashValueSerializer));} else if (rawValue instanceof Set && !(((Set) rawValue).isEmpty())) {values.add(deserializeSet((Set) rawValue, valueSerializer));} else if (rawValue instanceof Map && !(((Map) rawValue).isEmpty())&& ((Map) rawValue).values().iterator().next() instanceof byte[]) {values.add(SerializationUtils.deserialize((Map) rawValue, hashKeySerializer, hashValueSerializer));} else {values.add(rawValue);}}return values;}SerializationUtils類:public static <HK, HV> Map<HK, HV> deserialize(Map<byte[], byte[]> rawValues, RedisSerializer<HK> hashKeySerializer,RedisSerializer<HV> hashValueSerializer) {if (rawValues == null) {return null;}Map<HK, HV> map = new LinkedHashMap<HK, HV>(rawValues.size());for (Map.Entry<byte[], byte[]> entry : rawValues.entrySet()) {// May want to deserialize only key or valueHK key = hashKeySerializer != null ? (HK) hashKeySerializer.deserialize(entry.getKey()) : (HK) entry.getKey();HV value = hashValueSerializer != null ? (HV) hashValueSerializer.deserialize(entry.getValue()) : (HV) entry.getValue();map.put(key, value);}return map;}
再看下我修改的之後的反序列話方法:
@SuppressWarnings({ "unchecked", "rawtypes" })private List<Object> deserializeMixedResults(List<Object> rawValues,RedisSerializer hashKeySerializer, RedisSerializer hashValueSerializer,HashMapper<E, K, V> jhm) {if (rawValues == null) {return null;}List<Object> values = new ArrayList<Object>();for (Object rawValue : rawValues) {Map<byte[], byte[]> obj = (Map<byte[], byte[]>) rawValue;Map<K, V> map = new LinkedHashMap<K, V>(obj.size());for (Map.Entry<byte[], byte[]> entry : obj.entrySet()) {map.put((K) hashKeySerializer.deserialize((entry.getKey())), (V) hashValueSerializer.deserialize((entry.getValue())));}values.add(jhm.fromHash(map));}return values;}
以上就是我處理管道結果還原序列化亂碼的解決方案。如果有不明白的地方,請大家指正。謝謝。
Redis之序列化POJO