[Cache]RedisとMysqlの同期問題に対する解決策(feat.Redison)
最終位置決めで更新(または削除)された場合
redisとMysqlの間に同期の問題があることを発見しました!
今日はこれを宣伝します.
同期の方法で、最初に考えられることは、次のとおりです.
値を更新すると、redisも更新した値を同時に保存します.
これを書き込み-透過方式と呼びます!
ちなみに、既存のJedisは書き込み方式には適用されないので、Redisonを使って操作します.
pom.xml前回追加したspring-data-redis,jedisは注釈処理 を行った.コメントを行わないとNoSuchMethodError2 が生成する. Redissonとspring-sessession-data-Redis依存 を追加
SpringbootApplicationセクション Redisconクライアントを登録 RedisconクライアントにRedissサーバ上の情報 を登録する. redisサーバアドレスは「redis://host:port「フォーマットは です.構成(org.redisson.config)を使用すると、アドレス情報のほか、接続プールの大きさなど、多くの部分があります。構成 とすることができる.
RedisConfig.javaより前に作成されたJedisConnectionFactoryとRedisTemplateにコメントがあります RMAPCacheキャッシュのTTL(time to live) を設定できます. MapWriter<キー値タイプ、保存するタイプ>を上書きして、キャッシュに値を書き込みまたは削除するときに実行するアクション を指定します.の場合、キャッシュの値をデータベースに保存します. は、キャッシュからデータベースを削除することも求めます.
MapOptionsは 書き込みモードを採用している.WriteMode.WRITE BEHINDは書き込み後のキャッシュを許可する
- cf ) .writeMode(MapOptions.WriteMode.WRITE_THROUGH)); 値のStudioサービスを実現しました.
新しい値 の挿入/更新時のRMAPCache.put(id、保存する値)などのキャッシュのみを操作します.
値 を挿入する場合、TTLを指定することもできます.
Redisson.Test
まずselectの結果を表示します.
cacheとdbに同時に格納されるため、1回目の運転とその後の運転の速度差が以前ほど大きくないと判断できます.
updateの場合、同期済みが表示されます
deleteの場合、cacheの値を削除するだけでdbの値がnullであることを決定できます.
上記のようにすれば、同期は直ちに行われるものと考えられる.
ただし、write/update時にすべての情報を再格納するため、すべての情報を2つのリポジトリに格納する必要があります.
不要なキャッシュ処理がある場合
(実際には20%未満の情報だけが再利用されます)
そのためには、不要なデータを削除するために、アクティビティ(TTL)にデータの時間を指定する必要があります.
また、書き込みのたびにDBが使用されるため、速度も遅い.
write backは、すべてのwrite操作をredisに配置し、バッチを一定時間おきに実行します.
Redisサーバに格納されているデータをDBに移動する動作.
Insertでは複数のイベントを一度に処理し、速度が速いという利点があります
もう1つの欠点は、障害が発生し、キャッシュ・サーバにのみデータが格納されている場合、すべてのデータが失われる可能性があることです.
通常、ログデータを格納するために使用されます.
書き込み-転送と同様に、TTLを使用して不要なデータを削除する必要があります.
write-throughコードとあまり変わらない
RedisConfig.java MapWriter部分は書き込み時と同じ: RMAPCacheを実施する場合、writeModeをMapOptionsに設定します.WriteMode.WRITE BEHIND に設定 writeBehindDelay(1000)に設定し、毎秒間隔で展開(デフォルトは1000) @Rollback(false)を使用して、テスト後の結果をDBに保持します. Thread.展開(1000)が完了するまで展開を待ちましょう.
私たちはこのすべてが順調に進むことを確保することができます.
https://github.com/skshukla/SampleCacheWebApp
https://www.baeldung.com/redis-redisson
https://redisson.org/feature-comparison-redisson-vs-jedis.html
https://www.programcreek.com/java-api-examples/?api=org.redisson.api.RMapCache
https://www.javadoc.io/doc/org.redisson/redisson/3.7.2/org/redisson/api/RMapCache.html
https://www.javadoc.io/doc/org.redisson/redisson/3.6.0/org/redisson/api/MapOptions.html
https://www.javadoc.io/doc/org.redisson/redisson/3.4.4/org/redisson/api/map/MapWriter.html
https://stackoverflow.com/questions/51992484/caused-by-java-lang-nosuchmethoderror-org-springframework-data-redis-connectio
https://github.com/redisson/redisson/wiki/7.-distributed-collections#712-map-persistence
https://www.youtube.com/watch?v=mPB2CZiAkKM
https://waspro.tistory.com/697
https://dzone.com/articles/database-caching-with-redis-and-java
redisとMysqlの間に同期の問題があることを発見しました!
今日はこれを宣伝します.
書き込みスルーで解決
同期の方法で、最初に考えられることは、次のとおりです.
値を更新すると、redisも更新した値を同時に保存します.
これを書き込み-透過方式と呼びます!
ちなみに、既存のJedisは書き込み方式には適用されないので、Redisonを使って操作します.
透過コードの書き込み
pom.xml
<!-- <dependency>-->
<!-- <groupId>org.springframework.data</groupId>-->
<!-- <artifactId>spring-data-redis</artifactId>-->
<!-- <version>2.3.3.RELEASE</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>redis.clients</groupId>-->
<!-- <artifactId>jedis</artifactId>-->
<!-- <version>3.3.0</version>-->
<!-- <type>jar</type>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.1</version>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
SpringbootApplicationセクション
@SpringBootApplication
public class RedisPracticeApplication {
public static void main(String[] args) {
SpringApplication.run(RedisPracticeApplication.class, args);
}
@Bean
public RedissonClient redissonClient(){
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.0.20:6379");
return Redisson.create(config);
}
}
@SpringbootApplicationAnnotationセクションにbeanを登録してRedisConfig.java
@Configuration
@EnableCaching
public class RedisConfig{
private StudentRepository studentRepository;
private RedissonClient redissonClient;
public RedisConfig(StudentRepository studentRepository, RedissonClient redissonClient) {
this.studentRepository = studentRepository;
this.redissonClient = redissonClient;
}
@Bean
public RMapCache<String, Student> studentRMapCache(){
final RMapCache<String, Student> studentRMapCache
= redissonClient.getMapCache("Student", MapOptions.<String, Student>defaults()
.writer(getStudentMapWriter())
.writeMode(MapOptions.WriteMode.WRITE_BEHIND));
return studentRMapCache;
}
private MapWriter<String, Student> getStudentMapWriter(){
return new MapWriter<String, Student>(){
@Override
public void write(Map<String, Student> map) {
map.forEach((k, v) -> {
studentRepository.save(v);
});
}
@Override
public void delete(Collection<String> keys) {
keys.stream().forEach(key -> {
studentRepository.deleteById(key);
});
}
};
}
}
MapOptionsは
- cf ) .writeMode(MapOptions.WriteMode.WRITE_THROUGH));
StudentService.java
@Service
public class StudentService {
private StudentRepository studentRepository;
private RMapCache<String, Student> studentRMapCache;
@Autowired
public StudentService(StudentRepository studentRepository, RMapCache<String, Student> studentRMapCache) {
this.studentRepository = studentRepository;
this.studentRMapCache = studentRMapCache;
}
public Student save(Student student){
studentRMapCache.put(student.getId(), student);
return student;
}
public Student findById(String id){
return this.studentRMapCache.get(id);
}
public void update(String id, String name) throws Exception {
Student student = studentRMapCache.get(id);
student.setName(name);
studentRMapCache.put(id, student);
}
public void delete(String id){
studentRMapCache.remove(id);
}
}
動作新しい値
値
今からテストを行います
Redisson.Test
@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class RedissonTest {
StudentService studentService;
RedissonClient redissonClient;
StudentRepository studentRepository;
@Autowired
public RedissonTest(StudentService studentService, RedissonClient redissonClient
, StudentRepository studentRepository) {
this.studentService = studentService;
this.redissonClient = redissonClient;
this.studentRepository = studentRepository;
}
@Test
@Order(1)
void saveTest(){
Student student1 = new Student("1", "zzarbttoo1", Student.Gender.FEMALE, 1);
Student student2 = new Student("2", "zzarbttoo2", Student.Gender.FEMALE, 2);
Student student3 = new Student("3", "zzarbttoo3", Student.Gender.FEMALE, 3);
Student student4 = new Student("4", "zzarbttoo4", Student.Gender.FEMALE, 4);
Student student5 = new Student("5", "zzarbttoo5", Student.Gender.FEMALE, 5);
studentService.save(student1);
studentService.save(student2);
studentService.save(student3);
studentService.save(student4);
studentService.save(student5);
}
@Test
@Order(2)
void selectTest(){
long start1 = System.currentTimeMillis();
System.out.println(studentService.findById("1").toString());
System.out.println(studentService.findById("2").toString());
System.out.println(studentService.findById("3").toString());
System.out.println(studentService.findById("4").toString());
System.out.println(studentService.findById("5").toString());
long end1 = System.currentTimeMillis();
System.out.println(end1 - start1);
long start2 = System.currentTimeMillis();
System.out.println(studentService.findById("1").toString());
System.out.println(studentService.findById("2").toString());
System.out.println(studentService.findById("3").toString());
System.out.println(studentService.findById("4").toString());
System.out.println(studentService.findById("5").toString());
long end2 = System.currentTimeMillis();
System.out.println(end2 - start2);
long start3 = System.currentTimeMillis();
System.out.println(studentService.findById("1").toString());
System.out.println(studentService.findById("2").toString());
System.out.println(studentService.findById("3").toString());
System.out.println(studentService.findById("4").toString());
System.out.println(studentService.findById("5").toString());
long end3 = System.currentTimeMillis();
System.out.println(end3 - start3);
}
@Test
@Order(3)
void updateTest() throws Exception {
studentService.update("1", "updated Name");
Student selectStudent = studentRepository.findById("1").get();
System.out.println(selectStudent.toString());
Student redisStudent = studentService.findById("1");
System.out.println(redisStudent.toString());
}
@Test
@Order(4)
void deleteTest(){
System.out.println(studentService.findById("4"));
studentService.delete("4");
Assertions.assertNull(studentService.findById("4"));
}
}
テストコードは以下の通りですまずselectの結果を表示します.
cacheとdbに同時に格納されるため、1回目の運転とその後の運転の速度差が以前ほど大きくないと判断できます.
updateの場合、同期済みが表示されます
deleteの場合、cacheの値を削除するだけでdbの値がnullであることを決定できます.
書き込み方式に問題がある
上記のようにすれば、同期は直ちに行われるものと考えられる.
ただし、write/update時にすべての情報を再格納するため、すべての情報を2つのリポジトリに格納する必要があります.
不要なキャッシュ処理がある場合
(実際には20%未満の情報だけが再利用されます)
そのためには、不要なデータを削除するために、アクティビティ(TTL)にデータの時間を指定する必要があります.
また、書き込みのたびにDBが使用されるため、速度も遅い.
書き込みバックアップで解決
write backは、すべてのwrite操作をredisに配置し、バッチを一定時間おきに実行します.
Redisサーバに格納されているデータをDBに移動する動作.
Insertでは複数のイベントを一度に処理し、速度が速いという利点があります
もう1つの欠点は、障害が発生し、キャッシュ・サーバにのみデータが格納されている場合、すべてのデータが失われる可能性があることです.
通常、ログデータを格納するために使用されます.
書き込み-転送と同様に、TTLを使用して不要なデータを削除する必要があります.
コールバック
write-throughコードとあまり変わらない
RedisConfig.java
@Configuration
@EnableCaching
public class RedisConfig{
private StudentRepository studentRepository;
private RedissonClient redissonClient;
public RedisConfig(StudentRepository studentRepository, RedissonClient redissonClient) {
this.studentRepository = studentRepository;
this.redissonClient = redissonClient;
}
@Bean
public RMapCache<String, Student> studentRMapCache(){
final RMapCache<String, Student> studentRMapCache
= redissonClient.getMapCache("Student", MapOptions.<String, Student>defaults()
.writer(getStudentMapWriter())
.writeMode(MapOptions.WriteMode.WRITE_BEHIND)
.writeBehindBatchSize(5000)
.writeBehindDelay(1000)
);
//.writeMode(MapOptions.WriteMode.WRITE_THROUGH));
return studentRMapCache;
}
private MapWriter<String, Student> getStudentMapWriter(){
return new MapWriter<String, Student>(){
@Override
public void write(Map<String, Student> map) {
map.forEach((k, v) -> {
studentRepository.save(v);
});
}
@Override
public void delete(Collection<String> keys) {
keys.stream().forEach(key -> {
studentRepository.deleteById(key);
});
}
};
}
次にテストを行います
@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class RedissonTest {
StudentService studentService;
RedissonClient redissonClient;
StudentRepository studentRepository;
@Autowired
public RedissonTest(StudentService studentService, RedissonClient redissonClient
, StudentRepository studentRepository) {
this.studentService = studentService;
this.redissonClient = redissonClient;
this.studentRepository = studentRepository;
}
@Test
@Order(1)
@Rollback(false)
void saveTest(){
Student student1 = new Student("1", "zzarbttoo1", Student.Gender.FEMALE, 1);
Student student2 = new Student("2", "zzarbttoo2", Student.Gender.FEMALE, 2);
Student student3 = new Student("3", "zzarbttoo3", Student.Gender.FEMALE, 3);
Student student4 = new Student("4", "zzarbttoo4", Student.Gender.FEMALE, 4);
Student student5 = new Student("5", "zzarbttoo5", Student.Gender.FEMALE, 5);
studentService.save(student1);
studentService.save(student2);
studentService.save(student3);
studentService.save(student4);
studentService.save(student5);
}
@Test
@Order(2)
void selectTest(){
long start1 = System.currentTimeMillis();
System.out.println(studentService.findById("1").toString());
System.out.println(studentService.findById("2").toString());
System.out.println(studentService.findById("3").toString());
System.out.println(studentService.findById("4").toString());
System.out.println(studentService.findById("5").toString());
long end1 = System.currentTimeMillis();
System.out.println(end1 - start1);
long start2 = System.currentTimeMillis();
System.out.println(studentService.findById("1").toString());
System.out.println(studentService.findById("2").toString());
System.out.println(studentService.findById("3").toString());
System.out.println(studentService.findById("4").toString());
System.out.println(studentService.findById("5").toString());
long end2 = System.currentTimeMillis();
System.out.println(end2 - start2);
long start3 = System.currentTimeMillis();
System.out.println(studentService.findById("1").toString());
System.out.println(studentService.findById("2").toString());
System.out.println(studentService.findById("3").toString());
System.out.println(studentService.findById("4").toString());
System.out.println(studentService.findById("5").toString());
long end3 = System.currentTimeMillis();
System.out.println(end3 - start3);
}
@Test
@Order(3)
@Rollback(false)
void updateTest() throws Exception {
studentService.update("1", "updated Name");
Thread.sleep(1000);
}
@Test
@Order(4)
@Rollback(false)
void deleteTest() throws InterruptedException {
System.out.println(studentService.findById("4"));
studentService.delete("4");
Assertions.assertNull(studentService.findById("4"));
Thread.sleep(1000);
}
}
https://github.com/skshukla/SampleCacheWebApp
https://www.baeldung.com/redis-redisson
https://redisson.org/feature-comparison-redisson-vs-jedis.html
https://www.programcreek.com/java-api-examples/?api=org.redisson.api.RMapCache
https://www.javadoc.io/doc/org.redisson/redisson/3.7.2/org/redisson/api/RMapCache.html
https://www.javadoc.io/doc/org.redisson/redisson/3.6.0/org/redisson/api/MapOptions.html
https://www.javadoc.io/doc/org.redisson/redisson/3.4.4/org/redisson/api/map/MapWriter.html
https://stackoverflow.com/questions/51992484/caused-by-java-lang-nosuchmethoderror-org-springframework-data-redis-connectio
https://github.com/redisson/redisson/wiki/7.-distributed-collections#712-map-persistence
https://www.youtube.com/watch?v=mPB2CZiAkKM
https://waspro.tistory.com/697
https://dzone.com/articles/database-caching-with-redis-and-java
Reference
この問題について([Cache]RedisとMysqlの同期問題に対する解決策(feat.Redison)), 我々は、より多くの情報をここで見つけました https://velog.io/@zzarbttoo/CacheRedis와-Mysql의-동기화-문제에-대한-해결책들feat.-Redissonテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol